本文把 DGJ2 中容易混在一起的“大客户销售”“临采”“渠道平台订单”“渠道售后”拆成四类业务对象,再按接口请求、状态流转、金额与数量、库存记账、MQ 回写和异常补偿重新串起来。

阅读本文后,应能回答以下问题:

  1. 平台订单、DGJ 渠道订单和大客户销售出库单是不是同一张单。
  2. created、wait_ship、part_ship、finish 分别由谁推进。
  3. 普通、直采、临采只影响什么,是否等价于渠道来源。
  4. 哪一步才真正扣减库存,哪一步只是登记渠道意图。
  5. 一张渠道退单为什么可能拆成多张 180602 大客户销退单。
  6. OPS、订单中心、DGJ、服务站操作端之间分别传递什么。
  7. 重复下单、部分发货、重复售后、消息失败时如何定位。

1. 业务目标

大客户链路要解决的不是普通零售开单,而是服务站代表快准体系向指定大客户渠道履约:

目标业务含义DGJ 承担的职责
接收平台采购意图外部平台传入门店、商品、数量和销售价建立可追踪的渠道订单
人工代建平台订单服务站为孚创、车之谷等人工录入校验结算价、库存和限价,再推 OPS
订单审核渠道确认或拒绝人工订单将待审核变为待发货或取消
仓内履约服务站选择仓库、区位和配送人生成 180601 大客户销售单并扣库存
渠道回写告知 OPS/渠道实际出库与发货信息通过 MQ 和订单中心接口异步回传
签收接收渠道签收批次累计签收明细,形成履约证据
售后渠道申请退货,服务站审核入库生成 180602 销退单并增加库存
财务与利润保存销售价、结算成本、配送费为大客户报表、NC/OPS 同步提供依据

2. 先建立正确的对象模型

2.1 四类核心对象

对象代表表是否直接改库存状态拥有方关键编号
大客户类型/客户关系t_bs_big_customer、客户关系表否基础资料merchant_code、contact_id
渠道平台订单t_channel_order*否Channel ServiceCHS...、source_order_no
大客户销售/销退单t_scm_big_order*是InvCuServicebillNo,180601/180602
渠道售后申请t_channel_aftersale*否;审核通过后间接入库Channel AftersaleCHR...、source_refund_no
最重要的认知:t_channel_order 是平台业务编排单,t_scm_big_order 才是 DGJ 库存与财务事实单。
flowchart LR
  P["外部渠道订单"] --> C["渠道订单 t_channel_order"]
  C -->|"发货"| B["大客户销售单 180601"]
  B --> I["库存出库流水"]
  B --> O["OPS / NC / 渠道回写"]
  P --> A["渠道售后 t_channel_aftersale"]
  A -->|"审核入库"| R["大客户销退单 180602"]
  R --> J["库存入库流水"]

2.2 三个“来源/类型”维度不能混用

字段枚举回答的问题
scene_codedcd、fc、czg订单来自哪个渠道场景
srcOrderSourcedefault、channel大客户事实单是自制还是由渠道订单生成
serviceType1 普通、2 直采、3 临采大客户销售采用哪种服务模式
transType180601、180602事实单是销售出库还是销售退货
flowchart TD
  A["一张大客户事实单"] --> B{"来源是什么"}
  B -->|"自制"| C["srcOrderSource=default"]
  B -->|"平台"| D["srcOrderSource=channel"]
  A --> E{"服务方式是什么"}
  E -->|"普通"| F["serviceType=1"]
  E -->|"直采"| G["serviceType=2"]
  E -->|"临采"| H["serviceType=3"]
  A --> I{"业务方向是什么"}
  I -->|"销售"| J["transType=180601"]
  I -->|"退货"| K["transType=180602"]

3. 适用场景与非适用场景

3.1 本专题覆盖

  • 服务站手工创建普通、直采、临采大客户销售单。
  • 懂车帝、孚创、车之谷渠道商品、下单、审核、发货和签收。
  • 服务站人工代建孚创/车之谷平台订单。
  • 渠道退货申请、服务站拒绝、服务站确认入库。
  • 渠道订单与 180601/180602 事实单之间的映射。
  • OPS、订单中心、NC、MQ 的通知和补偿。
  • 临采白名单、渠道自动发货配置及一次性发货约束。

3.2 不在本文展开

内容应阅读
普通销售订单、销售出库和收款04_销售_出库_配送与收款.md、15_销售完整排查手册.md
全车件订单07_外部渠道_全车件与大客户订单.md
成本价公式45_成本价_动态成本_利润计算专题.md
MQ 基础设施13_MQ回调与补偿任务地图.md
导入模板通用机制20_导入导出和Excel模板.md

4. 代码地图

4.1 接口入口

层次文件主要方法
外部渠道订单application/controllers/inner/channel/Order.phpcreate、confirm_receipt、order_approve
外部渠道售后application/controllers/inner/channel/Aftersale.phpapply、get_return_stock_in
外部渠道商品application/controllers/inner/channel/Goods.phplists、detail
服务站大客户页面/APIapplication/controllers/scm/InvCu.php大客户单、渠道单、发货、售后、导入、补推、配置
MQ 消费application/controllers/tasks/ChannelOrderNotify.php四类渠道事件
历史/预发临时任务application/controllers/tasks/TmpChannelOrder.phpautoDelivery、autoRefundConfirm

4.2 服务层

服务责任
application/Services/Channel/BaseService.php场景公共模型、客户绑定、商家编码
application/Services/Channel/BaseOrder.php列表、详情、发货、签收、转大客户销售单
application/Services/Channel/Traits/ChannelOrderTrait.phpFC/CZG 人工建单、审核、复制、自动发货
application/Services/Channel/BaseAftersale.php售后申请、审核、按原出库单拆退货
application/Services/Channel/Dcd/*懂车帝差异实现
application/Services/Channel/Fc/*孚创差异实现
application/Services/Channel/Czg/*车之谷差异实现
application/service/scm/InvCuService.php180601/180602 事实单、库存、OPS/NC 同步
application/Services/BigCustomer/BigCustomerService.php大客户第三方商品销售控制

4.3 配置与校验

文件内容
application/Services/Channel/SceneConst.php场景映射、服务类映射、Apollo 配置读取
application/KzData/Enums/ChannelOrderEnums.php渠道订单状态和审核动作
application/KzData/Enums/ChannelAftersaleEnums.php售后状态和动作
application/KzData/Enums/BigOrderEnums.php来源、服务类型、平台单拦截
application/Validate/Channel/*外部及站内请求字段校验
application/Providers/OrderCenter/BigOrderProvider.php订单中心/OPS 对接地址封装

5. 渠道场景与能力矩阵

SceneConst 当前定义三种场景:

场景码名称source 值外部下单人工代建渠道审核复制订单
dcd懂车帝5支持明确不支持明确不支持不支持
fc孚创6支持支持支持支持
czg车之谷7支持支持支持支持

能力不是通过大量 if 堆叠,而是通过服务映射选择实现:

flowchart TD
  R["请求 sceneCode"] --> S["SceneConst 映射"]
  S --> G["CHANNEL_GOODS"]
  S --> O["CHANNEL_ORDER"]
  S --> A["CHANNEL_AFTERSALE"]
  O --> D["DcdOrderService"]
  O --> F["FcOrderService"]
  O --> C["CzgOrderService"]

5.1 Apollo 动态配置

配置键示例语义默认行为风险
dkh.max_price_rate销售价最多为结算价的倍数未配置场景时 1.0缺配置可能意外收紧限价
dkh.merchant_map场景到 OPS 大客户编码未配置返回 null无编码无法查结算价/推 OPS
dkh.max_submit_row单据最大商品行数50配置变更影响人工开单
dkh.once_delivery_merchant必须一次发完的场景空数组缺配置可能开放部分发货
flowchart LR
  A["Apollo 配置"] --> B["SceneConst 静态读取方法"]
  B --> C["人工开单限价"]
  B --> D["OPS 商家编码"]
  B --> E["最大明细行"]
  B --> F["是否允许部分发货"]

6. 客户绑定模型

所有 inner/channel/* 请求先经过 BaseChannel::initStationAndContact():

  1. 请求必须带 sceneCode 和 shopId。
  2. sceneCode 必须在 dcd/fc/czg 中。
  3. 调用 ContactSer::getStationAndContactBySourceId(shopId, sceneCode)。
  4. 找到渠道门店对应的 DGJ 服务站和修理厂。
  5. 注入 channelSid、channelContactId、stationInfo、contactInfo。
sequenceDiagram
  participant P as 渠道平台
  participant BC as BaseChannel
  participant CS as ContactSer
  participant DB as 客户关系/缓存
  P->>BC: sceneCode + shopId
  BC->>CS: getStationAndContactBySourceId
  CS->>DB: 查询渠道门店绑定
  DB-->>CS: stationInfo + contactInfo
  CS-->>BC: 服务站与修理厂
  alt 未绑定
    BC-->>P: 服务站和修理厂绑定信息异常
  else 已绑定
    BC->>BC: 注入 sid/contactId
  end

排查“服务站和修理厂绑定信息异常”时,不要先查订单表,应先查渠道门店关系和 ContactSer 的 300 秒关系缓存。


7. 核心表字典

7.1 渠道订单域

表关键字段业务意义
t_channel_orderid、order_no、source_order_noDGJ 渠道单号与外部单号
scene_code、merchant_code、source_shop_id渠道、商家和门店
sid、contact_id履约服务站和客户
status、approve_*渠道订单状态与审核信息
total_amount、cost_amount、profit_amount售价、成本和价差
t_channel_order_infochan_order_id、order_no关联主单
source_row_no、sku_code、inv_id外部行号、SKU、DGJ 商品
qty、shiped_num、refund_num下单、已发、已申请售后数量
sale_price、cost_price、profit销售价、成本、单件价差
t_channel_order_sign_detailsigning_no、order_row_no签收批次与订单行
qty、signing_person、signing_time签收数量与凭据

7.2 渠道售后域

表关键字段业务意义
t_channel_aftersalerefund_no、source_refund_noDGJ 售后号与渠道售后号
order_no、source_order_no关联渠道原单
status、apply_remark、audit_remark售后状态和意见
total_num、total_amount申请退货数量和金额
t_channel_aftersale_infoorder_row_no关联渠道订单明细行
qty、price、amount申请退货数量和金额
source_order_row_no渠道原始明细行号

7.3 大客户事实域

表关键字段业务意义
t_scm_big_orderbillNo、transType180601 销售或 180602 销退
srcOrderSourcedefault 自制或 channel 平台来源
srcChannelOrder关联 CHS/CHR 平台单号
srcOrderId/srcOrderNo销退关联原销售事实单
serviceType普通、直采、临采
t_scm_big_order_infoiid、invId、qty事实单明细和正负数量
srcOrderEntryId退货关联原出库明细
srcOrderId/srcOrderNo来源主单
t_scm_big_lincai_whitelistsid 等临采可用范围

7.4 关系图

erDiagram
  CHANNEL_ORDER ||--o{ CHANNEL_ORDER_INFO : contains
  CHANNEL_ORDER ||--o{ BIG_ORDER : delivers
  BIG_ORDER ||--o{ BIG_ORDER_INFO : contains
  CHANNEL_ORDER ||--o{ CHANNEL_AFTERSALE : applies
  CHANNEL_AFTERSALE ||--o{ CHANNEL_AFTERSALE_INFO : contains
  CHANNEL_ORDER_INFO ||--o{ CHANNEL_AFTERSALE_INFO : returns
  BIG_ORDER_INFO ||--o{ BIG_ORDER_INFO : returned_by
  CHANNEL_ORDER ||--o{ CHANNEL_ORDER_SIGN_DETAIL : signed
实际表没有统一外键约束,很多关系依赖业务字段和代码约定,因此排查时要同时比对 ID 与单号。

8. 编号与幂等键

编号生成/来源用途推荐唯一范围
source_order_no外部平台传入外部订单幂等scene_code + source_order_no
order_noDGJ 生成 CHS... 或雪花号渠道订单主键全局或 sid + order_no
source_row_no外部平台传入明细映射scene + source_order_no + source_row_no
billNoInvCuService 生成大客户事实单号sid + billNo
source_refund_no外部平台传入售后幂等scene + source_refund_no
refund_noDGJ CHR...渠道售后号全局或 sid + refund_no
signing_no渠道传入签收批次scene + signing_no

代码中的一秒 redis_lock(md5(request)) 只能削平瞬时重复请求,不能替代数据库唯一键和业务幂等。

flowchart TD
  R["重复请求"] --> L{"1 秒 Redis 锁命中?"}
  L -->|"是"| X["快速拒绝/串行"]
  L -->|"否或锁过期"| U{"业务唯一键存在?"}
  U -->|"是"| E["返回原结果"]
  U -->|"否"| N["创建新记录"]

9. 渠道订单状态机

9.1 状态字典

状态中文进入条件允许动作
created待审核FC/CZG 人工代建;部分场景创建渠道审核通过/拒绝
wait_ship待发货外部下单直接待履约,或审核通过整单/部分发货
part_ship部分发货累计已发小于订单量继续发货
finish已完成全部发货完成签收、售后、查询
cancel已取消审核拒绝只读
stateDiagram-v2
  [*] --> created: 人工代建
  [*] --> wait_ship: 外部订单直接进入履约
  created --> wait_ship: auditStatus=1
  created --> cancel: auditStatus=0
  wait_ship --> part_ship: 部分发货
  wait_ship --> finish: 一次发完
  part_ship --> part_ship: 再次部分发货
  part_ship --> finish: 剩余全部发完

9.2 状态真正推进的位置

转换入口核心实现
创建 -> 待审核manual_createChannelOrderTrait::create 强制 created
创建 -> 待发货外部 create场景实现/formatRequestToOrderEntry
待审核 -> 待发货/取消order_approveChannelOrderTrait::order_approve
待发货 -> 部分/完成channelOrderDeliveryBaseOrder::delivery
签收登记confirm_receiptBaseOrder::confirm_receipt,不等于发货状态推进

10. 渠道售后状态机

状态中文说明
created退单创建枚举存在,当前主要申请流程可能直接进入 pass
pass待收货已接受申请,等待服务站确认退货入库
reject已拒绝服务站拒绝并回退占用的 refund_num
finish已完成已生成 180602 销退事实单
stateDiagram-v2
  [*] --> pass: 当前 apply 逻辑
  created --> pass: 审核通过(兼容状态)
  created --> reject: 审核拒绝(兼容状态)
  pass --> finish: 服务站确认入库
  pass --> reject: 服务站拒绝
ACTION_PASS/ACTION_REJECT 是动作,STATUS_PASS/STATUS_REJECT 是状态。当前字符串都为 pass/reject,语义相同但代码阅读时必须区分。

11. 外部商品查询流程

渠道下单前通常先查询 DGJ 可售商品:

sequenceDiagram
  participant P as 渠道
  participant G as inner/channel/Goods
  participant S as 场景 GoodsService
  participant M as Mongo/商品缓存
  participant O as OPS结算价
  P->>G: sceneCode, shopId, 查询条件
  G->>G: GoodsValidate
  G->>S: lists/detail
  S->>M: 商品、库存、服务站可售范围
  S->>O: 大客户结算价/渠道规则
  O-->>S: 价格
  S-->>P: SKU、库存、售价/结算信息

商品列表成功不保证最终能下单。建单时还会重新检查:

  • SKU 是否存在于服务站 Mongo 商品缓存。
  • 数量是否大于零。
  • 价格是否大于等于 0.01。
  • price * qty 是否等于 amount。
  • 服务站成本价是否存在。
  • 人工代建时结算价是否存在、售价是否超过上限、即时库存是否大于零。

12. 外部渠道下单接口

12.1 逻辑入口

控制器方法:inner/channel/Order::create()。

部署 URL 受网关和 CodeIgniter 路由影响,排查时以网关配置为准;代码层可按以下逻辑请求理解:

POST /inner/channel/order/create
Content-Type: application/json

{
  "sceneCode": "dcd",
  "merchantCode": "<渠道商家编码>",
  "merchantName": "示例大客户",
  "shopId": "<渠道门店编号>",
  "orderNo": "<渠道采购单号>",
  "submitTime": "2026-07-16 10:00:00",
  "totalAmount": "260.00",
  "remark": "门店补货",
  "details": [
    {
      "orderRowNo": "ROW-001",
      "skuCode": "<SKU编码>",
      "qty": 2,
      "price": "130.00",
      "amount": "260.00",
      "remark": ""
    }
  ]
}

12.2 服务端处理顺序

sequenceDiagram
  participant P as 渠道
  participant C as Order Controller
  participant B as BaseChannel
  participant S as 场景 OrderService
  participant M as Mongo/成本服务
  participant DB as MySQL
  P->>B: sceneCode + shopId
  B->>B: 解析服务站/客户绑定
  P->>C: 订单请求
  C->>C: 1秒请求锁 + 参数校验
  C->>S: create(request)
  S->>DB: 按 source_order_no 查幂等
  alt 已存在
    DB-->>S: 原 id/order_no
  else 不存在
    S->>M: SKU、invId、成本价
    S->>S: 计算成本与利润
    S->>DB: 事务写主表和明细
  end
  C->>C: createCarType
  C-->>P: DGJ saleOrderNo + 服务站信息

12.3 金额计算

对每一行:

sale_amount   = qty * sale_price
cost_amount   = qty * cost_price
profit        = sale_price - cost_price
profit_amount = qty * profit

主单:

total_amount  = Σ sale_amount
cost_amount   = Σ cost_amount
profit_amount = total_amount - cost_amount

系统注释中将渠道订单的 profit_amount 称为“配送费”,报表或接口是否也采用该口径必须另行确认。


13. 服务站人工代建平台订单

13.1 适用范围

  • 支持 fc、czg。
  • dcd 的 manual_create 明确抛出“该渠道不支持该业务”。
  • 入口:InvCu::channelOrderManualCreate()。

13.2 请求示例

POST /scm/invCu/channelOrderManualCreate
Content-Type: application/json

{
  "sceneCode": "fc",
  "shopId": "<渠道门店编号>",
  "totalAmount": "260.00",
  "remark": "人工代建",
  "details": [
    {
      "skuCode": "<SKU编码>",
      "qty": 2,
      "price": "130.00",
      "amount": "260.00"
    }
  ]
}

13.3 校验链

flowchart TD
  A["人工开单请求"] --> B["scene 仅 FC/CZG"]
  B --> C["商品行不重复且不超过 Apollo 上限"]
  C --> D["查询 OPS 大客户结算价"]
  D --> E{"每行有结算价?"}
  E -->|"否"| X["拒绝"]
  E -->|"是"| F["售价 <= 结算价 * max_price_rate"]
  F --> G["Mongo 商品存在且即时库存 > 0"]
  G --> H["生成渠道单号和行号"]
  H --> I["写 created 渠道订单"]
  I --> J["发送 channel_order_manual_create"]
  J --> K["消费者推 OPS 等待审核"]

13.4 人工建单的两个成功层次

层次判定失败后结果
DGJ 本地建单成功t_channel_order* 已提交页面返回成功
推 OPS 成功MQ 消费调用订单中心成功OPS 可看到并审核

本地建单后的 MQ 发送异常只记日志,不回滚本地订单。因此“页面成功但 OPS 没单”应查 channel_order_manual_create 消息和补推接口,而不是重复建单。


14. 渠道审核流程

14.1 请求示例

POST /inner/channel/order/order_approve
Content-Type: application/json

{
  "sceneCode": "fc",
  "merchantCode": "<商家编码>",
  "merchantName": "示例大客户",
  "shopId": "<门店编号>",
  "saleOrderNo": "<DGJ渠道单号>",
  "sourceOrderNo": "<渠道正式订单号>",
  "auditStatus": 1,
  "submitTime": "2026-07-16 10:10:00",
  "cancelReason": ""
}

14.2 状态推进

sequenceDiagram
  participant P as FC/CZG
  participant C as Order::order_approve
  participant S as ChannelOrderTrait
  participant DB as t_channel_order
  participant MQ as dgj_channel
  P->>C: 审核请求
  C->>C: 参数锁和校验
  C->>S: order_approve
  S->>DB: 只查 status=created
  alt 通过
    S->>DB: 校验 source_order_no 未重复
    S->>DB: status=wait_ship
  else 拒绝
    S->>DB: status=cancel
  end
  S->>MQ: channel_order_approve
  MQ->>MQ: 尝试自动发货

审核通过后不等于已经扣库存,只表示允许发货。真正库存变更仍在下一步。


15. 自动发货决策

channel_order_approve 消费者会按 scene_code 找到 OrderService 并调用 auto_delivery()。

自动发货是否执行还受以下条件控制:

条件来源
订单状态必须允许发货t_channel_order.status
服务站开启渠道自动发货SettingSer::getChannelOrderSetting
仓库/区位能满足出库库存查询
场景是否要求一次发完dkh.once_delivery_merchant
商品实际库存足够库存服务
flowchart TD
  A["审核通过事件"] --> B{"服务站开启自动发货?"}
  B -->|"否"| M["留在待发货,人工处理"]
  B -->|"是"| C["加载订单及仓库区位库存"]
  C --> D{"全部明细可满足?"}
  D -->|"否"| M
  D -->|"是"| E["构造 delivery 请求"]
  E --> F["生成 180601 事实单"]

消费者捕获自动发货异常后仍返回 ACK,不会依赖 MQ 自动重投。此类失败只能靠人工发货、业务补偿或专门任务恢复。


16. 渠道发货接口

16.1 请求示例

POST /scm/invCu/channelOrderDelivery
Content-Type: application/json

{
  "orderNo": "<DGJ渠道单号>",
  "delieverId": 1001,
  "delieverName": "配送员",
  "remark": "第一批发货",
  "details": [
    {
      "rowNo": 12345,
      "outQty": 1,
      "locationId": 10000,
      "locationName": "主仓",
      "locationAreaId": 500001,
      "locationAreaName": "A区",
      "remark": ""
    }
  ]
}

16.2 发货主流程

sequenceDiagram
  participant U as 服务站
  participant BO as BaseOrder
  participant DB as 渠道订单表
  participant IC as InvCuService
  participant INV as InventorySer
  participant MQ as dgj_channel
  U->>BO: delivery(orderNo, details)
  BO->>BO: Redis 锁 + 参数校验
  BO->>DB: checkCanDelivery
  DB-->>BO: 状态、订单量、已发量
  BO->>BO: buildBigOrderDeliverData
  BO->>IC: submitOrderV2(transType=180601)
  IC->>INV: 保存库存出库事实
  IC-->>BO: billNo
  BO->>DB: 累加 shiped_num
  BO->>DB: 更新 part_ship/finish
  BO->>MQ: channel_order_delivery
  BO-->>U: 大客户销售单号

16.3 发货数量守恒

对每个渠道订单明细行:

0 <= shiped_num <= qty
本次 outQty <= qty - shiped_num

主单状态:

所有明细 shiped_num = qty  -> finish
至少一行 shiped_num > 0     -> part_ship
所有明细 shiped_num = 0     -> wait_ship

16.4 一次性发货

若 scene_code 在 dkh.once_delivery_merchant 中:

  • 请求必须覆盖所有未发数量。
  • 不能只选择部分明细。
  • 不能只发某行的一部分。

这项配置改变发货合法性,变更前必须回归人工发货和自动发货。


17. 大客户事实单如何落库存

BaseOrder::delivery() 将新渠道结构转换为老 InvCuService::submitOrderV2() 参数:

渠道字段大客户事实单字段说明
渠道 order_nosrcChannelOrder事实单追溯平台意图单
渠道主单 idsrcOrderId关联渠道主表
渠道明细 idsrcOrderEntryId关联渠道明细
outQtyqty本次实际出库数量
sale_priceprice大客户销售价
cost_price成本字段结算/成本快照
仓库与区位location*库存扣减位置
固定类型transType=180601大客户销售
固定来源srcOrderSource=channel禁止普通编辑/取消
flowchart LR
  C["渠道订单明细 qty"] --> D["本次发货 outQty"]
  D --> B["t_scm_big_order_info qty"]
  B --> I["库存流水负向"]
  B --> S["channel_order_info.shiped_num 累加"]

平台来源事实单被 channelSourceOrderIntercept() 拦截,普通页面不允许编辑或取消,提示:

大客户平台订单或退单,暂不支持编辑和取消操作

这是为了避免绕开渠道状态和数量守恒直接改库存事实。


18. 发货后的异步回写

发货成功发送 channel_order_delivery 到 DEST_DGJ_CHANNEL。

flowchart TD
  A["180601 创建成功"] --> B["sendChannelOrderDelivery"]
  B --> C["ChannelOrderNotify::channelOrderDelivery"]
  C --> D{"事实单存在且来源=channel?"}
  D -->|"否"| X["记录并 ACK 丢弃"]
  D -->|"是"| E["channelOrderDeliveryEvent"]
  E --> F["推渠道发货信息到 OPS"]
  E --> G["推 180601 出库事实到 OPS"]

这里有两个回写目标:

  1. 渠道履约信息:渠道关心发了哪些商品、多少数量、何时发货。
  2. 大客户事实单:OPS/SAP/NC 关心销售出库及财务事实。

因此只看到其中一个成功,不能判定整条链路完成。


19. 签收流程

19.1 请求示例

POST /inner/channel/order/confirm_receipt
Content-Type: application/json

{
  "sceneCode": "dcd",
  "merchantCode": "<商家编码>",
  "shopId": "<门店编号>",
  "signingPerson": "收货人",
  "signingTime": "2026-07-16 16:00:00",
  "signingNo": "SIGN-001",
  "totalQty": 2,
  "details": [
    {
      "orderNo": "<渠道订单号>",
      "orderRowNo": "<渠道明细行号>",
      "skuCode": "<SKU编码>",
      "qty": 2
    }
  ]
}

19.2 签收与发货的区别

维度发货签收
发起方服务站渠道/门店
库存变化扣减不再次扣减
事实单生成 180601记录签收凭据
数量字段shiped_numt_channel_order_sign_detail.qty
失败影响不能完成履约发货已发生但签收凭据缺失
flowchart LR
  O["下单数量"] --> S["已发数量"]
  S --> R["签收批次累计"]
  R --> C{"累计签收 <= 已发?"}
  C -->|"是"| OK["合法"]
  C -->|"否"| ERR["数量异常"]

20. 渠道售后申请接口

20.1 请求示例

POST /inner/channel/aftersale/apply
Content-Type: application/json

{
  "sceneCode": "dcd",
  "merchantCode": "<商家编码>",
  "shopId": "<门店编号>",
  "orderNo": "<渠道原订单号>",
  "sourceRefundNo": "<渠道售后申请号>",
  "submitTime": "2026-07-16 17:00:00",
  "totalAmount": "130.00",
  "remark": "商品退回",
  "details": [
    {
      "orderRowNo": 12345,
      "sourceOrderRowNo": "ROW-001",
      "skuCode": "<SKU编码>",
      "qty": 1,
      "price": "130.00",
      "amount": "130.00",
      "remark": ""
    }
  ]
}

RefundApplyValidate 顶层 scene 中没有显式列出 details,但 BaseAftersale::apply() 实际依赖明细。修改校验器时必须用真实服务逻辑反查,不能只根据 scene 列表判断接口契约。

20.2 申请校验

flowchart TD
  A["售后申请"] --> B["查渠道原单"]
  B --> C["按 orderRowNo 查原明细"]
  C --> D{"SKU/单价/小计匹配?"}
  D -->|"否"| X["拒绝"]
  D -->|"是"| E{"refund_num + qty <= order qty?"}
  E -->|"否"| X
  E -->|"是"| F{"申请 qty <= shiped_num?"}
  F -->|"否"| X
  F -->|"是"| G["生成 CHR 售后单"]
  G --> H["累计原行 refund_num"]
  H --> I["状态 pass,等待实物入库"]

20.3 申请数量占用

创建售后申请时立即执行:

channel_order_info.refund_num += abs(apply_qty)

这不是库存入库,而是占用“可申请售后数量”,防止同一已发数量被并发重复申请。


21. 服务站确认退货入库

21.1 请求示例

POST /scm/invCu/channelAftersalePass
Content-Type: application/json

{
  "refundNo": "<DGJ渠道售后号>",
  "delieverId": 1001,
  "delieverName": "收货员",
  "remark": "实物已验收",
  "details": [
    {
      "rowNo": 67890,
      "refundNo": "<DGJ渠道售后号>",
      "locationId": 10000,
      "locationName": "主仓",
      "locationAreaId": 500001,
      "locationAreaName": "退货区",
      "remark": "外观正常"
    }
  ]
}

21.2 为什么必须按原出库单拆单

一个渠道订单可以分多次发货:

渠道明细订购 10 件
第一次 180601-A 发 4 件
第二次 180601-B 发 6 件
渠道一次申请退 7 件

DGJ 销退必须关联原销售出库明细,因此 7 件要摊派:

180602-R1 -> 关联 180601-A,退 4 件
180602-R2 -> 关联 180601-B,退 3 件
flowchart TD
  A["渠道退货申请 7"] --> B["查询该渠道明细对应的历史 180601 明细"]
  B --> C["扣除历史已退数量"]
  C --> D["按 bigOutId 分组"]
  D --> E["依次摊派申请数量"]
  E --> F["生成 180602-R1: 4"]
  E --> G["生成 180602-R2: 3"]

21.3 通过流程

sequenceDiagram
  participant U as 服务站
  participant A as BaseAftersale
  participant O as OPS/订单中心
  participant DB as 渠道售后表
  participant IC as InvCuService
  participant INV as 库存
  participant MQ as dgj_channel
  U->>A: refund_pass
  A->>DB: status 必须为 pass
  A->>A: 校验明细并按原出库单拆分
  A->>O: 先推售后审核通过
  A->>DB: 开启本地事务
  loop 每个原出库单
    A->>IC: submitOrderV2(transType=180602)
    IC->>INV: 退货入库
    A->>DB: status=finish
  end
  A->>DB: 提交事务
  A->>MQ: 每张 180602 发送 channel_refund_action

注意:OPS 审核结果推送发生在本地事务之前。若推送成功而本地销退失败,会出现外部认为通过、本地未入库的不一致,需要补偿而不是再次盲目审核。


22. 服务站拒绝售后

sequenceDiagram
  participant U as 服务站
  participant A as BaseAftersale
  participant O as OPS
  participant DB as MySQL
  participant MQ as dgj_channel
  U->>A: refund_reject(refundNo, remark)
  A->>DB: 校验 status=pass
  A->>O: 推送拒绝
  A->>DB: 事务 status=reject
  A->>DB: 原渠道明细 refund_num -= qty
  A->>DB: 提交
  A->>MQ: channel_refund_action(action=reject)

拒绝不产生 180602,也不改变实物库存;它只释放售后申请占用。


23. 全链路数量守恒

定义:

Q_order   = 渠道订购数量
Q_ship    = 渠道累计发货数量
Q_apply   = 当前已占用售后申请数量 refund_num
Q_return  = 已生成 180602 的退货数量
Q_sign    = 累计签收数量

应满足:

0 <= Q_ship <= Q_order
0 <= Q_apply <= Q_ship
0 <= Q_return <= Q_ship
0 <= Q_sign <= Q_ship
拒绝售后后 Q_apply 应回退
flowchart LR
  O["Q_order 订购"] -->|"发货"| S["Q_ship 已发"]
  S -->|"申请占用"| A["Q_apply"]
  A -->|"审核通过"| R["Q_return 已退"]
  A -->|"审核拒绝"| B["释放 Q_apply"]
  S -->|"签收"| G["Q_sign"]

23.1 金额守恒

对象公式
渠道订单行sale_amount = qty * sale_price
售后申请行amount = refund_qty * original_price
大客户销退行amount = splitQty * original_price
订单总额各行金额求和,保留两位小数

接口按字符串两位小数比较时,前端和渠道应先统一舍入方式,避免浮点差异导致“小计有误”。


24. MQ 事件地图

目标 Topic:dgj_channel。

事件生产时点消费方法失败策略
channel_order_manual_create本地人工平台单创建后channelOrderManualCreate推 OPS 失败返回 NACK
channel_order_approve渠道审核后channelOrderApprove自动发货失败仍 ACK
channel_order_delivery180601 发货成功后channelOrderDelivery非法/找不到事实单时 ACK 丢弃
channel_refund_action售后通过/拒绝后channelRefundAction参数缺失时 ACK 丢弃
flowchart LR
  P["生产者"] --> T["DEST_DGJ_CHANNEL"]
  T --> C["ChannelOrderNotify"]
  C --> M["人工单推 OPS"]
  C --> A["审核后自动发货"]
  C --> D["发货/出库推 OPS"]
  C --> R["售后事实推 OPS"]

24.1 消息幂等关注点

  • channel_order_manual_create 重投必须按渠道单号幂等。
  • channel_order_delivery 重投不能重复通知渠道或重复创建事实单。
  • channel_refund_action 一张渠道退单可能对应多个 180602 billNo,不能只按 refundNo 粗暴去重。
  • handleBefore/handleAfter 的事件记录只解决消费追踪,不自动保证下游幂等。

25. OPS、订单中心与 NC 对接

BigOrderProvider 主要封装以下业务接口:

常量/方法语义业务用途
bigcustomer/order/submitorder推送大客户销售事实
bigcustomer/aftersale/dgj/save推送大客户售后事实
dgj/partner/order/delivery推渠道发货信息
dgj/partner/aftersale/confirm推渠道退货审核/入库结果
dgj/partner/purchase/part查询大客户结算价
flowchart TD
  CH["渠道平台"] --> OC["OPS / 订单中心"]
  OC --> DGJ["DGJ inner/channel"]
  DGJ --> BO["180601 / 180602"]
  BO --> OC
  BO --> NC["NC / 财务链路"]
  DGJ --> CH

生产排查必须区分:

  1. 渠道到订单中心失败。
  2. 订单中心到 DGJ 失败。
  3. DGJ 本地建单失败。
  4. DGJ 到订单中心回写失败。
  5. 订单中心到渠道回写失败。
  6. 大客户事实单到 NC 失败。

26. 普通、直采与临采

26.1 服务类型

serviceType名称代码语义
1普通平台订单也归普通业务
2直采运费、供应关系等有独立处理
3临采受临采资格/白名单控制

26.2 临采白名单

相关对象:

  • 表:t_scm_big_lincai_whitelist。
  • Redis:RedisKeys::HASH_LINCAI_WHITELIST。
  • 客户服务:ContactService 中支持临采的大客户类型查询。
flowchart TD
  A["选择 serviceType=3 临采"] --> B{"服务站/大客户类型在白名单?"}
  B -->|"否"| X["不展示或拒绝"]
  B -->|"是"| C["允许临采开单"]
  C --> D["仍需执行商品、价格、库存校验"]

临采白名单只决定能否使用该业务模式,不表示商品一定有库存,也不代表平台来源。


27. 服务站渠道配置

入口:

  • InvCu::channelGetConfig()。
  • InvCu::channelSaveConfig()。
  • SettingSer::getChannelOrderSetting()。
  • SettingSer::setChannelOrderSetting()。

配置影响自动发货等站点级行为。排查时应同时确认:

层级检查项
Apollo场景全局约束、限价、一次发货
服务站配置是否开启自动发货
大客户类型商家编码、客户关系
临采白名单是否允许临采
当前库存仓库和区位是否可满足

28. 导入、复制与最近价格

28.1 导入模板

SceneConst::IMPORT_GOODS_TEMPLATE:

Excel 列必填含义
* 物料编码是sku_id
* 数量是sale_num
单价(选填)否sale_price

入口包括:

  • channelGoodsImport()。
  • getImportTemplate()。
  • application/Services/Import/Type/BigSaleGoodsService.php。

28.2 最近价格

ChannelOrderInfoModel::getSkuLastSalePrice() 只从 status=finish 的渠道订单中按 sid + contact_id + sku_code 取最新明细,可选再按 scene_code 过滤。

28.3 复制订单

  • 只支持 FC/CZG。
  • 复制的是开单输入,不应复制旧状态、已发量和售后量。
  • 新单仍需重新校验结算价、库存、限价和客户绑定。
flowchart LR
  O["历史完成订单"] --> P["最近价格"]
  O --> C["复制订单草稿"]
  E["Excel 模板"] --> I["导入明细"]
  P --> N["新人工平台单"]
  C --> N
  I --> N
  N --> V["重新校验"]

29. 核心查询 SQL

以下 SQL 仅作为排查模板。先确认环境和分片,不执行写操作。

29.1 按渠道外部单号查主单

SELECT id, sid, scene_code, merchant_code, source_shop_id,
       order_no, source_order_no, status,
       total_amount, cost_amount, profit_amount,
       approve_user, approve_remark, create_time, update_time
FROM t_channel_order
WHERE scene_code = 'fc'
  AND source_order_no = '<外部订单号>';

29.2 查渠道订单明细数量

SELECT id, chan_order_id, order_no, source_row_no, sku_code, inv_id,
       qty, shiped_num, refund_num,
       sale_price, cost_price, profit, sale_amount
FROM t_channel_order_info
WHERE order_no = '<DGJ渠道单号>'
ORDER BY id;

29.3 查对应大客户事实单

SELECT id, sid, billNo, transType, billStatus,
       srcOrderSource, srcChannelOrder, srcOrderId, srcOrderNo,
       serviceType, totalQty, totalAmount, amount, createTime
FROM t_scm_big_order
WHERE sid = <sid>
  AND srcChannelOrder IN ('<CHS渠道单号>', '<CHR售后单号>')
ORDER BY id;

29.4 查事实单明细与原单关系

SELECT id, iid, skuId, invId, qty, price, amount,
       srcOrderId, srcOrderNo, srcOrderEntryId
FROM t_scm_big_order_info
WHERE iid IN (<大客户事实单ID列表>)
ORDER BY iid, id;

29.5 查渠道售后

SELECT id, sid, scene_code, refund_no, source_refund_no,
       order_no, source_order_no, status,
       total_num, total_amount, apply_remark, audit_remark,
       create_time, finish_time
FROM t_channel_aftersale
WHERE refund_no = '<CHR售后单号>'
   OR source_refund_no = '<渠道售后号>';
SELECT id, refund_id, refund_no, source_refund_no,
       order_row_no, source_order_row_no, sku_code,
       qty, price, amount, cost_price
FROM t_channel_aftersale_info
WHERE refund_no = '<CHR售后单号>'
ORDER BY id;

29.6 查数量守恒异常

SELECT id, order_no, sku_code, qty, shiped_num, refund_num
FROM t_channel_order_info
WHERE sid = <sid>
  AND (
       shiped_num < 0
    OR shiped_num > qty
    OR refund_num < 0
    OR refund_num > shiped_num
  );

29.7 查签收批次

SELECT signing_no, order_no, order_row_no, sku_code,
       qty, signing_person, signing_time, create_time
FROM t_channel_order_sign_detail
WHERE order_no = '<DGJ渠道单号>'
ORDER BY id;

30. 常用代码检索命令

rg -n "class (Order|Aftersale|Goods)" application/controllers/inner/channel
rg -n "function (create|delivery|confirm_receipt|order_approve)" application/Services/Channel
rg -n "function (apply|refund_pass|refund_reject|splitToRefundOrder)" application/Services/Channel
rg -n "STATUS_(CREATED|WAIT_SHIP|PART_SHIP|FINISH|CANCEL)" application
rg -n "STATUS_(PASS|REJECT)|ACTION_(PASS|REJECT)" application/Services/Channel application/controllers
rg -n "srcOrderSource|srcChannelOrder|serviceType|180601|180602" application/service/scm/InvCuService.php
rg -n "sendChannel(Order|Refund)|DEST_DGJ_CHANNEL" application
rg -n "dkh\.(max_price_rate|merchant_map|max_submit_row|once_delivery_merchant)" application
rg -n "LINCAI|lincai|临采" application

31. 排查 SOP:平台下单失败

flowchart TD
  A["平台下单失败"] --> B{"BaseChannel 初始化成功?"}
  B -->|"否"| C["查 sceneCode/shopId 绑定"]
  B -->|"是"| D{"Validate 通过?"}
  D -->|"否"| E["查必填字段/金额/明细"]
  D -->|"是"| F{"source_order_no 已存在?"}
  F -->|"是"| G["确认是否幂等返回原单"]
  F -->|"否"| H{"SKU 与成本价存在?"}
  H -->|"否"| I["查 Mongo 和成本服务"]
  H -->|"是"| J["查事务/唯一键/数据库异常"]

检查顺序:

  1. 用 sceneCode + shopId 验证服务站/修理厂绑定。
  2. 核对 orderNo 是否已写入 source_order_no。
  3. 核对每行 price * qty = amount。
  4. 查服务站 Mongo 是否存在 SKU。
  5. 查 CuOrderMaterielSer 是否返回成本价。
  6. 查主表有而明细无,判断事务或批量写异常。

32. 排查 SOP:页面成功但 OPS 没单

  1. 查 t_channel_order 是否存在,状态是否 created。
  2. 查日志“平台人工开单消息事件触发异常”。
  3. 查 channel_order_manual_create 是否生产。
  4. 查 ChannelOrderNotify 消费结果,失败时应为 NACK。
  5. 查 pushChannelManualCreateToOps() 请求和响应。
  6. 确认 Apollo merchant_map 是否有该场景。
  7. 使用 rePushChannelOrderToOps() 前先确认下游幂等。
flowchart LR
  A["DGJ 有单"] --> B["MQ 有消息?"]
  B -->|"无"| C["生产阶段失败"]
  B -->|"有"| D["消费者成功?"]
  D -->|"否"| E["NACK/重试"]
  D -->|"是"| F["订单中心有记录?"]
  F -->|"否"| G["查 Provider 响应与幂等"]

33. 排查 SOP:审核通过但未自动发货

  1. 查渠道订单是否从 created 到 wait_ship。
  2. 查 channel_order_approve 消息是否消费。
  3. 查 scene_code,老消息缺失时消费者会从 DB 查询并默认回退 FC。
  4. 查服务站自动发货配置。
  5. 查 dkh.once_delivery_merchant 是否要求一次发完。
  6. 查仓库、区位、实时库存能否覆盖全部明细。
  7. 查消费者日志“大客户自动发货异常”。
  8. 因异常被 ACK,确认是否需要人工发货或补偿任务。

34. 排查 SOP:发货失败或库存不一致

现象首查
“当前订单不能发货”渠道主单状态
“超过可发数量”qty - shiped_num
“需要一次性发完”Apollo 一次发货场景
仓库/区位错误locationId/locationAreaId 与实时库存
有渠道状态无事实单submitOrderV2 事务和返回
有事实单但渠道仍待发shiped_num 更新是否成功
库存已扣但 OPS 无记录channel_order_delivery 消费与 Provider

正确核对顺序:

渠道订单行 -> 180601 事实单行 -> 库存流水 -> 实时库存 -> MQ -> OPS

不要只看 t_channel_order.status 推断库存已经扣减。


35. 排查 SOP:售后申请失败

  1. 按 sourceRefundNo 查是否重复申请。
  2. 查原渠道订单和 orderRowNo。
  3. 对比 SKU、单价、金额。
  4. 对比 qty、shiped_num、refund_num。
  5. 查是否申请量大于已发数量。
  6. 查事务后 refund_num 是否已增加。
  7. 若主售后存在、明细缺失,查事务和批量插入。
flowchart TD
  A["售后失败"] --> B{"原订单已发货?"}
  B -->|"否"| X["不能退未发商品"]
  B -->|"是"| C{"申请量 <= 已发-已占用?"}
  C -->|"否"| Y["重复/超量申请"]
  C -->|"是"| D{"原行与金额一致?"}
  D -->|"否"| Z["渠道明细映射错误"]
  D -->|"是"| E["查写库异常"]

36. 排查 SOP:售后通过但库存没增加

  1. 查 t_channel_aftersale.status 是否仍为 pass 还是已 finish。
  2. 查 splitToRefundOrder() 是否找到历史 180601 明细。
  3. 查原出库明细已退数量,确认是否无剩余可退。
  4. 查生成了几张 180602,不要只按退单号期待一张。
  5. 查每张 180602 的 srcOrderEntryId。
  6. 查退货仓库/区位。
  7. 查库存流水与实时库存。
  8. 若 OPS 已收到通过、本地无 180602,按跨系统不一致处理。

37. 排查 SOP:拒绝后仍不能再次申请

  1. 查售后主单是否 reject。
  2. 汇总售后明细 qty。
  3. 查原渠道明细 refund_num 是否已减回。
  4. 查拒绝事务是否回滚。
  5. 查是否存在其他尚未完成的售后占用。
  6. 禁止直接将 refund_num 改为零,应按所有有效售后重新汇总。

推荐只读核对公式:

期望 refund_num = Σ(status in [pass, finish] 的售后明细绝对数量)

38. 已识别代码风险

编号风险可能后果建议
R44-01一秒请求锁过短延迟重试后重复建单数据库唯一键 + 幂等返回
R44-02source_order_no 幂等查询未显式带场景不同渠道同号可能串单确认唯一范围并补场景
R44-03人工单本地提交后 MQ 失败只记日志DGJ 有单、OPS 无单outbox/可查询补推状态
R44-04审核拒绝分支中 $sourceOrderNo 可能未初始化notice 或写入不确定值分支前初始化
R44-05自动发货异常仍 ACK消息不再自动重试业务补偿队列/失败状态
R44-06发货事实单与渠道累计更新为复合事务跨服务副作用难原子明确本地事务范围与补偿
R44-07OPS 发货和事实单为两次推送只成功一半分别记录回写状态
R44-08售后申请立即占用 refund_num中间失败可能残留占用确保同一事务并可重算
R44-09checkRefundDetails 中退单号判断表达式疑似优先级错误明细可能串售后单改为严格不等并补测试
R44-10售后通过先推 OPS、后开本地事务外部通过而本地未入库outbox/状态化补偿
R44-11多张销退单循环中反复更新主状态部分成功时状态难解释子任务状态与统一汇总
R44-12拒绝先推 OPS、后本地事务外部拒绝而本地占用未释放调整顺序或补偿
R44-13refund_num 使用算术增减并发和重复消费可能漂移条件更新/版本号/重算
R44-14场景能力散落在子类抛异常新场景容易漏能力声明建立显式能力矩阵
R44-15Apollo 缺配置采用默认值行为静默变化启动检查和配置监控
R44-16TmpChannelOrder 内有固定预发 sid/用户误调度有数据风险标记环境并禁止生产注册
R44-17平台事实单禁止普通取消错单只能走专门补偿建立渠道撤单流程
R44-18签收与发货状态分离已发未签长期悬挂签收差异报表/超时提醒

39. 数据修复原则

39.1 禁止的修复方式

  • 只改 t_channel_order.status=finish。
  • 只改 shiped_num 而不补 180601 和库存流水。
  • 直接把 refund_num 清零。
  • 只改售后 status=finish 而不生成 180602。
  • 重放 MQ 前不确认下游幂等。
  • 将平台来源事实单伪装成 srcOrderSource=default 后普通删除。

39.2 推荐修复顺序

flowchart TD
  A["冻结目标单据"] --> B["备份主表/明细/库存快照"]
  B --> C["计算期望数量和状态"]
  C --> D["优先调用原业务补偿入口"]
  D --> E["核对事实单和库存"]
  E --> F["补发 MQ/外部回写"]
  F --> G["记录审计与回滚 SQL"]

40. 日志定位关键词

业务关键词
外部下单平台订单创建异常、平台下单参数
人工建单平台人工开单异常、平台人工开单消息事件
审核审核平台异常、渠道方确认平台订单异常
自动发货大客户自动发货异常
人工发货平台订单发货异常、大客户库存变动通知
OPS 回写推送大客户发货信息到ops异常、大客户未到OPS单号
售后申请退货申请异常
售后拆单拆单结果、退货时拆单异常
售后入库平台退货确认入库异常
售后拒绝平台售后单拒绝异常

日志检索时至少带一个业务唯一号:source_order_no、CHS、billNo、source_refund_no 或 CHR。


41. 监控指标建议

指标告警条件
created 待审核时长超过渠道约定时限
wait_ship 待发货时长超过服务站履约时限
part_ship 悬挂时长长期无后续发货
渠道单无 180601状态已发但无事实单
180601 无库存流水事实单与库存不一致
refund_num > shiped_num数量守恒破坏,立即告警
pass 售后时长长期未确认入库/拒绝
OPS 回写失败数连续失败或积压
MQ ACK 丢弃数参数缺失或事实单不合法

42. 单元测试清单

42.1 订单

  • 同一外部订单重复请求返回原单。
  • 不同场景相同外部单号不串单。
  • 缺 SKU、成本价、结算价分别失败。
  • 金额小计不等失败。
  • 人工价格超过场景上限失败。
  • 明细重复、超过最大行数失败。
  • FC/CZG 人工建单成功,DCD 明确拒绝。
  • 只有 created 可审核。
  • 审核通过和拒绝分别推进正确状态。

42.2 发货

  • 待发货整单发货生成一张 180601。
  • 允许部分发货场景正确进入 part_ship。
  • 最后一批发货后进入 finish。
  • 一次发货场景拒绝部分发货。
  • 并发发货不超过订单数量。
  • 仓库/区位库存不足不生成事实单。

42.3 售后

  • 不能退未发数量。
  • 不能超过可售后数量。
  • 重复售后号幂等。
  • 申请成功增加 refund_num。
  • 拒绝成功释放 refund_num。
  • 一次售后跨两张原出库单时生成两张 180602。
  • 重复确认入库不重复增加库存。
  • 退货明细不能串到其他 refundNo。

43. 集成回归清单

43.1 DCD 外部下单链

  1. 查询商品。
  2. 外部创建订单。
  3. 服务站查看待发货。
  4. 整单发货并生成 180601。
  5. OPS 收到发货与事实单。
  6. 渠道签收。
  7. 渠道申请退货。
  8. 服务站确认入库并生成 180602。

43.2 FC/CZG 人工单链

  1. 导入/搜索商品。
  2. 校验结算价、限价和库存。
  3. 本地创建 created 订单。
  4. MQ 推 OPS。
  5. 渠道审核通过。
  6. 自动或人工发货。
  7. 验证渠道、OPS、NC 和库存。

43.3 临采链

  1. 无白名单时隐藏或拒绝。
  2. 加白名单后可选 serviceType=3。
  3. 保存、提交、库存和成本均正确。
  4. 移除白名单不影响历史单查询。

44. 上线前回归矩阵

变更点最少回归
SceneConst三渠道商品、下单、审核、发货
BaseChannel三渠道门店绑定与错误提示
BaseOrder整单、部分、重复、一次发货、签收
ChannelOrderTraitFC/CZG 人工单、审核、复制、自动发货
BaseAftersale申请、拆单、通过、拒绝、重复操作
InvCuService自制/平台来源、180601/180602、库存、OPS/NC
MQ 事件四种事件 ACK/NACK、重投和下游幂等
临采白名单缓存命中、失效、历史数据
Apollo 配置缺失、灰度、回滚和多场景差异

45. 待环境确认项

以下内容不能仅凭仓库代码最终确认,上线或事故复盘时应补入环境事实:

  1. t_channel_order 的生产唯一索引是否覆盖 scene_code + source_order_no。
  2. t_channel_aftersale 的唯一索引是否覆盖 scene_code + source_refund_no。
  3. t_channel_order_sign_detail 的签收幂等唯一键。
  4. 各环境实际 dkh.* Apollo 配置值及变更负责人。
  5. 服务站渠道自动发货配置的真实存储位置和默认值。
  6. created 售后状态是否仍有线上流量,当前 apply 为何直接写 pass。
  7. OPS/订单中心各接口的最终幂等键和超时重试策略。
  8. channel_order_delivery 和 channel_refund_action 下游是否支持重复消息。
  9. NC 同步任务调度频率、失败表和人工补推入口。
  10. 临采白名单 Redis 与 MySQL 的刷新/失效机制。
  11. DCD/FC/CZG 当前各自是否允许部分发货。
  12. 签收累计量与已发量是否有数据库级约束。
  13. 历史渠道单号生成规则及全局唯一性。
  14. 预发 TmpChannelOrder 是否被任何生产调度误引用。

46. 一页式业务脉络

flowchart TD
  A["渠道商品查询 / 人工选品"] --> B["渠道下单意图"]
  B --> C{"外部直接下单还是人工代建"}
  C -->|"外部"| D["创建渠道订单"]
  C -->|"人工"| E["结算价+限价+库存校验"]
  E --> F["created 待渠道审核"]
  F -->|"拒绝"| X["cancel"]
  F -->|"通过"| G["wait_ship"]
  D --> G
  G --> H["选择仓库/区位/数量"]
  H --> I["生成 180601 大客户销售单"]
  I --> J["扣库存"]
  I --> K["part_ship / finish"]
  I --> L["MQ 推 OPS/渠道/NC"]
  K --> M["渠道签收"]
  K --> N["渠道售后申请 CHR"]
  N --> O{"服务站审核"}
  O -->|"拒绝"| P["释放 refund_num"]
  O -->|"通过"| Q["按原 180601 拆单"]
  Q --> R["生成一张或多张 180602"]
  R --> S["退货入库"]
  R --> T["MQ 回写售后结果"]

47. 结论

大客户渠道链路的核心不是某一个接口,而是三层事实保持一致:

  1. 渠道业务事实:谁下单、谁审核、发了多少、签收多少、申请退多少。
  2. DGJ 库存事实:180601 扣了什么库存,180602 又退回了什么库存。
  3. 外部系统事实:OPS、订单中心、渠道和 NC 是否都收到同一个最终结果。

排查任何问题都应沿以下路径逐层确认:

外部编号
-> 渠道主单/明细状态
-> 180601/180602 事实单
-> 库存流水与实时库存
-> MQ 事件
-> OPS/订单中心/渠道/NC 回写

只修状态、不修事实单;只修事实单、不修库存;只补本地、不补外部,都会留下下一次更难解释的不一致。

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

多入口请求链路

场景调用方与入口请求载荷/上下文Controller/ConsumerService/Provider汇合点最终业务事实
渠道订单/inner/channel/Orderchannel、外部单、站点、商品、地址Channel Order ControllerBaseOrder + Dcd/Fc/Czg Servicechannel+source order渠道主明细和内部履约单
临采/大客户OPS scm/InvCu/BigOrder客户、需求、采购/销售转换参数InvCu ControllerInvCuService/BigCustomerServicebig/temporary order形成临采事实和订单中心关系
售后/inner/channel/Aftersale原渠道单、SKU、申请/退款量Aftersale ControllerBaseAftersalesource aftersale + original order售后、退货和退款
MQ/迁移ChannelOrderNotify/TmpChannelOrderaction/event、业务键、批次Task ConsumerChannel Servicemessage/batch + source order异步发货审核或历史数据补齐

日志证据矩阵

| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 接入 | Channel Controller | request_id、channel、source order/aftersale | ACK/返回本地单号 | 签名、重复、商品映射失败 | 外部单号查 CHANNEL 表 | | 事实单转换 | BaseOrder/InvCu/BigCustomer | 渠道/大客户单、内部 order/invoice/PO | 关系完整且数量一致 | 一边 commit、一边失败 | 关系字段串联事实单 | | 履约库存 | Order/Invoice/Inventory Service | internal bill、SKU、transType | 出入库和库存同向 | 状态变但无事实单/库存 | 单号查流水和实时量 | | 外部通知 | MqSer/ChannelNotify/OrderCenter Provider | message ID、source/internal bill | 对方 ACK 且状态推进 | 重复、NACK、外部已成功本地失败 | 外部/内部单号双向检索 |

环节数据变更台账

步骤代码位置事务读取事实写入表/缓存/MQ字段或数量变化回查证据
接收订单BaseOrder/BigCustomerService接入事务来源唯一性、客户商品映射CHANNEL/大客户主明细insert;status created;orderQty=nsource order+SKU
转事实单BaseOrder/InvCuService跨域事务需验证渠道需求、供给/价格/地址采购/销售/出库事实单、关系internal bill 写入;status -> wait_shipsource/internal 双向关系
履约销售/采购/库存 Service领域事务可发/可收/可退量invoice/PO/inventoryshipped/received/return +n;库存 -/+n每 SKU 数量守恒
售后退款BaseAftersale售后事务+外部异步原履约、可退量、原支付aftersale/return/refund/MQrefund/return +n,状态完成一次original/aftersale/refund IDs
迁移补偿TmpChannelOrder/领域补偿小批幂等缺失关系/状态与真实事实单关系/状态/MQ只补缺口;不重造事实单/库存batch、before/after、重跑 0 变化

子模块追踪:big-goods 大客户渠道商品查询

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
商品查询渠道按客户/站点查商品channel/customer、sid、query/SKUapplication/controllers/inner/channel/Goods.php -> application/Services/BigCustomer/BigCustomerService.php客户映射、商品状态、渠道价、库存和服务类型查询只读 不写;返回渠道可售快照request ID + channel/customer + SKU + hit count查不到分层核对映射/价/库存;不直接改渠道订单
导入范围批量维护大客户销售商品file/task、rowNo、customer/SKUapplication/Services/Import/Type/BigSaleGoodsService.php模板、客户商品、重复和现有范围校验不写;每批本地事务 old goods set -> importedtask + row/error/affected counts只重跑失败键;commit 后缓存/搜索刷新

子模块追踪:big-order-create 外部渠道下单

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
接收订单渠道 API/MQ 创建订单source/channelOrderNo、customer、itemsapplication/controllers/inner/channel/Order.php -> application/Services/Channel/BaseOrder.php来源唯一性、客户商品映射、价格地址和服务类型接入本地事务 insert CHANNEL 主明细,status none -> createdrequest/message ID + source/channel order + item count重复来源返回既有单;校验失败零写入
转内部单渠道单生成销售/采购事实单channelOrderNo、internal billNoapplication/Services/BigCustomer/BigCustomerService.php渠道需求、供给、价格、地址和已有关系跨域本地事务创建 internal bill/双向关系,渠道 created -> wait_shipchannel/internal billNos + relation部分失败回滚/补关系,不重复生成已存在事实单

子模块追踪:big-manual 服务站人工代建订单

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
人工代建OPS/站点为渠道手工创建operator、channel/customer、source key、itemsapplication/controllers/scm/InvCu.php -> application/service/scm/InvCuService.php操作权限、客户商品、来源唯一性和代建标记大客户本地事务 insert 渠道/事实关系,status created/wait auditrequest ID + operator + channel/internal bill人工来源也必须稳定幂等键;不能绕过渠道价/范围校验
代建回查页面成功渠道未识别channel/internal billNos、manual flagapplication/KzData/Enums/BigOrderEnums.php主明细、双向关系、创建人和来源类型查询只读 不写both billNos + source/manual type已有内部单只补渠道关系/通知,不重建事实单

子模块追踪:big-audit 渠道审核与自动发货决策

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
审核人工/渠道订单审核channelOrderNo、action、operatorapplication/controllers/tasks/ChannelOrderNotify.phpcreated/待审、客户规则、事实单和库存供给审核本地事务 created -> wait_ship/cancel/rejectmessage/request ID + channel order + action终态旧请求 0 变化;拒绝不触发库存
自动决策审核后判断自动发货/待处理order/service type、stock/sceneapplication/Services/Channel/SceneConst.php -> application/Services/Channel/Traits/ChannelOrderTrait.php普通/直采/临采场景、库存、事实单状态决策查询只读;满足时调用领域出库事务channel/internal bill + scene/decision自动分支必须窄守卫;库存/外部不确定转人工

子模块追踪:big-ship 渠道发货与库存事实单

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
发货出库渠道/人工确认发货channel/internal/invoice billNos、SKU、qtyapplication/service/scm/InvCuService.php可发量、事实销售/采购单、库存四维和当前态履约与库存本地事务 shippedQty +n、库存 qty -n,渠道到 part_ship/finishrequest/message ID + all billNos + SKU/transType超发/重复零增量;返回异常先查 invoice/库存流水
库存回查渠道发货有状态无库存单invoice/inventory billNo、SKUapplication/Services/Channel/BaseOrder.php出库事实单、库存流水/实时和渠道累计发货查询只读;三方数量守恒channel/invoice/inventory IDs只补缺失领域段,不直接改渠道状态代替库存

子模块追踪:big-sign 签收与异步回写

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
签收回调渠道/TMS 签收事件message ID、channel/delivery/invoice IDsapplication/controllers/tasks/ChannelOrderNotify.php配送/出库关系、当前渠道态和事件版本单消息本地事务 wait/part_ship -> finish,保存签收事实message ID + all IDs + sign time乱序/重复不回退终态;签收不重复扣库存
回写渠道本地签收后通知外部channel/internal order、statusapplication/Providers/OrderCenter/BigOrderProvider.php已提交本地终态和发送记录外部调用事务外;本地同步态 pending -> success/failedrequest ID + channel/internal + provider code失败只补回写,不重做签收/库存

子模块追踪:big-aftersale 渠道售后申请与审核

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建售后渠道发起退货/退款申请channelOrder/aftersaleNo、SKU、qty/amountapplication/controllers/inner/channel/Aftersale.php -> application/Services/Channel/BaseAftersale.php原履约、可退量/金额、已有售后和服务类型售后本地事务 insert,status none -> pendingrequest ID + original/aftersale + SKU超退/重复来源零写入;售后对象独立于正向状态
审核人工/渠道审核售后aftersaleNo、action、operatorapplication/controllers/tasks/ChannelOrderNotify.phppending、当前可退事实和审核权限本地事务 pending -> approved/rejectedmessage/request + aftersale + action通过后库存/退款分两条链;失败不重复审核

子模块追踪:big-return 渠道退货入库

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
退货入库approved 售后收货aftersale/return billNo、SKU、location、qtyapplication/Services/Channel/BaseAftersale.php可收/已收量、原出库和库存维度退货与库存本地事务 received +n、库存 qty +n、售后状态推进request ID + aftersale/return/inventory IDs超收/重复回滚;资金退款独立验收
退款回写退货后退款/渠道通知original/refundNo、channel aftersaleapplication/Services/Mq/MqSer.php原支付、已退额、退货终态和发送状态资金本地事务 refunded +n;渠道通知 commit 后original/refund/aftersale + message ID库存成功只补退款/通知;不重复入库

子模块追踪:big-service-type 普通、直采与临采

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
场景判定渠道建单/审核选择服务类型channel order、service type、supply sceneapplication/Services/Channel/SceneConst.php客户合同、商品供给、库存和类型白名单判定查询只读;创建时将 service type 固化到快照request ID + channel order + scene/type未知类型零写入;不能运行中静默换类型
差异履约普通/直采/临采各自生成内部单channel/internal bill、typeapplication/Services/Channel/BaseService.php固化类型、采购/销售路径和状态机对应领域本地事务创建所需事实单 none -> createdchannel/internal + type + source relation修改共享 BaseService 时三类都回归;补偿按原类型执行