6.3 KiB
库存成本结算可行性分析
背景
纺织/印花行业 ERP 中,库存成本核算通常使用两种方法:
- 先进先出(FIFO):先入库的批次先出库,出库成本按对应入库批次的实际采购价计算。
- 加权平均:每次入库后重新计算库存均价(总成本 ÷ 总数量),出库统一按当前均价计算。
本文档对当前系统的库存模块进行研判,评估是否具备实现这两种成本结算方法的要素。
1. 当前库存模块数据结构
1.1 核心模型
StockChangeRecord(库存变动记录)
├── type: 入库/出库
├── source_type: 来源类型(采购/销售/调拨/盘盈盘亏/红冲等)
├── warehouse: 仓库
├── is_finished: 是否已完成
└── details ────── StockChangeDetail(库存变动明细)
├── product: 产品
├── quantity: 数量
├── consume_with: 消耗关联(出库指向入库明细,自引用 FK)
└── is_consumed: 是否已被消耗
Inventory(库存汇总)
├── product: 产品
├── warehouse: 仓库
├── quantity: 当前库存数量
└── num_of_rolls: 匹数
StockSnapshot(库存变动快照)
├── delta: 变动量
├── quantity_before: 变动前库存
├── quantity_after: 变动后库存
└── (offset/cancelled 红冲链路)
1.2 价格数据所在位置
价格数据不在库存模块,而在业务模块的明细行中:
| 模型 | 价格字段 | 说明 |
|---|---|---|
business.PurchaseOrderItem.price |
采购单价 | 入库成本价的唯一来源 |
business.SalesOrderItem.price |
销售单价 | 出库售价,非成本价 |
1.3 关键发现:库存模块不做任何成本核算
所有 StockChangeDetail、Inventory、StockSnapshot 只记录数量(quantity),完全没有 cost_price、unit_cost、total_cost 等成本维度字段。
2. 按结算方法逐一分析
2.1 先进先出(FIFO)
原理
每次出库时,从最早未消耗的入库批次开始扣除,出库成本等于对应入库批次的实际采购单价。
已有基础设施 ✅
StockChangeDetail.consume_with 是一个自引用外键,在严进严出模式下,出库明细通过它指向被消耗的入库明细:
# stock/models.py
consume_with = models.OneToOneField(
'self',
on_delete=models.PROTECT,
null=True, blank=True,
related_name='consumed_by_detail',
verbose_name='所消耗的入库明细',
)
这是天然的 FIFO 追踪链。如果入库明细携带了成本价,出库成本可以直接通过 consume_with.unit_cost 确定。
缺失要素
| 缺失项 | 说明 |
|---|---|
StockChangeDetail.unit_cost |
入库明细需要记录该批次的采购成本单价 |
| 宽进宽出模式的批次追踪 | 当前 consume_with 仅在严进严出模式下使用,宽进宽出需要补充批次追踪或按 FIFO 规则自动匹配 |
落地难度:低
改动范围极小——给 StockChangeDetail 加一个 unit_cost 字段,在入库时从采购单同步价格,出库成本沿 consume_with 链读取即可。
2.2 加权平均
原理
每次入库后,重新计算加权平均单价:
加权均价 = (库存总成本 + 本次入库成本) ÷ (库存数量 + 本次入库数量)
出库时统一按当前加权均价计算成本。
已有基础设施 ⚠️
Inventory 表是天然的加权平均计算锚点——它汇总了每个产品在每个仓库的当前库存数量。只需增加一个总成本字段即可完成计算。
缺失要素
| 缺失项 | 说明 |
|---|---|
Inventory.total_cost |
库存表需要增加总成本字段,每次入库累加 |
StockChangeDetail.unit_cost |
同上,入库明细需要知道入库单价 |
| 红冲/退货的成本回冲逻辑 | 红冲或退货时需反向调整 total_cost 和重新计算均价 |
落地难度:低
给 Inventory 加一个 total_cost 字段,在 make_stock_change_completed() 中补充成本累加逻辑即可。
3. 两种方法对比
| 维度 | 先进先出 (FIFO) | 加权平均 |
|---|---|---|
| 追踪粒度 | 批次级别 | 仓库+产品级别 |
| 已有基础设施 | consume_with 链 ✅ |
Inventory 汇总表 ⚠️ |
| 需新增字段 | StockChangeDetail.unit_cost |
StockChangeDetail.unit_cost + Inventory.total_cost |
| 计算复杂度 | 需维护批次消耗顺序 | 每次入库后重新算均价 |
| 红冲处理 | 恢复原批次 | 重新计算均价 |
| 适用场景 | 价格波动大、需精确追踪每批成本 | 价格稳定、简化核算 |
4. 实施建议
最小改动方案
两个方法都需要以下共同改动:
-
StockChangeDetail增加unit_cost字段unit_cost = models.DecimalField(max_digits=15, decimal_places=6, null=True, verbose_name='成本单价') -
入库时填充
unit_cost- 采购入库:从
PurchaseOrderItem.price同步 - 销退入库:从原销售出库的成本回冲
- 调拨入库:从调出仓的当前成本同步
- 盘盈:可设为 0 或要求手动录入
- 采购入库:从
-
出库时计算成本
- FIFO:沿
consume_with链读取对应入库批次的unit_cost - 加权平均:需额外在
Inventory表增加total_cost字段,在make_stock_change_completed中维护
- FIFO:沿
建议优先实现 FIFO
对于纺织行业(布料批次间价格差异大),FIFO 更合适。且当前 consume_with 链已就绪,实现成本最低。
若后续需要加权平均,在 FIFO 的基础上给 Inventory 增加 total_cost 即可,两者不冲突。
5. 结论
当前系统不具备直接进行先进先出或加权平均成本结算的能力,库存模块完全是数量管理。
但 FIFO 所需的基础设施已经存在(consume_with 追踪链),改动范围极小——仅需给 StockChangeDetail 增加 unit_cost 字段,并在入库时同步采购价格。加权平均也只需在 Inventory 表增加 total_cost 字段即可。
两个方法的落地成本都很低,不存在结构性障碍。