库存确认契约

各业务模块确认时如何写 stock_ledgers、如何更新 stocks,只在本文定义一次;各模块写「确认见本文」。表字段见 04-仓库.md。

重量:库内 kg;界面可按斤展示(1 kg = 2 斤)。

真源

  1. 业务头表是业务真源(purchases / orders / process_orders / 调拨 / 盘点,或精简 stock_documents)。
  2. 唯一写入路径:确认(或调拨发出/收货)→ 写 stock_ledgers → 同步 stocks。
  3. 流水必填:source_type、source_id、source_no、purchase_id(对应已有 purchases.id)。
  4. 禁止绕过流水改结存;禁止确认后静默改已入账流水。

流水类型(全集)

type direction 说明
opening_in in 期初
purchase_in in 采购入库
process_out out 加工投入
process_in in 加工产出
sale_out out 销售出库
return_in in 退货/拒收入库
loss_out out 报损/加工损耗
transfer_out / transfer_in out / in 调拨发出 / 收货
split / merge 成对出入 同产品拆/合包装
check_adjust in 或 out 盘点差异

在途只落 stock_in_transits,不设 in_transit_* 流水。

冲销:写反向流水,type 仍用业务类型,source_no 带原单号 + -R(或记冲销关联);禁止改旧流水。

确认事件矩阵

事件 source 流水 结存
采购确认 purchases 每行 purchase_in(purchase_id=本单 id) 入库仓该批 +
销售出库 orders(external)待出库点出库 每行 sale_out(仓、批在明细;产品在头表) 该行出库仓该批 −
退货/拒收回库 orders(return / reject)子单 每行 return_in 入库仓该批 +
财务驳回采购 purchases 冲销全部 purchase_in −
财务驳回销售 orders 冲销全部 sale_out +
财务驳回退货/拒收 orders 冲销全部 return_in −
加工确认 process_orders process_out + process_in;损耗则 loss_out;source_no=(string) id ±
调拨发出 / 收货 stock_transfers transfer_out / transfer_in + 在途行 ±
盘点确认 stock_checks check_adjust 调至实盘
期初 / 报损 / 拆合 stock_documents opening_in / loss_out / split/merge 对应

销售出库即 sale_out(P0 不做销售在途)。人审通过只到待出库,不出库。

谁拥有头表

场景 头表 用 stock_documents?
采购 / 销售 / 加工 purchases / orders / process_orders 否
调拨 / 盘点 各自头表 否
期初 / 报损 stock_documents 是

确认与驳回冲销由共用写流水服务按 source 分支执行。

约定

  1. 幂等:同一业务单同一动作(确认 / 驳回冲销)只成功一次。
  2. 不得负库存(P0);扣减须命中具体结存键 (warehouse_id, product_id, purchase_id, spec_name, grade, pack)。
  3. 结存键与行属性:唯一键 (仓, 产品, purchase_id, spec_name, grade, pack)(不含单位);空属性与 unit 存 ''。入账写入流水;入库按属性命中或新建堆;冲销保留原属性。口径见 04。
  4. 批次:
    • purchase_in:purchase_id = 本采购 id;行属性可从采购明细带入。
    • 出库类(sale_out / process_out / loss_out / transfer_out):销售出库仓、采购批、规格/等级/包装都在明细行;产品取销售单头表。一单可多仓、多批。
    • 入库类(process_in / transfer_in):继承投入/源行 purchase_id;产出或目的仓可写/继承行属性。
    • return_in:优先继承原销售明细批次,否则行上必填。
    • opening_in:行上必填 purchase_id(无批则先建采购再期初)。
  5. 重量守恒:加工 from_weight_kg ≈ to_weight_kg + loss_weight_kg(件数可不守恒;多产出损耗按产出重量比例分摊)。重量语义为斤。降级走加工,不另设流水类型。
  6. 冲销:禁止改旧流水。财务驳回采/销 = 冲销 + 单据回 draft。主动作废已确认单同样冲销。
  7. 标称重量不可代替实重。

通用状态机

采购

draft → confirmed     # 写矩阵确认行;归档(经人审则 pending → confirmed)
confirmed → draft     # 仅财务驳回:先冲销再回草稿(必填意见)
draft → cancelled     # 从未入账

销售(对外)

draft → pending → to_ship → shipped → accepted   # 人审 → 出库(sale_out) → 可选验收
pending|作废 → draft                             # 审批驳回/作废:回草稿,无流水
shipped|accepted → 退拒子单                      # return_in;整单一次
已出库财务驳回 → draft                           # 先冲销 sale_out 再回草稿(见矩阵)
  • shipped / accepted:不可改影响流水的头表明细;纠错走财务驳回或退拒。
  • draft:可改;须有明细才能再提交。

调拨

draft → shipped → received;draft → cancelled。发出后不可直接取消。

唯一约束(实现必建)

表 约束
stocks unique (warehouse_id, product_id, purchase_id, spec_name, grade, pack)
products unique code
product_users unique (product_id, user_id)
stock_snapshots unique (warehouse_id, product_id, snapshot_date)
单号 purchase_no / order_no / transfer_no / check_no / document_no / claim_no / request_no / expense_no / invoice_no 各自全局唯一(含已软删)。加工单无业务单号;流水 source_no 用 (string) process_orders.id

成本口径(P0)

不做存货单位成本 / 移动平均入账。盈亏实收 = 单据金额 + 已确认挂单经营费用(可负)+ 已付/已批费用申请 + 报损等(规则 16)。

结存可存件数单价作预估:process_fee_unit_price、pack_fee_unit_price、overhead_unit_price。出库预估 单价 × 件数;不入支付、不进 document_expenses / expenses、不写成本层;报表须标「参考」。调拨/加工产出带出单价。各档加工写到对应产出结存堆。详见 04。

验收标准

  • [ ] 确认与财务驳回冲销均有明确流水;幂等。
  • [ ] 无 in_transit_* 流水。
  • [ ] 采/销出库(或采购 confirmed)后业务不可改;审批驳回与财务驳回均可回 draft 再走。
  • [ ] 三费件数单价仅预估参考,不当作存货成本入账。
  • [ ] 行属性随流水落库;结存键含行属性、不含单位。