本手册从一个采购单号出发,逐层回答:单据是否创建、状态为什么停住、明细数量去了哪里、支付/订单中心/配送消息是否生效、入库是否重复、库存是否一致、关闭和退货是否完成、特殊来源是否释放。

适用于普通采购、在线支付采购、急件、自提、云仓直发、预订单、赠品、秒杀、首配、自制订单和各类采购退货。本文强调“先证明事实,再决定补偿”,不提供未经核对即可执行的生产修改语句。

1. 业务目标

  • 拿到 billNo、采购 ID、支付单号、外部订单号任一标识后,能找回整条采购链。
  • 明确主单、采购明细、数量账、出库、入库、关闭、支付、库存和来源业务之间的关联键。
  • 用状态和数量守恒判断问题发生在创建、审核、支付、出库、入库、关闭还是退货。
  • 把“消息没来”“消息来了但被 ACK”“事务部分成功”“查询分表错了”区分开。
  • 修复前保留证据,修复后同时核对业务单、库存、资金和下游系统。

2. 适用现象

现象首查位置高概率原因
提交后列表看不到主单、sid、Session、保存事务查询站点错误、草稿/软删、保存回滚
一直待支付支付关系、支付回调、支付状态回调未到、单号不匹配、重复回调状态冲突
支付成功仍待支付PayCenterNotify、Payment 表消息 NACK、事务回滚、迟到支付
一直审核中/待处理订单中心回调和所有权routing key、采购服务迁移、审核结果未落库
外部已发货仍待出库配送中心消息、出库明细消息未消费、行号重复/缺失、状态前置不符
已出库但不能入库出库量、已入库量、仓库货位可入数量为 0、分表错误、仓库不匹配
入库提示超过发货量出库明细累计、重复入库单并发提交、历史重复 CG 单、软删数据遗漏
入库后库存没增加入库单、库存流水、实时库存事务回滚、库存维度错误、后置事件失败
主单完成但数量未收齐数量账和关闭量状态提前推进、部分明细遗漏、历史脏数据
关闭后在途仍存在关闭明细、数量账、库存预警事件close_qty 未更新、后置 MQ 未发
退货完成库存未减少退货出库、库存流水、退款出库事务失败、退货状态与资金状态分离
秒杀采购关闭但名额未释放Flash 关系和锁量来源关联缺失、关闭补偿未执行

3. 排查前必须收集的信息

3.1 最小事实卡

环境:
服务站 sid:
采购单号 billNo:
采购单 ID:
订单类型 orderType:
来源类型 srcOrderType:
来源单号 srcOrderNo / srcOrderId:
支付类型 paymentType:
当前状态 billStatus:
异常 SKU / invId:
外部订单号/支付单号/配送单号:
首次发现时间:
最近一次操作和操作者:
预期结果:
实际结果:

3.2 不要一开始就做的事

  • 不要先改 billStatus。
  • 不要先把入库单软删。
  • 不要先重放整批 MQ。
  • 不要只根据页面文案判断真实状态。
  • 不要忽略 sid 直接查默认分表。
  • 不要把 sign_qty 与 in_qty 相加当成当前已入库量。
  • 不要把支付中心“支付成功”直接等价为采购全部履约成功。

4. 采购主键和分表定位

4.1 表定位规则

数据物理表分表规则
采购主单t_scm_po_order不按 sid 分表
采购扩展t_scm_po_order_ext以当前代码/DDL 为准
采购明细t_scm_po_order_info_<n>n = sid % 32
采购数量账t_scm_po_order_info_qty当前 Model 不分表
采购出库明细t_scm_po_out_info_<n>n = sid % 32
关闭明细t_scm_po_close_info当前 Model 不分表
采购入库主单t_scm_pu_invoice_<n>n = sid % 32
采购入库明细t_scm_pu_invoice_info_<n>n = sid % 32
支付主明细t_scm_payment_<n> 等n = sid % 32,以 Model 为准
库存/实时库存库存分表sid % 128 或领域 Model 规则

例如 sid=100035:

采购/入库分表后缀 = 100035 % 32 = 3
库存分表后缀     = 100035 % 128 = 67

4.2 关联键总图

flowchart LR
    A["SCM_PO_ORDER.id"] --> B["PO_INFO.iid"]
    A --> C["PO_INFO_QTY.po_order_id"]
    A --> D["PO_OUT_INFO.srcOrderId"]
    A --> E["PO_CLOSE_INFO.srcOrderId/iid"]
    A --> F["PU_INVOICE.srcOrderId"]
    B --> G["srcOrderEntryId / orderEntryId"]
    G --> H["出库/关闭/入库明细行"]
    A --> I["PO_ORDER_PAYINFO.po_order_id"]
    A --> J["FLASH_ORDER.po_order_id"]
    A --> K["来源业务 srcOrderId/srcOrderNo"]

不同历史代码会同时使用 iid、orderId、po_order_id、srcOrderId 表示采购主单 ID。排查时应根据表和方法语义确认,不可只凭字段名字面判断。

5. 状态字典

值常量业务含义允许的下一步
0BILLSTATUS_DRAFT草稿修改、删除、提交
1BILLSTATUS_SUBMITED待处理/待审核审核或来源系统处理
2BILLSTATUS_AUDITING审核中等订单中心/OA
3BILLSTATUS_AUDIT_PASS待出库供应方出库、关闭未发量
4BILLSTATUS_DELIVERY配送中入库、部分关闭、物流更新
5BILLSTATUS_FINISHED已完成通常只读、按规则发起售后
8BILLSTATUS_AUDIT_UNPASS审核不通过查看原因、按规则重新操作
9BILLSTATUS_CLOSED已关闭终态,核对退款和来源释放
10BILLSTATUS_WAIT_PAY待支付支付、取消、超时
11BILLSTATUS_WAIT_REFUND待退款等支付中心退款
12BILLSTATUS_PAY_OVERTIME支付超时关闭/来源释放;警惕迟到支付
13BILLSTATUS_WAIT_STORAGE_IN退货待仓库入库仓库确认和退款

5.1 状态主链

stateDiagram-v2
    [*] --> 草稿
    草稿 --> 待支付: 在线支付提交
    草稿 --> 待处理: 挂账/无需先支付
    待支付 --> 待处理: 支付成功
    待支付 --> 支付超时: 超时任务
    待支付 --> 已关闭: 主动取消/异常关闭
    待处理 --> 审核中: 外部审核受理
    审核中 --> 待出库: 审核通过
    审核中 --> 审核不通过: 审核拒绝
    待出库 --> 配送中: 发生出库
    配送中 --> 已完成: 全部入库/履约
    待出库 --> 已关闭: 全部未发关闭
    配送中 --> 已关闭: 剩余量关闭且业务终止

状态只是汇总结果,必须与明细数量账互证。手工把状态改成 5 不会自动生成入库单或库存流水。

6. 数量账模型

6.1 字段含义

字段方向含义
qty基准原采购数量
wait_out_qty当前量待出库数量
going_out_qty当前量已出库配送中数量
sign_qty累计量累计签收量,不能参与当前量守恒求和
in_qty当前量当前已入库且未转入退货的数量
close_qty终结量未发货关闭数量
going_refund_qty当前量正在退货数量
finish_refund_qty终结量已完成退货数量

6.2 字段迁移

flowchart LR
    A["wait_out_qty"] -->|供应方出库 q| B["going_out_qty"]
    B -->|签收/入库 q| C["in_qty"]
    B -->|签收累计| D["sign_qty += q"]
    A -->|未发关闭 q| E["close_qty"]
    A -->|未发退货 q| F["going_refund_qty"]
    C -->|已入库退货 q| F
    F -->|退货完成 q| G["finish_refund_qty"]

6.3 守恒检查

对正常历史数据,可用以下关系做异常筛查:

qty ≈ wait_out_qty
    + going_out_qty
    + in_qty
    + close_qty
    + going_refund_qty
    + finish_refund_qty

其中:

  • sign_qty 是累计签收事实,不加入右侧。
  • 赠品按 sku_id + is_gift 分开核对。
  • 历史迁移、旧逻辑和部分特殊订单可能未完整维护数量账,应再用出库/入库/关闭明细反算。
  • 浮点数量、包装单位转换要先统一基本单位。

7. 一页式诊断树

flowchart TD
    A["拿到 billNo + sid"] --> B["查 SCM_PO_ORDER"]
    B --> C{"主单存在?"}
    C -->|否| D["查保存日志/来源单/环境/sid"]
    C -->|是| E["记录状态、类型、来源、支付方式"]
    E --> F["计算 sid%32,查明细与数量账"]
    F --> G{"状态停在哪"}
    G -->|0| H["草稿保存/提交权限与参数"]
    G -->|10/12| I["支付与超时"]
    G -->|1/2/8| J["订单中心审核"]
    G -->|3/4| K["出库、配送、入库"]
    G -->|5| L["核对数量是否真正完成"]
    G -->|9/11/13| M["关闭、退款、退货"]
    K --> N["出库表 -> 入库表 -> 库存流水"]
    M --> O["关闭明细 -> 支付退款 -> 来源释放"]
    L --> P["若数量不守恒,按行定位脏数据"]

8. 基础查询包

以下为只读模板。先用主表查 sid,再计算后缀,不要猜分表。

8.1 主单

SELECT id, sid, billNo, billStatus, orderType, transType,
       paymentType, srcOrderType, srcOrderId, srcOrderNo,
       totalQty, totalAmount, amount, createTime, updateTime, isDelete
FROM t_scm_po_order
WHERE billNo = '<采购单号>';

若目标库字段名与此不同,先 SHOW COLUMNS,不要直接改 SQL。

8.2 明细

SELECT id, iid, sid, billNo, invId, skuId, isGift, qty,
       price, amount, locationId, srcOrderEntryId, srcOrderId, srcOrderNo, isDelete
FROM t_scm_po_order_info_<sid_mod_32>
WHERE iid = <采购单ID>
ORDER BY id;

8.3 数量账

SELECT po_order_id, bill_no, sku_id, is_gift, qty,
       wait_out_qty, going_out_qty, sign_qty, in_qty,
       close_qty, going_refund_qty, finish_refund_qty
FROM t_scm_po_order_info_qty
WHERE po_order_id = <采购单ID>
ORDER BY sku_id, is_gift;

8.4 出库、关闭和入库

SELECT *
FROM t_scm_po_out_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
ORDER BY id;

SELECT *
FROM t_scm_po_close_info
WHERE (srcOrderId = <采购单ID> OR iid = <采购单ID>) AND isDelete = 0
ORDER BY id;

SELECT *
FROM t_scm_pu_invoice_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
ORDER BY id;

SELECT *
FROM t_scm_pu_invoice_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
ORDER BY id;

8.5 行级汇总

SELECT skuId, isGift,
       SUM(qty) AS po_qty
FROM t_scm_po_order_info_<sid_mod_32>
WHERE iid = <采购单ID> AND isDelete = 0
GROUP BY skuId, isGift;

SELECT skuId, isGift,
       SUM(qty) AS out_qty
FROM t_scm_po_out_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
GROUP BY skuId, isGift;

SELECT skuId, isGift,
       SUM(qty) AS in_qty
FROM t_scm_pu_invoice_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
GROUP BY skuId, isGift;

9. 创建、保存和提交问题

9.1 入口

入口方法作用
PCInvPo::saveInvPo保存草稿
PCInvPo::updateInvPo修改草稿
PCInvPo::submitInvPo提交采购单
AppapppurOrderAddDraftApp 草稿映射;当前映射目标文件需核对
AppapppurOrderAddApp 正式提交;当前映射目标文件需核对
新采购controllers/po/PurchaseOrder.php新采购服务适配

9.2 保存链路

sequenceDiagram
    participant U as 采购员
    participant C as InvPo
    participant V as InvPoValidate
    participant P as PoOrderSer
    participant S as NormalOrderSaveService
    participant DB as 采购表
    U->>C: postData JSON + Session
    C->>V: 仓库/订单类型/备注校验
    C->>P: checkOrderGoods
    P-->>C: 规范化商品明细
    C->>S: savePoOrder
    S->>DB: 主单/明细/扩展
    DB-->>U: 草稿 ID 或错误

9.3 保存失败检查表

检查预期异常说明
PU_ADD 等资源权限有权限Controller 直接拒绝
Session JXCSID与操作站一致数据落在别的站或被拒绝
仓库存在、启用、普通仓移动仓不能用于普通采购保存
orderType枚举有效不同类型进入不同 Service
明细 invId/skuId/qty商品有效、数量正数供应关系、包装、起订量失败
急件配送dispatchType/contactId/address 完整客户地址校验失败
事务主单和明细同时存在只有主单或只有明细即部分写入风险

9.4 提交后找不到订单

  1. 查草稿 ID 是否真实返回。
  2. 查主单 isDelete 和 billStatus。
  3. 查提交请求是否使用了同一个 orderId。
  4. 查 Controller 当前分支中的 submitInvPo Service 是否真实存在并部署。
  5. 查是否改走新采购服务或远程 Provider。
  6. 查提交事务和外部创建请求日志。
  7. 查来源业务是否生成了另一个采购单号。

10. 审核问题

10.1 调用链

sequenceDiagram
    participant D as DGJ采购
    participant O as 订单中心
    participant Q as ordercenter_notify
    participant C as OrderCenterNotify/采购服务
    participant DB as 采购表
    D->>O: 创建/提交采购单
    O-->>Q: ordercenter_order_audit
    Q->>C: 审核结果
    C->>DB: 校验来源、订单、前置状态
    C->>DB: 通过->3 / 驳回->8
    C-->>Q: ACK/NACK

10.2 排查步骤

  1. 主单当前应为 1 或 2;如果已经 3、5、9,重复审核不能再次推进。
  2. 查订单中心单号与 billNo/srcOrderNo/srcOrderId 的映射。
  3. 查 sourceChannel 是否为 DGJ 来源。
  4. 查 auditResult 是否能映射到通过或驳回。
  5. 查 ordercenter_notify.ordercenter_order_audit 是否到达当前消费服务。
  6. 采购售后四类回调已迁移到采购服务;不要只看 DGJ 的 ACK。
  7. 查驳回时来源活动、数量账和待支付关系是否需要回滚。

10.3 状态不动的判断

证据结论
订单中心无审核事实上游未处理,不是 DGJ 落库问题
MQ 无消息路由/生产问题
DGJ 有消费日志并 NACK校验、事务或依赖失败
DGJ 立即 ACK 且是已迁移售后方法去采购服务查真实消费者
DB 已是目标状态但页面旧缓存/读库/查询条件问题

11. 支付问题

11.1 关键链路

flowchart LR
    A["采购单状态10"] --> B["getPayInfo/noPagePay"]
    B --> C["支付中心支付单"]
    C --> D["paycenter_pay_result"]
    D --> E["PayCenterNotify"]
    E --> F["Payment + PaymentInfo"]
    E --> G["PoOrderPayInfo"]
    E --> H["采购状态推进"]
    H --> I["活动/预订单后置处理"]

11.2 支付查询包

SELECT *
FROM t_scm_po_order_payinfo
WHERE po_order_id = <采购单ID>
ORDER BY id;

SELECT *
FROM t_scm_payment_<sid_mod_32>
WHERE srcOrderNo = '<采购单号>' OR billNo = '<支付业务单号>'
ORDER BY id;

字段以目标环境 DDL 为准。先查主表,再根据 payment id 查 t_scm_payment_info_<n>。

11.3 支付成功仍待支付

  1. 从支付中心确认 sourceOrderNo 是否等于 DGJ billNo。
  2. 确认 sourceOrderType=01 是采购单,而非预订单 04。
  3. 确认 payStatus=02。
  4. 查 PayCenterNotify::paycenterPayResult 日志。
  5. 查是否因主单已超时、关闭或已支付而状态校验失败并 NACK。
  6. 查 Payment 主明细和采购支付关系是否部分写入。
  7. 查事务是否回滚。
  8. 秒杀来源继续查 FLASH_ORDER 状态和 locked_qty/used_qty。

11.4 迟到支付决策

flowchart TD
    A["支付成功消息迟到"] --> B["采购状态已12/9?"]
    B -->|否| C["按正常回调处理"]
    B -->|是| D["确认支付中心不可撤销事实"]
    D --> E["确认订单中心是否已取消"]
    E --> F["确认活动锁量是否释放"]
    F --> G{"能恢复履约?"}
    G -->|能| H["按领域方案恢复,补齐状态和占用"]
    G -->|不能| I["走退款,不直接改回待处理"]

12. 出库和配送问题

12.1 数量变化

供应方出库 q 时:

wait_out_qty  -= q
going_out_qty += q

同时写 t_scm_po_out_info_<sid%32>,其中 srcOrderOutId 可用于识别调拨中心明细重复。

12.2 出库排查步骤

  1. 查采购状态是否为 3 或 4。
  2. 查原采购行 qty 和 wait_out_qty。
  3. 查配送中心出库消息中的采购单号、明细行号、数量和仓库。
  4. 查 PoOutInfoModel::getCollectionBySrcOrderOutIds 是否判定重复。
  5. 查出库明细是否已写但数量账未更新。
  6. 查事务是否回滚。
  7. 查主单是否从 3 推进到 4。
  8. 多次部分出库按行汇总,不要只取最后一条。

12.3 出库量异常 SQL

SELECT srcOrderEntryId, skuId, isGift, SUM(qty) AS out_qty,
       COUNT(*) AS out_rows
FROM t_scm_po_out_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
GROUP BY srcOrderEntryId, skuId, isGift;

SELECT srcOrderOutId, COUNT(*) AS duplicate_count, SUM(qty) AS qty
FROM t_scm_po_out_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
GROUP BY srcOrderOutId
HAVING COUNT(*) > 1;

13. 入库和库存问题

13.1 入库入口

入口方法特点
PC 采购InvPo::invPoInStroage使用 Redis 锁,调用老采购 Service
WMS/AppappwmsInSto*扫码、选择商品、批量入库
PDApda/Purchase::inStoIn仓内扫码入库
采购入库单scm/InvPu入库单查询/管理

13.2 入库主链

sequenceDiagram
    participant U as 仓库/PDA
    participant C as 入库入口
    participant P as InvPoService
    participant PI as PuInvoice表
    participant INV as InventorySer
    participant Q as 数量账
    participant MQ as inventory_event/dgj_notify
    U->>C: 采购单行 + 入库数量 + 仓库货位
    C->>P: 防重复锁与可入量校验
    P->>PI: 写入库主单/明细
    P->>INV: 写库存流水并增加实时库存
    P->>Q: going_out -= q, sign += q, in += q
    P->>P: 判断主单是否完成
    P->>MQ: 库存预警/入库通知
    P-->>U: 成功或超过数量错误

13.3 两道超收校验

当前 Service 明确检查:

  1. 已入库数量 + 本次入库数量 <= 发货明细数量。
  2. 汇总后 已入库数量 + 本次入库数量 <= 原采购明细数量。

如果提示超量,不应直接去掉校验。先证明是哪一种事实异常:

  • 配送中心重复发货明细。
  • 已存在重复入库单。
  • 已软删入库单仍有库存流水。
  • 并发请求在锁外或不同入口进入。
  • 采购行替换/拆分后关联键错位。
  • 旧历史数据已超收。

13.4 入库后库存没变

flowchart TD
    A["入库接口返回成功"] --> B["PU_INVOICE 主单存在?"]
    B -->|否| C["响应误判/事务回滚"]
    B -->|是| D["入库明细存在且数量正确?"]
    D -->|否| E["部分写入/分表错误"]
    D -->|是| F["SCM_INVENTORY 流水存在?"]
    F -->|否| G["InventorySer 未完成"]
    F -->|是| H["REAL_TIME 仓库货位维度正确?"]
    H -->|否| I["实时库存聚合/维度错误"]
    H -->|是| J["页面缓存/ES/查询条件"]

13.5 库存维度

库存必须按以下组合核对:

sid + invId/skuId + warehouse/storage + location + 批次/效期(如启用)

只按 skuId 汇总可能把不同仓库、货位或批次抵消。

14. 重复入库和超收入库专项

14.1 识别重复 CG 单

SELECT srcOrderId, srcOrderNo, billNo, COUNT(*) AS cnt, SUM(totalQty) AS total_qty
FROM t_scm_pu_invoice_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
GROUP BY srcOrderId, srcOrderNo, billNo
HAVING COUNT(*) > 1;

如果每次入库生成不同 billNo,还要按来源采购行和商品汇总:

SELECT srcOrderEntryId, skuId, isGift, SUM(qty) AS total_in_qty, COUNT(*) AS row_count
FROM t_scm_pu_invoice_info_<sid_mod_32>
WHERE srcOrderId = <采购单ID> AND isDelete = 0
GROUP BY srcOrderEntryId, skuId, isGift;

14.2 四层事实核对

层要回答的问题
采购层原采购量、出库量、关闭量、允许入库量是多少
入库层创建了几张入库单,每张哪些行、数量多少
库存层每张入库单生成了哪些库存流水和实时库存变化
报表层报表读取业务表、历史月表还是 DWD,同步到哪一批

14.3 修复原则

flowchart TD
    A["发现重复入库"] --> B["冻结单据和库存快照"]
    B --> C["确认哪张是合法业务事实"]
    C --> D["识别重复入库主明细"]
    D --> E["识别对应库存流水/实时库存增量"]
    E --> F["设计成对冲销或领域撤销"]
    F --> G["更新数量账和主单状态"]
    G --> H["重算库存预警和报表"]
    H --> I["全链验证"]

不要只删入库表,也不要只减实时库存。过程事实、当前结果和报表来源必须一起处理。

15. 关闭和取消问题

15.1 关闭量公式

对未发采购行,通常可关闭量为:

可关闭量 = 原采购 qty - 已出库 outQty - 已入库 inQty - 已关闭 closeQty

代码中还会根据订单类型、支付方式、外部订单状态和是否已有售后决定能否关闭。

15.2 关闭数据流

flowchart LR
    A["closeInvPo/closeInvPoOrder"] --> B["锁与状态校验"]
    B --> C["PO_CLOSE_INFO"]
    C --> D["wait_out_qty -= q"]
    D --> E["close_qty += q"]
    E --> F{"全部行终结?"}
    F -->|是| G["主单状态9"]
    F -->|否| H["部分关闭"]
    G --> I["po_close_order_confirm"]
    H --> I
    I --> J["inventory_event 重算在途/预警"]
    G --> K["在线支付退款/来源释放"]

15.3 关闭后仍有在途

  1. 查关闭明细是否写入正确 srcOrderEntryId。
  2. 查数量账 close_qty 是否增加、wait_out_qty 是否减少。
  3. 查 PoOrderInfoSer::getOnWayQtyByInvIds 的在途计算是否包含该状态。
  4. 查 sendInventoryEvent(... po_order_id) 是否发送。
  5. 查 SaasOrderNotify::inventory_event 是否重算预警。
  6. 查缓存和报表刷新延迟。

16. 采购退货问题

16.1 类型

orderType含义数量来源
30-Cxx-04其他退货可退业务量
30-Cxx-05滞销品退货已入库可退量/额度
30-Cxx-06不良品退货不良品业务量
30-Cxx-16直采退货直采原单
30-Cxx-17未发货退货wait_out_qty
30-Cxx-18首配退货首配额度和时间

16.2 退货数量迁移

stateDiagram-v2
    [*] --> 待出库量
    待出库量 --> 待退货量: 未发货退货
    已入库量 --> 待退货量: 已入库商品退货
    待退货量 --> 退货完成量: 退货出库/退款完成
    待退货量 --> 待出库量: 未发退货取消
    待退货量 --> 已入库量: 已入库退货取消/驳回

16.3 退货排查步骤

  1. 查退货单自身 billNo/orderType/transType/srcOrderId/srcOrderNo。
  2. 查原采购单和原采购行。
  3. 查 going_refund_qty 从 wait_out_qty 还是 in_qty 转入。
  4. 查订单中心售后实际由采购服务消费还是 DGJ 历史消费者。
  5. 需要退实物时查退货出库库存流水。
  6. 在线支付退货查支付中心退款事实。
  7. 完成后 finish_refund_qty 应增加,going_refund_qty 应减少。
  8. 取消/驳回后数量必须回到原来源桶。

17. 特殊来源采购

17.1 来源识别

来源orderType/srcOrderType额外关联
普通采购30-Cxx-01 / 0无特殊来源
预订单对应预订类型 / 12SCM_PRE_ORDER
赠品活动30-Cxx-75GIFT_ACTIVITY_ORDER_REL
秒杀30-Cxx-77 / 13FLASH_ORDER、FLASH_ORDER_DETAIL
OPS 自制单30-01 等OPS 日志/订单中心单号
首批配货30-Cxx-09首配计划、额度
急件/自提30-Cxx-07、30-Cxx-70地址、配送方式、联系人

17.2 秒杀串查

SELECT id, sid, order_no, bill_no, po_order_id, status, pay_deadline, update_time
FROM t_flash_order
WHERE po_order_id = <采购单ID>
   OR bill_no = '<采购单srcOrderNo>';

SELECT d.*, g.locked_qty, g.used_qty
FROM t_flash_order_detail d
LEFT JOIN t_flash_activity_goods g ON g.id = d.activity_goods_id
WHERE d.order_id = <秒杀订单ID>;

17.3 秒杀数量变化

flowchart LR
    A["提交秒杀订单"] --> B["locked_qty += q"]
    B --> C["生成采购单"]
    C --> D{"支付结果"}
    D -->|成功| E["locked_qty -= q; used_qty += q"]
    D -->|取消/超时| F["locked_qty -= q"]
    E --> G{"采购关闭/退货"}
    G --> H["按关闭行释放 used/locked"]

采购状态正确但秒杀锁量错误时,应使用 releaseByPoOrder、releaseByPoCloseItems 或专项补偿能力,而不是直接改活动数量字段。

18. 按现象执行 SOP

18.1 页面显示待出库,但实际上已全部入库

  1. 查所有入库明细并按采购行汇总。
  2. 查数量账 going_out_qty/in_qty/sign_qty。
  3. 查是否存在未关闭、未入库的赠品行。
  4. 查软删明细是否被页面与状态计算使用不同条件过滤。
  5. 查主单状态更新事务是否失败。
  6. 查是否属于退货单状态 13,不套普通采购完成逻辑。
  7. 只在数量完全守恒后设计状态补偿。

18.2 页面显示已完成,但仍有待入库数量

  1. 不要以主单 5 作为完成证据。
  2. 对每个 sku_id + is_gift 计算守恒。
  3. 查是否有采购行被替换、拆分或软删。
  4. 查关闭量是否漏记。
  5. 查旧代码是否只按某一张入库单推进主单。
  6. 评估是补入库/关闭事实还是回退主单状态。

18.3 关闭成功但退款没到

  1. 确认 paymentType=1。
  2. 查采购状态 11/9/12 和关闭原因。
  3. 查支付关系与原支付单。
  4. 查退款申请是否发给支付中心。
  5. 查 paycenter_refund_result。
  6. 查退款账户/支付明细状态。
  7. 不要重复发起退款,先用支付中心退款单号去重。

18.4 App 能查列表但操作提示接口不存在

  1. 查 tradeCode。
  2. 查 appapis.php 映射。
  3. 拼出 application/service/api/app/...php。
  4. 当前静态审计显示部分旧采购映射目标不存在。
  5. 确认 App 版本是否仍调用旧 code。
  6. 确认是否已迁移到直接 Controller 或新采购服务。

19. 日志与代码定位

19.1 代码命令

rg -n "public function (saveInvPo|submitInvPo|cancelWaitPayOrder|closeInvPo|invPoInStroage)" application/controllers/scm/InvPo.php
rg -n "入库数量超过发货数量|入库数量超过采购数量" application/service/scm/InvPoService.php
rg -n "wait_out_qty|going_out_qty|sign_qty|going_refund_qty" application/models/orders/PoOrderInfoQtyModel.php
rg -n "ordercenter_order_audit|paycenter_pay_result|dispatchcenter_delivery_stock_out" application/controllers/tasks application/KzData/Enums
rg -n "sendInventoryEvent|sendPoCloseOrderConfirm|sendInStorageEvent" application/service/scm/InvPoService.php application/Services
rg -n "ORDERTYPE_FLASH_ORDER|releaseByPoOrder|compensatePaidOrders" application/Services/Activity/FlashSaleSer.php

19.2 日志关键词

billNo + InvPo
billNo + paycenterPayResult
billNo + orderCenterOrderAudit
billNo + dispatchCenterDeliveryStockOut
srcOrderOutId + 出库
采购单ID + 入库数量超过
po_order_id + inventory_event

19.3 时间线模板

时间系统/入口事件状态前状态后数据副作用ACK/响应
T1PC/App保存草稿无0主明细success
T2提交提交采购010/1外部订单/支付关系success
T3支付中心支付成功101/后续PaymentACK
T4订单中心审核通过1/23审核意见ACK
T5配送中心部分出库34PoOut/数量账ACK
T6WMS/PDA入库44/5PuInvoice/Inventorysuccess

把真实数据填进时间线,空白处就是需要继续查的断点。

20. 修复和补偿安全流程

20.1 修复前快照

  • 采购主单完整行。
  • 采购明细和数量账。
  • 出库、关闭、入库主明细。
  • 支付主明细和退款事实。
  • 库存流水、实时库存、库存预警。
  • 来源活动/预订单/渠道关系。
  • 相关 MQ 原始 payload 和消费日志。

20.2 补偿选择

flowchart TD
    A["确定断点"] --> B{"主业务事实存在?"}
    B -->|否| C["重做领域命令/补发原始事件"]
    B -->|是| D{"缺的是哪类副作用"}
    D -->|状态| E["领域状态补偿,先校验数量"]
    D -->|库存| F["库存冲销/补流水,不裸改实时库存"]
    D -->|支付| G["按支付中心事实补账或退款"]
    D -->|后置MQ| H["只补对应事件"]
    D -->|来源释放| I["调用活动/预订单领域补偿"]
    C --> J["单条验证"]
    E --> J
    F --> J
    G --> J
    H --> J
    I --> J

20.3 修复后四层验收

层验收内容
采购业务主单状态、明细数量账、关闭/退货事实
仓储库存入库/出库流水、实时库存、仓库货位
资金支付、退款、授信、采购支付关系
上下游订单中心、配送中心、活动/预订单、App/SAAS

21. 改动风险

改动影响面
InvPoService 入库逻辑PC、App/WMS、PDA、库存、报表
PoOrderInfoQtyModel::changeQty出库、入库、关闭、退货、在途统计
PoOrderSer 关闭/取消普通、在线支付、秒杀、赠品、首配
PayCenterNotify采购、预订单、支付表、活动订单
OrderCenterNotify审核、售后、迁移后的采购服务所有权
DispatchCenterNotify出库量、配送状态、入库上限
采购分表规则所有明细、出库和入库查询
库存写入库存流水、实时库存、预警、报表

22. 回归清单

22.1 普通采购

  • [ ] 草稿保存、修改、删除、提交。
  • [ ] 普通仓、不可用仓、移动仓校验。
  • [ ] 单商品、多商品、赠品、不同包装单位。
  • [ ] 审核通过、驳回、重复审核。
  • [ ] 整单出库、部分出库、多批次出库。
  • [ ] 整单入库、部分入库、多批次入库。
  • [ ] 部分关闭、整单关闭、已出库不可关闭。

22.2 在线支付

  • [ ] 支付成功、失败、异常、取消和超时。
  • [ ] 相同成功回调重复投递。
  • [ ] 超时关闭后迟到支付。
  • [ ] 关闭后退款成功、失败和重复退款回调。
  • [ ] Payment、PaymentInfo、PoOrderPayInfo 金额一致。

22.3 仓储数量

  • [ ] wait_out -> going_out -> in 数量守恒。
  • [ ] 入库不得超过发货量。
  • [ ] 入库不得超过采购量。
  • [ ] 并发重复入库被锁或数据库规则阻止。
  • [ ] 入库失败时采购入库表和库存都回滚。
  • [ ] 库存流水、实时库存、预警同步。

22.4 退货和特殊来源

  • [ ] 未发货退货、已入库退货、驳回和取消。
  • [ ] 退货出库减库存,完成量正确。
  • [ ] 秒杀支付成功、超时、采购关闭和行关闭释放数量。
  • [ ] 赠品活动、预订单、首配、自制单来源关系正确。
  • [ ] App 旧 code 和新入口兼容策略明确。

23. 已确认与待确认

23.1 静态代码已确认

  • 采购主单使用不分表 SCM_PO_ORDER。
  • 采购明细和出库明细按 sid % 32。
  • 采购入库主单、明细按 sid % 32。
  • 数量账 PoOrderInfoQtyModel 当前不分表。
  • 入库 Service 同时校验不得超过发货数量和采购数量。
  • 入库完成后发送库存事件和采购入库通知。
  • 关闭后会发送库存事件,部分场景发送采购关闭确认。
  • 支付中心、订单中心、配送中心通过 MQ 推进采购。
  • 订单中心四个售后回调已迁移到采购服务并在 DGJ 直接 ACK。
  • 秒杀采购使用 30-Cxx-77 和来源类型 13。

23.2 环境和历史数据待确认

  • 当前部署的提交采购真实 Service 和远程采购 Provider 版本。
  • 线上表 DDL、索引、唯一键和字段名。
  • 采购服务对已迁移订单中心消息的队列所有权。
  • 支付、订单、配送 MQ 的重试/死信配置。
  • 历史订单是否完整维护数量账。
  • App 旧采购映射是否仍有调用流量。
  • 入库 Redis 锁粒度、TTL 和多入口覆盖范围。
  • 报表读取业务表、历史月表还是 DWD,以及同步延迟。

24. 证据来源

主题文件
PC 采购入口application/controllers/scm/InvPo.php
采购老 Serviceapplication/service/scm/InvPoService.php
新采购领域application/Services/PoOrders/*
采购状态和类型application/KzData/Enums/PoOrderEnums.php
主单 Modelapplication/models/orders/PoOrderModel.php
明细分表PoOrderInfoModel.php
数量账PoOrderInfoQtyModel.php
出库分表与去重PoOutInfoModel.php
关闭明细PoCloseInfoModel.php
入库分表PuInvoiceModel.php、PuInvoiceInfoModel.php
支付回调application/controllers/tasks/PayCenterNotify.php
订单中心回调application/controllers/tasks/OrderCenterNotify.php
配送回调application/controllers/tasks/DispatchCenterNotify.php
库存写入application/Services/Storage/InventorySer.php
秒杀来源application/Services/Activity/FlashSaleSer.php
表常量application/config/tables.php

25. 一页式执行结论

先查不分表采购主单,拿到 sid 和 id。
再算 sid%32,查采购明细、出库明细、入库主明细。
用数量账判断卡在待出库、配送、入库、关闭还是退货。
状态10/12查支付;状态1/2/8查订单中心;状态3/4查配送和入库;状态9/11/13查关闭退款退货。
主单状态与数量不一致时,以过程事实和数量守恒为准,不先改状态。
任何修复都必须同时核对采购、库存、资金和来源业务。

请求-日志-数据变更追踪卡

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
PC 采购scm/InvPo 保存、提交、关闭、入出库sid、采购单、SKU、数量、动作InvPo.phpInvPoService、PoOrderSer采购 billNo主明细、履约量与状态
App/WMSappapis.php 采购/仓储 code采购单、仓位、实发/实收量App APIInvPoService、InventorySer采购单 + 库存业务单入库/退货及库存变化
支付/OA/ODCMQ 回调支付单、订单单、状态、消息 IDPayCenterNotify、OrderCenterNotify采购/支付 Service来源采购单号支付、审核、关闭状态推进
来源业务秒杀、赠品、预订单来源类型、来源 ID、商品数量来源 Service采购创建 ServicesrcOrderType + 来源 ID特殊采购单与来源关系

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 入口 | Web/App/MQ 日志 | request_id、sid、采购/来源单号、action | 命中对应方法 | 校验、鉴权、未知事件失败 | 单号进入 Service/主表 | | 主明细 | InvPoService/PoOrderSer | billNo、订单类型、当前状态 | commit,主明细数量金额一致 | rollback、状态冲突、重复来源 | 查 SCM_PO_ORDER* | | 支付 | PayCenterNotify | payOrderNo、采购单、payStatus | 支付记录一次落账 | 重复入账、迟到支付、金额不符 | 支付关系查采购状态 | | 履约库存 | 入出库 Service、InventorySer | 单号、SKU、transType、仓位 | 发/收/关数量守恒,库存同步 | 超收/超退、重复流水、实时不一致 | 采购明细 + 库存流水 + 实时表 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
创建提交InvPo.php -> PoOrderSer/保存 Service采购事务供应商、商品、来源唯一性SCM_PO_ORDER*、扩展/地址insert;status 0 -> 1/2/10/13主单、明细、来源关系
支付审核PayCenterNotify/OrderCenterNotify消费事务当前状态、回调结果支付表、采购主表10/12 收支付;1/2/8 按审核结果迁移回调键、状态、金额
发货入库InvPoService -> InventorySer业务与库存事务需确认订购、已发、已收、已关量SCM_PO_OUT_INFO、库存表out/inQty +n;入库库存 +n每 SKU 数量等式、150501 流水
关闭退货close/return Service关闭/退货事务未履约量、已支付、已入库close/return/logistics、退款/库存 MQ关闭量 +n;退货库存 -n 或按路径反向关闭明细、退货、退款、库存四方一致
修复验收领域补偿 Service小批事务支付中心最终态、业务过程事实必要业务表/MQ仅补缺失环节,不直接猜改状态重放零副作用、报表追平

子模块追踪:po-create-check 采购创建、保存与提交排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建提交PC/App 保存或提交采购request ID、sid、来源单号、采购 billNoapplication/controllers/scm/InvPo.php -> application/service/scm/InvPoService.php供应商、商品、来源唯一性、草稿当前态采购本地事务 insert 主明细/扩展/地址;提交 status: 0 -> 1/10/13request ID + sid + source/billNo校验失败零写入;超时按来源键和主单号查是否已提交
数据回查创建响应后核对主明细billNo、明细 entryIdapplication/models/orders/PoOrderModel.php主单、明细、数量账和来源关系查询只读 不写;主明细合计应与请求一致request ID + billNo + affected rows只有主单无明细视为部分失败,优先走领域删除/重建而非手工补行

子模块追踪:po-audit-check 采购审核排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
审核回调订单中心审核通过/拒绝message ID、来源采购单、审核状态application/controllers/tasks/OrderCenterNotify.php -> application/service/scm/InvPoService.php采购当前 1/2/8、外部订单关系、审核版本单消息本地事务 1/2 -> 3 或 1/2 -> 8,重复结果零变化message ID + event + billNo未知/乱序结果不回退;先查 ODC 最终态和本地主单再重放
审核后置通过后进入支付/履约分支billNo、订单类型、支付类型application/KzData/Enums/PoOrderEnums.php主状态、线上/线下支付、来源类型查询判路;必要后置 MQ 在 commit 后,DB 主状态不因发送失败回滚billNo + orderType/paymentType + routing key主状态正确但下游缺失时仅补消息,不重新执行审核

子模块追踪:po-pay-check 采购支付排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
支付回调PayCenter 成功结果message ID、payOrderNo、采购 billNo、payStatus=02application/controllers/tasks/PayCenterNotify.php采购 10/12、已有支付明细、回调金额回调本地事务首次写 Payment/Info/PoOrderPayInfo,采购状态单向推进message ID + payOrderNo + billNo重复回调不重复落账;状态 12 迟到支付先核实关闭/释放副作用
金额回查支付成功仍待支付或金额不平payOrderNo、采购单、账户明细application/models/orders/PoOrderPayInfoModel.php支付中心分单位金额、本地元单位金额、分摊合计查询只读 不写;统一单位后核对总额payOrderNo + sourceOrderNo + amount外部最终成功且本地缺失时用幂等补偿 Service,禁止直接改采购状态

子模块追踪:po-delivery-check 采购出库与配送排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
发货出库WMS/配送中心发货事件message ID、采购单、SKU、实发量application/controllers/tasks/DispatchCenterNotify.php -> application/service/scm/InvPoService.php订购量、已发/已关量、当前状态履约本地事务写 SCM_PO_OUT_INFO,outQty: old -> old+n,主状态到配送态message ID + billNo + SKU + deliveryNo超发或重复事件零增量;先按行级数量账判断是否需重放
配送回查发货后未进入待入库/配送中采购/配送单号、外部状态application/controllers/tasks/DispatchCenterNotify.php本地出库行、配送关系、当前状态查询/映射阶段只读;状态回调单向 old -> new两端单号 + event + timestamp外部已发本地无关系先补关系/回调,不直接补库存入库

子模块追踪:po-inbound-check 采购入库与库存排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
入库提交PC/PDA/WMS 采购入库sid、采购单、SKU、仓库货位、实收量application/controllers/scm/InvPo.php -> application/service/scm/InvPoService.php可收量、已发/已收/已关量、库存维度业务与库存本地事务写入库单和 150501 流水,inQty +n、实时库存 qty +nrequest ID + billNo + SKU + transType 150501超收/维度非法回滚;返回异常先同时查入库单、流水和实时表
完成回查入库后状态或库存未推进采购单、库存业务单、entryIdapplication/Services/Storage/InventorySer.php有效入库量、库存流水、实时四维量查询只读 不写;满足数量等式才允许主状态完成billNo + inventory billNo + entryId有流水无实时或反向时按事实选择库存补偿,不能重复整单入库

子模块追踪:po-overreceipt-check 重复入库与超收排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
防超收同采购行重复提交或并发入库request ID、billNo、entryId、实收量application/service/scm/InvPoService.php -> application/models/orders/PoOrderInfoQtyModel.php订购、已发、已收、已关和本次量,必要时锁行同一本地事务校验并 inQty: old -> old+n;超限零写入request ID + billNo/entryId + old/new qty并发失败回滚;重试前重新读取数量账,禁止沿用旧可收量
历史修复已存在重复 CG 单或超收流水采购单、CG 入库单、库存业务单application/Services/Storage/InventorySer.php四层事实:采购量、入库单、库存流水、实时库存修复事务走反向业务/冲销 qty: old -> corrected,保留原流水原/补偿单号 + SKU + operator/request ID生产写修复需授权;先快照、小批、验证重跑零变化

子模块追踪:po-close-check 采购关闭取消排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
关闭动作用户/ODC 取消关闭采购billNo、关闭原因、未履约量application/controllers/tasks/OrderCenterNotify.php -> application/service/scm/InvPoService.php当前状态、已发/收/关量、已支付额关闭本地事务写 close info,closeQty: old -> old+n、主状态到 9/11/相关态message/request ID + billNo + closeQty已入库部分不反向关闭;并发履约时重新计算剩余量
退款补偿关闭后发起或接收退款原支付单、退款单、关闭单application/controllers/tasks/PayCenterNotify.php原支付成功额、已退额、关闭事实退款事务 refunded: old -> old+n;外部支付调用是事务外边界payOrderNo + refundNo + billNo外部超时先查退款终态;已退款只补本地关系,不重复发起

子模块追踪:po-return-check 采购退货与特殊来源排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
退货出库PC/PDA 创建并执行采购退货原采购单、退货单、SKU、退货量application/controllers/scm/InvPo.php -> application/service/scm/InvPoService.php原入库量、已退量、库存四维量、退货类型退货与库存本地事务写退货/物流和负向流水,returnQty +n、库存 qty -nrequest ID + 原/退货单 + SKU + transType超退或库存不足零写入;部分成功按退货单查流水后补缺失环节
关闭退款退货物流关闭或退款结果退货单、物流单、退款单、message IDapplication/controllers/tasks/DispatchCenterNotify.php退货出库、签收/关闭和原支付事实回调事务推进退货态;退款另一个资金事务 old -> refundedmessage ID + 三种单号配送完成不等于退款完成;两个子链分别重放和验收