# 库存余额、批次、流水、调整、盘点 约定见 [00-conventions.md](./00-conventions.md)。数量桶的业务含义见 [01-er-overview.md](./01-er-overview.md) 与 [05-inventory.md](../05-inventory.md)。Phase 1 锁定,Phase 7、8 写迁移。 冻结、锁定不单独建单据表。冻结是可用与 `hold_qty` 的对转;订单锁定是可用与 `reserved_qty` 的对转,锁定对象记在 `wms_outbound_allocation` 和流水的 `biz_no` 上。 ## wms_batch 批次 用途:批次属性。不管理批次的库存使用 `batch_no = ''`,不写本表。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | customer_code | VARCHAR(64) | N | | 货主 | | sku_code | VARCHAR(64) | N | | SKU 编码 | | batch_no | VARCHAR(64) | N | | 批次号,禁止空字符串 | | supplier_code | VARCHAR(64) | Y | | 供应商 | | mfg_date | DATE | Y | | 生产日期 | | expiry_date | DATE | Y | | 失效日期 | | created_at | TIMESTAMP | Y | | | | updated_at | TIMESTAMP | Y | | | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | uk_tenant_customer_sku_batch | UNIQUE | (tenant_code, customer_code, sku_code, batch_no) | 定位批次 | | idx_tenant_sku_expiry | INDEX | (tenant_code, sku_code, expiry_date) | 临期查询 | 关联:`wms_inventory.batch_no`。首次收货写入,之后只允许补空缺的日期,不改已有批次号。 并发 / 审计:无软删除。属性变更写 `wms_operation_log`。 ## wms_inventory 库存余额 用途:某一货主、仓库、库位、SKU、批次上的数量结果。余额是结果,不是事实。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | customer_code | VARCHAR(64) | N | | 货主 | | warehouse_code | VARCHAR(32) | N | | 仓库编码 | | location_code | VARCHAR(64) | N | '' | 库位;在途为空字符串 | | sku_code | VARCHAR(64) | N | | SKU 编码 | | batch_no | VARCHAR(64) | N | '' | 批次;无批次为空字符串 | | available_qty | DECIMAL(16,4) | N | 0 | 可用 | | reserved_qty | DECIMAL(16,4) | N | 0 | 预占 | | picked_qty | DECIMAL(16,4) | N | 0 | 已拣 | | hold_qty | DECIMAL(16,4) | N | 0 | 冻结 | | damaged_qty | DECIMAL(16,4) | N | 0 | 残损 | | in_transit_qty | DECIMAL(16,4) | N | 0 | 在途 | | version | INT UNSIGNED | N | 0 | 乐观锁,随数量更新递增 | | created_at | TIMESTAMP | Y | | | | updated_at | TIMESTAMP | Y | | | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | uk_inventory_grain | UNIQUE | (tenant_code, customer_code, warehouse_code, location_code, sku_code, batch_no) | 定位余额行 | | idx_tenant_wh_sku | INDEX | (tenant_code, warehouse_code, sku_code) | 仓库内某 SKU 库存 | | idx_tenant_customer_sku | INDEX | (tenant_code, customer_code, sku_code) | 货主下某 SKU 库存 | 关联:维度对应仓库、库位、SKU、批次。在途行的 `location_code` 不引用 `wms_location`。 并发 / 审计: - 禁止先查出数量再在 PHP 里相减后回写。 - 扣减使用单条条件更新,例如可用转预占: ```sql UPDATE wms_inventory SET available_qty = available_qty - :qty, reserved_qty = reserved_qty + :qty, version = version + 1 WHERE tenant_code = :tenant_code AND id = :id AND available_qty >= :qty; ``` - 受影响行数为 0:库存不足或版本冲突,抛业务异常,整单事务回滚。 - 同一事务插入对应的 `wms_stock_log`。 - 各数量桶不得为负。不软删除;数量全为 0 的行保留,避免并发下反复插入同一粒度。 ## wms_stock_log 库存流水 用途:库存变化的事实。任何余额变化都要有流水。只追加。 一次业务动作若改变多个数量桶或多行余额,每个桶一行,共用同一个 `movement_no`。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | movement_no | VARCHAR(64) | N | | 同一次动作的组号 | | customer_code | VARCHAR(64) | N | | 货主 | | warehouse_code | VARCHAR(32) | N | | 仓库 | | location_code | VARCHAR(64) | N | '' | 库位 | | sku_code | VARCHAR(64) | N | | SKU | | batch_no | VARCHAR(64) | N | '' | 批次 | | qty_bucket | VARCHAR(32) | N | | `available`、`reserved`、`picked`、`hold`、`damaged`、`in_transit` | | qty_before | DECIMAL(16,4) | N | | 该桶变更前 | | qty_delta | DECIMAL(16,4) | N | | 有符号变化量 | | qty_after | DECIMAL(16,4) | N | | 该桶变更后 | | biz_type | VARCHAR(32) | N | | 业务类型 | | biz_no | VARCHAR(64) | N | | 业务单号 | | biz_line_no | INT UNSIGNED | Y | | 单据行号 | | operator_user_code | VARCHAR(64) | Y | | 操作人 | | request_id | VARCHAR(64) | Y | | 请求标识 | | created_at | TIMESTAMP | Y | | 发生时间 | | updated_at | TIMESTAMP | Y | | 行创建后不更新 | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | idx_tenant_sku_created | INDEX | (tenant_code, sku_code, created_at) | 某 SKU 的库存为何变化 | | idx_tenant_biz | INDEX | (tenant_code, biz_type, biz_no) | 某单据的流水 | | idx_tenant_movement | INDEX | (tenant_code, movement_no) | 同一次动作的各行 | `biz_type`:`inbound_confirm`、`inbound_receive`、`inbound_putaway`、`reserve`、`release`、`pick`、`ship`、`hold`、`unhold`、`damage`、`transfer`、`adjust`、`stocktake`。 关联:`biz_no` 指向入库单、出库单、调整单或盘点单。 并发 / 审计:禁止更新和删除。`qty_after = qty_before + qty_delta`,且 `qty_after` 等于同一事务中余额桶的新值。 ## wms_stock_adjust 库存调整单 用途:人工调整的单据头。确认时才改余额并写流水。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | adjust_no | VARCHAR(64) | N | | 调整单号 | | warehouse_code | VARCHAR(32) | N | | 仓库 | | customer_code | VARCHAR(64) | N | | 货主 | | status | VARCHAR(32) | N | draft | 状态 | | reason | VARCHAR(500) | N | | 原因 | | version | INT UNSIGNED | N | 0 | 乐观锁 | | created_at | TIMESTAMP | Y | | | | updated_at | TIMESTAMP | Y | | | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | uk_tenant_adjust_no | UNIQUE | (tenant_code, adjust_no) | 按单号查找 | | idx_tenant_status_created | INDEX | (tenant_code, status, created_at) | 列表 | 状态:`draft → confirmed`,`draft → cancelled`。`confirmed`、`cancelled` 为终态。 确认时按明细对指定数量桶做原子增减,`biz_type = adjust`。减少时受影响行数为 0 则整单回滚。状态变更写 `wms_status_log`。不软删除。 ## wms_stock_adjust_line 调整明细 用途:调整单的一行,只改一个数量桶。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | adjust_no | VARCHAR(64) | N | | 调整单号 | | line_no | INT UNSIGNED | N | | 行号,单内从 1 递增 | | location_code | VARCHAR(64) | N | '' | 库位 | | sku_code | VARCHAR(64) | N | | SKU | | batch_no | VARCHAR(64) | N | '' | 批次 | | qty_bucket | VARCHAR(32) | N | | 数量桶 | | qty_delta | DECIMAL(16,4) | N | | 有符号数量,禁止 0 | | created_at | TIMESTAMP | Y | | | | updated_at | TIMESTAMP | Y | | | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | uk_tenant_adjust_line | UNIQUE | (tenant_code, adjust_no, line_no) | 定位明细 | 关联:头表 `wms_stock_adjust`。确认后明细不再修改。 ## wms_stocktake 盘点单 用途:盘点单据头。确认时把账面与实盘的差额写成流水,`biz_type = stocktake`。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | stocktake_no | VARCHAR(64) | N | | 盘点单号 | | warehouse_code | VARCHAR(32) | N | | 仓库 | | status | VARCHAR(32) | N | draft | 状态 | | version | INT UNSIGNED | N | 0 | 乐观锁 | | created_at | TIMESTAMP | Y | | | | updated_at | TIMESTAMP | Y | | | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | uk_tenant_stocktake_no | UNIQUE | (tenant_code, stocktake_no) | 按单号查找 | | idx_tenant_status_created | INDEX | (tenant_code, status, created_at) | 列表 | 状态:`draft → counting → confirmed`,`draft` 或 `counting` 可到 `cancelled`。`confirmed`、`cancelled` 为终态。 盘点期间不锁死全仓。确认时按当时账面重算差额;若并发导致目标桶不足,确认失败并回滚。不软删除。 ## wms_stocktake_line 盘点明细 用途:一个库位、SKU、批次、数量桶上的账面与实盘。 | 字段 | 类型 | 可空 | 默认 | 说明 | | --- | --- | --- | --- | --- | | id | BIGINT UNSIGNED | N | AUTO_INCREMENT | 内部主键 | | tenant_code | VARCHAR(32) | N | | 租户编码 | | stocktake_no | VARCHAR(64) | N | | 盘点单号 | | line_no | INT UNSIGNED | N | | 行号 | | customer_code | VARCHAR(64) | N | | 货主 | | location_code | VARCHAR(64) | N | | 库位 | | sku_code | VARCHAR(64) | N | | SKU | | batch_no | VARCHAR(64) | N | '' | 批次 | | qty_bucket | VARCHAR(32) | N | available | 盘点的数量桶 | | system_qty | DECIMAL(16,4) | N | | 生成明细时的账面 | | counted_qty | DECIMAL(16,4) | Y | | 实盘,未盘为空 | | created_at | TIMESTAMP | Y | | | | updated_at | TIMESTAMP | Y | | | 索引: | 名称 | 类型 | 字段 | 支撑的查询 | | --- | --- | --- | --- | | uk_tenant_stocktake_line | UNIQUE | (tenant_code, stocktake_no, line_no) | 定位明细 | | idx_tenant_stocktake_loc | INDEX | (tenant_code, stocktake_no, location_code) | 按库位录入 | 关联:头表 `wms_stocktake`。确认时差额为 `counted_qty - 当前账面`,不以生成时的 `system_qty` 作为扣减依据。`system_qty` 只说明生成时刻的账面。