FIFO 库存方法(先进先出)


首页 » 资源 » 此处
作者:Joannès Vermorel,2016 年 7 月

FIFO(先进先出)库存方法是指最先买入的产品也会最先售出。FIFO 库存可以视为实际商品流的一种理论模型,适合用于会计或财务用途。FIFO 库存也可以视为一种供应链做法,其意图是限制因到期或过时而对库存商品产生不利影响。通过 FIFO 库存分析,可以计算库存货龄,以及识别周转缓慢的库存或积压库存。本页介绍了如何在实践中执行 FIFO 分析,同时也概述了这种方法的理论和实践限制。

物理 FIFO

供应链侧重于实际商品流,从这个角度来说,最早买入的商品最先出货通常被认为是一种优良的做法。如遵循这样的流程,只要商品存货不是过多,那么公司通常可以减轻大部分过期贬值。这种做法也可以限制与较长储存期相关的轻度商品过时(例如包装污损)。

举个例子,在时装电子商务中,产品收益率可能接近所出货商品的 50%。在这种情况下,最好的做法往往是首先发出以前退回的商品。这一规则是在适当考量收益率的情况下对 FIFO 方法进行的延展。这种方法有助于处理产品收集结束前的二级销售渠道。

许多仓库系统完全不区分出货的产品,它们不是将时间最久的产品先出货,而是随机出货。这种做法的分析超出了本文的范畴。

FIFO 分析

与物理 FIFO 不同,FIFO 分析则是站在理论角度分析库存,即假定最早买入的产品最先出货,而不论实际商品流如何。FIFO 角度极大地简化了对库存的财务分析。

实际上,执行 FIFO 分析时只需要具备:

  • 当前库存水平
  • 包含交付日期的采购订单历史记录

根据这些数据,FIFO 分析提供了用于计算以下项目的途径:

  • 存货估价(考虑变化的采购价格)
  • 预期的毛利润(取决于采购价格)
  • 平均库存货龄(极端情况也要予以考虑)

下一节阐述了如何从实际角度计算上面这些值。

Envision 和 FIFO 分析

Lokad 使用了一种叫做 Envision 的脚本语言,这种语言专门用于对供应链进行定量优化。Envision 中的 fifo() 函数专门用于执行 FIFO 分析。

从直观上说,在给定当前库存水平和过去采购订单的情况下,fifo() 方法会以某种方式返回详细的库存组成,并指出库存中剩余的每个单位的货龄。每个单位关联一个并且仅此一个采购订单行。这种方法尽管略显复杂,但是会在考量当前库存水平的前提下,针对每个采购订单行计算“未售出”的单位数。

因此,可以很直观地看出如何利用这些未售出的数量来推导出相关财务 KPI。我们来看一下 fifo() 函数是如何准确计算与每个采购订单行相关的未售出数量的:
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_PurchaseOrders.tsv" as PO

PO.Unsold = fifo(StockOnHand, PO.Date, PO.Quantity)
show table "Unsold" with Id, PO.Date, PO.Unsold
在上面这段脚本中,最先是读取样本数据集提供的两个文件:项目列表和采购订单列表。在第 4 行调用了 fifo() 函数。此函数有三个参数:

  • 当前库存水平
  • 采购订单的日期(通过为交付日期)
  • 与采购订单相关的数量

第 5 行的 show table 语句用于显示计算结果。

存货估价

使用以下脚本可以很简单地计算存货估价:
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_PurchaseOrders.tsv" as PO

PO.Unsold = fifo(StockOnHand, PO.Date, PO.Quantity)
StockValue = sum(PO.Unsold * PO.NetAmount / PO.Quantity)

show table "Total stock value" with sum(StockValue)
在上面这段脚本中,未售出数量 PO.Unsold 乘以原始采购单价,也即 {PO.NetAmount / PO.Quantity}}。

平均采购价格

采购价格可以通过 FIFO 方法求平均值。下面这段脚本在以前专门针对此用途创建的脚本基本上进行了一些变化:
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_PurchaseOrders.tsv" as PO

PO.Unsold = fifo(StockOnHand, PO.Date, PO.Quantity)
PP = sum(PO.Unsold * PO.NetAmount / PO.Quantity) /. sum(PO.Unsold)

where PP > 0 // filter items without purchase orders
show table "Purchase prices" with Id, Name, PP
向量 PP 包含所有项目的采购价格。由于使用了 /. 除运算符(如果分母为 0,那么就会返回 0),在第 7 行,我们过滤了所有无法计算采购价格的项目(因为缺乏相应的采购订单)。

库存货龄

计算库存货龄同样相对简单,但是,需要提供一个额外的要素:用于反映“当前”的日期。实际上,库存数据有时可能是若干天之前的数据,并且当前日历日期也可能不是执行分析的正确日期。在下例中,我们引入了一种叫做 `today` 的变量,在实际情形中可以对该变量进行进一步调整。
read "/sample/Lokad_Items.tsv"
read "/sample/Lokad_PurchaseOrders.tsv" as PO

today := max(PO.Date) + 1 // last observed date in data

PO.Unsold = fifo(StockOnHand, PO.Date, PO.Quantity)
PO.Age = (today - PO.Date) // age in days
Age = sum(PO.Unsold * PO.Age) /. sum(PO.Unsold)

show table "Stock age" with Id, Name, Age order by Age desc
上面这段脚本显示了库存中所有项目的列表,列表按库存货龄排序,货龄最老的项目位于列表最上方。第 6-8 行执行计算,即根据未售出的数量求每个采购订单的货龄平均值。

FIFO 分析的局限性

FIFO 分析的表示来自于对库存流的基本假设。但是,当数据或实际商品流与这些假设有出入时,结果的准确性容易大打折扣。在这节中,我们将了解与 FIFO 分析相关的常见问题。

采购历史记录不完整

尽管在执行准确的 FIFO 分析之初并不要求具备完整的采购历史记录,但经常存在最久远的库存单位在库存中的时间超过可用采购历史记录深度的情况。尽管在某些行业里,比如航空航天,10 年之久的部件并不罕见,但要从公司系统中提取 10 年前的采购历史记录可能是一项非常严峻的挑战。

缺货重演凭运气

如果采购订单历史记录和销售订单历史记录都完备,那么就可以按照年代次序来重演采购订单和销售订单的完整序列,以此重新计算缺货的完整历史记录。理论上,通过这样的模拟,可以重新计算出任意时间点的所有存货水平,因而可以将存货水平为零的所有时间段标记为缺货。

但遗憾的是,根据 Lokad 的经验,这种方法几乎从来不顶用。实际上,哪怕是最细微的库存差异,例如没有将某一个单位纳入考量范围,都会给整个分析带来巨大影响,原因就是缺货时段几乎从不为零,但较小的数值(无论正负与否)无法用于识别缺货。

序列库存

如果所有单位都有唯一的序列号,并且所有库存周转都在序列号级别标识,那么,FIFO 分析通常可以视为一种非常粗略的近似法。实际上,如果库存值得在序列号 (S/N) 级别进行物理跟踪,那么基本可以肯定,库存也值得利用序列号提供的额外细节度来进行分析。