102 lines
2.0 KiB
Markdown
102 lines
2.0 KiB
Markdown
# 05 库存设计
|
||
|
||
**库存设计是整个 WMS 的核心。**
|
||
|
||
## 库存模型
|
||
|
||
不要简单设计 `sku + warehouse + quantity`,必须建立完整库存模型。
|
||
|
||
### 数量维度
|
||
|
||
| 字段 | 含义 |
|
||
| --- | --- |
|
||
| available_qty | 可用数量 |
|
||
| reserved_qty | 预占 / 锁定数量 |
|
||
| picked_qty | 已拣货数量 |
|
||
| hold_qty | 冻结数量 |
|
||
| damaged_qty | 残损数量 |
|
||
| in_transit_qty | 在途数量 |
|
||
|
||
### 位置与属性维度
|
||
|
||
- 仓库
|
||
- 库区
|
||
- 库位
|
||
- SKU
|
||
- 批次
|
||
- 库存状态
|
||
|
||
### 核心关系
|
||
|
||
```
|
||
SKU
|
||
↓
|
||
Warehouse
|
||
↓
|
||
Zone
|
||
↓
|
||
Location
|
||
↓
|
||
Batch
|
||
↓
|
||
Inventory
|
||
```
|
||
|
||
## 库存流水必须是核心数据
|
||
|
||
必须设计 `wms_stock_log`,任何库存变化必须能够追溯。例如:
|
||
|
||
```
|
||
入库 +100
|
||
锁定 -20
|
||
拣货 -5
|
||
出库 -5
|
||
盘点 +2
|
||
调整 -1
|
||
```
|
||
|
||
需要能够回答:**为什么这个 SKU 的库存从昨天的 100 变成今天的 76?**
|
||
|
||
- 库存余额是结果。
|
||
- 库存流水是事实。
|
||
|
||
不要只修改 `inventory.quantity` 而不留下完整流水。库存余额更新与流水写入必须在同一事务中完成,流水需记录业务单据类型与单号(如 inbound_no / outbound_no)。
|
||
|
||
## 库存并发安全
|
||
|
||
库存扣减必须考虑并发。
|
||
|
||
**禁止:** 先 SELECT → PHP 计算 → UPDATE。这种方式容易产生超卖。
|
||
|
||
根据实际业务采用:
|
||
|
||
- 数据库事务
|
||
- 行锁(`SELECT ... FOR UPDATE`)
|
||
- 乐观锁(version 字段)
|
||
- 原子更新
|
||
- Redis 分布式锁(必要时)
|
||
|
||
原子更新示例:
|
||
|
||
```sql
|
||
UPDATE wms_inventory
|
||
SET available_qty = available_qty - :qty,
|
||
reserved_qty = reserved_qty + :qty
|
||
WHERE tenant_code = :tenant_code
|
||
AND id = :id
|
||
AND available_qty >= :qty;
|
||
-- 受影响行数为 0 即库存不足,必须抛出业务异常
|
||
```
|
||
|
||
必须保证:
|
||
|
||
- `available_qty >= 扣减数量`
|
||
- 多个并发请求不能导致负库存或重复扣减
|
||
|
||
## 并发验收用例
|
||
|
||
两个请求同时扣减 8 个库存,而实际只有 10 个库存。必须保证:
|
||
|
||
- 不会出现 `available_qty = -6`
|
||
- 不能出现两个订单都成功扣减 8
|