1. 文档目标
本文把 DGJ2 中容易混在一起的两组能力拆开说明,再解释它们如何连接采购、销售、库存和外部系统:
- 渠道大客户订单:懂车帝、孚创、车之谷等渠道通过 DGJ 接口查询商品、下单、审核、签收和申请售后;DGJ/OPS 负责人工开单、发货、退货入库及结果通知。
- 全车件:DGJ 提供登录态、功能开通校验和统一路由,把采购、销售、库存、资金等请求转发到全车件微服务;部分导入、导出、打印在 DGJ 本地完成。
本文适用于:
- 接手渠道订单、渠道退货或全车件需求;
- 排查外部订单已创建但 DGJ 不显示;
- 排查渠道订单不能审核、不能发货或未自动发货;
- 排查退货数量、原出库单拆分或退货入库异常;
- 排查渠道方、OPS 没有收到状态通知;
- 排查全车件接口 404、未开通、转发失败、导入或打印失败;
- 修改公共大客户出库逻辑前确认影响范围。
本文结论来自当前代码静态核对。网关域名、鉴权签名和 Apollo 实际配置值属于环境事实,必须在对应环境再次确认。
2. 先看结论
2.1 一张图理解模块边界
flowchart LR
EXT["外部渠道\nDCD / FC / CZG"]
INNER["inner/channel\n商品、订单、售后 API"]
CHANNEL["Channel Service\n场景工厂 + 公共流程"]
CT["渠道业务表\nt_channel_order / aftersale"]
OPS["DGJ / OPS\nscm/InvCu"]
BIG["大客户单据\n180601 出库 / 180602 退货"]
INV["库存、货位、成本价"]
MQ["dgj_channel MQ"]
ACPUI["DGJ 全车件页面"]
ACP["allcarpart/* 门面"]
MICRO["全车件微服务\nMICRO_URL"]
EXT --> INNER --> CHANNEL --> CT
OPS --> CHANNEL
CHANNEL --> BIG --> INV
CHANNEL --> MQ --> OPS
EXT <-->|"创建结果 / 签收 / 售后"| INNER
ACPUI --> ACP --> MICRO
ACP -. "导入、打印、导出\n存在本地补充处理" .-> INV
2.2 五个不能混淆的编号
| 名称 | 示例前缀/来源 | 保存位置 | 作用 |
|---|---|---|---|
| 渠道来源订单号 | 外部渠道生成 | t_channel_order.source_order_no | 外部系统识别原采购订单,也是创建幂等依据 |
| DGJ 渠道订单号 | CHS | t_channel_order.order_no | DGJ 内部平台订单主标识,发货和售后关联使用 |
| 渠道来源明细行号 | 外部渠道生成 | t_channel_order_info.source_row_no | 对齐外部订单明细 |
| 大客户出库单号 | DGJ 大客户单据生成 | t_scm_big_order.billNo | 实际库存出库事实,交易类型为 180601 |
| DGJ 渠道退单号 | CHR | t_channel_aftersale.refund_no | DGJ 内部售后标识,退货入库和 MQ 通知使用 |
排查时至少同时收集:sceneCode、shopId、外部订单号、CHS、CHR、大客户出库单号、sid。
2.3 三个渠道的关键差异
| 能力 | 懂车帝 dcd | 孚创 fc | 车之谷 czg |
|---|---|---|---|
| 来源类型 | 5 | 6 | 7 |
| 外部渠道直接创建 | 支持 | 支持 | 支持 |
| 创建后初始状态 | wait_ship | created | created |
| 渠道审核接口 | 不支持 | 支持 | 支持 |
| DGJ 人工创建 | 不支持 | 支持 | 支持 |
| OPS 详情转换 | 不支持 | 支持 | 支持 |
| 审核后自动发货 | 无此分支 | 受 fc_auto_send 控制 | 受 czg_auto_send 控制 |
| 售后公共流程 | 支持 | 支持 | 支持 |
初始状态差异来自具体实现:
DcdOrderService::create()使用BaseOrder::formatRequestToOrderEntry()的默认wait_ship。- FC/CZG 复用
ChannelOrderTrait::create(),创建后主动覆盖为created,必须再走渠道审核。
3. 代码入口地图
3.1 外部渠道入口
| 业务 | 控制器 | 方法 | 下游 Service |
|---|---|---|---|
| 商品列表 | application/controllers/inner/channel/Goods.php | lists() | 场景对应 *GoodsService::lists() |
| 商品详情 | 同上 | detail() | 场景对应 *GoodsService::detail() |
| 渠道创建订单 | application/controllers/inner/channel/Order.php | create() | 场景对应 *OrderService::create() |
| 渠道审核订单 | 同上 | order_approve() | FC/CZG ChannelOrderTrait::order_approve() |
| 渠道确认签收 | 同上 | confirm_receipt() | BaseOrder::confirm_receipt() |
| 渠道申请售后 | application/controllers/inner/channel/Aftersale.php | apply() | BaseAftersale::apply() |
| 查询退货入库 | 同上 | get_return_stock_in() | 当前仅返回空成功结果 |
控制器的相对路径可推导为:
inner/channel/goods/listsinner/channel/goods/detailinner/channel/order/createinner/channel/order/order_approveinner/channel/order/confirm_receiptinner/channel/aftersale/applyinner/channel/aftersale/get_return_stock_in
生产实际 URL、HTTP 方法、签名和网关前缀需要以网关配置或联调文档为准;本文只确认应用内控制器路径和请求字段。
3.2 DGJ/OPS 操作入口
| 业务 | 控制器方法 | Service 方法 |
|---|---|---|
| 人工创建 FC/CZG 渠道订单 | scm/InvCu::channelOrderManualCreate() | 场景 Service manual_create() |
| 渠道订单列表 | scm/InvCu::channelOrder() | BaseOrder::lists() |
| 渠道订单详情 | scm/InvCu::channelOrderDetail() | BaseOrder::detail() |
| 渠道订单发货 | scm/InvCu::channelOrderDelivery() | BaseOrder::delivery() |
| 渠道售后列表 | scm/InvCu::channelAftersale() | BaseAftersale::lists() |
| 渠道售后详情 | scm/InvCu::channelAftersaleDetail() | BaseAftersale::detail() |
| 退货通过并入库 | scm/InvCu::channelAftersalePass() | BaseAftersale::refund_pass() |
| 拒绝退货 | scm/InvCu::channelAftersaleReject() | BaseAftersale::refund_reject() |
3.3 核心 Service
| 文件 | 职责 |
|---|---|
application/Services/Channel/SceneConst.php | 场景字典、Service 工厂映射、Apollo 配置读取 |
application/Services/Channel/BaseService.php | 渠道公共依赖和基础辅助能力 |
application/Services/Channel/BaseGoods.php | 商品搜索、详情、库存和属性组装 |
application/Services/Channel/BaseOrder.php | 列表、详情、发货、签收、渠道单到大客户出库转换 |
application/Services/Channel/BaseAftersale.php | 售后申请、原出库拆分、退货入库、拒绝 |
application/Services/Channel/Dcd/DcdOrderService.php | 懂车帝直接创建逻辑 |
application/Services/Channel/Traits/ChannelOrderTrait.php | FC/CZG 人工开单、创建、审核、复制、自动发货 |
application/Services/Mq/MqSer.php | 渠道 MQ 生产者 |
application/controllers/tasks/ChannelOrderNotify.php | dgj_channel 消费者及 OPS/自动发货处理 |
application/service/scm/InvCuService.php | 大客户出库/退货、OPS 推送和大客户类型 |
3.4 场景服务选择
sequenceDiagram
participant C as 渠道请求
participant B as BaseChannel
participant R as ContactSer
participant S as SceneConst
participant I as 具体 Service
C->>B: sceneCode + shopId + 业务参数
B->>B: 校验 sceneCode 为 dcd/fc/czg
B->>R: getStationAndContactBySourceId(shopId, sceneCode)
R-->>B: stationInfo + contactInfo
alt 未建立绑定
B-->>C: 服务站和修理厂绑定信息异常
else 绑定存在
B->>S: 根据 sceneCode 读取 Goods/Order/Aftersale 类
S-->>B: 具体 Service 类名
B->>I: 执行场景实现
I-->>C: 统一响应
end
BaseChannel 构造时就要求 sceneCode 和 shopId,并解析:
- 渠道映射的服务站
sid; - 渠道客户对应的 DGJ
contactId; - 服务站和客户基础信息。
因此,业务方法还没执行就报“绑定信息异常”时,应先查渠道客户关系,不要先查订单表。
4. 渠道商品接口
4.1 商品列表请求
应用内入口:inner/channel/goods/lists。
示意请求:
{
"sceneCode": "dcd",
"merchantCode": "渠道商家编码",
"shopId": "渠道客户标识",
"keywords": "刹车片",
"skuCategoryCodes": ["分类编码"],
"carCode": "车型层级编码",
"page": 1
}
处理过程:
BaseChannel根据shopId + sceneCode获取 DGJ 登录上下文。BaseGoods::lists()组装数据平台检索参数。OfferProvider::searchList()查询候选商品和库存。- 使用快准侧 Mongo 物料补商品名称、分类等基础信息。
- 返回
skuCode、skuName、分类、图片和inventoryQty。
搜索最大条数默认 100,可由 Redis 键 S_CHANNEL_MAX_SEARCH 覆盖。这里返回的是渠道可见商品结果,不等于最终下单一定成功;下单还会重新校验服务站物料和成本价。
4.2 商品详情请求
应用内入口:inner/channel/goods/detail。
{
"sceneCode": "fc",
"merchantCode": "渠道商家编码",
"shopId": "渠道客户标识",
"skuCodes": ["SKU001", "SKU002"]
}
详情链路:
flowchart LR
API["goods/detail"] --> BIND["解析 sid/contactId"]
BIND --> MONGO["服务站 Mongo 物料\n名称、fineQty"]
BIND --> VIN["VinProvider\n属性、图片"]
MONGO --> MERGE["按 skuCode 合并"]
VIN --> MERGE
MERGE --> OUT["skuList\n库存、图片、attributes"]
详情最多截取 maxLimit 个 SKU。物料在服务站 Mongo 中不存在时,该 SKU 会被跳过。
5. 渠道创建订单接口
5.1 请求字段
应用内入口:inner/channel/order/create。
{
"sceneCode": "fc",
"merchantCode": "渠道商家编码",
"merchantName": "渠道商家名称",
"shopId": "渠道客户标识",
"orderNo": "外部渠道订单号",
"submitTime": "2026-07-15 10:00:00",
"totalAmount": "200.00",
"remark": "渠道备注",
"details": [
{
"orderRowNo": "外部明细行号",
"skuCode": "SKU001",
"qty": 2,
"price": "100.00",
"amount": "200.00",
"remark": "明细备注"
}
]
}
关键字段规则来自 OrderCreateValidate:
| 字段 | 规则 | 业务含义 |
|---|---|---|
sceneCode | dcd/fc/czg | 选择具体渠道实现 |
shopId | 必填 | 解析 DGJ 服务站和客户绑定 |
orderNo | 必填,最长 50 | 外部来源订单号,也是创建幂等依据 |
submitTime | 合法日期 | 外部渠道提交时间 |
totalAmount | 必填 | 外部声明总金额;代码会按明细重新计算实际总额 |
details | 非空数组 | 至少包含行号、SKU、数量、单价、小计 |
price | 不小于 0.01 | 订单销售单价 |
amount | 必填 | 必须等于 price * qty,保留两位比较 |
5.2 创建主流程
sequenceDiagram
participant EXT as 外部渠道
participant CTRL as Order::create
participant BIND as BaseChannel/ContactSer
participant SER as DCD 或 FC/CZG Service
participant MONGO as 服务站物料 Mongo
participant COST as CuOrderMaterielSer
participant DB as 渠道订单表
EXT->>CTRL: 创建请求
CTRL->>BIND: sceneCode + shopId
BIND-->>CTRL: sid + contactId
CTRL->>CTRL: 请求哈希 Redis 短锁 + 参数校验
CTRL->>SER: create(request + stationInfo + contactInfo)
SER->>DB: 按 source_order_no 查重
alt 已存在
DB-->>SER: 返回现有 CHS,不重复写表
else 首次创建
SER->>MONGO: 按 skuCode 查询服务站物料
SER->>COST: 按 invId 查询服务站成本价
SER->>SER: 校验数量、价格、小计并计算成本/价差
SER->>DB: 事务写主表和明细表
end
SER-->>CTRL: CHS 订单号
CTRL->>CTRL: createCarType(merchantCode)
CTRL-->>EXT: saleOrderNo + sourceOrderNo + 站点和明细
5.3 数据计算
每个明细重新计算:
sale_amount = qty * price
cost_amount = qty * purPrice
profit = price - purPrice
profit_amount = qty * profit
订单汇总:
total_amount = 所有明细 sale_amount 之和
cost_amount = 所有明细 cost_amount 之和
profit_amount = total_amount - cost_amount
业务界面把这部分价差称为“配送费”,它不等于财务核算意义上的最终净利润。
5.4 创建落表
| 业务数据 | 主表字段 | 明细字段 |
|---|---|---|
| 场景 | scene_code | scene_code |
| 服务站/客户 | sid, contact_id | sid, contact_id |
| 外部客户 | source_shop_id | source_shop_id |
| 外部订单 | source_order_no | source_order_no, source_row_no |
| DGJ 渠道订单 | order_no | order_no, chan_order_id |
| 商品 | 汇总 goods_num | sku_code, sku_name, inv_id, qty |
| 金额 | total_amount, cost_amount, profit_amount | 售价、成本、金额和价差字段 |
| 进度 | status, shiped_num | shiped_num, refund_num |
5.5 创建幂等的边界
当前存在两层保护:
- 控制器按整个请求 JSON 的 MD5 加
1秒 Redis 锁,拦截极短时间的完全相同请求。 - Service 按
source_order_no = request.orderNo查询已存在订单,存在时直接返回原CHS。
需要注意:代码查重没有附加 scene_code 或 shopId。如果不同渠道可能生成相同来源订单号,必须确认数据库约束和上游编号规则,否则存在跨渠道误判为同一订单的风险。
6. 人工创建与渠道审核
6.1 DGJ 人工创建 FC/CZG 订单
入口:scm/InvCu::channelOrderManualCreate()。
支持 fc 和 czg,不支持 dcd。处理过程:
- 校验渠道、客户、总金额和明细。
- 限制同一单据不能出现重复 SKU。
- 通过
dkh.max_submit_row限制每单最大商品行数。 - 向 OPS 查询大客户结算价。
- 根据
dkh.max_price_rate计算允许的最高销售价。 - 校验服务站物料存在且库存大于零。
- 系统生成外部风格的
orderNo和每个orderRowNo。 - 复用 FC/CZG
create()写渠道订单,状态为created。 - 发送
channel_order_manual_createMQ,消费者再推送 OPS。
flowchart TD
OPSUI["DGJ 人工开单"] --> VALID["校验 FC/CZG、行数、重复 SKU"]
VALID --> PRICE["OPS 查询结算价"]
PRICE --> LIMIT["校验最高价 dkh.max_price_rate"]
LIMIT --> STOCK["校验服务站物料和库存"]
STOCK --> GEN["生成渠道订单号和行号"]
GEN --> CREATE["复用 ChannelOrderTrait::create"]
CREATE --> CREATED["状态 created"]
CREATED --> MQ["channel_order_manual_create"]
MQ --> PUSH["ChannelOrderNotify 推送 OPS"]
MQ 推送失败在人工开单方法中只记录日志,不回滚已经创建的渠道订单。因此出现“订单已存在但 OPS 没收到”时,应补偿消息或重新推送,不要再次人工创建同一业务单。
6.2 FC/CZG 渠道审核接口
应用内入口:inner/channel/order/order_approve。
{
"sceneCode": "fc",
"merchantCode": "渠道商家编码",
"merchantName": "渠道商家名称",
"shopId": "渠道客户标识",
"saleOrderNo": "CHS...",
"sourceOrderNo": "渠道最终订单号",
"auditStatus": 1,
"submitTime": "2026-07-15 10:30:00",
"cancelReason": "拒绝时填写"
}
状态变化:
stateDiagram-v2
[*] --> created: FC/CZG 创建
created --> wait_ship: auditStatus = 1
created --> cancel: auditStatus = 0
wait_ship --> part_ship: 部分发货
wait_ship --> finish: 一次发完
part_ship --> part_ship: 再次部分发货
part_ship --> finish: 累计全部发完
[*] --> wait_ship: DCD 创建
审核约束:
- 只有当前状态为
created的订单可审核。 - 只允许
fc/czg。 - 审核通过前检查
sourceOrderNo + sceneCode是否已存在。 - 通过后状态为
wait_ship,拒绝后为cancel。 - 写入审核人、审核备注和最终来源订单号。
- 发送
channel_order_approveMQ。
6.3 审核后自动发货
ChannelOrderNotify::channelOrderApprove() 消费审核消息后:
- 从消息读取
scene_code;老消息没有时按订单查询,仍没有则回退 FC。 - 调用具体场景 Service 的
auto_delivery()。 - 仅 FC/CZG 且状态为
wait_ship才可继续。 - 分别检查服务站配置
fc_auto_send或czg_auto_send。 - 获取服务站默认管理账号和默认仓库。
- 每个商品选择第一个有足够库存的仓位结果。
- 组装普通
delivery()请求,以“系统发货”执行完整出库流程。
自动发货异常在消费者中记录后返回 ACK,不会依赖 MQ 自动重试。因此配置未开、默认账号异常、默认仓库缺失、库存不足都需要业务侧重新处理或人工发货。
7. 渠道发货与库存出库
7.1 发货请求
DGJ/OPS 入口:scm/InvCu::channelOrderDelivery()。
{
"orderNo": "CHS...",
"delieverId": 1001,
"delieverName": "操作人",
"remark": "渠道订单发货",
"details": [
{
"rowNo": 12345,
"outQty": 2,
"locationId": 10,
"locationName": "主仓",
"locationAreaId": 100,
"locationAreaName": "A区",
"remark": ""
}
]
}
rowNo 是 t_channel_order_info.id,不是外部渠道明细行号。
7.2 可发校验
BaseOrder::checkCanDelivery() 依次检查:
- 主单存在且状态为
wait_ship或part_ship; - 主单累计已发数量小于总数量;
- 请求明细属于当前
CHS; - 每个明细尚未发完;
shiped_num + outQty <= qty;- 若场景在
dkh.once_delivery_merchant中,每行和整单必须一次发完。
代码对 outQty 使用 abs(),因此业务上仍应只传正数,并由请求校验和测试限制异常符号输入。
7.3 发货数据流
sequenceDiagram
participant OPS as DGJ/OPS
participant BO as BaseOrder
participant CU as InvCuService
participant BIG as t_scm_big_order
participant INV as 库存/货位
participant CH as 渠道订单表
participant MQ as dgj_channel
OPS->>BO: orderNo + 发货明细 + 仓位
BO->>BO: 状态、剩余数量、一次发完校验
BO->>BO: buildBigOrderDeliverData()
BO->>CU: submitOrderV2(postData)
CU->>BIG: 创建 180601 大客户出库及明细
CU->>INV: 执行出库和库存变化
CU-->>BO: billNo
BO->>CH: 明细 shiped_num += outQty
BO->>CH: 主单 shiped_num += 发货总数
BO->>CH: part_ship 或 finish
BO->>MQ: channel_order_delivery(orderNo=billNo)
MQ-->>OPS: 消费后推送出库信息
7.4 渠道单如何转换为大客户出库
buildBigOrderDeliverData() 生成旧大客户提交结构,关键字段如下:
| 大客户字段 | 值来源 | 说明 |
|---|---|---|
transType | 固定 180601 | 大客户销售出库 |
contactId/contactName | 渠道主单 | DGJ 客户 |
sourceOrder | CHS | 业务来源订单 |
srcOrderNo | 请求 orderNo | 同一 CHS |
srcChannelOrder | 请求 orderNo | 标记渠道来源 |
srcOrderId | 渠道主单 ID | 写入大客户明细 |
srcOrderEntryId | 渠道明细 ID | 后续售后反查原出库的关键 |
sourceInfoRowNo | 外部渠道行号 | 对齐渠道明细 |
location* | 发货请求 | 实际扣减仓库和货位 |
buId/buType | 服务站成本价记录 | 采购主体和类型 |
submitOrderV2() 未返回 data.billNo 时,渠道发货事务回滚并返回系统服务异常。只有拿到单号后才提交事务并发 MQ。
7.5 状态和数量更新
明细已发数量 = 原 shiped_num + 本次 outQty
主单已发数量 = 原 shiped_num + 本次所有 outQty 之和
主单已发数量 < goods_num -> part_ship
主单已发数量 >= goods_num -> finish,并写 finish_time
finish 表示 DGJ 已全部发货,不等同于渠道已经回传签收。
8. 渠道签收
8.1 签收请求
应用内入口:inner/channel/order/confirm_receipt。
{
"sceneCode": "dcd",
"merchantCode": "渠道商家编码",
"shopId": "渠道客户标识",
"signingPerson": "签收人",
"signingTime": "2026-07-15 15:00:00",
"signingNo": "渠道签收批次号",
"totalQty": 2,
"details": [
{
"orderNo": "大客户出库单号",
"orderRowNo": 98765,
"skuCode": "SKU001",
"qty": 2
}
]
}
这里的 orderRowNo 是 t_scm_big_order_info.id,不是渠道明细 ID。
8.2 校验和幂等
签收前会校验:
- 每个大客户出库明细存在;
- 大客户明细
skuId与请求skuCode一致; - 出库数量绝对值与签收数量一致;
- 所有明细数量之和等于
totalQty。
唯一键计算:
unique_id = md5(signingNo + "." + shipOrderNo + "." + shipOrderRowNo)
最后调用 batchInsertOrIgnore() 写 t_channel_order_sign_detail。相同签收批次、出库单和出库明细重复回调时不会重复插入。
9. 渠道售后与退货入库
9.1 售后申请请求
应用内入口:inner/channel/aftersale/apply。
{
"sceneCode": "dcd",
"merchantCode": "渠道商家编码",
"shopId": "渠道客户标识",
"orderNo": "CHS...",
"sourceRefundNo": "外部售后申请号",
"submitTime": "2026-07-15 16:00:00",
"totalAmount": "100.00",
"remark": "退货原因",
"details": [
{
"orderRowNo": 12345,
"sourceOrderRowNo": "外部订单行号",
"skuCode": "SKU001",
"qty": 1,
"price": "100.00",
"amount": "100.00",
"remark": "明细原因"
}
]
}
RefundApplyValidate 的场景字段没有显式列出 details,但 BaseAftersale::apply() 会直接读取并深度校验明细;调用方必须传非空明细。
9.2 申请校验
每个售后明细必须满足:
orderRowNo对应t_channel_order_info.id;- 明细属于请求中的
CHS; - SKU 与原渠道订单一致;
- 申请数量不超过原订单数量;
- 申请价格不超过原销售价;
price * qty = amount;- 累计
refund_num + qty <= 原 qty; - 申请数量不超过已发数量
shiped_num。
创建成功后:
- 生成
CHR退单号; - 事务写售后主表和明细表;
- 渠道订单明细
refund_num += 申请数量; - 返回
aftersaleNo=CHR和来源售后号。
9.3 售后状态机
stateDiagram-v2
[*] --> pass: 当前 apply() 直接创建为 pass
pass --> finish: DGJ 审核通过并完成退货入库
pass --> reject: DGJ 拒绝并归还 refund_num
created --> pass: 枚举保留,但当前 apply() 未经过此状态
| 状态 | 显示名称 | 当前代码中的业务事实 |
|---|---|---|
created | 退单创建 | 枚举存在,当前 apply() 没有落此状态 |
pass | 待收货 | apply() 创建后的实际初始状态,可继续通过或拒绝 |
reject | 已拒绝 | 回退原渠道明细占用的 refund_num |
finish | 已完成 | 已按原出库拆分并创建 180602 退货单 |
9.4 为什么退货要按原出库拆分
一个渠道订单可以多次部分发货,因此一个渠道明细可能分散到多个 180601 出库明细。旧大客户退货必须关联具体原出库单和明细,所以不能直接按 CHR 创建一张总退货单。
flowchart TD
APPLY["CHR 售后明细\n申请退 5 件"] --> FIND["按 channel_order_info.id\n查询所有原 180601 出库明细"]
FIND --> USED["扣除每个原出库明细\n已经退回的数量"]
USED --> A["原出库 A 可退 2"]
USED --> B["原出库 B 可退 3"]
A --> SPLIT1["拆分退货参数 2 件"]
B --> SPLIT2["拆分退货参数 3 件"]
SPLIT1 --> RET1["创建 180602 退货单 A"]
SPLIT2 --> RET2["创建 180602 退货单 B"]
RET1 --> FINISH["CHR 状态 finish"]
RET2 --> FINISH
拆分计算:
某原出库明细剩余可退 = abs(原出库 qty) - 已关联该出库明细的退货数量
本次分配数量 = min(申请剩余数量, 当前原出库明细剩余可退数量)
9.5 退货通过流程
DGJ/OPS 入口:scm/InvCu::channelAftersalePass()。
sequenceDiagram
participant OPS as DGJ/OPS
participant A as BaseAftersale
participant OUT as 原 180601 出库
participant CU as InvCuService
participant RET as 180602 退货单
participant DB as 渠道售后表
participant MQ as dgj_channel
OPS->>A: refundNo + 售后行 + 入库仓位
A->>DB: 校验当前状态 pass
A->>A: 校验售后明细
A->>OUT: 查询原出库及已退数量
A->>A: 按原出库单拆分数量
A->>OPS: pushChannelRefundAuditToOps(pass)
loop 每个原出库单
A->>CU: submitOrderV2(transType=180602)
CU->>RET: 创建关联原单的退货入库
end
A->>DB: 状态 finish + finish_time
A->>MQ: channel_refund_action(pass, billNo)
构造 180602 时关键关联:
srcOrderId/srcOrderNo:原大客户出库主单;srcOrderEntryId:原大客户出库明细;srcChannelOrder:CHR;qty:当前拆分到这张原出库单的splitQty;- 仓库、货位来自 OPS 退货请求。
9.6 拒绝退货
refund_reject() 只允许当前状态为 pass 且售后明细存在:
- 推送拒绝结果给 OPS。
- 事务把售后状态改为
reject,写审核备注和完成时间。 - 对每个原渠道订单明细执行
refund_num -= 售后申请数量。 - 发送
channel_refund_action,action=reject,billNo为空。
如果拒绝失败但 refund_num 没有回退,后续新售后会错误提示超过可售后数量。
10. 核心表和关联关系
10.1 表清单
| 常量 | 物理表 | 用途 | 关键字段 |
|---|---|---|---|
CHANNEL_ORDER | t_channel_order | 渠道订单主表 | id, scene_code, sid, order_no, source_order_no, status, goods_num, shiped_num |
CHANNEL_ORDER_INFO | t_channel_order_info | 渠道订单明细 | id, chan_order_id, order_no, source_row_no, sku_code, qty, shiped_num, refund_num |
CHANNEL_AFTERSALE | t_channel_aftersale | 渠道售后主表 | refund_no, source_refund_no, order_no, status, total_num, total_amount |
CHANNEL_AFTERSALE_INFO | t_channel_aftersale_info | 渠道售后明细 | refund_id, refund_no, order_row_no, sku_code, qty, price |
CHANNEL_ORDER_SIGN_DETAIL | t_channel_order_sign_detail | 签收明细及幂等 | unique_id, signing_no, order_no, order_row_no, sku_code, qty |
SCM_BIG_ORDER | t_scm_big_order | 大客户出库和退货主单 | id, billNo, transType, srcChannelOrder, srcOrderSource |
SCM_BIG_ORDER_INFO | t_scm_big_order_info | 大客户出库和退货明细 | id, iid, srcOrderId, srcOrderEntryId, sourceInfoRowNo, skuId, qty |
10.2 关系图
erDiagram
CHANNEL_ORDER ||--o{ CHANNEL_ORDER_INFO : "order_no / chan_order_id"
CHANNEL_ORDER ||--o{ CHANNEL_AFTERSALE : "order_no"
CHANNEL_AFTERSALE ||--o{ CHANNEL_AFTERSALE_INFO : "refund_id / refund_no"
CHANNEL_ORDER_INFO ||--o{ CHANNEL_AFTERSALE_INFO : "order_row_no"
CHANNEL_ORDER_INFO ||--o{ BIG_ORDER_INFO : "srcOrderEntryId"
BIG_ORDER ||--o{ BIG_ORDER_INFO : "iid"
BIG_ORDER_INFO ||--o{ CHANNEL_SIGN_DETAIL : "order_row_no"
CHANNEL_ORDER {
string order_no
string source_order_no
string status
int shiped_num
}
CHANNEL_ORDER_INFO {
int id
string source_row_no
int qty
int shiped_num
int refund_num
}
CHANNEL_AFTERSALE {
string refund_no
string source_refund_no
string status
}
BIG_ORDER {
string billNo
int transType
string srcChannelOrder
}
10.3 三组数量必须守恒
渠道明细:0 <= shiped_num <= qty
渠道明细:0 <= refund_num <= shiped_num
售后拆分:所有 splitQty 之和 = 本次申请 qty
数据库只记录过程结果,真正判断库存事实时还要联查 180601/180602 大客户明细和库存流水。
11. MQ 消息地图
所有渠道消息目标:dgj_channel。
| routing key | 生产位置 | 主要参数 | 消费方法 | 消费结果 |
|---|---|---|---|---|
channel_order_manual_create | FC/CZG 人工开单后 | sid, scene_code, channel_order_no | channelOrderManualCreate() | 推送人工订单到 OPS;失败返回 NACK |
channel_order_delivery | BaseOrder::delivery() 提交后 | orderNo,实际为大客户 billNo | channelOrderDelivery() | 校验 180601/180602 和渠道来源;180601 推送 OPS |
channel_refund_action | 退货通过/拒绝后 | refundNo, billNo, action | channelRefundAction() | 推送售后动作到 OPS |
channel_order_approve | FC/CZG 审核后 | sid, order_no, action, scene_code | channelOrderApprove() | 尝试按服务站设置自动发货 |
flowchart LR
P1["人工开单"] --> K1["channel_order_manual_create"]
P2["发货成功"] --> K2["channel_order_delivery"]
P3["退货通过/拒绝"] --> K3["channel_refund_action"]
P4["渠道审核"] --> K4["channel_order_approve"]
K1 --> C["ChannelOrderNotify"]
K2 --> C
K3 --> C
K4 --> C
C --> O1["推送 OPS"]
C --> O2["触发 FC/CZG 自动发货"]
消费者统一注册 MqEventHandleSer::handleBefore/handleAfter。排查重复消费或处理历史时,需要同时看 MQ 事件处理记录和各回调业务日志。
11.1 ACK/NACK 语义要点
- 人工开单推送 OPS 异常时返回
NACK,有机会按 MQ 策略重试。 - 发货消息缺
orderNo、找不到大客户单、交易类型或来源不匹配时直接ACK丢弃。 - 自动发货异常被捕获后返回
ACK,不会依靠 MQ 重试。 - 退货动作参数不完整时直接
ACK丢弃。
所以“MQ 消费成功”只能证明消息被确认,不代表自动发货或 OPS 推送一定完成,必须结合业务日志和落表事实判断。
12. 全车件模块
12.1 正确理解全车件边界
application/controllers/allcarpart/Controller.php 的注释已经明确:继承该控制器即可自动转发。它不是在 DGJ 内重新实现一套完整采购、销售、库存和资金领域,而是一个带 DGJ 登录态的全车件访问门面。
sequenceDiagram
participant UI as DGJ 全车件页面
participant C as allcarpart Controller
participant ST as StationSer
participant M as MICRO_URL 微服务
UI->>C: /allcarpart/{module}/{resource}/{action}
C->>ST: enableAllCarPart(sid)
alt 服务站未开通
C-->>UI: status=500 服务站未开通全车件功能
else 已开通
C->>C: 解析 JSON 请求
C->>M: POST 原 URI,Headers 带 sid/uid/user_name
M-->>C: JSON 结果
C-->>UI: 原样输出或包装结果
end
公共转发行为:
- 先调用
StationSer::enableAllCarPart(sid)检查服务站是否开通; - 从 DGJ 登录态获取
sid、uid、用户名称; - 通过
Client向MICRO_URL + uri_string()发 JSON POST; - 请求头带
sid、uid、user_name; - 异常统一转换成 JSON
code/message。
12.2 路由和业务域
| 业务域 | 代表控制器 | 代表显式路由 | 主要能力 |
|---|---|---|---|
| 商品 | goods/GoodSearch.php, goods/Goods.php | allcarpart/goods/good-search/* | 商品搜索与查询 |
| 采购 | purchase/Order.php, PoReturnOrder.php, Rfq.php | purchase/po-return-order/*, purchase/rfq/inquiry | 询价、采购、采购退货 |
| 销售 | sale/SaOrder.php, SaInvoiceOrder.php, SaReturnOrder.php | sale/sa-order/*, sale/sa-invoice-order/* | 销售订单、出库、退货 |
| 库存 | storage/Inventory.php, Pd.php, StorageArea*.php | storage/inventory/*, storage/pd/import | 库存、仓库、货位、盘点 |
| 资金 | fund/PaymentOrder.php, Account.php | fund/payment-order/*, fund/account/* | 付款单和账户 |
兜底路由:
allcarpart/(:any)/(:any)/(:any)
-> allcarpart/$1/$2/index/$3
带中划线、导入、导出、打印等不适合默认映射的接口,在 application/config/routes.php 中单独声明。
12.3 本地增强而不是纯转发的场景
| 场景 | DGJ 本地处理 | 微服务处理 |
|---|---|---|
| 销售订单导入 | 上传并解析 Excel,限制不超过 500 行 | 接收结构化商品,校验并返回结果 |
| 销售订单打印 | 请求微服务获取订单数据,再补 DGJ 客户、门店和系统打印配置 | 返回订单和明细 |
| 采购退货导入 | 本地上传、解析和错误文件处理 | 接收解析后的业务数据 |
| 库存/盘点导入导出 | 本地文件、模板或导出流处理 | 查询、校验或保存业务数据 |
询价 Rfq::inquiry() | 补 platform=1 和 sid | 通过 AllCarPartProvider 完成询货 |
因此全车件问题要先判断是在:
- DGJ 路由/登录态/功能开通层;
- DGJ 文件解析、打印或导出层;
MICRO_URL网络调用层;- 全车件微服务业务层。
13. 外部依赖与配置
13.1 Apollo 配置键
| 配置键 | 用途 | 缺失时当前行为 |
|---|---|---|
dkh.max_price_rate | FC/CZG 人工开单最高价比例 | 当前场景回退 1.0 |
dkh.merchant_map | 场景映射 OPS 商家编码 | 当前场景返回 null,后续询价/开单可能失败 |
dkh.max_submit_row | 人工开单最大明细行数 | 回退 50 |
dkh.once_delivery_merchant | 必须整单一次发完的渠道列表 | 非数组时回退空数组 |
不要在文档中保存配置值;应在配置中心按环境核对键是否存在、JSON 是否合法、场景是否覆盖。
13.2 其他依赖
| 依赖 | 用途 | 典型失败现象 |
|---|---|---|
ContactSer | shopId + sceneCode 映射服务站和客户 | 所有渠道 API 在构造阶段失败 |
| 服务站 Mongo 物料 | 商品、库存、商品名称和 invId | 商品被跳过、创建提示物料不存在、自动发货库存不足 |
CuOrderMaterielSer | 服务站成本价、主体 | 创建或发货提示未设置成本价 |
| OPS/OrderCenter Provider | 大客户结算价、人工订单推送 | 人工开单或 OPS 显示异常 |
InvCuService | 180601/180602 大客户单据和库存 | 发货/退货失败或无 billNo |
| RabbitMQ | 审核、发货、退货、人工开单事件 | 订单落表但 OPS/自动发货没有动作 |
MICRO_URL | 全车件微服务地址 | 全车件统一转发异常 |
14. 常用排查 SQL
以下 SQL 仅用于只读排查。先替换业务键,不要直接在生产修改数据。
14.1 按外部订单或 CHS 查主单
SELECT id, scene_code, sid, contact_id, source_shop_id,
order_no, source_order_no, status,
goods_num, shiped_num, total_amount,
submit_time, finish_time, create_time
FROM t_channel_order
WHERE order_no = 'CHS...'
OR source_order_no = '外部订单号'
ORDER BY id DESC;
14.2 查渠道明细数量是否守恒
SELECT id, order_no, source_row_no, sku_code, inv_id,
qty, shiped_num, refund_num,
sale_price, cost_price, sale_amount
FROM t_channel_order_info
WHERE order_no = 'CHS...'
ORDER BY id;
重点检查:
shiped_num <= qty
refund_num <= shiped_num
sale_amount = qty * sale_price
14.3 查 CHS 关联的大客户出库
SELECT o.id, o.billNo, o.transType, o.srcChannelOrder,
i.id AS out_info_id, i.srcOrderId, i.srcOrderEntryId,
i.sourceInfoRowNo, i.skuId, i.qty
FROM t_scm_big_order o
JOIN t_scm_big_order_info i ON i.iid = o.id
WHERE o.srcChannelOrder = 'CHS...'
ORDER BY o.id, i.id;
若实际表按 sid 分库或存在环境差异,应先用模型 SQL 或数据库连接确认真实库表位置。
14.4 查售后申请和明细
SELECT id, scene_code, sid, order_no, source_order_no,
refund_no, source_refund_no, status,
total_num, total_amount, finish_time, create_time
FROM t_channel_aftersale
WHERE refund_no = 'CHR...'
OR source_refund_no = '外部售后号'
ORDER BY id DESC;
SELECT id, refund_id, refund_no, order_row_no,
source_order_row_no, sku_code, qty, price, amount
FROM t_channel_aftersale_info
WHERE refund_no = 'CHR...'
ORDER BY id;
14.5 查签收幂等记录
SELECT unique_id, scene_code, sid, signing_no,
signing_person, signing_time,
order_no, order_row_no, sku_code, qty
FROM t_channel_order_sign_detail
WHERE signing_no = '签收批次号'
ORDER BY order_row_no;
15. 日志与代码检索
15.1 本地快速定位
rg -n "平台订单创建异常|平台订单发货服务异常|平台退货确认入库异常|大客户自动发货异常" application
rg -n "channel_order_manual_create|channel_order_delivery|channel_refund_action|channel_order_approve" application
rg -n "source_order_no|srcChannelOrder|srcOrderEntryId|sourceInfoRowNo" application/Services/Channel application/models
rg -n "allcarpart/|MICRO_URL|enableAllCarPart" application/config application/controllers/allcarpart
15.2 业务日志分类
| 链路 | Logger 分类/关键字 |
|---|---|
| 外部渠道 API | inner/api,搜索“平台订单创建异常”“退货申请异常” |
| 渠道 Service | 对应 Channel Service logger,搜索 CHS/CHR/外部单号 |
| 人工开单 MQ | tasks/channelOrderManualCreate |
| 发货 MQ | tasks/channelOrderDelivery |
| 售后 MQ | tasks/channelRefundAction |
| 审核自动发货 | tasks/channelOrderApprove,关键字“大客户自动发货异常” |
排查顺序建议:业务表事实 -> 大客户单据事实 -> 库存事实 -> MQ 处理 -> OPS/外部回传,不要只凭接口返回判断。
16. 按现象排查
16.1 外部渠道创建失败:绑定异常
flowchart TD
A["服务站和修理厂绑定信息异常"] --> B{"sceneCode 是否 dcd/fc/czg"}
B -->|否| C["修正场景码"]
B -->|是| D{"shopId 是否正确"}
D -->|否| E["修正渠道客户标识"]
D -->|是| F["检查 ContactSer 渠道关系和 300 秒缓存"]
F --> G["确认返回 stationInfo/contactInfo"]
不要先补订单数据。绑定不成立时控制器不会进入创建事务。
16.2 渠道说下单成功,DGJ 查不到
- 用外部
orderNo查t_channel_order.source_order_no。 - 查请求是否实际到达当前环境和节点。
- 查
sceneCode/shopId映射出的sid是否是业务人员查看的服务站。 - 查创建日志中的物料、成本价、小计错误。
- 若存在主单但无明细,检查事务和表引擎;正常逻辑应一起提交或一起回滚。
16.3 FC/CZG 一直待审核
- 确认状态确实为
created。 - 检查渠道是否调用
order_approve。 - 检查
saleOrderNo、shopId、sceneCode是否与原单一致。 - 检查通过时新的
sourceOrderNo是否已被同场景占用。 - 查审核接口日志和
channel_order_approve消息。
16.4 审核通过但没有自动发货
- 确认订单已经是
wait_ship。 - 查
channel_order_approve是否生产和消费。 - 核对
fc_auto_send/czg_auto_send服务站设置。 - 检查默认管理账号和默认仓库。
- 检查每个 SKU 在返回的第一个仓位中是否有足够库存。
- 查“大客户自动发货异常”;消费者会
ACK,不要只看队列无积压。 - 自动发货失败后按业务规则人工发货。
16.5 发货提示超出待发数量
- 查渠道明细
qty和shiped_num。 - 查请求
rowNo是否属于当前CHS。 - 查是否有上次发货已创建
180601,但前端仍提交旧数量。 - 检查是否配置为一次性发货渠道。
- 对照所有关联大客户出库明细数量,确认渠道累计值是否一致。
16.6 发货接口失败但库存已变化
这是高风险不一致场景:
- 按返回或日志中的
billNo查t_scm_big_order。 - 查库存流水和实时库存是否已经发生出库。
- 查渠道
shiped_num/status是否提交。 - 查事务边界内
InvCuService::submitOrderV2()是否使用同一数据库连接,外部副作用是否可回滚。 - 在确认事实前不要重复发货;重复调用可能再次扣库存。
16.7 退货提示没有可退商品
- 查
CHR明细的order_row_no。 - 用该 ID 查
180601明细的srcOrderEntryId。 - 确认原出库单真实存在且来源关系完整。
- 汇总该原出库明细已关联的
180602数量。 - 计算剩余可退是否大于零。
- 检查历史出库是否缺
srcOrderEntryId/sourceInfoRowNo,这类老数据可能无法自动拆分。
16.8 渠道/OPS 没收到通知
- 先确认订单、出库或退货事务已成功。
- 根据业务动作确认 routing key。
- 查生产日志和 MQ 事件记录。
- 查
ChannelOrderNotify对应回调日志。 - 判断是
NACK可重试,还是业务校验后ACK丢弃。 - 按业务主键补偿消息,不要重复创建业务单据。
16.9 全车件统一返回未开通
- 确认当前登录服务站
sid。 - 检查
StationSer::enableAllCarPart(sid)的开通事实。 - 确认不是登录到了其他站点。
- 开通后重新请求,不需要修改全车件业务数据。
16.10 全车件 404 或转发异常
- 对照
routes.php是否有中划线路由或特殊动作映射。 - 确认兜底路由的模块、控制器、动作三段是否正确。
- 查控制器是否继承
allcarpart\Controller。 - 检查
MICRO_URL和网络连通性。 - 检查下游接口是否接受 JSON POST 和当前 URI。
- 导入、导出、打印问题继续检查 DGJ 本地文件处理,不要只查微服务。
17. 当前代码风险与待确认项
以下是当前代码事实或静态审查风险,不应在没有回归验证时顺手修改公共逻辑。
17.1 售后明细单号判断疑似存在运算优先级问题
BaseAftersale::checkRefundDetails() 当前条件为:
if (!$dbAfterInfo['refund_no'] != $item['refundNo'])
这不是直观的“不相等”写法,! 会先作用于字符串再比较。需要补测试确认实际行为,预期逻辑大概率应是比较数据库 refund_no 与请求 refundNo 是否一致。
17.2 OPS 退货通过入口可能掩盖异常
scm/InvCu::channelAftersalePass() 捕获异常后构造了错误 $result,但最终响应固定使用 code=0 和请求明细 ID,没有返回该错误结果。调用方可能看到成功,而 Service 实际已经回滚。
排查退货通过时必须查 CHR.status、180602 单据和日志,不能只相信接口 code。
17.3 查询退货入库接口是空实现
inner/channel/Aftersale::get_return_stock_in() 当前直接返回空成功结果,没有查询退货入库单。外部渠道若依赖该接口获得入库状态,需要先确认真实合同,不能把空数组解释为“已完成”。
17.4 创建查重范围可能过宽
创建按 source_order_no 单字段查重,没有附加场景和客户。需确认外部订单号是否全渠道全局唯一,并检查数据库唯一索引。
17.5 发货金额字段需要回归确认
buildBigOrderDeliverData() 的总金额使用请求明细 price,而单行 amount 使用数据库明细的 price 键;渠道明细常见字段名是 sale_price。需要通过现有发货回归和实际数组结构确认是否由模型别名补齐,避免金额为零或字段未定义。
17.6 自动发货消息异常不会重试
审核消费者捕获异常后仍返回 ACK。这是当前容错策略,不代表业务自动恢复;应有人工待发列表或补偿入口兜底。
17.7 全车件错误码并不完全统一
公共转发异常返回 code/message,未开通分支直接输出 status/message,个别导入接口又使用自身结构。前端和联调文档不能假设所有全车件接口只有一种响应 envelope。
18. 改动影响面
| 修改位置 | 直接影响 | 必须回归 |
|---|---|---|
BaseChannel / 绑定解析 | 所有渠道商品、订单、售后 | 三个场景、有效/无效 shopId、缓存 |
BaseOrder::formatRequestToOrderEntry() | 三渠道订单主数据 | 编号、客户地址、金额、初始状态差异 |
ChannelOrderTrait | FC/CZG | 人工开单、审核、自动发货、复制订单 |
DcdOrderService | DCD | 直接待发、成本价、创建幂等 |
BaseOrder::delivery() | 三渠道和大客户出库 | 部分/全部发货、库存、金额、MQ、重复请求 |
BaseAftersale | 三渠道售后和大客户退货 | 申请占用、拆单、入库、拒绝回退、MQ |
InvCuService::submitOrderV2() | 所有大客户出入库 | 普通大客户业务和渠道业务都要回归 |
MqSer / ChannelOrderNotify | OPS 通知和自动发货 | 四种 routing key、ACK/NACK、幂等 |
routes.php / allcarpart Controller | 全车件所有页面 | 显式路由、兜底路由、开通校验、转发头 |
19. 回归测试清单
19.1 商品和绑定
- DCD/FC/CZG 有效绑定能查询商品列表和详情。
- 无效
sceneCode、缺shopId、关系不存在返回清晰错误。 - 商品列表和详情库存口径符合预期。
- 超过最大 SKU 数时截断行为可接受。
19.2 创建
- DCD 创建后为
wait_ship。 - FC/CZG 创建后为
created。 - 同一来源订单重复请求返回同一
CHS,不重复明细。 - 商品不存在、数量非正、单价过低、小计错误、成本价缺失均失败且不留半单。
- 主单汇总金额、成本和价差等于明细合计。
19.3 人工开单与审核
- 仅 FC/CZG 可人工开单;DCD 明确拒绝。
- 行数上限、重复 SKU、结算价、最高价、库存校验生效。
- 审核通过
created -> wait_ship,拒绝created -> cancel。 - 重复审核被拒绝。
- 审核消息能触发已开启服务站自动发货,未开启时订单仍保留待发。
19.4 发货
- 待发订单可一次全部发货并进入
finish。 - 非一次发完渠道可部分发货并进入
part_ship,累计发完进入finish。 - 一次发完渠道拒绝部分行或部分数量。
- 发货创建
180601,仓库和货位库存正确减少。 - 渠道主明细
shiped_num与大客户出库数量一致。 submitOrderV2()无billNo时渠道数据不提交。- 发货 MQ 能在 OPS 侧找到对应大客户单。
19.5 签收
- SKU、数量、总数完全一致时写签收明细。
- SKU 不一致、数量不一致、行不存在时拒绝。
- 同一签收批次重复回调只保留一条唯一记录。
19.6 售后
- 申请数量不超过已发且未占用数量。
- 相同
CHS + sourceRefundNo重复请求返回同一CHR。 - 申请后
refund_num正确增加。 - 一张渠道明细跨两个出库时,退货能拆成对应的
180602。 - 拆分数量总和等于申请数量,不超原出库可退数量。
- 拒绝后
refund_num完整回退。 - Service 异常时 OPS 接口不能被误判为业务成功。
19.7 MQ
- 四种 routing key 都能被消费者识别。
- 缺参数、业务单不存在、重复消息有明确 ACK/NACK 行为。
- 人工开单推 OPS 失败可重试。
- 自动发货失败可人工补偿,且不会重复扣库存。
19.8 全车件
- 未开通服务站被功能开通校验拦截。
- 已开通服务站能通过显式路由和兜底路由转发。
- 下游收到正确
sid/uid/user_name。 - 销售订单导入超过 500 行被拒绝,合法文件可返回成功和错误数据。
- 打印能补齐客户、门店、系统配置并支持现有打印方式。
- 微服务不可用时前端获得可识别错误,不出现 HTML 500 页面。
20. 代码证据索引
| 结论 | 证据文件 |
|---|---|
| 场景、来源类型、Apollo 键 | application/Services/Channel/SceneConst.php |
| 绑定和场景工厂 | application/controllers/inner/channel/BaseChannel.php |
| 外部创建、审核、签收入口 | application/controllers/inner/channel/Order.php |
| 商品查询入口和数据组装 | application/controllers/inner/channel/Goods.php, application/Services/Channel/BaseGoods.php |
| DCD 创建后直接待发 | application/Services/Channel/Dcd/DcdOrderService.php |
| FC/CZG 创建、审核、人工开单、自动发货 | application/Services/Channel/Traits/ChannelOrderTrait.php |
| 发货、签收和大客户出库转换 | application/Services/Channel/BaseOrder.php |
| 售后申请、拆原出库、退货通过/拒绝 | application/Services/Channel/BaseAftersale.php |
| 订单和售后状态 | application/KzData/Enums/ChannelOrderEnums.php, ChannelAftersaleEnums.php |
| OPS 操作入口 | application/controllers/scm/InvCu.php |
| MQ 生产和消费 | application/Services/Mq/MqSer.php, application/controllers/tasks/ChannelOrderNotify.php |
| 物理表名 | application/config/tables.php |
| 全车件转发门面 | application/controllers/allcarpart/Controller.php |
| 全车件显式和兜底路由 | application/config/routes.php |
| 全车件导入和打印示例 | application/controllers/allcarpart/sale/SaOrder.php |
21. 仍需环境确认
以下内容无法仅凭仓库静态代码证明,联调或上线前必须补证据:
- 生产网关对
inner/channel/*暴露的完整 URL、HTTP 方法、鉴权和签名规则。 - 三个渠道的外部订单号是否真正全局唯一。
- 各环境四个
dkh.*Apollo 键的实际覆盖情况。 t_channel_order.source_order_no、签收unique_id等字段的真实唯一索引。submitOrderV2()的数据库事务边界能否覆盖全部库存副作用。- OPS 对四类 MQ 的最终接收接口、重试和告警机制。
MICRO_URL对应服务、超时、重试和熔断配置。get_return_stock_in()是否已经被外部渠道调用以及预期返回合同。
完成这些确认后,应把结果更新回本文,而不是只留在聊天或临时联调记录里。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| 渠道创建订单 | 大客户/渠道内部接口 /inner/channel/* | channel、外部订单号、sid、商品、收货信息 | controllers/inner/channel/Order.php | Channel/BaseOrder.php | channel + source_order_no | 渠道单主明细创建并转换为内部履约单 |
| 渠道发货/动作 | 外部渠道通知或 OPS 操作 | 外部单号、发货数量、物流、动作类型 | Channel Order/OPS InvCu | BaseOrder | 渠道单号 + 内部出库单号 | 发货累计、物流和渠道状态推进 |
| 售后申请/审核 | 渠道售后接口或 OPS | 原订单、商品、申请数量、原因、审核结果 | Channel Aftersale Controller | BaseAftersale.php | 外部售后单号 + 原出库单 | 售后主明细、退货入库和退款结果 |
| 渠道 MQ | DEST_DGJ_CHANNEL | 手工创建、发货、退款、审核等事件 | tasks/ChannelOrderNotify.php | Channel Service + MqSer | 事件业务键 + 渠道单号 | 异步动作落库并将结果发送渠道 |
| 全车件代理 | /allcarpart/* 显式/兜底路由 | 路由模块、接口参数、来源单号 | allcarpart/Controller.php 及子 Controller | MICRO_URL 对应微服务 | 路径 + 来源单号 | 请求转发到全车件服务或处理导入/打印 |
日志证据矩阵
| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 |
|---|---|---|---|---|---|
| 渠道接入 | Inner API/Channel Controller | channel、source_order_no、URI、request_id | 返回内部渠道单号 | 签名/参数失败、来源单重复 | 外部单号查 CHANNEL_ORDER |
| 内部转换 | BaseOrder/销售出库 Service | 渠道单号、内部销售/出库单号 | 关系建立且状态 created -> wait_ship | 商品映射、库存、地址或事务失败 | 关系字段串联渠道与内部单据 |
| 发货签收 | BaseOrder + Channel MQ | 渠道单、出库单、物流单、消息 ID | shipped_num 累计且状态推进 | 超发、重复消息、物流回传失败 | SKU 明细 + CHANNEL_ORDER_SIGN_DETAIL |
| 售后 | BaseAftersale | 售后单号、原单、SKU、审核动作 | 申请/审核/入库/退款节点完整 | 超退、原单不存在、退款失败 | 售后单关联原渠道单和内部退货单 |
| 全车件转发 | allcarpart/Controller/HTTP Provider | 原始路径、来源单号、下游返回码 | 下游 2xx/业务成功码 | 超时、非成功码、兜底路由错误 | request_id + 来源单号在两端检索 |
环节数据变更台账
| 步骤 | 代码位置 | 事务 | 读取事实 | 写入表/缓存/MQ | 字段或数量变化 | 回查证据 |
|---|---|---|---|---|---|---|
| 接收订单 | inner/channel/Order.php -> BaseOrder | 渠道订单事务 | 来源单唯一性、站点、商品映射 | CHANNEL_ORDER、CHANNEL_ORDER_INFO | 主明细 insert;status=created;订购数量/金额写入 | channel + 外部单号 + SKU |
| 转内部履约 | BaseOrder::submitOrderV2 等 | 跨渠道/销售事务边界需核对 | 渠道明细、可售库存、地址 | 销售/出库相关表、渠道关系字段 | 写内部单号;渠道状态 -> wait_ship | 渠道单与内部单双向回查 |
| 发货 | BaseOrder 发货方法 | 发货事务 | order_num - shipped_num - cancel_num | 渠道明细、物流/签收表、渠道 MQ | shipped_num: old -> old+n;状态 wait_ship -> part_ship/finish | 每 SKU 数量等式、消息 ACK |
| 售后申请 | BaseAftersale | 售后事务 | 原出库与可退数量 | CHANNEL_AFTERSALE、CHANNEL_AFTERSALE_INFO | insert;申请量写入;状态初始化 | 售后单号、原单、SKU |
| 售后通过/退货 | BaseAftersale + 库存/退款 Service | 本地事务与外部退款异步边界 | 已审核、已退、可入库量 | 售后表、内部退货/库存、退款 MQ | refund_num/return_num: old -> old+n;库存按退货增加;状态 -> 完成 | 原单/售后单/退货单/支付单闭环 |
| 签收回传 | 渠道签收处理 | 消费事务 | 物流状态、unique_id | CHANNEL_ORDER_SIGN_DETAIL、订单状态/MQ | 签收记录 insert;最终状态按全部履约推进 | unique_id 幂等、物流单、渠道状态 |
子模块级独立追踪
子模块追踪:channel-goods 渠道商品查询
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 查询 | /inner/channel/Goods | request ID、channel、external station/customer、SKU/query | application/controllers/inner/channel/Goods.php -> Channel Goods Service | 渠道绑定、商品映射、价格、可售库存和范围 | 只读,不写业务表;外部商品请求 -> 渠道可售结果 | request ID + channel + SKU + sid | 无映射/不可售返回明确业务码;不为修页面结果直接改库存或商品状态 |
| 外部回查 | 渠道方核对商品 | external request ID、channel SKU、DGJ SKU | application/Services/Channel/BaseService.php 及渠道子类 | 本地响应快照和渠道合同 | DB 不变;仅对比两端字段/单位 | 两端 request ID + channel SKU/DGJ SKU | 字段合同差异修适配层并回归所有渠道,不污染基础商品 |
子模块追踪:channel-create 渠道订单创建
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 接单 | /inner/channel/Order | request ID、channel、source_order_no、sid、商品、地址 | application/controllers/inner/channel/Order.php -> Channel/BaseOrder.php | 外部单唯一性、渠道绑定、商品和地址 | 渠道事务 insert CHANNEL_ORDER/INFO;status 无 -> created,order_num/amount 写入 | request ID + channel + source order + local channel order | 重复来源返回/查询原单;参数失败零写入,不能用随机 message ID 代替业务幂等 |
| 转内部单 | 接单后提交 | channel order、内部 source/type、SKU/qty | application/Services/Channel/BaseOrder.php::submitOrderV2 -> 销售/大客户 Service | 渠道明细、价格库存、服务类型 | 跨域事务 insert 内部销售/大客户事实单,关系无 -> internal bill;status -> wait_ship | source order + channel order + internal bill | 部分成功按关系补缺失段;禁止重建渠道意图单或已存在事实单 |
子模块追踪:channel-audit 人工代建与渠道审核
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 人工代建 | OPS scm/InvCu | request ID、operator、channel/source order、客户商品 | application/controllers/scm/InvCu.php -> InvCuService/Channel Service | 渠道配置、客户绑定、来源唯一性 | 事务 insert 渠道/大客户单和操作记录;status 无 -> created/pending | request ID + operator + source/local bill | 页面超时先按 source order 查重;权限失败零写入 |
| 审核 | OPS 或渠道 MQ approve/action | message ID、channel order、action、reason | application/controllers/tasks/ChannelOrderNotify.php -> BaseOrder | 当前状态、审核人、自动发货条件 | 单消息事务 status created/pending -> approved/rejected/wait_ship | message ID + channel/source order + action | 并发/重复审核 0 副作用;自动发货失败保留已审核事实并进入独立补偿 |
子模块追踪:channel-ship 渠道发货与库存出库
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 发货 | 渠道发货 API、OPS 或 MQ | request ID/message ID、channel/source/internal bill、SKU、qty | application/Services/Channel/BaseOrder.php -> 大客户/销售出库 Service -> InventorySer | 可发=order-shipped-canceled、内部事实单和库存 | 发货事务 shipped_num: old -> old+n;内部出库 insert;库存实时 old -> old-n | 三种单号 + SKU + transType + message ID | 超发/重复拒绝;出库库存部分成功按内部出库单补断点,不重做渠道发货 |
| 外部回写 | 本地 commit 后 | source order、shipment/logistics no、routing key | application/Services/Mq/MqSer.php / Channel Provider | 已提交发货和渠道目标 | 本地 DB 不变;外部状态 wait_ship -> part_ship/finish | source order + shipment + message ID | 推送失败只补外部通知;外部已收 ACK 丢失时重发必须幂等 |
子模块追踪:channel-sign 渠道签收
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 签收事件 | 渠道/配送签收回调 | message ID、source order、shipment、unique_id、sign qty/time | application/Services/Channel/BaseOrder.php 签收处理 | 发货明细、已有 unique_id、当前状态 | 消费事务 insert CHANNEL_ORDER_SIGN_DETAIL;signed qty old -> old+n,全部签收 status -> finish | unique_id + source/channel order + shipment | unique_id 重复为 0 变化;签收量超发拒绝并告警 |
| 通知 | 签收 commit 后 | source order、sign record、message ID | application/Services/Mq/MqSer.php | 本地最终签收事实 | DB 不变;渠道/订单中心派生状态 old -> signed | sign unique_id + routing key | 通知失败只补签收事件,不重复写签收明细 |
子模块追踪:channel-aftersale 渠道售后申请与审核
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 申请 | /inner/channel/Aftersale | request ID、source aftersale/order、SKU、qty、reason | application/controllers/inner/channel/Aftersale.php -> BaseAftersale.php | 原发货、已申请/已退量、来源唯一性 | 售后事务 insert CHANNEL_AFTERSALE/INFO;apply/refund num 0 -> n,status 无 -> pending | request ID + source aftersale + original source order | 超退/重复来源零写入;响应未知按 source aftersale 查原申请 |
| 审核 | OPS/ChannelNotify approve/reject | message ID、aftersale order、action、operator | application/controllers/tasks/ChannelOrderNotify.php -> BaseAftersale | 当前售后态、原单可退量 | 单消息事务 pending -> approved/rejected;拒绝释放占用量 old -> old-n | message ID + aftersale/original order + action | 重复审核 0 变化;审核通过后退货单创建失败作为下一模块补偿,不回退审批事实 |
子模块追踪:channel-return 渠道退货入库与退款
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 退货入库 | 服务站确认收货 | request ID、aftersale/original invoice、SKU、warehouse/location、qty | application/Services/Channel/BaseAftersale.php -> 销退 Service -> InventorySer | approved qty、已收量、原出库明细 | 退货事务 return qty old -> old+n;库存实时 old -> old+n,写销售退货流水 | aftersale + internal return bill + transType + SKU | 多原出库拆单分别幂等;库存已加状态未写只补状态/关系 |
| 退款完成 | 支付/渠道退款事件 | refund/pay ID、aftersale、amount、message ID | application/Services/Channel/BaseAftersale.php -> Payment/Mq Service | 原支付、已退金额、退货入库事实 | 财务事务 refunded old -> old+n;售后 status -> finish,外部通知异步 | refund/pay ID + aftersale/source order | 资金与库存分段补偿;重复退款消息不得重复入账或加库存 |
子模块追踪:allcarpart 全车件路由、代理与本地能力
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 路由代理 | /allcarpart/* 显式或兜底路由 | request ID、原 path、用户/sid、source order、payload | application/config/routes.php -> application/controllers/allcarpart/Controller.php -> MICRO_URL | 路由分类、身份上下文、转发参数 | 纯代理时本地 DB 不变;外部系统状态由无 -> 受理 | request ID + path + source order + external request ID | HTTP 200 仍核业务码;超时用外部 ID 查对方,避免重复提交 |
| 本地能力 | 导入、打印、导出等全车件子 Controller | request ID、file/task/order ID | application/controllers/allcarpart/sale/SaOrder.php 等 -> 本地 Service | 文件、模板、本地订单/打印数据 | 按具体事务写导入任务/业务表或只读生成文件,不能一概视为代理 | request/task/order ID + Controller 方法 | 区分本地失败与微服务失败;只补对应段,文件重放按稳定业务键去重 |