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

172 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 库存成本结算可行性分析
## 背景
纺织/印花行业 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` 字段即可。
两个方法的落地成本都很低,不存在结构性障碍。