1. 业务目标与边界

采购域负责服务站从供应商、云仓、活动、预订单或订单中心获得商品,并把“采购意向”推进为可支付、可发货、可签收、可入库的业务单据。反向链路包含未发货关闭、采购退货、退货出库、退款和仓库确认。

本篇覆盖:

  • PC 老采购入口、新采购/购物车入口和 App 采购入口。
  • 草稿保存、修改、提交、审核、在线支付、发货、配送、入库、完成。
  • 整单/明细关闭、待支付取消、退货申请和退货出库。
  • 订单中心、支付中心、仓储库存和特殊活动来源联动。

不在本篇展开:采购报表每个指标的 DWD 口径、所有金融通道内部字段、每一种特殊订单的独立 PRD。这些在报表、财务、首配、活动专题中继续细分。

2. 参与角色和前置条件

角色/系统主要动作前置条件
服务站采购员选品、保存草稿、提交、支付、关闭、退货登录上下文有 JXCSID/JXCUID/JXCUNAME;有采购权限
供应商/云仓审核、出库、配送、处理退货商品、供应商、仓库和配送规则有效
订单中心审核采购/售后、取消、关闭、自制单同步DGJ2 来源单与订单中心单号能关联
支付中心支付、退款、异常回调在线支付采购单已创建支付关系
服务站仓库/PDA签收、扫码、确认入库、退货出库普通仓和货位有效;数量未超过可操作数量
库存域写采购入库/退货出库流水和实时库存单据、明细、交易类型和仓库货位完整
活动域生成赠品/秒杀特殊采购单,接收支付和关闭结果orderType/srcOrderType/srcOrderId/srcOrderNo 正确

3. 入口和代码地图

入口类型文件/配置主要职责
PC 老采购application/controllers/scm/InvPo.php草稿、提交、支付、关闭、退货、入/出库
新采购application/controllers/po/PurchaseOrder.php新版采购接口和远程采购服务适配
采购购物车application/controllers/po/Cart.php选品暂存和提交前商品集合
新售后application/controllers/po/AfterSale.php退货创建、提交、出库、物流、确认
采购入库application/controllers/scm/InvPu.php入库单列表、新增、更新、删除、导出
App API 映射application/config/appapis.phpapppur*、appwms* code 到实现映射
采购核心 Serviceapplication/Services/PoOrders/PoOrderSer.php校验、关闭、待支付取消、来源联动
老采购 Serviceapplication/service/scm/InvPoService.php列表详情、关闭、退货、入/出库、支付参数
保存实现application/Services/PoOrders/OrderService/NormalOrderSaveService.php普通采购草稿保存
查询实现PoOrderQuerySer.php、PoOrderInfoQuerySer.php主单和明细查询
订单中心消费者application/controllers/tasks/OrderCenterNotify.php审核、售后、取消、关闭、自制单消息
支付消费者application/controllers/tasks/PayCenterNotify.php支付/退款/异常落账和状态推进
库存 Serviceapplication/Services/Storage/InventorySer.php库存流水和实时库存变化

4. 采购接口总表

4.1 PC 主链路

接口方法核心请求Controller 后续调用关键结果数据副作用
saveInvPopostData JSON校验 -> PoOrderSer::checkOrderGoods -> NormalOrderSaveService::savePoOrder草稿 id新增采购主单/明细/扩展
updateInvPopostData JSON,内含 id查草稿 -> 重校验商品 -> 保存原草稿 id更新草稿和明细
submitInvPoorderId、postData$this->InvPoService->submitInvPo提交结果草稿进入后续状态、可能创建外部单
deleteInvPoidInvPoService::deleteInvPo成功/失败仅允许可删除状态,通常软删
cancelWaitPayOrderidRedis 锁 -> PoOrderSer::cancelWaitPayOrder取消成功待支付转过期/关闭并触发来源释放
cancelInvPo页面请求数据InvPoService::cancelInvPo状态和消息取消适用状态的采购单
closeInvPotoken 和明细关闭数据校验 -> Redis 锁 -> InvPoService::closeInvPo关闭结果写关闭明细,更新数量/关闭状态
closeInvPoOrdertoken、id校验 -> Redis 锁 -> InvPoService::closeInvPoOrder整单关闭结果写关闭明细、主单关闭或部分关闭
getPayInfo/getPayInfoNew采购单/支付上下文InvPoService 同名方法支付方式、金额、支付参数通常只读/创建支付前置参数
noPagePay采购单、支付方式InvPoService::noPagePay支付受理结果调支付中心,后续靠回调落账
exclusivePay专属授信支付上下文InvPoService::exclusivePay空表示成功或错误字符串授信/额度联动
unionPayCode采购单和支付方式InvPoService::unionPayCode二维码信息发起银联支付
getPayResult采购单/支付单标识InvPoService::getPayResult当前支付结果只读
当前分支证据缺口:Controller 明确调用 InvPoService->submitInvPo($data,$orderId),但在当前工作区没有检索到这个方法定义。发布/联调前应确认是否由未同步代码、扩展装载或历史分支提供。本文不虚构其内部实现,提交后的状态以枚举、回调和其他可见 Service 为依据。

4.2 退货和仓内操作

接口方法核心请求业务分支数据副作用
saveOrderBackpostData、postDataGoods不良品退货;按服务站白名单切新旧采购服务保存退货草稿
saveOrderBack_unsalepostData滞销品退货;新服务走 RefundProvider保存退货草稿
submitOrderBackorderId先限制秒杀来源退货,再提交新/旧售后退货单进入审核/处理
submitOrderActivityBackpostData套餐/活动商品退货创建或提交活动退货
cancelReturnInvPoid新服务 RefundProvider::cancel 或老 Service取消退货
invPoOutStorageorderId、postData退货商品选择仓库/货位出库减库存、写退货出库事实
invPoInStroagegoodsList 等Redis 锁防重复入库增库存、更新采购已入库量

4.3 App 和 WMS code

code映射业务用途
apppurOrderAddDraftapp.purchase.purOrderAddDraftApp 新增采购草稿
apppurOrderAddapp.purchase.purOrderAddApp 正式提交采购
apppurOrderListapp.purchase.purOrderList采购列表
apppurOrderListDetailapp.purchase.purOrderlistDetail采购详情
apppurCloseOrderapp.purchase.purCloseOrder关闭采购商品
apppurOrderReturnGetGoodsapp.purchase.purOrderReturnGetGoods获取可退商品
apppurOrderReturnAddapp.purchase.purOrderReturnAdd提交采购退货
apppurOrderCheckMoqapp.purchase.purOrderCheckMoq起订组校验
appwmsSignInapp.wms.sign.signIn物流签收
appwmsInStoSkuInfoapp.wms.insto.inStoSkuInfo扫码识别待入库商品
appwmsInStoSkuDetailapp.wms.insto.inStoSkuDetail待入库商品详情
appwmsInStoInStorageapp.wms.insto.inStoInStorage批量采购入库和发货差异
appwmsCodeVerifyapp.wms.codeVerify校验扫描码类型

code 是应用协议入口,不等同固定 URL。联调时还要确认 API 网关请求结构、签名和统一返回包装。

5. 保存采购草稿请求

PC 老入口把业务 JSON 放在 postData 字符串中,登录上下文不应由业务表单伪造。

{
  "postData": "{\"locationId\":101,\"orderType\":\"30-Cxx-01\",\"description\":\"门店补货\",\"strict\":1,\"entries\":[{\"invId\":10001,\"qty\":10,\"packSpecType\":1}]}"
}

服务端处理顺序:

  1. 检查 PU_ADD 权限。
  2. 解析 postData,InvPoValidate::saveInvPo 校验仓库、备注和订单类型。
  3. 从会话注入 sid/uid/name。
  4. 校验仓库存在且不是移动仓,采购只能入普通仓。
  5. 加载服务站区域,PoOrderSer::checkOrderGoods 校验物料、数量、供应关系和严格模式。
  6. 急件配送到客户时校验 dispatchType/contactId 和联系人地址。
  7. NormalOrderSaveService::savePoOrder 保存草稿。
  8. 保存服务站最后一次商品包装规格,返回草稿 ID。

核心字段:

字段来源规则
locationId表单必填,必须属于可用普通仓
orderType表单/来源业务必填,决定校验、支付、履约和退货规则
description表单最长 50 字;草稿保存会追加草稿说明
entries表单商品明细;具体字段由 checkOrderGoods 归一
strict表单默认 1,控制商品校验严格程度
dispatchType急件表单急件时必传,配送到站或维修客户
contactId急件客户配送配送到客户时必传并校验地址
sid/uid/name登录上下文服务端注入,不信任业务 JSON

修改草稿多传 id,并强制当前 billStatus=0;已提交订单不能通过更新草稿接口改回去。

6. 正向采购主流程

flowchart TD
    A[服务站选品] --> B[保存采购草稿]
    B --> C[校验权限 仓库 订单类型 商品 数量 供应关系]
    C --> D{校验通过}
    D -- 否 --> X[返回错误 不生成有效草稿]
    D -- 是 --> E[写采购主单 明细 扩展]
    E --> F[提交采购单]
    F --> G{支付类型/来源规则}
    G -- 在线支付 --> H[采购单待支付]
    G -- 线下挂账或无需支付 --> I[待处理/审核]
    H --> J[支付中心处理]
    J --> K{支付结果}
    K -- 成功 --> I
    K -- 超时/取消 --> L[已过期或关闭 释放来源占用]
    I --> M[订单中心/供应方审核]
    M --> N{审核结果}
    N -- 驳回 --> O[审核不通过]
    N -- 通过 --> P[待出库]
    P --> Q[供应方出库]
    Q --> R[配送中]
    R --> S[服务站签收/入库]
    S --> T[库存增加 已入库数量累加]
    T --> U{全部履约或仍有未完成量}
    U -- 全部完成 --> V[采购单已完成]
    U -- 部分 --> W[保留待入库/配送或处理差异关闭]

7. 系统调用时序

sequenceDiagram
    participant U as 服务站用户
    participant C as InvPo Controller
    participant PS as 采购 Service
    participant DB as 采购数据库
    participant OC as 订单中心
    participant PAY as 支付中心
    participant WMS as 仓储/PDA
    participant INV as InventorySer

    U->>C: saveInvPo(postData)
    C->>PS: checkOrderGoods + savePoOrder
    PS->>DB: INSERT po_order/po_order_info
    DB-->>U: draft orderId
    U->>C: submitInvPo(orderId,postData)
    C->>PS: 提交采购
    alt 在线支付
        PS->>DB: billStatus=待支付
        PS->>PAY: 发起支付
        PAY-->>C: PayCenterNotify 支付回调
        C->>DB: payment/payment_info/payinfo + 采购状态
    else 线下或无需支付
        PS->>DB: billStatus=待处理/审核
    end
    PS->>OC: 同步正向采购
    OC-->>C: OrderCenterNotify 审核/取消/关闭消息
    C->>DB: 更新审核和履约状态
    OC-->>WMS: 发货/物流
    WMS->>C: 签收/入库请求
    C->>INV: 保存库存流水并增加实时库存
    INV->>DB: inventory/inventory_info/real_time
    C->>DB: 更新采购明细已入库数量和主单完成状态

8. 采购状态机

状态定义在 application/KzData/Enums/PoOrderEnums.php:

值常量业务含义典型可执行动作
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退货待仓库入库仓库确认退货收货
stateDiagram-v2
    [*] --> 草稿: saveInvPo
    草稿 --> 待支付: 提交且在线支付
    草稿 --> 待处理: 提交且线下/无需支付
    待支付 --> 待处理: 支付成功
    待支付 --> 已过期: 超时或待支付取消
    待支付 --> 待退款: 支付异常/退回场景
    待处理 --> 审核中: 外部受理
    审核中 --> 待出库: 审核通过
    审核中 --> 审核不通过: 审核拒绝
    待出库 --> 配送中: 出库/发货
    配送中 --> 已完成: 全部签收并入库
    待处理 --> 已关闭: 允许取消/关闭
    待出库 --> 已关闭: 未发货关闭满足条件
    已过期 --> [*]
    审核不通过 --> [*]
    已关闭 --> [*]
    已完成 --> [*]

这是一张主流程图,不表示所有订单类型都能走每条边。特殊活动、直发、自提、首配、退货、授信订单会额外限制动作。

9. 订单类型和来源类型

orderType 决定“这是什么业务单”;srcOrderType/srcOrderId/srcOrderNo 决定“它从哪里来”。两者不能混用。

orderType名称特殊点
30-Cxx-01普通采购基础正向流程
30-Cxx-07急件到付需要配送类型,可能直接送客户
30-Cxx-70急件自提自提履约路径
30-Cxx-08预订采购与预订单来源关联
30-Cxx-05滞销品退货反向出库/审核
30-Cxx-06不良品退货可能关联理赔/原采购明细
30-Cxx-16直采退货直采售后规则
30-Cxx-17未发货退货关闭/退款联动更强
30-Cxx-18首配退货首配来源限制
30-Cxx-75赠品活动订单活动生成,普通关闭受限
30-Cxx-77秒杀采购订单来源类型 13,支付/关闭回调活动域

关键来源值:0 普通、8 采购退货、10 在线支付未发货退款、12 预订单、13 秒杀活动。

10. 支付数据链路

flowchart LR
    A[采购单待支付] --> B[获取支付信息/二维码/无页面支付]
    B --> C[支付中心受理]
    C --> D[PayCenterNotify 消费支付结果]
    D --> E{payStatus 是否成功口径}
    E -- 否 --> F[异常/退款分支 记录日志并按事件处理]
    E -- 是 --> G[写 t_scm_payment]
    G --> H[写 t_scm_payment_info]
    H --> I[写 t_scm_po_order_payinfo]
    I --> J[采购单退出待支付并记录 paymentTime]
    J --> K{特殊来源}
    K -- 秒杀 --> L[locked_qty 转 used_qty]
    K -- 预订单/活动/授信 --> M[调用各来源后处理]
    K -- 普通 --> N[进入审核/待处理链路]

支付成功并不只改采购状态。当前回调还可能:

  • 生成付款主单和明细。
  • 更新账户/授信/专属活动额度。
  • 写采购支付关联表。
  • 推进预订单或秒杀来源状态。
  • 触发任务记录和后续审核/工单逻辑。

所以支付问题不能通过手工把 billStatus 改成 1 解决;必须核对账务、来源单和重复回调幂等。

11. 采购关闭流程

11.1 关闭类型

常量值含义
CLOSETYPE_NONE0未关闭
CLOSETYPE_PART1部分明细关闭
CLOSETYPE_ALL2整单关闭

11.2 主流程

flowchart TD
    A[用户/订单中心/超时任务发起关闭] --> B[校验服务站 单据 状态 token]
    B --> C[Redis 锁防重复]
    C --> D{待支付取消?}
    D -- 是 --> E[PoOrderSer cancelWaitPayOrder]
    D -- 否 --> F[校验明细未发货/可关闭量和特殊订单限制]
    F --> G{可关闭}
    G -- 否 --> X[返回 can_not_close_reason]
    G -- 是 --> H[写 po_close_info]
    H --> I[更新明细数量拆分和主单 closeStatus/billStatus]
    I --> J[向订单中心/下游发送关闭确认或取消]
    J --> K{来源为秒杀/赠品/预订单}
    K -- 是 --> L[来源域释放锁量/额度/状态]
    K -- 否 --> M[完成关闭]

关闭必须区分“未发货关闭”和“已出库后的退货”。已出库数量不能伪装成关闭数量,否则采购、库存和财务三套事实会不一致。

12. 采购退货流程

sequenceDiagram
    participant U as 服务站
    participant C as InvPo/AfterSale
    participant S as 退货 Service/Provider
    participant DB as 采购退货表
    participant OC as 订单中心
    participant W as 仓库
    participant PAY as 支付中心

    U->>C: 选择原采购明细和可退数量
    C->>S: saveOrderBack / create
    S->>DB: 保存退货草稿和原单关联
    U->>C: submitOrderBack(orderId)
    C->>S: 校验来源和可退数量
    S->>OC: 提交售后审核
    OC-->>C: aftersale audit/update callback
    alt 审核通过需服务站出库
        U->>C: invPoOutStorage(locationId,areaId)
        C->>DB: 写退货出库事实并减少库存
        W-->>C: confirm warehouse/close
    else 未发货退货
        OC-->>C: 直接关闭未发货量
    end
    PAY-->>C: refund result
    C->>DB: 更新退款/退货状态和付款关联

退货保存有新旧双路径:PoPrivilegeAuthSer::isCallNewPoServer(sid) 为真时走 RefundProvider,否则走 DGJ2 本地 Service。排查同一接口在不同服务站表现不同时,第一步先确认这个路由开关。

秒杀来源退货还会经过 assertFlashActivityReturnAllowed... 限制,不能绕过活动生命周期直接做普通滞销品退货。

13. 采购入库与库存变化

13.1 仓内流程

flowchart TD
    A[物流签收/待入库列表] --> B[扫描箱码/商品码]
    B --> C[定位采购单 明细 仓库 货位]
    C --> D[计算发货量 已入库量 差异量 可入库量]
    D --> E{数量和货位合法}
    E -- 否 --> X[拒绝并返回超量/货位/商品错误]
    E -- 是 --> F[Redis 锁 goodsList 防重复提交]
    F --> G[写采购入库单和明细]
    G --> H[InventorySer 写库存流水]
    H --> I[实时库存和货位库存增加]
    I --> J[更新采购明细已入库数量]
    J --> K{全部完成}
    K -- 是 --> L[采购单已完成]
    K -- 否 --> M[保留配送/待入库并记录差异]

InvPo::invPoInStroage 使用 sid + md5(goodsList) 的 Redis 锁,锁时长 30 秒。它只能降低重复点击风险,最终仍需依靠数据库数量校验和事务保证不超入。

13.2 数量事实

采购数量排查至少同时看:

  • 采购明细原始数量。
  • 数量拆分表中的发货、入库、关闭、退货等累计值。
  • 采购出库/物流明细事实。
  • 采购入库单和库存流水。
  • 关闭明细。
  • 报表同步数据及延迟。

不能仅通过主单状态判断“是否已经入库”,也不能只修采购表而不核对库存流水和实时库存。

14. 外部消息入口

OrderCenterNotify 在订单中心 destination 上注册以下采购相关事件:

事件类别消费方法主要作用
售后审核orderCenterAfterSaleAudit更新采购退货审核状态
售后关闭orderCenterAfterSaleClose关闭售后并联动数量/退款
售后更新orderCenterAfterSaleUpdate同步售后字段和进度
仓库确认orderCenterAfterSaleConfirmWareHouse推进退货仓库确认
正向采购审核orderCenterOrderAudit审核通过/驳回映射采购状态
订单取消orderCenterOrderCancel同步取消和来源释放
售后退款完成对应 refund finish callback更新退货退款结果
自制订单同步/关闭orderCenterSelfOrderNotify、orderCenterSelfOrderCloseNotify赠品等自制采购单同步

支付中心由 PayCenterNotify 消费支付成功、支付异常、退款、白条退回和分期等事件。具体 destination/routing key 和 payload 在 MQ 专题统一维护。

15. 核心表和读写矩阵

物理表关键字段业务作用典型读写时机
t_scm_po_orderid、billNo、sid、billStatus、orderType、来源三字段采购主单保存/提交新增更新;回调、关闭、完成更新
t_scm_po_order_ext采购单 ID、配送/地址扩展主单扩展急件客户配送、特殊业务保存
t_scm_po_order_info主单 ID、商品、采购数量、价格采购明细快照保存草稿、提交、详情、退货关联
t_scm_po_order_info_qty采购明细 ID、数量分类发货/入库/关闭等数量拆分出库、入库、关闭、退货累计
t_scm_po_order_payinfo采购单、支付单、支付方式/金额采购支付关联发起或支付回调落关联
t_scm_po_out_info采购明细、发货数量、物流供应方发货事实出库/配送消息写入,入库查询
t_scm_po_close_info来源采购单/明细、关闭数量未履约关闭事实明细/整单关闭和补偿查询
t_scm_payment付款单号、服务站、状态、总额付款主单支付回调创建/更新
t_scm_payment_info付款单、采购单、账户、金额付款分录支付回调写明细和账户
t_scm_inventorysid、单号、transType、业务日期库存业务主事实采购入库/退货出库写入
t_scm_inventory_info商品、仓库、货位、数量、成本库存流水明细每次库存变动新增
t_scm_inventory_real_timesid、SKU/物料、仓库、货位、实时数量当前库存结果入库增加、退货出库减少

16. 数据关系图

erDiagram
    SCM_PO_ORDER ||--|{ SCM_PO_ORDER_INFO : order_id
    SCM_PO_ORDER ||--o| SCM_PO_ORDER_EXT : order_id
    SCM_PO_ORDER_INFO ||--o{ SCM_PO_ORDER_INFO_QTY : order_info_id
    SCM_PO_ORDER ||--o{ SCM_PO_ORDER_PAYINFO : order_id
    SCM_PO_ORDER_INFO ||--o{ SCM_PO_OUT_INFO : order_info_id
    SCM_PO_ORDER_INFO ||--o{ SCM_PO_CLOSE_INFO : src_order_info_id
    SCM_PO_ORDER ||--o{ SCM_PAYMENT_INFO : stl_id
    SCM_PAYMENT ||--|{ SCM_PAYMENT_INFO : payment_id
    SCM_PO_ORDER ||--o{ SCM_INVENTORY : source_order
    SCM_INVENTORY ||--|{ SCM_INVENTORY_INFO : inventory_id

旧表字段命名同时存在驼峰和下划线,ER 图使用业务语义名。写 SQL 前必须以目标环境表结构和模型字段为准。

17. 事务、锁和幂等边界

场景当前保护仍需注意
保存草稿服务端校验后保存主明细重复点击可能生成多个草稿,前端需使用返回 ID
修改草稿强制状态为草稿提交与修改并发时依赖最新状态校验
待支付取消sid+id Redis 锁锁不是账务幂等,支付回调并发仍需事务判断
明细/整单关闭请求内容 MD5 Redis 锁 + 状态/数量校验不同 payload 可能获得不同锁,数据库数量条件是最后防线
采购入库sid+md5(goodsList) Redis 锁 30 秒锁过期/不同排序 payload 仍可能重复,必须核对已入库量
支付回调数据库事务、支付单/采购状态判断重复回调必须不重复记账和扣额度
订单中心回调消费 ACK/NACK 和业务状态判断payload 幂等键、重试次数需在 MQ 专题确认

资金、库存和来源活动同时变化的操作属于高风险链路。禁止只改一个主状态来“修好页面”。

18. 按采购单号排查 SOP

18.1 主单、明细和来源

SELECT id, billNo, sid, billStatus, orderType, paymentType,
       closeStatus, srcOrderType, srcOrderId, srcOrderNo,
       paymentTime, createTime, modifyTime, isDelete
FROM t_scm_po_order
WHERE sid = :sid AND billNo = :po_bill_no;

SELECT *
FROM t_scm_po_order_info
WHERE sid = :sid AND orderId = :po_order_id
ORDER BY id;

SELECT *
FROM t_scm_po_order_info_qty
WHERE sid = :sid AND orderId = :po_order_id
ORDER BY orderInfoId, id;

先确认 orderType 和来源三字段,再决定查普通采购、活动、预订单还是退货链路。

18.2 支付

SELECT *
FROM t_scm_po_order_payinfo
WHERE sid = :sid AND orderId = :po_order_id
ORDER BY id;

SELECT p.id, p.billNo, p.billStatus, p.totalAmount, p.createTime,
       i.stlId, i.stlNo, i.amount, i.accId, i.wayId, i.createTime AS infoCreateTime
FROM t_scm_payment p
JOIN t_scm_payment_info i ON i.billId = p.id AND i.sid = p.sid
WHERE i.sid = :sid AND (i.stlId = :po_order_id OR i.stlNo = :po_bill_no)
ORDER BY p.id, i.id;

若字段名与环境不一致,以模型和 SHOW COLUMNS 的只读结果校正。核对支付中心回调时间、采购 paymentTime、付款分录和来源单状态是否同时推进。

18.3 发货、关闭和库存

SELECT * FROM t_scm_po_out_info
WHERE sid = :sid AND srcOrderId = :po_order_id
ORDER BY id;

SELECT * FROM t_scm_po_close_info
WHERE sid = :sid AND srcOrderId = :po_order_id AND isDelete = 0
ORDER BY id;

SELECT i.id, i.billNo, i.transType, i.billDate, d.*
FROM t_scm_inventory i
JOIN t_scm_inventory_info d ON d.billId = i.id AND d.sid = i.sid
WHERE i.sid = :sid AND (i.srcOrderId = :po_order_id OR i.srcOrderNo = :po_bill_no)
ORDER BY i.id, d.id;

18.4 代码定位

rg -n "function saveInvPo|function submitInvPo|function cancelWaitPayOrder" application
rg -n "function closeInvPo|function invPoInStorage|function invPoOutStorage" application
rg -n "TYPE_ORDERCENTER_ORDER_AUDIT|paycenterPayResult" application/controllers/tasks application/KzData/Enums
rg -n "SRC_ORDER_TYPE_FLASH_ACTIVITY|ORDERTYPE_FLASH_ORDER" application

19. 常见故障决策表

现象先查再查常见原因
草稿保存失败权限、仓库、订单类型商品校验和供应关系移动仓、物料停用、起订量、急件地址
草稿已提交仍能编辑/编辑失败billStatus提交与页面缓存状态已不是 0 或并发提交
支付成功仍待支付支付回调日志付款三表和采购事务回调失败、来源处理抛错、重复回调冲突
审核一直不推进订单中心消息消费 ACK/NACK、单号映射外部未回调或业务异常 NACK
不能关闭主状态和可关闭量特殊订单类型/来源已出库、赠品/秒杀限制、token 或锁冲突
入库提示超量采购数量拆分发货、关闭、历史入库重复入库或关闭量未扣除
库存增加但采购未完成库存流水采购明细已入库量和事务跨表更新部分失败或历史修复不完整
报表超入/数量不一致业务四类数量事实Hologres 同步时间和口径只修业务表、同步延迟、状态过滤不同
不同服务站退货行为不同新采购服务开关RefundProvider/本地 Service灰度路由差异

20. 改动风险和回归清单

高风险公共点

  • InvPoService.php:关闭、入库、退货、支付方法调用面广。
  • PoOrderSer.php:普通、活动、预订单、关闭和来源释放共用。
  • PayCenterNotify.php:资金、采购状态、账户、来源业务同事务联动。
  • InventorySer.php:所有库存方向共用,交易类型或正负号错误会扩散。
  • PoOrderEnums.php:状态和订单类型被页面、任务、报表、MQ 共同使用。

最小回归集

  • 普通采购保存、修改、删除、提交;移动仓被拒绝。
  • 急件配送到站/客户、自提,联系人地址校验正确。
  • 在线支付成功、失败、重复回调、超时取消;付款分录不重复。
  • 线下挂账提交后不错误进入在线支付。
  • 订单中心审核通过、驳回、取消、重复消息。
  • 明细部分关闭、整单关闭、已有出库量时关闭被限制。
  • 入库一次、分批入库、重复点击、超量、错误货位、发货差异。
  • 不良品、滞销品、未发货、直采和首配退货的新旧服务路径。
  • 秒杀/赠品/预订单来源在支付和关闭后正确回写。
  • 采购主明细、数量拆分、关闭、库存流水和报表口径核对。

21. 证据索引和待确认项

已确认

  • PC 请求入口:application/controllers/scm/InvPo.php。
  • 请求校验:application/Validate/Po/InvPoValidate.php。
  • 草稿保存:PoOrderSer::checkOrderGoods、NormalOrderSaveService::savePoOrder。
  • 关闭/入出库入口:InvPoService::closeInvPo、closeInvPoOrder、invPoInStorage、invPoOutStorage。
  • 支付入口和回调:InvPo 支付方法、tasks/PayCenterNotify.php。
  • 订单中心事件:tasks/OrderCenterNotify.php。
  • App/WMS code:application/config/appapis.php。
  • 状态和类型:application/KzData/Enums/PoOrderEnums.php。
  • 表名:application/config/tables.php。

待代码/环境确认

  • 当前分支 InvPoService::submitInvPo 的真实定义位置和提交内部调用链。
  • 各环境 PC、App、新采购服务的流量切换规则和真实网关 URL。
  • 支付、订单中心和采购服务的消息幂等键及生产重试策略。
  • 采购入库单物理表、库存主单来源字段在各历史版本的实际字段名。
  • 报表中心同步频率和采购完成状态过滤口径。

这些缺口必须在对应专项文章继续回填;在确认前不要把推测当成生产事实。

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

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
PC 新建/提交采购PC 采购页 scm/InvPo/*sid、供应商、仓库、商品数量、价格、付款方式controllers/scm/InvPo.phpPoOrderSer、NormalOrderSaveService、InvPoService采购 billNo采购主表/明细创建并进入合法状态
App/WMS 入库appapis.php 采购/仓储 API code采购单号、SKU、仓位、实收数量App API 映射 ControllerInvPoService::invPoInStorage、InventorySer采购单号 + 入库业务单号入库数量、采购状态和库存同步增加
支付中心回调DEST_PAYCENTER_NOTIFYpayOrderNo、来源单号、payStatus、金额tasks/PayCenterNotify.phpPaymentModel、PaymentInfoModel、PoOrderPayInfoModel采购单号/支付单号支付流水落库,采购待支付状态推进
订单/配送中心通知ODC/配送 MQ来源单号、发货/关闭/签收结果OrderCenterNotify.php、DispatchCenterNotify.php采购关闭、出入库、物流 Service内部单号 + 外部消息业务键发货、关闭、退货状态及数量更新
秒杀派生采购秒杀服务站下单秒杀订单、活动商品、收货信息FlashSaleSer::submitOrdercreatePoOrdersForFlashOrder、采购 ServiceFLASH_ORDER 与采购单关系每个供给关系生成采购单并等待支付/履约

日志证据矩阵

链路段日志来源可检索锚点成功信号失败信号与下一段关联方式
PC 请求Web/CI 日志、InvPo 方法URI、sid、采购 billNo、供应商返回采购主键/单号校验失败、状态冲突、价格或库存异常单号查 SCM_PO_ORDER 与明细
保存/提交PoOrderSer、NormalOrderSaveService、InvPoService 异常类方法、billNo、订单类型主明细提交且事务 commitrollback、重复单、非法状态主表状态 + 明细数量/金额
支付消费PayCenterNotify::paycenterPayResultpayOrderNo、来源采购单号、payStatus=02三类支付记录生成/更新非 02、金额/订单不匹配、消费异常支付单号关联 SCM_PO_ORDER_PAYINFO
入库与库存invPoInStorage、InventorySer采购单号、SKU、transType=150501实收量和实时库存同时增加超收、重复入库、仓位无效、回滚单号查采购明细、库存流水、实时表
异步关闭/发货Order/Dispatch Consumer消息类型、来源单号、消息 IDACK 且订单状态/数量符合消息NACK、重复、顺序倒置消息键 + 单号回查状态历史/业务表

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
保存草稿NormalOrderSaveService::savePoOrder采购事务供应商、仓库、商品、价格SCM_PO_ORDER、SCM_PO_ORDER_EXT、SCM_PO_ORDER_INFO新增主明细;status=0;数量/金额写入sid + billNo + 明细 SKU
提交审核/履约InvPoService、PoOrderSer采购事务当前 status、支付方式、订单类型采购主表、地址/扩展、可能的 MQstatus: 0 -> 1/2/10/13,具体按类型和支付路径主表状态、提交时间、MQ 业务键
支付成功PayCenterNotify回调本地事务回调状态、采购状态、既有支付记录SCM_PAYMENT、SCM_PAYMENT_INFO、SCM_PO_ORDER_PAYINFO、采购主表支付金额累计;待支付 10 -> 后续履约态;重复回调不应重复累计payOrderNo、采购单号、三表金额
发货/出库InvPoService::invPoOutStorage 等履约事务可发数量、当前状态SCM_PO_OUT_INFO、采购明细/主表outQty: old -> old+n;状态进入待入库/配送单号、SKU 发货累计与消息回执
采购入库InvPoService::invPoInStorage -> InventorySer入库与库存事务边界需环境确认采购/发货可收量、仓位、实时库存采购入库事实、SCM_INVENTORY*inQty: old -> old+n;库存 qty: old -> old+n;完成后状态 -> 5采购数量等式 + transType=150501 流水
关闭/退货closeInvPo*、退货 Service关闭/退货事务未履约量、已支付/已入库量SCM_PO_CLOSE_INFO、退货/物流表、库存/退款消息未履约量关闭;退货库存按 170402/对应入出库类型反向变化关闭明细、退货单、退款单、库存流水

子模块级独立追踪

以下每张卡只描述一个采购子模块。排查时先用稳定业务键选中卡片,再沿同一行从请求、代码、数据库走到日志和补偿,不把相邻采购动作混为一次事务。

子模块追踪:po-draft 采购草稿保存与编辑

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
保存PC scm/InvPo/saveInvPo,postData JSONrequest ID、sid、供应商、仓库、SKU;返回采购 id/billNoapplication/controllers/scm/InvPo.php -> PoOrderSer::checkOrderGoods -> NormalOrderSaveService::savePoOrder商品状态、供应关系、采购价、仓库和移动仓规则采购事务内 insert SCM_PO_ORDER/EXT/INFO;status=0,订购量和金额由无记录 -> 草稿快照URI + request ID 找 Controller;再用 sid+billNo 查主明细创建时间校验失败零写入;事务异常 rollback;未知结果先按 sid+billNo/SKU 查重,禁止盲目重提
编辑PC updateInvPo,载荷含草稿 idrequest ID、sid、采购 id/billNo、新商品集合application/controllers/scm/InvPo.php -> 草稿查询 -> 商品重校验 -> 保存 Service主单必须仍为 status=0;读取旧明细、价格和数量同一采购事务 update 主表;明细按代码增删改,数量/金额 old -> new;非草稿不写request ID + billNo 对照更新前后 updated_at、明细主键和合计并发提交导致状态已变时拒绝编辑;不手工覆盖状态,按最新主单重新加载页面

子模块追踪:po-submit-audit 采购提交与审核

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
提交PC submitInvPo 或对应 App API coderequest ID、sid、orderId/billNo、订单类型、支付方式application/controllers/scm/InvPo.php -> InvPoService::submitInvPo;具体实现位置仍按当前分支确认草稿状态、明细有效性、支付/审核路径、来源类型采购事务内 status: 0 -> 1/2/10/13 中符合分支的一种;写提交时间,commit 后可能发订单中心消息request ID + billNo + Controller/Service 方法;MQ 用来源单号/消息 ID 续查状态条件更新 0 行视为并发冲突;本地 commit 后 MQ 失败只补消息,不重复提交采购
审核回调订单中心 DEST_ODC_NOTIFY 审核事件message ID、来源 billNo、外部订单号、审核结果application/controllers/tasks/OrderCenterNotify.php -> 采购审核处理 Service当前 status、订单中心映射、消息是否已处理单消息事务内 status: 1/2 -> 3/8 等合法目标;拒绝写原因;重复结果应为 new -> new 零副作用destination/event + message ID + 来源单号;回查主表状态、审核时间和外部关系未知事件/找不到关系应 NACK 或告警;旧回调不得把终态回退,人工补偿前以订单中心最终态仲裁

子模块追踪:po-payment 采购支付与支付回调

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
发起支付PC noPagePay/unionPayCode/exclusivePayrequest ID、sid、采购 billNo、金额、支付方式;支付中心返回 payOrderNoapplication/controllers/scm/InvPo.php -> application/service/scm/InvPoService.php -> 支付 Provider采购应付、已付、待付状态和账户/授信条件本地与支付中心不是同一事务;保存或返回支付关系,采购通常保持 status=10 等待最终回调request ID + billNo + payOrderNo 串联 Provider 请求和支付中心查询HTTP 超时不能直接判失败;先用 payOrderNo/billNo 查询支付中心和本地关系,再决定重试
成功回调DEST_PAYCENTER_NOTIFY,payStatus=02message ID、payOrderNo、来源采购单号、金额application/controllers/tasks/PayCenterNotify.php::paycenterPayResult -> Payment/PoOrderPayInfo Model -> 来源 Service当前采购状态 10/12、既有支付主明细和支付关系回调事务 insert/update SCM_PAYMENT/INFO、SCM_PO_ORDER_PAYINFO;已付金额 old -> old+n,采购进入后续履约态;同支付单重复为 0 增量message ID + payOrderNo + billNo;三表金额、采购状态和 ACK 同时取证迟到支付先核对此前关闭/库存释放;本地失败按原 payOrderNo 幂等补偿,禁止新建随机支付单重复入账

子模块追踪:po-delivery 采购发货与配送

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
供应方发货订单/采购/配送中心发货事件message ID、采购 billNo、SKU、发货量、物流单application/controllers/tasks/OrderCenterNotify.php 或 DispatchCenterNotify.php -> 采购发货 Service订购量、已发量、关闭量、当前 3/4/13 状态消费事务 insert/update SCM_PO_OUT_INFO;outQty: old -> old+n,部分/全部发货推进主状态event + message ID + billNo+SKU;对照发货明细、主状态和 ACK超发或旧状态拒绝;重复消息按来源发货业务键幂等,不能只凭随机 message ID 去重
配送回写配送中心揽收、运输、到达/签收通知message ID、采购单、配送/运单号、外部状态application/controllers/tasks/DispatchCenterNotify.php -> 物流/采购 Service本地发货关系和当前物流状态本地事务 update 物流/关系状态 old -> next;不直接增加库存,库存要等实际入库外部配送号 + 采购单 + message ID;两端按绝对时间对齐回调乱序不得状态回退;外部已成功本地失败只补关系/状态,不重新创建配送单

子模块追踪:po-inbound 采购入库与库存增加

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
入库确认PC invPoInStroage、App/WMS 采购入库 APIrequest ID、sid、采购 billNo/orderId、SKU、仓库、货位、实收量application/controllers/scm/InvPo.php -> InvPoService::invPoInStorage -> InventorySer::save/changeInventoryQty可收=订购-已收-关闭 或按发货口径;仓位和实时库存四维行入库与库存事务边界需按调用点确认;采购 inQty: old -> old+n,写 SCM_INVENTORY/INFO 的 transType=150501,实时 qty: old -> old+nrequest ID + billNo+SKU+transType;同时查采购明细、库存主分片和实时四维键超收、仓位无效或 Redis 锁冲突时不应部分写;若采购量与库存部分成功,按原入库业务键补断点,禁止再做整单入库
完成判定每次入库事务尾部billNo、每 SKU 订购/发货/已收/关闭量application/service/scm/InvPoService.php 的入库完成判断全部明细的数量等式和当前主状态满足全部履约时主状态 old -> 5;未全部满足保持待入库/配送态;无额外库存变化billNo 查所有明细数量合计和主表更新时间主状态与数量不一致时以过程事实为准;先修缺失入库/关闭事实,再由领域逻辑重算状态

子模块追踪:po-close 采购关闭与取消

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
明细/整单关闭PC closeInvPo/closeInvPoOrder/cancelWaitPayOrderrequest ID、sid、采购 billNo/id、关闭 SKU/数量、tokenapplication/controllers/scm/InvPo.php -> Redis 锁 -> InvPoService::closeInvPo* 或 PoOrderSer::cancelWaitPayOrder当前状态、未发未收量、已支付和特殊来源限制关闭事务 insert SCM_PO_CLOSE_INFO;closeQty: old -> old+n;全部关闭时 status -> 9/12 等代码目标request ID + token/锁 key + billNo+SKU;回查关闭明细、主状态和影响行数锁过期不等于可重做;先查关闭明细。支付/来源释放在事务外失败时仅补对应后置副作用
外部关闭回调订单中心采购关闭/取消事件message ID、来源采购单、关闭原因/明细application/controllers/tasks/OrderCenterNotify.php -> 采购关闭 Service -> MqSer 来源通知本地已履约/已关闭量和外部最终态单消息事务补关闭量和主状态;commit 后通知秒杀/预订单等来源释放,DB 状态 old -> closedevent + message ID + billNo;来源订单 ID 用于下一段日志外部已关本地失败按来源单号重放;来源释放失败不回滚已提交采购关闭,交由来源补偿任务处理

子模块追踪:po-return 采购退货与退货出库

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建提交退货PC saveOrderBack*、submitOrderBack 或新售后入口request ID、sid、原采购单、退货单、SKU、申请量、原因application/controllers/scm/InvPo.php / application/controllers/po/AfterSale.php -> RefundProvider 或老退货 Service原采购已入库、已申请/已退数量、站点新旧服务开关退货事务 insert/update 退货主明细和原单关系;申请量由 0 -> n,状态草稿 -> 待审核request ID + 原采购/退货单号 + SKU;新服务再关联外部售后单超可退量、秒杀限制或来源不符时零写入;新服务超时先查外部售后单,避免重复申请
退货出库PC invPoOutStorage/PDA 仓内动作request ID、退货 billNo/orderId、SKU、仓库货位、实出量application/controllers/scm/InvPo.php -> InvPoService::invPoOutStorage -> InventorySer审核通过状态、可出退货量、实时库存退货出库事务 returnOutQty: old -> old+n;写采购退货库存流水,实时库存 qty: old -> old-n;物流状态初始化退货单 + SKU + transType + 库存四维键;对照物流关系重复扫描/消息必须 0 增量;库存已扣但退货量未写时按原退货出库键补业务明细,不再次扣库

子模块追踪:po-source 秒杀、赠品与预订单来源联动

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
来源建采购秒杀、赠品、预订单 Service 创建采购来源 ID/单号、srcOrderType、orderType、sid、SKU、数量application/Services/Activity/FlashSaleSer.php、预订单/赠品 Service -> 采购保存 Service来源订单状态、锁定/可提额度、商品供给与地址跨域事务边界需分别核对;insert SCM_PO_ORDER*,写 srcOrderId/srcOrderNo/srcOrderType;来源关系由无 -> 有来源单号 + 采购 billNo + source type;两边关系和数量同时回查采购创建失败必须回滚/释放来源锁量;不允许只补一张无来源关系的采购单
支付关闭回传采购支付成功、关闭或退款事件payOrderNo/message ID、采购单、来源 ID、SKU 数量application/controllers/tasks/PayCenterNotify.php / OrderCenterNotify.php -> PoOrderSer -> 来源领域 release/mark Service采购最终态、来源已用/锁定/可提数量和幂等记录采购事务 commit 后异步更新来源:锁定 old-n、已用 old+n,或关闭时可用量 old+n;重复事件 0 变化采购单 + 来源单 + 支付/消息 ID 串联两域日志采购已成功而来源未更新时只重放来源动作;先证明支付/关闭最终态,禁止重做采购主流程