forked from erp-dev/erp
172 lines
6.3 KiB
Markdown
172 lines
6.3 KiB
Markdown
# 库存成本结算可行性分析
|
||
|
||
## 背景
|
||
|
||
纺织/印花行业 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` 字段即可。
|
||
|
||
两个方法的落地成本都很低,不存在结构性障碍。 |