forked from erp-dev/erp
feat: completed
This commit is contained in:
172
docs/cost/inventory-cost-feasibility.md
Normal file
172
docs/cost/inventory-cost-feasibility.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 库存成本结算可行性分析
|
||||
|
||||
## 背景
|
||||
|
||||
纺织/印花行业 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` 字段即可。
|
||||
|
||||
两个方法的落地成本都很低,不存在结构性障碍。
|
||||
Reference in New Issue
Block a user