1
0
forked from erp-dev/erp
Files
erpnew/docs/cost/inventory-cost-feasibility.md
2026-06-09 11:15:02 +08:00

6.3 KiB
Raw Permalink Blame History

库存成本结算可行性分析

背景

纺织/印花行业 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 关键发现:库存模块不做任何成本核算

所有 StockChangeDetailInventoryStockSnapshot 只记录数量quantity,完全没有 cost_priceunit_costtotal_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. 实施建议

最小改动方案

两个方法都需要以下共同改动:

  1. StockChangeDetail 增加 unit_cost 字段

    unit_cost = models.DecimalField(max_digits=15, decimal_places=6, null=True, verbose_name='成本单价')
    
  2. 入库时填充 unit_cost

    • 采购入库:从 PurchaseOrderItem.price 同步
    • 销退入库:从原销售出库的成本回冲
    • 调拨入库:从调出仓的当前成本同步
    • 盘盈:可设为 0 或要求手动录入
  3. 出库时计算成本

    • FIFO沿 consume_with 链读取对应入库批次的 unit_cost
    • 加权平均:需额外在 Inventory 表增加 total_cost 字段,在 make_stock_change_completed 中维护

建议优先实现 FIFO

对于纺织行业(布料批次间价格差异大),FIFO 更合适。且当前 consume_with 链已就绪,实现成本最低。

若后续需要加权平均,在 FIFO 的基础上给 Inventory 增加 total_cost 即可,两者不冲突。


5. 结论

当前系统不具备直接进行先进先出或加权平均成本结算的能力,库存模块完全是数量管理。

FIFO 所需的基础设施已经存在consume_with 追踪链),改动范围极小——仅需给 StockChangeDetail 增加 unit_cost 字段,并在入库时同步采购价格。加权平均也只需在 Inventory 表增加 total_cost 字段即可。

两个方法的落地成本都很低,不存在结构性障碍。