1. 业务目标与边界

销售域负责服务站向维修厂、E站用户、移动商城客户、APP/机器人客户和外部 ERP 客户销售商品。完整链路不是一张“销售表”,而是三类业务事实:

  1. 销售订单:客户要什么、数量和价格是多少,尚未必然扣库存。
  2. 销售出库单:实际从哪个仓库/货位出了多少货,是扣库存和履约的事实。
  3. 销售退货单:客户退回了什么,是库存增加和财务冲回的反向事实。

出库之后还会进入配送、自提、送达、对账、核销、撤销和下游同步。本文把这些状态、表、接口和数据变化分开说明。

2. 最容易混淆的入口

当前 InvSa.php 中的方法名带有明显历史包袱:

方法实际业务不要误解为
addInvSaOr()新增或修改销售订单销售退货
addOutBound()从销售订单生成销售出库单只改销售订单状态
add()新增销售退货单,使用 NormalSaleReturn Factory普通销售开单
updateInvSa()修改销售退货单修改销售订单
cancel()撤销销售出库单并生成反向业务关闭未出库销售订单
closeSaleOr()关闭/结单销售订单撤销已出库单

阅读代码或排查时,必须先确认当前单号属于 t_scm_sa_order、t_scm_sa_invoice 还是销售退货交易,再进入对应方法。

3. 参与角色和前置条件

角色/系统主要动作前置条件
服务站销售员开销售订单、选客户、定价、出库有销售/出库权限,客户和物料有效
仓库人员选择仓库货位、确认出库或销退入库实时库存充足,货位未删除
客户/维修厂收货、自提、挂账、付款、退货联系人和地址有效,支付方式可用
配送中心/TMS配送建单、出库、轨迹、送达、自提出库单和配送关系完整
财务/对账人员收款、对账、撤销对账、核销账户、结算方式、金额和单据状态有效
SAAS/E站/APP/机器人创建来源订单、接收出库和状态sourceType、来源单号和同步事件正确
库存域销售出库减库存、销退入库加库存交易类型、仓库、货位、数量正确

4. 代码入口地图

层级文件/配置主要职责
PC Controllerapplication/controllers/scm/InvSa.php销售订单、出库、销退、对账、撤销
新出库 Controllerapplication/controllers/sale/OutOrder.php新销售出库入口
报价 Controllerapplication/controllers/sale/Offer.php、RapidOffer.php报价和快速报价
App codeapplication/config/appapis.php 的 appsale*App 销售、出库、状态更新
销售订单 Serviceapplication/Services/SaOrders/SaOrderSer.php创建订单、来源映射、出库数量推进
出库单 Serviceapplication/Services/SaOrders/SaInvoiceSer.php出库单查询、详情和状态
老销售 Serviceapplication/service/scm/InvSaService.php页面列表、出库、对账、销退、导出
配送 ServiceTransportSer.php、ThirdDeliverySer.php配送、自提、三方配送
下游同步application/Services/SyncOrder/SyncOrderSer.php销售创建/更新、出库创建/撤销同步
MQ 发送application/Services/Mq/MqSer.php出库、状态、SAAS 事件
配送回调application/controllers/tasks/DispatchCenterNotify.php配送出库、物流、自提、退货关闭
库存application/Services/Storage/InventorySer.php出库/退货库存流水和实时库存
枚举application/KzData/Enums/SaOrderEnums.php订单、出库、来源、支付和收款状态

5. PC 接口总表

5.1 销售订单

接口方法核心请求权限/锁关键调用数据副作用
addInvSaOrpostData,客户、商品、价格、来源新增/修改权限;按 sid+buId Redis 锁 3 秒salesOrValidform -> InvSaService::addInvSaOr新增/更新销售主单和明细
getSalesOrderlist筛选、分页SAOR_QUERYSaOrderSer::list只读
getSalesOrderListNew筛选、分页SAOR_QUERY新服务 getSaOrderList只读,数据源不同
salesOrUpdate销售订单 IDSAOR_UPDATEInvSaService::salesOrUpdate读取编辑数据
closeSaleOrID、type、关闭原因/明细SAOR_CLOSE(部分分支)InvSaService::closeSaleOr待出库量关闭,订单转部分/完成/关闭
SaleOrDetail销售订单 IDSAOR_QUERYInvSaService::SaleOrDetail只读
descUpdateID、type=order/invoice、备注订单/出库备注权限InvSaService::descUpdate更新备注

5.2 销售出库和履约

接口方法核心请求权限/锁关键调用数据副作用
addOutBoundpostData,销售单 ID、出库明细、仓库货位SA_OUT_ADD;按 sid+销售单ID 锁 3 秒InvSaService::addOutBound新增出库主明细、扣库存、同步下游
outBoundList筛选、分页SA_OUT_QUERY老 InvSaService::outBoundList只读
outboundListNew筛选、分页SA_OUT_QUERY新 InvSaSer::outboundList只读,可能与老口径不同
outBoundDetails出库单 IDSA_OUT_QUERYSaInvoiceSer::outBound只读
cancel出库单 ID、原因SA_OUT_RECONCILIATION;按 sid+id 锁InvSaFactory('NormalSale')->deleteInvoice撤销出库、反向库存、删配送、通知下游
reconciliation出库单 ID、对账信息SA_OUT_CANCELInvSaService::reconciliation出库单转已对账/核销
cancelReconciliation出库单 ID撤销对账权限InvSaService::cancelReconciliation回退对账状态
batchReconciliation逗号分隔 ids页面校验逐批对账批量更新状态

5.3 销售退货

接口方法核心请求核心调用数据副作用
addpostData,原出库/客户/退货商品/货位/结算InvSaFactory('NormalSaleReturn')->addInvoice新增销退单、退货入库、登记待对账客户
update/returnupdate销退单 IDInvSaService 查询编辑数据只读
updateInvSapostDataNormalSaleReturn::updateInvoice更新可编辑销退单

6. App 接口 code

code映射用途
appsaleOrderAddapp.sale.saleOrderAdd新增销售单
appsaleOrderListapp.sale.saleOrderList销售列表
appsaleOrderlistDetailapp.sale.saleOrderlistDetail销售出库/退货详情
appsaleOrderListDetailSingleapp.sale.saleOrderListDetailSingle销售订单详情
appsaleOrderDetailOptionapp.sale.saleOrderDetailOption获取修改选项
appsaleOrderlistDeleteapp.sale.saleOrderlistDelete删除销售订单
appsaleOutListapp.sale.out.saleList出库列表
appsaleOutDetailapp.sale.out.saleDetail出库详情
appsaleOutStatUpapp.sale.out.saleStatUp配送/履约状态更新
appsaleOutStockOutapp.sale.out.saleStockOut销售出库
appsale2CanOutapp.sale2.SaleCanOutv2 出库前可操作校验
appsale2StockOutapp.sale2.SaleStockOutv2 销售出库
appsale2SaleAreaQtyapp.sale2.SaleAreaQtyv2 选择货位和库存数量

App code 只表示协议映射。鉴权、签名、网关 URL 和统一 request envelope 需在 API 字典中按环境补齐。

7. 销售订单请求与主流程

7.1 请求结构示例

{
  "postData": "{\"buId\":20001,\"billNo\":\"SO-EXAMPLE\",\"sourceType\":0,\"payType\":0,\"description\":\"门店销售\",\"entries\":[{\"invId\":10001,\"skuId\":\"SKU-001\",\"qty\":4,\"price\":199.00}]}"
}

示例只表达业务结构,具体字段和金额单位必须以当前页面 Network、InvSaService 校验和模型为准。服务端会从会话加入 sid/uid/userName,不应接受前端跨服务站指定。

核心字段:

字段含义风险点
buId客户/往来单位 IDRedis 锁以客户维度限制短时重复操作
entries商品、数量、价格等明细同一 SKU 不应重复;出库时还需仓库货位
sourceType/场景字段来源业务实际常由 saOrderMainFields() 根据场景映射
payType挂账/现金/微信与来源、是否已支付、收款状态联动
billNo/来源单号业务单号外部来源必须保持唯一和可追踪

7.2 创建流程

flowchart TD
    A[PC/APP/E站/SAAS/机器人发起销售] --> B[识别 order_scene/sourceType]
    B --> C[校验登录服务站 客户 商品 数量 价格 支付方式]
    C --> D{重复 SKU/客户/规则是否通过}
    D -- 否 --> X[返回校验错误]
    D -- 是 --> E[saOrderMainFields 映射来源和支付口径]
    E --> F[写销售订单主表]
    F --> G[写销售订单明细和扩展快照]
    G --> H{是否立即出库}
    H -- 否 --> I[销售单状态=待出库]
    H -- 是 --> J[进入出库流程]
    I --> K[SyncOrderSer 同步销售创建/更新]
    J --> K

SaOrderSer::saOrderMainFields() 是来源理解的关键:它把 E站商城、维修、活动、E站 APP IM/商城/活动和机器人等场景映射为 DGJ2 的 sourceType/payType/is_pay。不要由页面展示名称反推来源值。

8. 销售来源和支付口径

sourceType来源支付差异
0普通销售开单表单选择挂账/现金等
1ERP/快维 SAAS下游同步和撤销规则特殊
8E站商城挂账
9E站商城微信支付
10E站维修马上送挂账
15E站维修马上送微信支付
14E站活动按活动场景
16AI 助手挂账
17/18E站 APP IM挂账/微信
19/20E站 APP 商城挂账/微信
21/22E站 APP 活动挂账/微信

历史保留值 2、5、6、7、11、12、13 仍可能存在数据库中。新增逻辑不要删除旧枚举,否则列表、报表和消息反序列化会失去解释能力。

9. 销售出库请求与流程

9.1 请求结构示例

{
  "postData": "{\"id\":30001,\"buId\":20001,\"billNo_type\":0,\"entries\":[{\"saOrderInfoId\":40001,\"invId\":10001,\"skuId\":\"SKU-001\",\"qty\":4,\"locationId\":101,\"locationAreaId\":1001}]}"
}

出库的真实依据不是销售订单页面数量,而是每行剩余可出数量和当前货位实时库存。Controller 还会:

  • 对微仓铺货校验客户已绑定微仓、仓库是可用移动仓。
  • 判断销售单是否已有 SAAS 关联,决定 enableSync。
  • 从报价关系补充每个商品的 saOfferBillNo。
  • 使用 sid + 销售订单ID Redis 锁降低重复出库。

9.2 主流程

flowchart TD
    A[选择待出库销售单] --> B[读取每行销售量 已出库量 已关闭量]
    B --> C[选择仓库 货位 出库数量]
    C --> D[校验货位有效和实时库存]
    D --> E{数量是否超过剩余可出或库存}
    E -- 是 --> X[拒绝出库]
    E -- 否 --> F[Redis 锁销售订单]
    F --> G[事务新增销售出库主表]
    G --> H[新增出库明细和快照]
    H --> I[InventorySer 写销售出库流水]
    I --> J[扣减货位和实时库存]
    J --> K[更新销售订单明细已出库量]
    K --> L{订单剩余数量}
    L -- 全部出库 --> M[销售订单=已完成]
    L -- 仍有未出 --> N[销售订单=部分出库]
    M --> O[创建配送/自提并同步下游]
    N --> O

10. 出库系统时序

sequenceDiagram
    participant U as 服务站
    participant C as InvSa Controller
    participant S as InvSaService/SaOrderSer
    participant DB as 销售数据库
    participant INV as InventorySer
    participant MQ as dgj_notify/MQ
    participant D as 配送中心/TMS
    participant DOWN as SAAS/E站/APP

    U->>C: addOutBound(postData)
    C->>S: addOutBound(登录上下文+enableSync)
    S->>DB: 锁/读取销售单和剩余数量
    S->>INV: 保存销售出库库存事务
    INV->>DB: 新增 inventory/info 并扣实时库存
    S->>DB: 新增 sa_invoice/info 更新 sa_order
    S->>D: 创建配送或自提关系
    S->>MQ: syncOutboundCreate / sendSaOrderOutToMq
    MQ-->>DOWN: dgj_notify 出库创建事件
    S-->>U: 出库单 ID/单号和状态
    D-->>C: 配送出库/物流/自提回调
    C->>DB: 更新出库履约状态
    C->>MQ: 同步状态到下游

11. 两套状态机

11.1 销售订单状态

值常量含义关注数量
0SA_STATUS_WAIT待出库已出库量为 0,仍有可出量
1SA_STATUS_OUT_FEW部分出库已出库量 > 0 且仍有剩余
2SA_STATUS_OUTED已完成可履约数量已全部出库/结单
3SA_STATUS_CLOSE已关闭剩余未履约量被关闭
stateDiagram-v2
    [*] --> 待出库: 创建销售订单
    待出库 --> 部分出库: 部分数量出库
    待出库 --> 已完成: 一次全部出库
    部分出库 --> 已完成: 剩余数量全部出库
    待出库 --> 已关闭: 关闭全部未出数量
    部分出库 --> 已关闭: 结单/关闭剩余数量
    已完成 --> [*]
    已关闭 --> [*]

11.2 销售出库单状态

值常量含义触发方
0OUT_BILLSTATUS_RE_OUTED历史已出库/销退已出库历史兼容
1OUT_BILLSTATUS_OUTED已出库出库成功
2OUT_BILLSTATUS_SENDIND派送中配送中心/APP
3OUT_BILLSTATUS_ARRIVE已送达签收/送达回调
4OUT_BILLSTATUS_CHECK已对账人工/批量对账
5OUT_BILLSTATUS_OVER已核销收款和核销逻辑
6OUT_BILLSTATUS_CANCELED已撤销出库撤销
stateDiagram-v2
    [*] --> 已出库: 出库成功
    已出库 --> 派送中: 配送接单/发车
    已出库 --> 已送达: 自提确认或直接送达
    派送中 --> 已送达: 配送完成
    已送达 --> 已对账: reconciliation
    已对账 --> 已送达: cancelReconciliation
    已对账 --> 已核销: 收款核销完成
    已出库 --> 已撤销: cancel 满足条件
    已撤销 --> [*]
    已核销 --> [*]

出库单状态不等于收款状态。已送达 可以仍未收款,已对账 也不必然全部核销。

12. 配送、自提和三方履约

DispatchCenterNotify 处理的关键回调:

方法外部动作DGJ2 影响
orderTypeChange配送订单类型变更更新配送/订单类型关联
dispatchCenterDeliveryStockOut配送中心确认出库推进出库和配送状态
dispatchCenterLogisticeInfoNotify物流轨迹/状态更新物流信息并通知页面/下游
dispatchCenterSelfPickUpNotify自提完成出库单可直接推进送达
dispatchCenterReturnClose配送退回关闭关闭配送关系并联动业务单

三方配送至少涉及:

  • t_scm_third_party_delivery_order:三方配送业务单。
  • t_sa_invoice_delivery_relation:销售出库单和配送单关系。
  • 出库单自身履约状态。
  • 外部配送单号、轨迹和回调状态。

排查“已送达但页面仍派送中”时,要先确认回调有没有命中正确的出库单关系,而不是直接改出库状态。

13. 对账、收款和核销

13.1 三个独立维度

维度典型字段/表含义
履约状态t_scm_sa_invoice.billStatus是否出库、派送、送达、对账、核销、撤销
收款状态hxStateCode0 未收、1 部分、2 全部
资金分录t_scm_payment、t_scm_payment_info实际收款单和每张业务单核销金额

13.2 收款方式

  • 0 挂账:形成客户应收,后续对账和收款核销。
  • 1 现金:开单/出库时按业务规则形成现金收款。
  • 2 微信:依赖外部支付结果或来源系统已支付口径。

13.3 对账流程

flowchart TD
    A[已送达/可对账出库单] --> B[选择单据或批量 ids]
    B --> C[校验出库状态 客户 金额和权限]
    C --> D[reconciliation/batchReconciliation]
    D --> E[出库单状态=已对账]
    E --> F{是否已足额收款/满足自动核销}
    F -- 否 --> G[保留未收或部分收款]
    F -- 是 --> H[写/关联收款分录]
    H --> I[收款状态=全部 出库单=已核销]
    E --> J[cancelReconciliation]
    J --> K[满足条件时回退对账状态]

对账不是简单改一个 billStatus。批量对账、自动对账和撤销对账需要核对付款金额、差额、客户、已核销记录及是否已进入不可逆财务流程。

14. 销售退货和出库撤销

14.1 销售退货

InvSa::add() 实际创建销售退货:

  1. 校验 SA_ADD 权限。
  2. 解析 postData。
  3. 校验结算方式 wayId 未禁用。
  4. 校验每个 locationAreaId 货位未删除。
  5. 校验结算账户 accId 未禁用。
  6. 从登录态注入 sid/uid/userName。
  7. InvSaFactory('NormalSaleReturn')->addInvoice 创建销退单。
  8. 将客户登记进需要生成对账单的用户集合。

销售退货交易类型通常是 150602,库存方向为增加;普通销售出库交易类型是 150601,方向为减少。历史撤销单可能仍以销售出库类型配合 billStatus=6 展示为销售退货效果,报表必须兼容。

14.2 出库撤销

sequenceDiagram
    participant U as 操作人
    participant C as InvSa::cancel
    participant F as NormalSale Factory
    participant INV as 库存域
    participant TMS as TransportSer
    participant SYNC as SyncOrderSer

    U->>C: cancel(id,reason)
    C->>C: 权限+参数+Redis锁
    C->>F: deleteInvoice(id,pageData)
    F->>INV: 生成反向库存/销退事实
    F-->>C: 原出库单和反向单结果
    C->>TMS: transportDelete(出库单撤销)
    C->>SYNC: syncOutboundCancel
    C-->>U: 撤销成功和业务 ID

来源为 ORDER_SOURCE_TYPE_KW 的出库撤销不走同样的下游同步条件。已配送、已对账或已核销的单据是否允许撤销,必须由 Service 状态校验决定,不能只依赖按钮是否显示。

15. 下游同步事件

SyncOrderSer 使用 destination dgj_notify 发送销售域事件:

方法业务时点典型下游用途
syncOrderCreate销售订单创建SAAS/E站建立对应订单
syncOrderUpdate销售订单修改同步商品、金额或备注变更
syncOutboundCreate销售出库成功下游推进履约和库存
syncOutboundCancel出库撤销下游回退出库/履约

MqSer 还提供 sendSaOrderOutToMq、sendSaOrderStatusToMq、sendSaOrderStatusToSass 等发送方法。维护时先查实际调用方和路由键,避免同一动作重复发两套消息。

16. 核心表读写矩阵

物理表关键字段业务作用典型读写时机
t_scm_sa_orderid、billNo、sid、status、sourceType、客户、金额销售订单主表创建/修改;出库后推进部分/完成;关闭剩余量
t_scm_sa_order_info销售订单 ID、商品、数量、价格、已出库量订单明细创建;出库/关闭累计数量
t_scm_sa_order_info_ext明细 ID、来源/业务扩展明细扩展快照E站、报价、活动等场景保存
t_scm_sa_invoiceid、billNo、sid、billStatus、来源、收款状态销售出库/销退主单出库、配送、对账、核销、撤销
t_scm_sa_invoice_info出库单 ID、原订单明细、商品、数量、金额、仓库货位实际出库/退货明细出库减库存或销退加库存
t_scm_sa_invoice_info_ext出库明细扩展报价、来源和特殊场景出库保存/查询
t_scm_abnormal_return_log出库/退货单、异常原因异常退货审计失败/异常退货记录
t_scm_third_party_delivery_order配送单号、状态、承运商三方配送主单建单、回调、取消
t_sa_invoice_delivery_relation出库单 ID、配送单 ID履约关联配送创建和查询
t_scm_inventory单号、transType、来源单库存业务主事实销售出库/退货新增
t_scm_inventory_info商品、仓库、货位、数量、成本库存流水出库负向、退货正向
t_scm_payment收款单号、客户、状态、总额收款主单现金/微信/挂账收款、核销
t_scm_payment_info收款单、出库单、账户、金额核销分录对账/收款/核销

17. 数据关系图

erDiagram
    SCM_SA_ORDER ||--|{ SCM_SA_ORDER_INFO : order_id
    SCM_SA_ORDER_INFO ||--o| SCM_SA_ORDER_INFO_EXT : order_info_id
    SCM_SA_ORDER ||--o{ SCM_SA_INVOICE : source_order
    SCM_SA_INVOICE ||--|{ SCM_SA_INVOICE_INFO : invoice_id
    SCM_SA_INVOICE_INFO ||--o| SCM_SA_INVOICE_INFO_EXT : invoice_info_id
    SCM_SA_INVOICE ||--o{ DELIVERY_RELATION : invoice_id
    THIRD_DELIVERY_ORDER ||--o{ DELIVERY_RELATION : delivery_id
    SCM_SA_INVOICE ||--o{ SCM_PAYMENT_INFO : settlement_order
    SCM_PAYMENT ||--|{ SCM_PAYMENT_INFO : payment_id
    SCM_SA_INVOICE ||--o{ SCM_INVENTORY : source_order
    SCM_INVENTORY ||--|{ SCM_INVENTORY_INFO : inventory_id

业务追踪键至少包括 sid、销售订单 ID/单号、出库单 ID/单号、原订单明细 ID、来源类型和来源单号。不要只按数字 ID 跨服务站关联。

18. 数量、库存和金额口径

18.1 数量

对每个销售订单明细:

剩余可出数量 = 销售数量 - 已出库有效数量 - 已关闭数量

实际字段和历史撤销口径以 Service 为准。撤销出库不能继续计入有效已出库量;销售退货是新的反向事实,不等价于恢复原销售订单“未出库量”。

18.2 库存

  • 销售出库:交易类型 150601,从指定仓库/货位扣减实时库存。
  • 销售退货:交易类型 150602,退回指定仓库/货位并增加库存。
  • 撤销出库:通常生成反向库存事实或销退效果,不能删除原库存流水抹掉审计。
  • 列表库存、货位库存和报表库存可能来自不同数据源,排查时核对来源时间。

18.3 金额

  • 销售订单是客户承诺和价格快照,出库单是实际履约金额事实。
  • 折扣、赠品、报价单、历史售价和客户价可能影响每行金额。
  • 收款金额通过付款/收款明细与出库单关联,部分收款不能标记全部核销。
  • 销售退货要冲减收入/应收并可能影响原收款,不能只增加库存。

19. 事务、锁和幂等

场景当前保护主要风险
新增销售订单sid+buId Redis 锁 3 秒同一客户不同请求、锁超时后仍依赖单号/数据库幂等
销售出库sid+销售单ID Redis 锁 3 秒并发出库最后库存和剩余量必须在事务内重校验
出库撤销sid+出库单ID Redis 锁库存反向、配送取消、下游同步可能部分成功
批量对账输入 ids 后逐批处理中途失败可能部分完成,需要结果和重跑策略
配送回调外部消息消费重复回调不得重复推进或创建关系
下游同步MQ dgj_notify业务事务提交与发消息先后、重试幂等需确认

Redis 锁只是短期互斥,不等同业务幂等。销售出库必须同时依靠销售剩余量和实时库存的数据库级校验。

20. 按单号排查 SOP

20.1 销售订单和出库单

SELECT id, billNo, sid, status, sourceType, buId,
       payType, totalAmount, createTime, modifyTime, isDelete
FROM t_scm_sa_order
WHERE sid = :sid AND billNo = :sa_order_no;

SELECT *
FROM t_scm_sa_order_info
WHERE sid = :sid AND orderId = :sa_order_id
ORDER BY id;

SELECT id, billNo, sid, billStatus, sourceType, buId,
       hxStateCode, totalAmount, createTime, modifyTime, isDelete
FROM t_scm_sa_invoice
WHERE sid = :sid AND (srcOrderId = :sa_order_id OR srcOrderNo = :sa_order_no)
ORDER BY id;

SELECT *
FROM t_scm_sa_invoice_info
WHERE sid = :sid AND billId IN (:invoice_ids)
ORDER BY billId, id;

字段名可能因历史版本存在大小写差异,先以模型或只读 SHOW COLUMNS 校正。

20.2 库存和收款

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 IN (:invoice_ids) OR i.srcOrderNo IN (:invoice_nos))
ORDER BY i.id, d.id;

SELECT p.id, p.billNo, p.billStatus, p.totalAmount,
       d.stlId, d.stlNo, d.amount, d.accId, d.wayId
FROM t_scm_payment p
JOIN t_scm_payment_info d ON d.billId = p.id AND d.sid = p.sid
WHERE d.sid = :sid
  AND (d.stlId IN (:invoice_ids) OR d.stlNo IN (:invoice_nos))
ORDER BY p.id, d.id;

20.3 配送关系

SELECT r.*, d.*
FROM t_sa_invoice_delivery_relation r
LEFT JOIN t_scm_third_party_delivery_order d ON d.id = r.delivery_order_id
WHERE r.sid = :sid AND r.sa_invoice_id IN (:invoice_ids)
ORDER BY r.id;

实际关系字段以模型为准。查不到关系时继续按外部配送单号、出库单号和回调日志搜索。

20.4 代码定位

rg -n "function addInvSaOr|function addOutBound|function cancel\(" application/controllers/scm/InvSa.php
rg -n "function saCreate|function saOrderOut|function saOrderMainFields" application/Services/SaOrders
rg -n "syncOutboundCreate|syncOutboundCancel|dgj_notify" application/Services
rg -n "dispatchCenterDeliveryStockOut|dispatchCenterLogisticeInfoNotify" application/controllers/tasks

21. 常见故障决策表

现象第一检查点第二检查点常见原因
销售订单不能创建客户、商品、来源、支付方式Redis 锁和重复单号客户禁用、商品重复、来源映射缺字段
销售单不能出库订单状态/剩余量货位实时库存已关闭、并发出库、货位删除或库存不足
出库成功库存未减库存主/明细流水实时库存和事务日志库存步骤失败、历史部分提交或查询维度错
库存已减但销售单仍待出库出库主明细订单明细已出库量事务/修复不完整或来源关联错
下游没收到出库syncOutboundCreate 日志dgj_notify 路由和消费enableSync=false、MQ 失败或下游幂等拒绝
出库撤销后仍在配送配送关系transportDelete 和回调配送取消失败或外部状态不可回退
已送达仍未对账出库状态客户/金额/对账权限履约完成不自动等于对账
已对账但未核销hxStateCode收款明细和差额未收/部分收款或分录未关联
E站来源展示错误sourceTypesaOrderMainFields 场景输入历史值或支付场景映射错误
不同列表结果不一致老/新列表接口数据源开关和过滤条件outBoundList 与 outboundListNew 口径差异

22. 改动风险和回归清单

高风险公共点

  • InvSaService.php:老 PC 列表、出库、对账和退货仍大量共用。
  • SaOrderSer.php:来源映射、订单和出库数量更新影响多个入口。
  • InventorySer.php:销售出库、销退和其他库存业务共用。
  • SyncOrderSer.php:SAAS/E站/APP 下游合同,字段改动会导致跨系统失败。
  • SaOrderEnums.php:历史来源值和两套状态被页面、报表、MQ 共同依赖。

最小回归集

  • 普通销售订单新增、修改、关闭;重复 SKU 和禁用客户拦截。
  • E站商城、E站维修、活动、机器人、E站 APP 的挂账/微信来源映射。
  • 一次全部出库、分批出库、剩余关闭、并发抢同一库存。
  • 普通仓、移动仓和微仓铺货校验,已删除货位不可出库。
  • 出库库存流水、实时库存、销售订单数量和出库单状态一致。
  • 配送、自提、送达和重复回调;三方配送关系正确。
  • 出库撤销生成反向库存、取消配送并通知下游。
  • 挂账、现金、微信;未收、部分、全部收款。
  • 单笔/批量对账、撤销对账、核销和有财务分录时的限制。
  • 销售退货新增/修改、退货入库、原单/收款冲回。
  • 老/新列表、PC/App 接口对同一单据的状态和金额一致。

23. 证据索引和待确认项

已确认

  • PC 方法语义:application/controllers/scm/InvSa.php。
  • 状态/来源/支付:application/KzData/Enums/SaOrderEnums.php。
  • 表名:application/config/tables.php。
  • 销售订单/出库核心:SaOrderSer.php、SaInvoiceSer.php、InvSaService.php。
  • App code:application/config/appapis.php。
  • 下游同步:application/Services/SyncOrder/SyncOrderSer.php。
  • 配送回调:application/controllers/tasks/DispatchCenterNotify.php。
  • 交易类型:application/KzData/Enums/TransTypeEnums.php。

待代码/环境确认

  • addOutBound() 前段对请求进行 JSON 解析的历史代码和当前运行时实际数据形态。
  • 老/新销售、出库列表的数据源切换开关和各环境覆盖范围。
  • 销售创建/出库/撤销 MQ 的完整 payload、幂等键和重试策略。
  • 自动核销的金额容差、差额和账户优先级规则。
  • 第三方配送关系表在各版本的实际字段名和外部状态映射。
  • E站微信支付成功与 DGJ2 收款分录的最终回调链。

待确认项会在销售排查、配送和财务专题继续回填,当前文章不把未验证行为写成绝对结论。

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

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
PC 销售开单PC scm/InvSa/*sid、客户、SKU、数量、价格、仓库、收款方式controllers/scm/InvSa.phpSaOrderSer、InvSaService销售单号销售主明细创建并进入待出库/履约状态
App/E站下单appapis.php 销售 API 或 inner/moveMall/OrderE站来源、收货人、商品、价格和支付上下文App 映射入口/moveMall/Order.phpSaOrderSersourceOrderType + 来源单号与 PC 汇合到销售订单及明细
仓内出库PC/PDA/WMS 出库请求销售单号、SKU、仓位、实出量InvSa/App API ControllerSaInvoiceSer、InvSaService、InventorySer销售单号 + 出库单号出库单生成,库存减少,销售履约量增加
配送/签收回调配送中心 MQ配送单、出库单、骑手/三方状态tasks/DispatchCenterNotify.php配送关系与销售 Service出库单号/配送单号配送、签收、拒收状态回写
收款/退货PC、支付回调或售后流程销售单、金额、支付单;退货商品数量收款 Controller/Consumer、销售退货入口账户/支付 Service、退货入库 Service销售单号、支付单号、退货单号应收核销或退货入库并反向库存/金额

日志证据矩阵

链路段日志来源可检索锚点成功信号失败信号与下一段关联方式
下单入口Web/App/Inner 请求日志、InvSa/Order 方法URI、sid、来源单号、客户返回销售单号参数、价格、来源重复或权限失败来源单号映射内部销售单号
订单落库SaOrderSer/InvSaService 异常类方法、销售单号、sourceOrderType主明细事务提交状态冲突、库存策略/客户校验失败单号查 SCM_SA_ORDER*
出库扣减SaInvoiceSer/InventorySer销售单、出库单、SKU、transType=150601出库明细与库存流水一致重复出库、超出可出量、负库存限制出库单查 SCM_SA_INVOICE* 和库存
配送消费DispatchCenterNotify配送/出库单号、事件名、消息 IDACK 且配送关系状态推进NACK、找不到关系、回调乱序出库单号关联配送关系和销售状态
收款/退货财务或退货 Service/Consumer销售单、支付/退货单号、金额/数量应收核销或退货入库完成金额不平、重复退款、退货超量单号串联账户、支付、退货和库存流水

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
创建销售单SaOrderSer销售事务客户、商品、价格、来源唯一性SCM_SA_ORDER、SCM_SA_ORDER_INFO、SCM_SA_ORDER_INFO_EXTinsert 主明细;status 进入初始/待履约态;应收金额生成sid、销售单号、来源单号、金额合计
创建出库单SaInvoiceSer、InvSaService出库事务可出数量、仓位、订单状态SCM_SA_INVOICE、SCM_SA_INVOICE_INFO、扩展表outQty: old -> old+n;出库单状态初始化销售明细可出量等式、出库单明细
扣减库存InventorySer应与出库事实同一业务边界实时库存四维键SCM_INVENTORY*、SCM_INVENTORY_REAL_TIMEqty: old -> old-n;写 transType=150601 流水出库单号、流水明细、实时库存
配送履约DispatchCenterNotify + 配送 Service消息消费事务当前配送/签收状态SCM_THIRD_PARTY_DELIVERY_ORDER、SCM_SA_INVOICE_DELIVERY_RELATION、销售/出库状态配送状态按事件推进;重复/旧事件不得回退配送单号、出库单号、消费 ACK
收款核销支付/账户 Service财务事务应收余额、支付结果SCM_ACCOUNT*、SCM_PAYMENT*、销售收款事实已收金额 old -> old+n;余额 old -> old-n销售单、支付单、分录借贷/金额平衡
销售退货退货 Service -> InventorySer退货与库存事务边界需核对已出/已退数量、退款金额退货单/日志、库存流水、退款消息returnQty: old -> old+n;库存 old -> old+n;应收/退款反向transType=150502/150602、退货单与原出库单

子模块级独立追踪

子模块追踪:sale-create 销售订单创建

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
PC 创建scm/InvSa 保存/提交销售单request ID、sid、客户、SKU、数量、售价、来源单号application/controllers/scm/InvSa.php -> InvSaService/SaOrderSer客户状态、商品可售、价格权限、来源唯一性销售事务 insert SCM_SA_ORDER/INFO/INFO_EXT;状态由无 -> 初始/待履约,应收和订购量写入request ID + sid+saleBillNo/sourceOrderNo 查 Controller、主明细和创建时间参数或价格校验失败零写入;来源重复先返回/查询原销售单,不新建第二单
E站/App 创建App code 或 inner/moveMall/Orderrequest ID、E站来源、用户、地址、商品价格application/controllers/inner/moveMall/Order.php -> SaOrderSerE站用户/站点、商品、来源场景和收货信息同一销售事务写销售主明细及来源关系,sourceOrderType/sourceOrderNo 由空 -> E站值来源单号 + 内部销售单号贯穿 Inner 日志和表关系远程/响应超时先按来源单查内部单;禁止因未收到响应重复创建

子模块追踪:sale-outbound 销售出库单创建

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建出库PC InvSa/sale/OutOrder、App/PDA 出库 APIrequest ID、销售单号、SKU、仓库货位、实出量application/controllers/scm/InvSa.php / application/controllers/sale/OutOrder.php -> SaInvoiceSer/InvSaService销售状态、可出=订购-已出-关闭/退回、仓位和配送方式出库事务 insert SCM_SA_INVOICE/INFO/INFO_EXT;出库量 old -> old+n,状态由无 -> 待出库/配送request ID + 销售/出库单号 + SKU;回查两类明细数量超量、非法状态或仓位失败不应有出库单;部分创建按出库业务键补明细,不重建主单
状态回写出库主明细 commit 后销售单号、出库单号、各 SKU 累计量application/Services/SaOrders/SaInvoiceSer.php -> 销售状态更新全部销售明细的订购、已出、关闭量销售状态 wait -> part_out/outed;仅部分出库保持部分态,DB 事务内影响行数应为 1两种单号、状态更新时间、明细数量合计主状态与数量不一致时不直接改状态,先补缺失出库/关闭过程事实再重算

子模块追踪:sale-stock 销售出库库存扣减

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
扣减出库确认事务调用库存域出库单号、sid、SKU/invId、仓库货位、数量、transType=150601application/Services/SaOrders/SaInvoiceSer.php / InvSaService -> application/Services/Storage/InventorySer.php实时库存四维行、负库存配置、出库明细写 SCM_INVENTORY/INFO 流水;实时 qty: old -> old-n,流水明细 qty=-n出库单 + transType + 库存四维键;比较流水净额和实时前后值库存不足/死锁应 rollback 出库;出库已成库存未扣时只按原业务键补库存,防止新增第二出库单
派生同步库存 commit 后 MQ/缓存动作出库单、routing key、库存键、message IDapplication/Services/Mq/MqSer.php、库存缓存/预警 Service已提交实时库存和商品维度本地业务表 DB 不变;MQ/cache/search 从旧量 -> 新量,属于异步边界message ID + 出库单 + 库存键串联生产、消费、缓存时间派生层失败不反向重做扣库存;仅补发库存事件/刷新缓存和预警

子模块追踪:sale-delivery 配送、自提与签收

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建履约出库选择自提、自配送或三方配送request ID、出库单、地址快照、配送模式application/Services/Transport/SelfDeliverySer.php / SaOrders/ThirdDeliverySer.php -> TMS/三方 Provider出库状态、地址坐标、已有配送关系本地事务 insert 配送单/SCM_SA_INVOICE_DELIVERY_RELATION,status 由无 -> pending;外部下单在异步/HTTP 边界request ID + 出库单 + local/external delivery ID外部超时先查对方是否有单;外部有、本地无时补关系,禁止直接再次下单
状态签收配送中心 MQ、自提核销或骑手动作message ID、配送/出库单号、核销码、eventapplication/controllers/tasks/DispatchCenterNotify.php 或配送 Controller -> Delivery Service当前配送状态、事件时序、签收幂等键单消息事务 pending -> accepted/picked/delivering/signed/canceled;签收后出库/销售履约态同步推进message ID + local/external delivery ID + 出库单;按事件绝对时间排序旧事件不得回退状态;重复核销/回调为 new -> new,外部成功本地失败按原配送号补写

子模块追踪:sale-receipt 对账、收款与核销

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
收款制单PC Receipt/销售收款或支付回调request ID、销售/出库单、支付单、客户、金额application/service/scm/ReceiptService.php / 财务 Service应收总额、已收/已核销、账户余额和支付最终态财务事务 insert 收款/账户分录;已收 old -> old+n,未收余额 old -> old-nrequest ID + 销售/出库/收款/支付单号;核对分录和来源分摊合计金额超额、重复支付或账户异常 rollback;支付中心成功本地失败按原 payOrderNo 幂等落账
对账核销对账任务、人工核销或收款同步 MQmessage ID、对账单、源出库单集合、核销金额application/Services/Statement/* / Receipt Service全部来源出库和收款状态、账单快照单事务/单消息 update 核销关系和状态;核销额 old -> old+n,全额才进入完成对账单 + 源出库单 + message ID;账单明细总额=核销+未核销漏消息按源出库单重算;不可只改账单完成态而不补核销关系和资金分录

子模块追踪:sale-revoke 撤销出库与状态恢复

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
撤销申请PC 销售出库撤销动作request ID、sid、销售/出库单、原因、操作人application/controllers/scm/InvSa.php -> InvSaService 撤销方法出库是否配送/签收/对账/收款,已撤数量和库存事实撤销事务将出库有效态 old -> canceled/revoked,销售已出量 old -> old-n;仅符合条件可执行request ID + 出库单 + action;查主明细、操作日志、影响行数已配送/核销时先撤外部/财务依赖;任何前置失败都不得直接改出库状态
库存与外部恢复撤销领域 Service 后续出库单、SKU、原库存流水、配送 ID、message IDapplication/Services/Storage/InventorySer.php + Delivery/Mq Service原 150601 流水、配送最终态和实时库存反向库存流水 insert,实时 qty: old -> old+n;配送/下游状态同步撤销,属于跨系统边界出库单 + 原/反向 transType + 配送/消息 ID本地撤销成功、外部失败只补外部取消;库存已回加不得再次执行整段撤销

子模块追踪:sale-return 销售退货与退货入库

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建退货PC InvSa 或审核通过的 ApplyReturnrequest ID、原销售/出库单、退货单、SKU、数量、金额application/Services/InvSa/NormalSaleReturnSer.php / ApplyReturnSer原已出、已退、已申请数量和可退金额退货事务 insert 销退主明细/关系;退货量由无 -> n,原单累计退货 old -> old+nrequest ID + 原出库/退货/申请单号 + SKU超退或重复来源零写入;响应未知先按申请/来源关系查已有退货单
退货入库退款仓库确认入库、退款回调退货单、SKU、仓库货位、实收量、refund/pay IDapplication/Services/InvSa/NormalSaleReturnSer.php -> InventorySer;财务退款 Service可收退货量、实时库存、原支付及已退款金额退货事务写 transType=150602,库存 qty: old -> old+n;实收和退款金额 old -> old+n退货单 + 150602 + 原支付/refund ID;库存和资金分别回查库存与退款非同一原子边界;只补失败一侧,重复回调不得重复入库或退款

子模块追踪:sale-sync 销售与出库下游同步

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
生产消息销售创建、出库、撤销或退货 commit 后销售/出库单、source type、routing key、稳定业务键application/Services/SyncOrder/SyncOrderSer.php -> SyncOrderBuilder.php -> MqSer已提交主明细、状态、数量金额和来源映射本地核心表 DB 不变;构造 dgj_notify 等消息快照,状态由本地已提交 -> 下游待消费业务单号 + routing key + message ID;保存脱敏 payload 摘要序列化/发送失败只补同一业务事件;不能再次执行销售或出库主事务
下游消费确认App/SAAS/订单中心消费者message ID、业务单号、event/version外部消费者;本地回调入口位于 application/controllers/tasks/*Notify.php下游当前状态和消息版本下游 old -> new;本地仅在合同需要时写同步/回调状态,消费属于异步边界生产与消费端用同一业务单号,不以随机 message ID 判断业务幂等下游未收到按业务键重发;下游已处理但 ACK 丢失时重复消息必须零副作用