本手册从一个销售单号、出库单号或来源订单号出发,完整追踪“销售开单 -> 分仓出库 -> 扣减库存 -> 配送签收 -> 对账核销 -> 销退入库 -> APP/SAAS 同步”。

适用于销售单不能出库、部分出库状态错误、库存已扣但单据失败、配送状态不推进、收款后未核销、销退后库存未恢复、APP/SAAS 未收到事件等问题。本文只提供只读查询和安全判断方法;任何生产修复必须先保存证据、明确业务唯一键并完成多方复核。

1. 业务目标

  • 用 sid + billNo 找到正确的 16 分片销售单和 64 分片出库单。
  • 区分销售订单状态、销售出库状态、配送状态、收款状态和下游状态。
  • 解释每一行商品的订购量、有效出库量、结单量、退货量和库存变化。
  • 判断故障发生在入口校验、事务落库、库存扣减、TMS、资金还是 MQ。
  • 识别“数据库提交但 MQ 未发”和“MQ 已发但数据库回滚”两种跨系统不一致。
  • 为重复出库、重复销退、重复回调和重复同步建立幂等检查依据。
  • 修复后同时验收销售、库存、配送、资金、报表和下游六个视图。

2. 适用现象与首查方向

现象首查对象常见原因
销售单列表看不到t_scm_sa_order_{sid%16}查错分片、isDelete、站点上下文错误
销售单一直待出库订单明细与有效出库明细出库未落库、出库单撤销、来源行关联错误
已出一部分却显示待出库srcOrderEntryId、订单状态回写出库行未关联原销售行、状态统计未执行
已全部出库仍显示部分出库出库量、结单量、退货量口径撤销单被计入、数量符号处理错误、历史脏数据
页面显示有库存但不能出库实时库存、锁定量、货位qty - lockNum 不足、货位删除、仓库维度不一致
出库成功库存没减少出库明细、库存流水、实时库存库存事务失败、维度错误、实时表未同步
接口报错但库存已扣本地事务与异常路径非同一连接、事务边界外写入、历史实现差异
出库成功但 TMS 无配送单站点开关、来源类型、配送创建AI 工作台开关未开、来源不支持、远程调用失败
配送中一直不送达三方配送主表与关系表回调未到、配送号关联错误、状态映射不一致
已送达但未对账出库状态、支付明细、人工操作配送只推进到 3,对账动作未执行
已收全款仍未核销amount/payAmount/diffAmount金额精度、支付单未关联、状态回写失败
撤销对账失败PaymentInfo 与出库状态已存在有效收款,不允许直接回退
销退后库存没恢复150602 出库表记录与库存流水退货事务失败、货位错误、可退量计算错误
APP 未收到出库通知dgj_notify、routing key生产失败、下游未消费、业务键不一致
SAAS 出现重复出库消息 ID 与业务唯一键uniqid() 每次不同,下游未按出库单号幂等

3. 五类单号不要混用

标识常见字段用途
销售单主键saOrder.id关联订单明细和出库单 srcOrderId
销售单号saOrder.billNo页面、日志、下游订单业务键
来源订单主键srcOrderIdE站、APP、机器人、活动或 SAAS 原订单
来源订单号srcOrderNo跨系统人工追踪
出库单主键saInvoice.id关联出库明细 iid 和资金 stlId
出库单号saInvoice.billNo配送、收款、下游出库业务键
原销售单主键saInvoice.srcOrderId由出库单反查销售单
原销售单号saInvoice.srcOrderNo跨页面展示和撤销同步
配送单号delivery_no 等TMS 主表和关系表关联

排查记录至少保留:

环境:
sid:
销售单 id / billNo:
来源 sourceType / srcOrderId / srcOrderNo:
出库单 id / billNo:
配送单号:
客户 buId:
异常 invId / skuId:
仓库 locationId / 货位 locationAreaId:
支付方式 payType / 收款状态 hxStateCode:
首次异常时间:
最近一次操作:
期望结果:
实际结果:

4. 真实分表规则

4.1 先计算两个后缀

销售订单分片 = sid % 16
销售出库分片 = sid % 64

以 sid = 10001 为例:

10001 % 16 = 1
10001 % 64 = 17

销售主单:t_scm_sa_order_1
销售明细:t_scm_sa_order_info_1
出库主单:t_scm_sa_invoice_0_17
出库明细:t_scm_sa_invoice_info_0_17

4.2 表定位字典

业务数据物理表规则是否分片主要关联键
销售主单t_scm_sa_order_{sid%16}16id、sid、billNo
销售明细t_scm_sa_order_info_{sid%16}16iid -> 销售主单.id
销售明细扩展t_scm_sa_order_info_ext当前 Model 不分片iid、invId、vin
销售日志t_scm_sa_order_log以线上 DDL 为准iid、状态和操作类型
销售出库主单t_scm_sa_invoice_0_{sid%64}64,前缀含业务类型 0id、srcOrderId、billNo
销售出库明细t_scm_sa_invoice_info_0_{sid%64}64iid -> 出库主单.id
出库明细扩展t_scm_sa_invoice_info_ext当前 Model 不分片iid、invId、vin
异常退货日志t_scm_abnormal_return_log当前 Model 为准原出库、退货、异常原因
三方配送主表t_scm_third_party_delivery_order当前 Model 不按 sid 拼后缀配送单号、sid
出库配送关系t_sa_invoice_delivery_relation当前 Model 不按 sid 拼后缀出库单、配送单
库存流水主表t_scm_inventory_*参考库存手册,通常按 sid 分片sid、单号、transType
实时库存t_scm_inventory_real_time_*参考 Model 实际后缀sid/inv_id/location_area_id
支付明细t_scm_payment_info_{sid%32}32stlId -> 出库单.id

4.3 常见误查

  • 查 t_scm_sa_order 基表但真实数据在 _0 到 _15。
  • 出库表写成 t_scm_sa_invoice_{sid%64},漏掉中间业务类型 _0_。
  • 用销售单号查出库明细的 billNo,实际应先通过 srcOrderId/srcOrderNo 找出库主单。
  • 扩展表机械追加站点后缀;当前两个 Ext Model 都直接使用常量表。
  • 只查 isDelete = 0,却忘了撤销使用 billStatus = 6 而不是软删。
flowchart LR
    SID["sid"] --> S16["sid % 16"]
    SID --> S64["sid % 64"]
    S16 --> SO["sa_order_n"]
    S16 --> SOI["sa_order_info_n"]
    S64 --> SI["sa_invoice_0_n"]
    S64 --> SII["sa_invoice_info_0_n"]
    SO -->|"id = iid"| SOI
    SO -->|"id = srcOrderId"| SI
    SI -->|"id = iid"| SII

5. 入口、Service 和模型

5.1 入口文件

入口文件作用
PC 销售application/controllers/scm/InvSa.php销售单、出库、销退、对账等老入口
销售出库页面application/controllers/sale/OutOrder.php新销售出库入口
App codeapplication/config/appapis.phptradeCode 到 Controller 的映射
PDA 销售application/Services/Pda/SaleSer.php扫码/移动出库场景
外部/内部 APIapplication/config/apis.php、application/config/appapis.php外部状态、库存和销售相关调用
MQ 任务application/controllers/tasks/*下游和配送回调

5.2 核心业务实现

模块文件排查重点
老销售编排application/service/scm/InvSaService.php入口参数、事务、权限、配送、同步
销售工厂application/Services/InvSaFactory.php根据类型选择销售/销退实现
普通销售出库application/Services/InvSa/NormalSaleSer.php数量校验、负数量明细、库存写入
普通销退application/Services/InvSa/NormalSaleReturnSer.php可退量、正数量入库、退款
新销售领域application/Services/SaOrders/SaOrderSer.php新接口销售/销退编排
出库查询和同步application/Services/SaOrders/SaInvoiceSer.php出库查询、状态和 SAAS 消息
三方配送application/Services/SaOrders/ThirdDeliverySer.php配送创建、关系、取消和状态
库存application/Services/Storage/InventorySer.php流水与实时库存
下游同步application/Services/SyncOrder/*dgj_notify 事件构建和发送
MQ 生产application/Services/Mq/MqSer.phpAPP/SAAS/TMS 相关 routing key
收款application/service/scm/ReceiptService.php收款、退款、核销和撤销对账

5.3 核心 Model

rg -n "class SaOrderModel|class SaOrderInfoModel|class SaInvoiceModel|class SaInvoiceInfoModel" application/models/saOrders
rg -n "SCM_SA_ORDER_SUB_NUM|SCM_SA_INVOICE_SUB_NUM" application/config/tables.php
rg -n "setSid\(|getTable\(|invoice_sub|invoice_info_sub" application/models/saOrders

6. 状态不是一条链,而是五条链

6.1 销售单状态 billStatus

值枚举业务含义允许的后续动作
0SA_STATUS_WAIT待出库编辑、出库、无出库关单
1SA_STATUS_OUT_FEW部分出库继续出库、结单
2SA_STATUS_OUTED已完成查看、收款、售后
3SA_STATUS_CLOSE已关闭通常只读,不再出库

订单状态主要由有效出库量和结单量决定,不能只依赖前端传值。

stateDiagram-v2
    [*] --> 待出库
    待出库 --> 部分出库: 部分有效出库
    待出库 --> 已完成: 一次全部出库
    待出库 --> 已关闭: 无有效出库时关单
    部分出库 --> 部分出库: 再次部分出库
    部分出库 --> 已完成: 全部出库或结单
    已完成 --> [*]
    已关闭 --> [*]

6.2 出库单状态

值枚举业务含义注意
0BILL_STATUS_RE_OUTED历史单据已出库与 1 文案相同,兼容旧数据
1BILL_STATUS_OUTED已出库库存已经扣减
2BILL_STATUS_SENDIND派送中配送状态推进
3BILL_STATUS_ARRIVE已送达可进入对账
4BILL_STATUS_CHECK已对账尚未必全部收款
5BILL_STATUS_OVER已核销资金已结清或金额为 0
6BILL_STATUS_CANCELED已撤销原出库不再计有效履约
stateDiagram-v2
    [*] --> 已出库
    已出库 --> 派送中: 配送接单/发车
    派送中 --> 已送达: 签收回调
    已出库 --> 已送达: 自提/跳过配送
    已送达 --> 已对账: 对账
    已对账 --> 已核销: 收款和优惠覆盖应收
    已出库 --> 已撤销: 撤销出库
    派送中 --> 已撤销: 配送取消且业务允许

6.3 收款状态 hxStateCode

值含义与出库状态的关系
0未收款出库可已完成,但资金未结清
1部分收款通常仍处于对账或未核销
2全部收款满足金额条件时出库单可到 5

6.4 交易类型

transType含义库存方向资金方向
170502销售订单不直接代表库存事实订单金额
150601销售出库/销售收款出库,明细通常为负数应收/收款
150602销售退货/销售退款入库,明细通常为正数应退/退款

6.5 配送和下游状态

配送平台可能有自己的接单、取货、配送、签收、取消状态;下游 APP/SAAS 也有订单和出库状态。它们只能通过业务键映射,不能直接把外部数字写进本地 billStatus。

flowchart TB
    SO["销售单状态 0/1/2/3"]
    OUT["出库状态 0..6"]
    PAY["收款状态 0/1/2"]
    TMS["配送平台状态"]
    DOWN["APP/SAAS状态"]
    SO --> OUT
    OUT --> PAY
    OUT --> TMS
    SO --> DOWN
    OUT --> DOWN
    TMS -.回调映射.-> OUT
    PAY -.金额判断.-> OUT

7. 来源类型与下游映射

sourceType来源支付/场景下游订单类型
0销售开单普通线下7
1快维 ERP外部 ERP当前同步逻辑会排除部分事件
8E站商城挂账2
9E站商城微信2
10E站维修马上送挂账4
14E站活动活动订单5
15E站维修马上送微信4
16AI 助手/机器人挂账8
17/18E站 APP IM挂账/微信9
19/20E站 APP 商城挂账/微信2
21/22E站 APP 活动挂账/微信5

历史值 2、5、6、7、11、12、13 仍可能存在于数据库,不能因为当前页面不再创建就忽略。

风险点:SyncOrderBuilder::ORDER_TYPE_MAP 不是对所有历史 sourceType 都天然完备。执行关单、结单同步前,应确认当前值存在映射,否则数组索引异常可能让本地状态已改、下游消息未发。

8. 正向销售主流程

sequenceDiagram
    participant U as PC/App/PDA
    participant C as Controller
    participant S as InvSaService
    participant O as 销售订单16分片
    participant I as 销售明细16分片
    participant M as dgj_notify
    U->>C: 创建/编辑销售单
    C->>S: 注入 sid/uid 后校验
    S->>S: 客户/商品/价格/来源校验
    S->>O: 写主单
    S->>I: 写明细
    S->>O: 写扩展/日志/关联
    S->>M: garage_repair_order_created/updated
    M-->>U: 下游异步同步

8.1 创建前校验

  • Session 注入的 sid 必须覆盖或校验客户端站点。
  • 客户 buId、仓库、商品 invId/skuId 必须属于当前站点。
  • 数量大于 0;金额、折扣、优惠和总额应能重算。
  • 来源订单的 srcOrderId/srcOrderNo/sourceType 要形成稳定唯一关系。
  • 销售站点黑名单、系统锁定和权限菜单会阻断出库。
  • 0 元行在下游构建中会被视为赠品,需要确认业务是否允许。

8.2 主单写入字段

字段用途
idSnowflake 主键
sid分片和租户边界
billNo销售单业务号
buId客户/修理厂
sourceType来源类型
srcOrderId/srcOrderNo来源系统关联
billStatus订单状态,初始通常 0
payType挂账/现金/微信
hxStateCode收款状态
totalAmount/disAmount/totalDiscount金额与优惠
billNo_type销售、铺货、微仓铺货
delivery_info配送相关上下文,具体结构以 DDL/代码为准

8.3 明细写入字段

字段用途
iid销售主单 ID
id销售行 ID,出库行通过 srcOrderEntryId 关联
invId/skuId站内物料和中心 SKU
qty订购数量,订单层通常为正
price/deduction/amount单价、行优惠、金额
srcOrderId/srcOrderNo来源行/来源单关联
vin/carModel车型信息,部分写入扩展表

8.4 创建后的验证

-- 将 :so_suffix 替换为 sid % 16,只读执行
select id, sid, billNo, sourceType, srcOrderId, srcOrderNo,
       billStatus, payType, hxStateCode, totalQty, totalAmount,
       createTime, modifyTime, isDelete
from t_scm_sa_order_:so_suffix
where sid = :sid and billNo = :sale_bill_no;

select id, iid, billNo, invId, skuId, qty, price, deduction, amount,
       srcOrderId, srcOrderNo, isDelete
from t_scm_sa_order_info_:so_suffix
where sid = :sid and iid = :sale_order_id
order by id;

预期:主单唯一、有效明细非空、sum(qty) 与主单总数量一致、行金额汇总能解释主单金额。

9. 销售出库请求与校验

9.1 脱敏请求结构

InvSaService::addOutBound() 接收页面 postData JSON,并注入登录上下文。示例仅用于理解字段:

{
  "saleOrId": "1000000000001",
  "saleOrNo": "XS202607150001",
  "buId": 20001,
  "billNo_type": 0,
  "entries": [
    {
      "srcOrderEntryId": "1000000000101",
      "invId": 30001,
      "skuId": "SKU-DEMO-001",
      "qty": 2,
      "totalQty": 5,
      "lockNum": 0,
      "price": "100.00",
      "amount": "200.00",
      "qtyGrid": [
        {
          "locationId": 10,
          "locationAreaId": 101,
          "outqty": 2
        }
      ]
    }
  ]
}

9.2 入口层已确认校验

  1. PC 校验 SA_OUT_ADD 权限;App 走权限 ID 248。
  2. Redis HASH_SALE_STATION_BLACKLIST 命中时阻止销售。
  3. OpsMenuRuleNewModel 的 lock_status = 1 时阻止销售。
  4. 每行必须有 qtyGrid,否则提示货位已删除。
  5. qty <= totalQty - lockNum,否则提示锁定数量导致库存不足。
  6. 批量检查货位是否已删除。
  7. NormalSaleSer 再校验可出数量、原销售行和库存。

9.3 出库事务主链

sequenceDiagram
    participant U as 操作端
    participant S as InvSaService
    participant F as NormalSaleSer
    participant DB as 销售/出库/库存DB
    participant T as TMS
    participant MQ as dgj_notify
    U->>S: addOutBound(postData)
    S->>S: 权限/黑名单/锁定/货位校验
    S->>DB: begin transaction
    S->>F: addInvoice(data)
    F->>DB: 写出库主单和负数明细
    F->>DB: 写库存流水并扣实时库存
    F->>DB: 回写销售订单状态
    S->>T: 条件满足时创建/更新配送单
    S->>MQ: outbound created/updated
    S->>DB: commit
    S-->>U: 出库单 id/billNo

9.4 事务边界风险

当前入口在本地数据库事务提交前调用 TMS 处理和 SyncOrderSer 发布消息。由此必须考虑:

场景本地 DB外部/MQ结果
全部成功提交成功正常
MQ 抛异常回滚失败本地无单,通常可重试整个操作
MQ 成功后 DB 提交失败回滚已成功下游出现本地不存在的出库事件
TMS 成功后后续异常回滚配送单已创建TMS 孤儿配送单
DB 提交后客户端超时已提交可能成功用户重试可能重复出库

因此“接口报错”绝不等于“什么都没发生”。必须先查本地出库单、库存、TMS 和 MQ 日志,再决定是否重试。

10. 出库数量与状态回写

10.1 数量符号

  • 销售订单明细 qty 通常为正数,表示订购量。
  • 销售出库明细在 NormalSaleSer 中写为 -abs(outqty)。
  • 销售退货明细使用正数量,表示库存回流。
  • 页面展示通常会取绝对值;SQL 对账不能直接把正负号抹掉。

10.2 当前状态判断

老实现 getOutboundQty() 的核心判断可抽象为:

orderedQty = 销售订单总数量
outQty = 有效销售出库数量绝对值
closeQty = 结单不再出库数量

outQty = 0                              -> 待出库 0
0 < outQty 且 orderedQty > outQty+closeQty -> 部分出库 1
outQty > 0 且 orderedQty = outQty+closeQty -> 已完成 2

closeQty 的真实来源由 InvSaModel::getOutboundQty() 聚合口径决定。历史版本可能存在主表和分表并存的 SQL,排查时要以当前部署代码生成的物理表为准。

flowchart TD
    A["汇总有效出库量"] --> B{"outQty = 0?"}
    B -->|是| W["待出库 0"]
    B -->|否| C{"orderedQty > outQty + closeQty?"}
    C -->|是| P["部分出库 1"]
    C -->|否| D{"orderedQty = outQty + closeQty?"}
    D -->|是| F["已完成 2"]
    D -->|否| E["数量异常,禁止直接改状态"]

10.3 有效出库口径

建议只把以下记录视为正向有效出库:

  • 主单 transType = 150601。
  • isDelete = 0。
  • billStatus < 6,撤销单排除。
  • 明细 isDelete = 0。
  • srcOrderId、srcOrderEntryId 正确指向原销售单和销售行。

11. 出库落库与库存核对

11.1 出库主单

-- :out_suffix = sid % 64
select id, sid, billNo, billStatus, transType, sourceType,
       srcOrderId, srcOrderNo, buId, totalQty, amount,
       hxStateCode, createTime, modifyTime, cancelTime, isDelete
from t_scm_sa_invoice_0_:out_suffix
where sid = :sid
  and (billNo = :out_bill_no or srcOrderId = :sale_order_id or srcOrderNo = :sale_bill_no)
order by createTime, id;

11.2 出库明细

select id, iid, sid, billNo, transType, invId, skuId,
       qty, price, amount, locationId, locationAreaId,
       srcOrderId, srcOrderEntryId, srcOrderNo, isDelete
from t_scm_sa_invoice_info_0_:out_suffix
where sid = :sid and iid in (:invoice_ids)
order by iid, srcOrderEntryId, id;

11.3 行级对账

select soi.id as sale_line_id,
       soi.invId,
       soi.skuId,
       soi.qty as ordered_qty,
       coalesce(sum(case
           when si.transType = 150601 and s.billStatus < 6 and si.isDelete = 0
           then abs(si.qty) else 0 end), 0) as valid_out_qty,
       coalesce(sum(case
           when si.transType = 150602 and s.billStatus < 6 and si.isDelete = 0
           then abs(si.qty) else 0 end), 0) as return_qty
from t_scm_sa_order_info_:so_suffix soi
left join t_scm_sa_invoice_info_0_:out_suffix si
       on si.srcOrderEntryId = soi.id and si.sid = soi.sid
left join t_scm_sa_invoice_0_:out_suffix s
       on s.id = si.iid and s.sid = si.sid
where soi.sid = :sid and soi.iid = :sale_order_id and soi.isDelete = 0
group by soi.id, soi.invId, soi.skuId, soi.qty;

这段 SQL 是排查模板,字段和索引必须先与目标环境 DDL 对齐。

11.4 库存方向

flowchart LR
    SALE["销售订单 170502"] --> OUT["销售出库 150601"]
    OUT -->|"出库明细 qty < 0"| FLOW1["库存流水减少"]
    FLOW1 --> RT1["实时库存减少"]
    OUT --> RETURN["销售退货 150602"]
    RETURN -->|"退货明细 qty > 0"| FLOW2["库存流水增加"]
    FLOW2 --> RT2["实时库存恢复"]

11.5 库存核对维度

不能只按 invId 汇总,至少包含:

sid + invId/skuId + locationId + locationAreaId + storageType

套包、微仓、特殊仓、批次或效期场景还需加入对应维度。

12. 出库失败故障树

flowchart TD
    A["点击出库失败"] --> B{"权限通过?"}
    B -->|否| B1["检查 PC 权限/APP 248"]
    B -->|是| C{"站点被黑名单或锁定?"}
    C -->|是| C1["核对 Redis 和 lock_status"]
    C -->|否| D{"qtyGrid 非空且货位有效?"}
    D -->|否| D1["货位被删或页面缓存旧"]
    D -->|是| E{"qty <= totalQty-lockNum?"}
    E -->|否| E1["锁定量或库存不足"]
    E -->|是| F{"原销售行关联正确?"}
    F -->|否| F1["srcOrderEntryId 错误"]
    F -->|是| G{"本地出库/库存已落库?"}
    G -->|否| H["查事务异常和 SQL"]
    G -->|是| I{"TMS/MQ 是否失败?"}
    I -->|是| I1["跨系统部分成功"]
    I -->|否| J["检查客户端超时/响应解析"]

12.1 错误文案到检查项

错误检查
没有权限进行出库操作App 权限 248、PC SA_OUT_ADD
销售业务已被暂时停止Redis 销售站点黑名单
系统已被锁定OpsMenuRuleNewModel.lock_status
货位已删除qtyGrid、StorageArea 软删、页面缓存
库存不足,请检查锁定数量qty、totalQty、lockNum
单据不存在或已删除sid/16 分片、id、billType=SALE、isDelete
已经生成出库单不能关闭64 分片存在状态小于等于 5 的有效 150601 出库

13. 配送与 TMS 排查

13.1 创建条件

当前 addOutBound() 中,TMS 创建/更新至少受以下条件影响:

  • StationSer::isShowAIwork(sid) 返回开启。
  • 销售来源属于普通、ERP、E站商城、维修、活动、机器人或新 E站 APP 来源集合。
  • 出库已经由 NormalSaleSer::addInvoice() 成功返回。
  • handleTransportUpsert() 返回配送号后,继续保存配送号关系。

13.2 数据关系

flowchart LR
    SO["销售单"] --> OUT["销售出库单"]
    OUT --> REL["出库配送关系"]
    REL --> TMS["三方配送主单"]
    TMS --> EXT["外部TMS订单"]
    EXT --> CALLBACK["配送状态回调"]
    CALLBACK --> TMS
    CALLBACK --> OUT
    OUT --> APP["APP状态通知"]

13.3 配送排查顺序

  1. 从出库单拿 id/billNo/sid。
  2. 查 t_sa_invoice_delivery_relation 是否有出库到配送的关系。
  3. 查 t_scm_third_party_delivery_order 的配送号、外部号、状态和时间。
  4. 查 TMS 请求日志,确认创建还是更新。
  5. 查回调日志,确认外部状态及本地映射。
  6. 查出库主单是否从 1 推到 2/3。
  7. 查 sendSaOrderStatusToMq() 是否通知 APP。

13.4 配送异常分类

类别本地出库配送关系外部订单处理方向
未触发有无无检查开关、来源、调用条件
创建失败有无/半条无查请求异常,确认能否安全重试
关系丢失有无有先核对唯一键,再补关系
回调未到有有状态已变查网关、回调地址、签名和日志
回调未落库有有状态已变查状态映射、事务和幂等
本地撤销外部未撤销6有配送中检查取消调用和补偿

14. 对账、收款与核销

14.1 三个金额

代码对账时会比较:

出库应收 amount
已收 payAmount
优惠/差额 diffAmount

已确认判断:

amount == payAmount + diffAmount 或 amount == 0
    -> billStatus = 5,hxStateCode = 2,记录 hasTime
否则
    -> billStatus = 4

14.2 资金流程

flowchart TD
    O["出库单已送达/可对账"] --> P["汇总 PaymentInfo"]
    P --> C{"amount = payAmount + diffAmount?"}
    C -->|是| H["状态5 已核销\nhxStateCode=2"]
    C -->|否| R["状态4 已对账"]
    R --> MORE["继续收款"]
    MORE --> C
    R --> CANCEL["撤销对账"]
    CANCEL --> CHECK{"已有有效收款?"}
    CHECK -->|是| DENY["禁止直接撤销"]
    CHECK -->|否| BACK["回退到配送/送达状态"]

14.3 支付关联

PaymentInfo 通常通过 stlId = 销售出库单.id 关联,且按 sid % 32 分片。排查时同时带 transType、billStatus、isDelete,避免把已撤销收款或退款混入。

-- :pay_suffix = sid % 32
select id, sid, stlId, billNo, transType, amount, billStatus,
       createTime, modifyTime, isDelete
from t_scm_payment_info_:pay_suffix
where sid = :sid and stlId in (:invoice_ids)
order by createTime, id;

14.4 金额问题常见原因

  • 金额精度和字符串/浮点比较导致等式不成立。
  • 收款关联到错误出库单 stlId。
  • 退款与收款正负号被混算。
  • PaymentInfo 软删或状态 6 未排除。
  • 出库单优惠字段与支付差额字段口径不同。
  • 销退退款已经发生,但原销售核销状态未重算。

15. 关单与结单

15.1 两个动作

动作请求 type前置条件订单结果下游事件
关闭1无有效正向出库状态 3garage_repair_order_cancelled
结单2已有部分出库,不再继续状态 2garage_repair_order_stopped

15.2 关闭校验

closeSaleOr() 会查 64 分片出库主表,判断是否存在:

srcOrderId = 销售单.id
sid = 当前站点
isDelete = 0
transType = 150601
billStatus <= 5

非 App 关闭还会排除 sourceType = 4 的核销式撤销记录。存在有效出库时返回“已经生成出库单不能关闭”。

15.3 本地与下游边界

关单/结单先在本地事务中更新销售单并提交,然后调用 SyncOrderSer 发送下游消息。消息异常是否会回传取决于调用路径;排查时必须分别确认本地状态和 dgj_notify 日志。

16. 撤销出库

撤销不是简单把原出库单设为 6。需要确认:

  • 原出库单是否允许撤销,是否已配送、对账、收款。
  • 是否生成反向库存事实或撤销类型记录。
  • 原销售单的有效出库量是否重算。
  • 配送单是否取消,关系是否保留审计。
  • 收款、对账、核销是否回退。
  • 下游是否收到 garage_repair_outbound_cancelled。
sequenceDiagram
    participant U as 操作人
    participant S as 销售服务
    participant DB as 出库/库存
    participant T as TMS
    participant MQ as dgj_notify
    U->>S: 撤销出库
    S->>S: 校验状态和资金
    S->>DB: 原单状态6/反向库存
    S->>DB: 重算销售单状态
    S->>T: 取消配送
    S->>MQ: garage_repair_outbound_cancelled
    MQ-->>U: 下游异步回执

16.1 不能直接改状态 6 的原因

如果只改主表状态:

  • 库存不会自动恢复。
  • 销售单仍可能显示已完成。
  • TMS 继续配送。
  • PaymentInfo 仍然有效。
  • APP/SAAS 仍认为已出库。
  • 报表可能按库存流水而不是主表状态统计。

17. 销售退货完整链路

17.1 两类入口

  • 基于原销售/出库单退货:校验原始可退数量。
  • 异常退货或无原单退货:需要异常日志和更严格权限。

17.2 可退数量

基础思想:

可退数量 = 有效销售出库数量 - 已完成有效销退数量 - 在途/审核中退货占用

真实代码可能按出库行、商品、客户和来源单聚合,不能只按 invId。同一商品在不同价格、货位、VIN 或多个出库单上时,必须保留原行关联。

17.3 销退库存和资金

sequenceDiagram
    participant U as 操作端/APP
    participant R as 销退服务
    participant DB as 64分片出库表
    participant INV as 库存
    participant PAY as 资金
    participant MQ as APP/SAAS
    U->>R: 提交退货
    R->>R: 校验原出库和可退量
    R->>DB: 写150602销退主单/正数明细
    R->>INV: 增加对应仓库货位库存
    R->>PAY: 生成/更新退款关系
    R->>MQ: 退货审核/完成通知

17.4 销退排查 SQL

select id, billNo, transType, sourceType, return_type,
       srcOrderId, srcOrderNo, billStatus, amount, hxStateCode,
       createTime, cancelTime, isDelete
from t_scm_sa_invoice_0_:out_suffix
where sid = :sid
  and transType = 150602
  and (srcOrderId in (:source_ids) or srcOrderNo in (:source_nos))
order by createTime, id;

select iid, srcOrderId, srcOrderEntryId, invId, skuId,
       qty, amount, locationId, locationAreaId, isDelete
from t_scm_sa_invoice_info_0_:out_suffix
where sid = :sid and iid in (:return_invoice_ids)
order by iid, id;

17.5 销退异常判断

现象判断
提示超过可退量原出库有效量、历史销退、审核中占用
退货单存在但库存未增查库存流水事务、货位和实时库存
库存增加但退款未生成本地事务和资金调用边界
APP 一直显示审核中sendApplyReturnCheckedToAPP 是否发送/消费
APP 显示完成但本地未入库回调或下游事件顺序错误
重复退货原出库行和外部售后号未幂等

18. 下游 MQ 同步地图

18.1 新同步事件

目标 destination:dgj_notify。

routing key触发业务唯一键建议
garage_repair_order_created销售单创建stationId + orderNo
garage_repair_order_updated销售单编辑stationId + orderNo + version/modifyTime
garage_repair_order_cancelled关闭stationId + orderNo + cancelType
garage_repair_order_stopped结单stationId + orderNo
garage_repair_outbound_created新建出库sid + saInvoiceBillNo
garage_repair_outbound_updated编辑出库sid + saInvoiceBillNo
garage_repair_outbound_cancelled撤销出库stationId + outBoundNo

18.2 旧 APP/SAAS 事件

方法用途
sendSaOrderCloseToAPP销售单关闭
sendSaOrderCutOffToAPP销售单结单
sendApplyReturnFinishToAPP退货入库完成
sendApplyReturnCheckedToAPP退货审核通过
sendSaOrderOutToMq销售出库通知
sendSaOrderStatusToMq配送状态变化
sendSaOrderStatusToSass销售创建/出库状态给 SAAS
sendOutBoundToSaas出库同步 SAAS
sendInvoiceToSass微仓/销售出库同步

18.3 消息幂等风险

SyncOrderSer 和多个 MqSer 生产方法用 uniqid() 生成消息 ID。每次补发都会得到新 ID,消费者不能只按消息 ID 去重,必须按订单号/出库单号等业务键幂等。

flowchart LR
    DB["本地销售/出库"] --> P["Producer"]
    P -->|"messageId=uniqid()"| Q["dgj_notify"]
    Q --> C["APP/SAAS消费者"]
    C --> IDEM{"业务键已处理?"}
    IDEM -->|是| ACK["幂等ACK"]
    IDEM -->|否| WRITE["落下游订单/出库"]
    WRITE --> ACK

18.4 同步消息字段风险

  • buildOrderEvent() 新建/编辑都把订单状态固定构造为待出库,需要下游理解事件语义。
  • 普通销售创建的 orderType 固定为 7;取消则通过来源映射取下游类型。
  • 出库消息依赖 saleOrId/saleOrNo/billNo,编辑页面不一定原样带回,入口已经有补齐逻辑。
  • qtyGrid 中多货位时,Builder 当前循环会保留最后一个非零货位作为消息字段;实际出库明细仍可能有多货位,需确认下游合同。
  • goodsOutPrice 字段当前填入行 amount,命名像单价但实际是金额,前后端和下游不能望文生义。

19. 下游未收到的排查 SOP

flowchart TD
    A["下游未收到"] --> B{"本地业务已提交?"}
    B -->|否| B1["先查本地事务错误"]
    B -->|是| C{"该sourceType应同步?"}
    C -->|否| C1["例如快维来源可能跳过"]
    C -->|是| D{"生产日志存在?"}
    D -->|否| D1["代码分支未走或生产异常"]
    D -->|是| E{"队列有消息/积压?"}
    E -->|是| E1["查消费者、重试、死信"]
    E -->|否| F{"下游消费日志存在?"}
    F -->|否| F1["binding/routing key配置"]
    F -->|是| G{"业务键幂等拒绝?"}
    G -->|是| G1["核对旧数据和版本"]
    G -->|否| H["查下游事务和字段合同"]

19.1 日志关键字

rg -n "SyncOrder发送消息|garage_repair_order_|garage_repair_outbound_" application
rg -n "sendSaOrderOutToMq|sendSaOrderStatusToMq|sendInvoiceToSass|sendOutBoundToSaas" application
rg -n "saleOrId|saleOrNo|saInvoiceBillNo|outBoundNo" application/Services/SyncOrder application/service/scm/InvSaService.php

19.2 补发安全门槛

补发前逐项确认:

  • 本地销售/出库事实已经提交且不会回滚。
  • 下游是否已有同业务键数据。
  • routing key 与当前消费者所有权一致。
  • 事件应是 create、update、cancel 还是 stop。
  • payload 的站点、订单号、出库单号和数量与当前事实一致。
  • 消费者支持按业务键幂等。
  • 单次、单号、可追踪;禁止无条件整批重放。

20. 一单到底只读 SQL 包

下面模板中的表后缀必须先计算;字段以目标环境 DDL 为准。先在只读连接执行。

20.1 销售主单和明细

select *
from t_scm_sa_order_:so_suffix
where sid = :sid
  and (id = :sale_order_id or billNo = :sale_bill_no
       or srcOrderId = :src_order_id or srcOrderNo = :src_order_no)
order by id;

select *
from t_scm_sa_order_info_:so_suffix
where sid = :sid and iid = :sale_order_id
order by id;

20.2 有效出库和销退

select id, billNo, transType, billStatus, sourceType, srcOrderId,
       srcOrderNo, totalQty, amount, hxStateCode, createTime, cancelTime
from t_scm_sa_invoice_0_:out_suffix
where sid = :sid and srcOrderId = :sale_order_id and isDelete = 0
order by createTime, id;

select i.iid, i.id, i.srcOrderEntryId, i.invId, i.skuId,
       i.transType, i.qty, i.amount, i.locationId, i.locationAreaId,
       m.billNo, m.billStatus
from t_scm_sa_invoice_info_0_:out_suffix i
join t_scm_sa_invoice_0_:out_suffix m on m.id = i.iid
where i.sid = :sid and m.srcOrderId = :sale_order_id
order by i.srcOrderEntryId, m.createTime, i.id;

20.3 孤儿关系

-- 出库主单找不到销售主单
select o.id, o.billNo, o.srcOrderId, o.srcOrderNo
from t_scm_sa_invoice_0_:out_suffix o
left join t_scm_sa_order_:so_suffix s
       on s.id = o.srcOrderId and s.sid = o.sid
where o.sid = :sid and o.isDelete = 0 and s.id is null;

-- 出库明细找不到销售明细
select i.id, i.iid, i.srcOrderEntryId, i.invId, i.qty
from t_scm_sa_invoice_info_0_:out_suffix i
left join t_scm_sa_order_info_:so_suffix s
       on s.id = i.srcOrderEntryId and s.sid = i.sid
where i.sid = :sid and i.isDelete = 0 and s.id is null;

20.4 重复出库候选

select srcOrderEntryId, invId, locationId, locationAreaId,
       count(*) as row_count, sum(abs(qty)) as total_out_qty
from t_scm_sa_invoice_info_0_:out_suffix i
join t_scm_sa_invoice_0_:out_suffix m on m.id = i.iid
where i.sid = :sid
  and m.transType = 150601
  and m.billStatus < 6
  and m.isDelete = 0
  and i.isDelete = 0
group by srcOrderEntryId, invId, locationId, locationAreaId
having count(*) > 1;

多次部分出库本身合法,因此该 SQL 只产生候选,必须与订单数量、操作时间和幂等键继续核对。

21. 按现象执行的分支 SOP

21.1 销售单不能出库

  1. 固定 sid,计算 16/64 后缀。
  2. 查销售主单:状态必须是 0 或 1,且未删除。
  3. 查销售明细和 srcOrderEntryId。
  4. 查站点黑名单、系统锁定和权限。
  5. 查货位是否存在、实时库存和锁定量。
  6. 查已经有效出库的数量,计算剩余可出。
  7. 查应用日志中的具体异常。
  8. 若接口报错,再查是否已经产生出库单、库存和 TMS。

21.2 出库后销售状态不对

  1. 查所有 150601 出库主单,排除状态 6 和软删。
  2. 按 srcOrderEntryId 汇总 abs(qty)。
  3. 查结单量口径。
  4. 与订单明细 qty 逐行比较。
  5. 查状态回写日志和 getOutboundQty() 是否执行。
  6. 先修数量/关联事实,再重算状态;不要先改主单状态。

21.3 出库单有记录但库存没扣

  1. 确认出库明细是负数量。
  2. 按单号和 150601 查库存流水。
  3. 按站点、商品、仓库、货位查实时库存。
  4. 查数据库连接和事务是否一致。
  5. 查是否只有主明细落库、库存步骤失败。
  6. 修复时用已有领域库存接口,不直接改单个实时库存值。

21.4 库存扣了但出库单没有

  1. 查请求时间附近库存流水的来源单号。
  2. 查出库分片是否算错。
  3. 查软删/撤销出库。
  4. 查事务是否跨连接导致库存先提交。
  5. 查重试是否生成新出库号。
  6. 保留流水,评审反向业务单或补齐业务单,禁止直接删除流水。

21.5 配送状态不推进

  1. 查出库状态是否 1。
  2. 查配送关系和三方配送主单。
  3. 查外部 TMS 实际状态。
  4. 查回调是否到达、验签和幂等结果。
  5. 查映射后是否更新出库状态 2/3。
  6. 查 APP 状态 MQ 是否发送。

21.6 收款后未核销

  1. 查出库 amount/hxStateCode/billStatus。
  2. 查 32 分片 PaymentInfo。
  3. 排除软删、撤销、退款记录。
  4. 计算 payAmount + diffAmount。
  5. 检查金额精度和币种/单位。
  6. 查对账/收款状态回写日志。

21.7 销退后库存未恢复

  1. 查 150602 销退主明细是否提交。
  2. 确认明细数量为正,仓库货位正确。
  3. 查库存入库流水和实时库存。
  4. 查退款和 APP 完成通知。
  5. 查原出库可退量是否被正确占用/释放。

22. 修复决策树

flowchart TD
    A["发现销售链不一致"] --> B["冻结单号并保存证据"]
    B --> C{"哪一层是真实业务事实?"}
    C --> D["订单"]
    C --> E["出库/销退"]
    C --> F["库存"]
    C --> G["配送"]
    C --> H["资金"]
    C --> I["下游"]
    D --> J["建立期望状态和数量"]
    E --> J
    F --> J
    G --> J
    H --> J
    I --> J
    J --> K{"现有领域补偿接口可用?"}
    K -->|是| L["单号级幂等补偿"]
    K -->|否| M["评审修复脚本和回滚方案"]
    L --> N["六层验收"]
    M --> N

22.1 修复优先级

  1. 使用原业务重试/补偿接口。
  2. 使用已有领域 Service 创建反向业务事实。
  3. 单号级可审计脚本,带前置断言和影响行数。
  4. 最后才考虑直接 SQL,且必须有备份、复核和回滚。

22.2 禁止操作

  • 只改销售主单 billStatus。
  • 直接把出库单改成 6 而不恢复库存。
  • 删除库存流水以“让报表好看”。
  • 未检查下游时重复发 create 事件。
  • 按消息 ID 判断业务是否已处理。
  • 不算分片就跨全库更新同名表。

23. 回归测试矩阵

23.1 销售订单

  • [ ] 普通挂账销售创建、编辑、关闭。
  • [ ] 现金/微信销售创建和收款状态。
  • [ ] E站商城、维修、活动、机器人和新 APP 来源。
  • [ ] 快维来源不应发送的下游事件保持不发送。
  • [ ] 0 元赠品、折扣、优惠和多 VIN。
  • [ ] 旧来源类型历史单查询和关闭不报映射错误。

23.2 出库和库存

  • [ ] 一次全部出库,订单 0 -> 2。
  • [ ] 多次部分出库,订单 0 -> 1 -> 2。
  • [ ] 多仓库、多货位同一商品出库。
  • [ ] 锁定量不足、货位删除、站点锁定。
  • [ ] 出库明细负数量,库存流水和实时库存同步减少。
  • [ ] 客户端超时后重试不产生重复出库。
  • [ ] 事务异常时业务表和库存共同回滚。

23.3 配送

  • [ ] AI 工作台关闭时不创建 TMS。
  • [ ] 支持来源创建和更新配送单。
  • [ ] 配送中、送达、自提、取消状态映射。
  • [ ] 出库撤销同步取消配送。
  • [ ] 配送回调重复投递幂等。

23.4 资金

  • [ ] 未收、部分收、全收。
  • [ ] amount = pay + diff 自动核销。
  • [ ] 零金额出库核销。
  • [ ] 对账、撤销对账和已有收款阻断。
  • [ ] 销退退款、重复退款和撤销。

23.5 下游同步

  • [ ] order created/updated/cancelled/stopped。
  • [ ] outbound created/updated/cancelled。
  • [ ] 消费者按业务单号幂等,而非随机消息 ID。
  • [ ] MQ 发送异常时入口行为符合预期。
  • [ ] MQ 成功后本地回滚场景有对账补偿。
  • [ ] APP、SAAS 的订单号和出库单号映射正确。

24. 监控与告警建议

指标维度建议告警
销售单待出库时长sid/sourceType超过业务 SLA
部分出库停留时长sid/sourceType长时间无后续出库/结单
出库事务失败数接口/异常类型突增
负库存拦截数sid/invId突增或集中 SKU
出库无库存流水sid/billNo任意新增
库存流水无出库单sid/srcBillNo任意新增
TMS 创建失败sid/来源连续失败
配送回调延迟配送商/状态超过 SLA
已对账未核销金额sid/账龄超过阈值
dgj_notify 生产失败routing key任意持续失败
下游消费积压routing key队列深度/最老消息超阈值

25. 已确认与待确认

25.1 静态代码已确认

  • 销售主单和明细按 sid % 16。
  • 销售出库主单和明细按 _0_{sid % 64}。
  • 销售和出库两个 Ext Model 当前直接使用不分片常量表。
  • 销售订单状态为 0/1/2/3。
  • 出库状态为历史 0,以及 1/2/3/4/5/6。
  • 销售出库交易类型为 150601,销退为 150602。
  • 普通销售出库明细数量写成负数。
  • 出库入口校验 PC/App 权限、销售黑名单、系统锁定、货位和锁定量。
  • 出库事务内会处理 TMS,并在提交前发送新的下游出库事件。
  • 快维来源在新出库同步逻辑中被排除。
  • 新下游事件目标为 dgj_notify,消息 ID 使用 uniqid()。
  • 关单要求没有有效出库;结单用于已有部分出库后停止。
  • 对账时金额完全覆盖会直接到状态 5,否则到状态 4。

25.2 环境待确认

  • 生产 DDL、唯一索引和扩展表实际分片/归档策略。
  • 当前 PC、App、PDA 各入口真实流量占比。
  • TMS 服务的幂等键、超时、重试和取消 SLA。
  • dgj_notify 的 exchange、binding、prefetch、重试和死信配置。
  • 下游消费者对七类销售事件的幂等实现。
  • 历史主表与 16/64 分表是否仍有双写或迁移遗留。
  • 库存流水与实时库存的线上分片和唯一键。
  • PaymentInfo 的线上分片、有效状态和金额精度。
  • closeQty 的当前生产聚合实现及历史数据完整性。
  • 事务内远程调用成功、本地提交失败后的现有补偿任务。

26. 证据来源

主题文件
表常量和分片数application/config/tables.php
销售状态和来源application/KzData/Enums/SaOrderEnums.php
出库状态application/KzData/Enums/SaInvoiceEnums.php
交易类型application/KzData/Enums/TransTypeEnums.php
销售主单 Modelapplication/models/saOrders/SaOrderModel.php
销售明细 Modelapplication/models/saOrders/SaOrderInfoModel.php
销售扩展application/models/saOrders/SaOrderInfoExtModel.php
出库主单 Modelapplication/models/saOrders/SaInvoiceModel.php
出库明细 Modelapplication/models/saOrders/SaInvoiceInfoModel.php
出库扩展application/models/saOrders/SaInvoiceInfoExtModel.php
老销售编排application/service/scm/InvSaService.php
普通销售出库application/Services/InvSa/NormalSaleSer.php
普通销退application/Services/InvSa/NormalSaleReturnSer.php
新销售领域application/Services/SaOrders/SaOrderSer.php
出库领域application/Services/SaOrders/SaInvoiceSer.php
三方配送application/Services/SaOrders/ThirdDeliverySer.php
下游构建application/Services/SyncOrder/SyncOrderBuilder.php
下游生产application/Services/SyncOrder/SyncOrderSer.php
MQ 事件application/Services/Mq/MqSer.php
收款核销application/service/scm/ReceiptService.php

27. 一页式执行结论

先拿 sid,算 sid%16 和 sid%64;销售表与出库表不是同一种后缀。
先查销售主单和明细,再按 srcOrderId/srcOrderEntryId 找出库主明细。
销售订单量为正;有效销售出库明细通常为负;销退通常为正。
状态异常先重算有效出库量和结单量,不先改 billStatus。
出库失败后必须同时查本地出库、库存、TMS 和 MQ,接口报错不代表全部失败。
配送、对账、核销、APP/SAAS 各有独立状态,不能互相替代。
补发 MQ 按销售单号或出库单号幂等,随机 messageId 不能证明未处理。
任何修复最后验收订单、出库、库存、配送、资金、下游和报表。

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

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
PC 销售scm/InvSa/sale/OutOrdersid、客户、SKU、数量、价格、动作InvSa.php/OutOrder.phpInvSaService、SaOrderSer销售单号销售订单与应收事实
App/PDA/SAASappapis.php/老 apis.php来源单、销售/出库参数App/PDA/OpenAPIPda/SaleSer、领域 Service来源单号 + 销售/出库单共用销售出库库存链
配送回调配送中心 MQ出库单、配送状态、消息 IDDispatchCenterNotifyThirdDeliverySer出库/配送单号配送状态和签收结果
收款/退货财务入口、售后申请销售单、金额;原出库、退货量Receipt/退货 Controller/ConsumerReceiptService、NormalSaleReturnSer销售/支付/退货单号核销应收或反向入库退款

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 下单 | 请求日志、SaOrderSer | request_id、来源单、销售单、sid | 主明细 commit | 来源重复、价格/客户校验失败 | 来源单映射销售单 | | 出库 | SaInvoiceSer/InventorySer | 销售单、出库单、SKU、150601 | 出库量和库存扣减一致 | 超出可出量、负库存限制、部分成功 | 出库单查发票明细与库存流水 | | 配送 | DispatchCenterNotify | 配送单、出库单、event、message ID | 状态单向推进且 ACK | 乱序、重复、关系缺失 | 两种单号查配送关系 | | 收退 | ReceiptService/退货 Service | 支付/退货单、原销售单、金额/数量 | 核销或退货库存完成 | 超退、重复核销/退款、金额不平 | 原单贯穿资金与库存 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
创建订单InvSaService/SaOrderSer销售事务客户、商品、来源唯一性SCM_SA_ORDER*insert;应收和订购量写入;状态初始化主明细金额数量合计
生成出库SaInvoiceSer出库事务可出量、订单状态、仓位SCM_SA_INVOICE*出库量 old -> old+n;状态待/部分/完成销售与出库明细数量等式
扣库存InventorySer应与出库同业务边界实时库存和限制配置SCM_INVENTORY*、实时表qty: old -> old-n,流水 150601 insert流水净额、实时量
配送收款ThirdDeliverySer、ReceiptService各自事务/异步边界出库状态、应收余额配送关系、账户/支付表配送态推进;已收 +n、应收余额 -n配送回执、资金分录
退货修复NormalSaleReturnSer、补偿任务退货与库存边界需确认已出、已退、已退金额退货单、库存、退款/MQ退货量 +n;库存 +n;退款一次原出库、退货、库存、支付闭环

子模块追踪:sale-create-check 销售创建排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建订单PC/App/OpenAPI 提交销售request ID、sid、来源单号、客户、SKUapplication/controllers/scm/InvSa.php -> application/Services/SaOrders/SaOrderSer.php来源唯一性、客户、商品、价格、税额规则销售本地事务 insert 主表、明细、扩展,状态初始化 none -> createdrequest ID + sourceOrderNo + sale billNo参数/来源重复零写入或返回既有关系;超时按来源单回查
创建回查响应后核对订单金额数量销售单、entryId、来源单application/models/saOrders/SaOrderModel.php主明细、扩展、应收和来源关系查询只读 不写;主明细数量金额应守恒sale billNo + sourceOrderNo + affected rows主单无明细按部分失败处理,使用领域撤销/重建,不手补明细

子模块追踪:sale-out-check 销售出库排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
出库提交PC/PDA 创建销售出库sid、销售单、SKU、仓库货位、数量application/controllers/sale/OutOrder.php -> application/Services/SaOrders/SaInvoiceSer.php可出量、订单状态、库存维度、负库存配置出库本地事务 insert SCM_SA_INVOICE*,有效出库量 old -> old+nrequest ID + sale/invoice billNo + SKU超出可出量或状态不允许时零写入;网络重试前查已有出库行
状态回算出库后销售状态不正确销售单、出库单、entryIdapplication/service/scm/InvSaService.php有效未软删出库量、结单量、订单量回算事务仅在等式满足时 billStatus: old -> partial/finish两种单号 + qty summary软删/撤销行必须排除;先修过程事实再重算状态

子模块追踪:sale-stock-check 出库与库存一致性排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
扣减库存销售出库事务调用库存服务出库单、SKU/invId、仓库货位、数量application/Services/SaOrders/SaInvoiceSer.php -> application/Services/Storage/InventorySer.php实时四维库存、库存策略和出库明细应在本地业务事务写 150601 流水,实时 qty: old -> old-ninvoice billNo + SKU + transType 150601库存失败应回滚出库;若只一侧成功,先固定事务事实再补偿
一致性核对出库单与库存不一致出库 entryId、库存业务单application/models/saOrders/SaInvoiceInfoModel.php有效出库量、流水净额、实时前后值查询只读 不写;同一 SKU/仓位数量方向应一致invoice/inventory billNo + entryId禁止只改实时库存;走反向出库或库存领域补偿并保留流水

子模块追踪:sale-delivery-check 配送与 TMS 排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建配送满足配送条件的销售出库出库单、地址、承运方式application/Services/SaOrders/ThirdDeliverySer.php出库状态、已有配送关系、地址本地事务写配送关系 none -> pending;TMS 调用为事务外边界request ID + invoice/deliveryNo外部超时先按出库单查询是否已创建,避免重复配送单
状态回调TMS 配送、签收、失败事件message ID、配送单、出库单、状态application/controllers/tasks/DispatchCenterNotify.php当前配送态和映射关系回调本地事务单向 old -> in_transit/signed/failedmessage ID + delivery/invoiceNo乱序不回退签收;关系缺失只补映射/回调,不重做出库

子模块追踪:sale-receipt-check 对账收款核销排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
收款制单PC/财务对销售单收款销售单、收款单、账户、金额application/service/scm/ReceiptService.php应收、已收、已核、退款和有效单据财务本地事务 insert 收款/分录,received: old -> old+nrequest ID + sale/receipt billNo超收、账户非法零写入;重复来源键不得生成第二笔收款
核销回查已收款但销售未核销销售/收款/支付单、金额application/service/scm/InvSaService.php有效收款过滤、核销关系和金额单位核销事务 verified: old -> old+n;报表查询只读三种单号 + transType + amount分录正确但状态旧时只重算核销;金额不平先查软删/退款

子模块追踪:sale-close-check 关单、结单与撤销出库排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
关闭结单用户关闭未履约量或系统结单销售单、action、关闭原因application/service/scm/InvSaService.php订单量、有效出库、退货、已关量、资金状态关闭本地事务 closeQty +n、billStatus: old -> closed/finishedrequest ID + sale billNo + action已出库部分不能直接关闭;并发出库后重新计算剩余可关量
下游同步关结单后通知 App/SAAS销售单、最终状态、eventapplication/Services/SyncOrder/SyncOrderSer.php本地已提交终态和上次发送事实commit 后异步 MQ,DB 主状态不变sale billNo + event + routing key发布失败仅补消息;下游未收到不重复执行关结单

子模块追踪:sale-return-check 销售退货排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建销退原销售/出库单发起退货原单、销退单、SKU、退货量application/Services/InvSa/NormalSaleReturnSer.php原有效出库量、已退量、可退金额、库存维度销退与库存本地事务写退货单,退货量 old -> old+n、库存 qty +nrequest ID + original/return billNo + SKU超退零写入;若库存已回但退货单失败,按流水事实走领域补偿
退款回查销退完成后退款/核销反向销退单、原收款/支付单application/service/scm/ReceiptService.php原已收已核额、已退额、销退终态反向财务事务 refunded +n、核销额按规则减少return/original/payment billNo库存完成不等于退款完成;库存和资金分别验收、分别幂等补偿

子模块追踪:sale-mq-check 销售下游 MQ 排查

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
构造发布销售创建、出库、撤销、退货 commit 后销售/出库单、event、来源单application/Services/SyncOrder/SyncOrderBuilder.php -> application/Services/Mq/MqSer.php已提交业务快照、来源和目标类型本地核心 DB 不变;事务外发布新旧 App/SAAS 事件billNo + event/routing key + message ID序列化/发布失败只补发消息,不重做订单和库存
下游未到App/SAAS 查询不到已完成业务两端单号、发送时间窗application/Services/SyncOrder/SyncOrderSer.php本地主明细、已发送事件和下游回执查询只读 不写;补发沿用稳定业务键local/source billNo + publish/consume logs随机 messageId 不证明幂等;目标环境消费日志需实际确认