# 库存成本结算可行性分析 ## 背景 纺织/印花行业 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` 是一个自引用外键,在严进严出模式下,出库明细通过它指向被消耗的入库明细: ```python # 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` 字段** ```python 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` 字段即可。 两个方法的落地成本都很低,不存在结构性障碍。