# 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