多供应商下的采购方案

综合计算供应商的价格和物流成本,以达到成本最优的目的

Posted by Baymax on April 21, 2018

当前采购系统方案

目前某书的采购系统是按周进行采购的,这里的采购的都是自营商品,由内部掌控全部流程,一个完整的采购周期会包含下面几个步骤:

  1. 数据组根据历史的销量数据,生成一份下一个月的销量预测
  2. 根据销量预测可以知道哪些商品面临缺货风险,自动生成待补货商品数据
  3. 采购小姐姐们可以在后台添加待补货商品,因为预测对爆品新品会不太准确
  4. 根据待补货商品和供应商供货目录生成询价单,并发送给供应商,按照固定格式回收报价单
  5. 然后是流程中的核心步骤,系统根据报价单在每个商品上选择不同的供应商,自动生成采购单
  6. 再由采购们查看修改和确认,并交由物流系统执行采购入库流程

核心过程

生成采购单,本质上是针对每一件商品,对供应商的报价进行排序比较,选择价格最低的供应商。并且最早的版本,也是这么处理的,在每一件商品上进行成本最优测算,从而达到最后的成本最优。然而这么做是有几个非常明显的缺陷的:

  • 不同供应商的质量、准点交货的能力都是不同的,没有加入影响
  • 其实单个采购的商品数量是可以支持浮动的,系统内支持 $\pm5\%$ 的采购数量浮动
  • 没有考虑到集中采购的优势,在同一家供应商采购数量越多,折扣越大,不过目前没有支持阶梯报价的功能
  • 最需要挖掘的一点是,商品的采购成本中,没有算上物流成本

针对上面的每一个优化点,我们都设计了优化方案。

供应商惩罚

为了应对供应商可能会多报商品少发货、延迟发货、质量差等问题,加入了供应商惩罚系数。这个系数在 $-5\%$ 到 $+10\%$ 之间,由销售部门根据供应商的表现进行手动控制。在生成采购单的流程中,原有的根据采购成本排序就变成了根据采购分数排序,这里的采购分数 $=$ 采购成本 $\times$ 供应商惩罚系数。

采购数量浮动

针对采购数量浮动,目前是写死逻辑的,基本的逻辑为:针对单一商品,按照采购分数排序之后,进行依次购买:

  • 如果第一家供应商的供应数量在采购数量的 $95\%$ 到 $105\%$ 之间,那么直接买满数量
  • 如果数量还不够,那么看第二家供应商,此时只要满足 $95\%$ 的采购数量即可,若还不满足再找第三家,以此类推

采用这种逻辑的主要原因是尽量减少小采购单的产生,虽然物流成本没有被纳入计算,但是小采购单的物流成本是非常高的。采购数量的浮动导致了新的评判标准:将采购数量折算成 $100\%$ 之后再平均的平摊采购成本

支持阶梯报价

支持阶梯报价是一个比较复杂的过程,最终的目的是降低每件商品的平摊采购成本。由于这一步和物流成本类似,都是在原有的商品固定成本的基础上,加入不确定因素,因此我们会放到物流成本那一起解决这个问题。

物流成本

物流成本需要纳入采购成本分析,这个功能其实很早就提出,但是一直没有执行。一是因为采购系统本身还有许多功能要做,另外一个是物流那边还没有提供详细的报价逻辑,他们也在做各个物流公司之间的报价整合。在没有和物流的同事交流之前,我们以为在发货地不变的情况下,物流成本函数是这样的:

但是物流成本的问题,远比我们想象的复杂很多。在物流组计算的时候,他们需要综合考虑到货周期、货物重量和类型等各种问题,最后才能给出报价。

到货周期

到货周期实际上会很大程度的影响报价,比如从美国发货:

  • 最快的空运可能一周内就能抵达国内仓库所在地的海关
  • 最慢的海运就需要一个月甚至数月的时间才能抵达

但是在价格上,空运可能比海运贵几倍甚至数十倍。在采购单上,我们会标明缺货紧急的货物,会对到货周期有更高的要求,因此他们也会选择更贵但是更快的方式发货。同时在报价接口上,他们也提供了到货周期的参数。

合包运输

在物流的过程中,会分成好几个层级来进行,比如从美国发货:

  • 首先是对方仓库出来,到省市一级,有些公司是自带这一段配送的,没有的则需要当地物流公司接货
  • 然后到了省市一级,可能会有合包操作,这个看当地的物流公司是不是提供这个服务,然后走到海关
  • 如果是空运那么就运走了,如果是海运还会涉及大量的货物合并装箱的情况,然后运往国内

这一点的逻辑也打破了之前单个供应商的物流成本只影响它自己的假设,也就是说,如果在同一个国家内采购越多(某书的主要采购地点是香港、日本、美国和欧洲,一般欧洲会视为一个地区,因为经常会在同一地点合包),那么合包的机会越大,最后的物流成本也越低。

真实曲线

调研了这么多,最后的总结下来的物流成本曲线大概是下面这样:

也就是说,我们大概知道在同一个地方买的越多,那么物流成本是更低的。但是这里也受到很多其他因素的影响,因此这个函数是阶梯式的,另外里面有太多复杂的逻辑,我们不能将物流成本计算纳入采购系统,只能使用物流部门提供的报价接口。

在计算中加入物流成本

上面众多的背景知识已经非常复杂了,但由于采购方案一定在有限的可能性之内,所以我们总可以找到一种最优方案,使得下面的值最小:

总加权成本 $=\displaystyle \sum_{i}^n \frac {\sum_{j}^m 单品采购得分_j} {单品实际采购数量i}$ $\times$ $单品预计采购数量_i$ $+$ 总物流成本

其中 $n$ 表示采购商品,$m$ 表示供应商,总物流成本可以由不同地区的物流成本加总得到。

但是,对于上千个供应商和几万的SKU数量来说,这个搜索空间带来的时间复杂度可能已经是宇宙常数级别的了。因此我们几乎不可能在限定的时间内找到这个结果,另外计算物流成本的接口在另一个组的机器上,也要考虑这只是一个对内的低负载接口,想清楚这些我们就能很明白我们需要找的是一个渐进算法,即在有限的时间内找到一个相对更好的方案。

模拟退火

那么既然是寻找一个不确定算法,可选的大部分就和随机性相关了,模拟退火算法是我最后的选择。

百度百科 - 模拟退火算法

模拟退火简单来说就是不停地寻找更优解,直到收敛在某个状态之下,记录下这个局部最优解,然后再随机打乱之后重复上一过程,找到另一个局部最优,最后在多个局部最优中寻找一个最优解。

实际操作

实际操作的时候,需要有更多的预处理减少不必要的判断,否则的话我们跑的退火轮次就很少了,步骤说明如下:

步骤 操作内容 说明
1 所有的报价做加权处理,如果供应商的价格超过最便宜报价的 $20\%$,并且删除后还能满足补货要求,那么不考虑这个供应商 这是一个经验总结,一般来说一件商品的物流成本不会超过 $10\%$,因此即使物流成本减少到 $0\%$,也难以节约 $20\%$ 的采购成本
2 按照原始方法生成采购单,并计算加权总成本,包括物流成本占比等信息 我们需要一个基准,另外原方案也会被记录,和新方案对比,首轮不从随机组合开始
3 忽略只有一个供应商或者全买全也不足补货数量的采购单品,穷举剩下单品,寻找替代采购方案,并计算加权总成本 寻找替代方案的时候,并不需要一件件进行检查,而是批量把单品从一个供应商换到另一个供应商采购
4 找到加权总成本最低的替代方案,并替换当前的方案 我们找到下降最快的方案,可以加快收敛速度
5 循环进行步骤3和4,直到收敛或者达到最大轮次 模拟退火中不断退火的过程,加入最大轮次是避免时间太久
6 随机打乱方案,回到步骤2,并继续执行,直到达到时间限制 模拟退火中重新回到无序的过程

一般来说,报价单会在大部分供应商的下班时间之前收集到位,在晚上8:00的时候禁止提交。需要在第二天上班之前生成相应的采购单,否则采购小姐姐就没法操作了,因此算法可以执行8小时来找到相对优秀的结果。

另外,在前几次运行的时候,需要先输出并且备份原版采购单,方便第二天上班的时候发现算法挂了还可以恢复现场。

再讨论阶梯报价

前面的阶梯报价,其实也是在模拟退火的方案中解决的。对比阶梯报价我们发现它其实和物流成本有着相似的关系,我们只需要在步骤3中,加入阶梯报价的计算即可,算法会根据加权总成本寻找最优解。

总结

其实在做这个项目之前我也查阅了很多资料,但是几乎没有讲到这么具体的问题,而且不同电商的采购方案可能大相径庭,最后也只能自己找解决方案。这里也是抛砖引玉,如果你看到这里,希望在你以后遇到类似问题的时候,有一点微小的贡献。

流程变更

在新的流程里面,我们在生成采购单的时候,加入了物流成本的影响,其他流程不变。

成本节约

最后的方案相比原有不考虑物流成本的方案,我们成功减少了采购总成本,由于每周采购的会有变化,因此效果也会不同:

物流成本占比,减少了 $1\%$ 到 $2\%$ 单品采购总价,增加了 $0.5\%$ 到 $1\%$ 商品采购总价,减少了 $0.5\%$ 到 $1.5\%$

这个数字看起来不大,但是每周采购量大约在5000W到10000W这个数量级,遇到大促则会是这个数量的3-4倍。在这样的加持下,每一周节约的成本大概是40W-100W之间,也非常可观了。

改进之外

这个功能本身是纯后端,没有任何前端展示的。但是这样的效果并不好,因为总会有采购来问,为什么她修改了某个采购单之后,采购成本反而减少了,是不是系统计算出问题了。

其实这里并不是算错了,而是原有采购总成本的显示是不包含物流成本的,我们后来加上物流成本以后,就没有这个问题了。所以说,算法的效果很重要,但是给使用者展现这个效果也非常重要。