本文说明 DGJ2.0 从“销售已出库或采购已下单”到“接单、取货、轨迹、送达、签收、取消、退回”的完整履约链路。
这里最容易产生误解的地方是:代码中的“配送”不是一套系统,而是四条业务链同时存在。
| 链路 | 发起业务 | 主系统 | DGJ 本地主要凭据 | 单号特征 |
|---|---|---|---|---|
| 商家自配送 | 销售出库后由服务站员工配送 | TMS | 销售出库单 delivery_no、关系表 | KZPS... |
| 三方配送 | 销售出库后调用达达/美团等运力 | TMS 三方物流聚合 | 配送订单表、关系表、销售出库单 | SPS... |
| 急件自提 | 服务站采购时到供给仓自提 | 调拨中心 | 采购单扩展表 | 采购类型 30-Cxx-70 |
| 采购配送履约 | 普通采购由调拨中心/WMS 出库配送 | 调拨中心、TMS、WMS | 采购出库流水 | 采购单、配送单、箱号、运单号 |
结论:排查问题时必须先识别业务链和单号类型。只查 t_scm_third_party_delivery_order 无法覆盖商家自配送和采购履约,只查销售出库单也无法解释急件自提地址。
1. 业务目标
配送域承担以下职责:
- 将销售出库单转换为可执行的配送任务。
- 支持服务站自有骑手接单、改派、轨迹上报、到达和签收。
- 支持向三方运力询价、下单、取消、退回确认和轨迹查询。
- 将 TMS 状态转换为 DGJ 销售出库状态。
- 接收调拨中心采购出库、预计到货、急件自提和退货关闭消息。
- 保存 DGJ 单据与外部配送单、运单、箱号之间的可追踪关系。
- 在撤销出库、撤销对账、取消配送等逆向流程中恢复正确业务状态。
flowchart LR
subgraph DGJ["DGJ2.0"]
SO["销售订单"] --> SI["销售出库单"]
PO["采购订单"] --> POX["采购扩展/出库流水"]
SI --> REL["配送关系"]
end
subgraph TMS["TMS/运力中心"]
SELF["商家自配送 KZPS"]
THIRD["三方配送 SPS"]
TRACK["骑手与轨迹"]
end
subgraph DC["调拨中心/WMS"]
STOCK["采购出库"]
PICK["急件自提信息"]
EXPRESS["箱号/运单/预计到货"]
end
REL --> SELF
REL --> THIRD
SELF --> TRACK
THIRD --> TRACK
STOCK --> POX
PICK --> POX
EXPRESS --> POX
2. 适用场景
本文适用于以下研发、联调和线上排查场景:
| 场景 | 优先阅读章节 |
|---|---|
| 出库后没有生成配送单 | 10、11、26 |
| 骑手看不到待配送任务 | 12、13、27 |
| 接单后出库单状态不对 | 14、22、28 |
| 轨迹不更新 | 15、27 |
| 三方配送询价失败或余额不足 | 16、17、29 |
| 三方下单成功但 DGJ 没记录 | 18、24、30 |
| 取消后仍显示派送中 | 19、22、28 |
| 三方退回无法确认 | 20、29 |
| 急件自提地址未显示 | 21、31 |
| 采购单未进入配送中 | 23、32 |
| 运单号或预计到货时间缺失 | 24、32 |
| 撤销对账后状态错误 | 25、28 |
| 修改配送公共逻辑的回归评估 | 33、34、35 |
3. 先建立统一业务语言
3.1 容易混用的名词
| 名词 | 本文定义 | 代码中的主要字段 |
|---|---|---|
| 销售订单 | 客户购买商品的业务订单 | t_scm_sa_order* |
| 销售出库单 | 商品实际从服务站库存出库的单据 | t_scm_sa_invoice_0_n.billNo |
| 商家自配送 | 服务站自己的配送员履约,由 TMS 管任务和轨迹 | delivery_no=KZPS... |
| 三方配送 | 达达、美团等外部运力履约 | delivery_order_no=SPS... |
| 配送关系 | 销售出库单与 TMS 配送单的桥接记录 | t_sa_invoice_delivery_relation |
| 急件自提 | 服务站采购后自行到供给仓取货 | orderType=30-Cxx-70 |
| 采购出库流水 | 调拨中心/WMS 已经从供给仓发出的行级事实 | t_scm_po_out_info_0_n |
| 到达 | 骑手到收货点或执行到达动作,不等同于财务对账 | TMS arrive |
| 签收/送达 | 履约完成,销售出库状态通常变为 3 已送达 | TMS 90、DGJ 3 |
3.2 三类“自提/自配送”不能混为一谈
| 名称 | 是谁取/送 | 发生在哪一侧 | 核心实现 |
|---|---|---|---|
| 商家自配送 | 服务站骑手送到修理厂 | 销售侧 | SelfDeliverySer |
| 急件自提 | 服务站去供给仓取采购货物 | 采购侧 | PoOrderEnums::ORDERTYPE_SELF_PICK_ORDER |
| 三方退回 | 三方骑手把拒收商品退回门店 | 销售逆向 | ThirdDeliverySer::backConfirm |
flowchart TD
Q{"看到“自提/自配送”"}
Q -->|"销售出库单、KZPS"| A["商家自配送"]
Q -->|"采购单、30-Cxx-70"| B["急件自提"]
Q -->|"SPS、backStatus"| C["三方配送退回"]
A --> A1["查 TMS 自配送和销售出库"]
B --> B1["查调拨中心回调和采购扩展表"]
C --> C1["查三方配送订单、退回状态和确认接口"]
4. 系统边界与责任归属
| 系统/模块 | 负责什么 | 不负责什么 |
|---|---|---|
| DGJ 销售域 | 出库单、配送员引用、配送单号、单据状态 | 骑手实时轨迹的主存储 |
| DGJ 配送服务 | 参数组装、关系落库、状态翻译、入口鉴权 | 外部配送任务调度算法 |
| TMS 自配送 | 配送任务、接单、改派、轨迹、送达 | DGJ 财务对账与核销 |
| TMS 三方物流聚合 | 询价、运力下单、取消、三方状态和轨迹 | DGJ 销售库存扣减 |
| 调拨中心 | 采购订单履约、仓库出库、自提信息 | DGJ 销售配送 |
| WMS | 箱号、运单和仓库出库事实 | 最终客户收款 |
| RabbitMQ | 异步投递状态和物流事件 | 业务幂等本身 |
| Redis | 白名单、APP 选店、未匹配运单临时补偿 | 配送主状态事实 |
flowchart TB
UI["PC/APP/AI 控制台"] --> CTRL["Controller / app apiCode"]
CTRL --> SER["DGJ Service"]
SER --> DB[("DGJ MySQL")]
SER --> TMS["TMS/运力中心 HTTP"]
DC["调拨中心/WMS"] --> MQ["RabbitMQ"]
TMS --> MQ
MQ --> TASK["DGJ CLI Consumer"]
TASK --> DB
TASK --> REDIS[("Redis 补偿缓存")]
5. 代码入口地图
5.1 Web、APP 和 AI 入口
| 入口 | 文件 | 作用 |
|---|---|---|
| PC 三方配送 | application/controllers/sale/ThirdDelivery.php | 白名单、地址、询价、下单、详情、取消、退回 |
| PC 商家自配送 | application/controllers/sale/SelfDelivery.php | 创建、取消、详情 |
| APP 配送 | application/config/appapis.php | transport* API Code 映射 |
| APP API 实现 | application/service/api/app/transport/*.php | 列表、接单、到达、签收、取消、轨迹、选店 |
| AI 控制台 | application/controllers/aiConsole/Courier.php | 骑手列表、补推 TMS、按单号查详情、收货人列表 |
| 销售出库页面 | application/views_v2/scm/invSa/deliveryList.php | 配送列表与三方配送组件入口 |
| AI IM 工作台 | application/views_v2/settings/ai-im-workspace.php | 从客户会话发起三方配送 |
| 配送地图 | application/views_v2/settings/delivery-map.php | 骑手、修理厂和轨迹地图 |
5.2 核心服务与 Provider
| 层 | 文件 | 关键职责 |
|---|---|---|
| 商家自配送编排 | application/Services/Transport/SelfDeliverySer.php | 从销售出库构建 TMS 请求、保存单号和关系 |
| TMS 统一适配 | application/Services/Transport/TransportSer.php | 自配送 API 包装、列表格式化、状态回写 |
| TMS Provider | application/Providers/OpsCenter/Tmsprod/TmsProvider.php | 调用 /tmsprod/tramgr/* |
| 三方配送编排 | application/Services/SaOrders/ThirdDeliverySer.php | 询价、下单、取消、地址、轨迹、退回 |
| 三方 Provider | application/Providers/OpsCenter/ThirdLogistics/ThirdLogisticsProvider.php | 调用 /tmsprod/zgj/third/logistics/v2/* |
| 配送状态消费者 | application/controllers/tasks/ExpressNoSyncNotify.php | TMS 自配送、三方、运单、预计到货事件 |
| 调拨中心消费者 | application/controllers/tasks/DispatchCenterNotify.php | 采购出库、自提、预计到货、退货关闭 |
| 运单补偿任务 | application/controllers/tasks/ExpressNoSupplement.php | 未匹配运单号的延迟补偿 |
5.3 枚举和模型
| 文件 | 内容 |
|---|---|
application/KzData/Enums/TransportEnums.php | 自配送状态、单号前缀、地图和收发方类型 |
application/KzData/Enums/ThirdDeliveryEnums.php | 三方渠道、配送、退回状态 |
application/KzData/Enums/SaInvoiceEnums.php | 销售出库状态 |
application/KzData/Enums/PoOrderEnums.php | 采购状态和急件自提类型 |
application/KzData/Enums/MqEventEnums.php | TMS、调拨中心 Routing Key |
application/KzData/Enums/RedisKeys.php | 白名单、APP 选店、运单补偿 Key |
application/models/saOrders/ThirdPartyDeliveryOrderModel.php | 三方配送订单 CRUD |
application/models/saOrders/ThirdPartyDeliveryRelationModel.php | 出库单与配送单关系 |
application/models/scm/PoOrderExtModel.php | 急件自提信息 |
6. API 入口字典
以下为代码中的逻辑入口。APP 的对外 URL 由统一 API 分发器和部署路由决定,联调时以当前环境网关为准;apiCode 与处理类映射是仓库可确认事实。
6.1 APP 配送 API Code
apiCode | 处理类 | 用途 | 关键参数 |
|---|---|---|---|
transportStore | app.transport.store | 查询/切换门店 | user_id、store_id |
transportList | app.transport.transportList | 配送列表 | sid、senderCode、courierCode、transportStatus |
transportCancelList | app.transport.transportCancelList | 中止配送列表 | sid |
transportCourierStatusCount | app.transport.transportCourierStatusCount | 骑手状态统计 | sid、senderCode、courierCode |
transportTaking | app.transport.transportTaking | 骑手接单 | sid、uid、outShipmentNo |
transportArrive | app.transport.transportArrive | 到达收货点 | TMS 配送参数 |
transportReceipt | app.transport.transportReceipt | 签收 | TMS 配送参数、签收凭据 |
transportCancel | app.transport.transportCancel | 中止/改派 | sid、outShipmentNo、changeCourier |
trasnportOtherCourierList | app.transport.otherCourierList | 可改派人员 | sid、uid |
courierReportRegister | app.transport.courierReportRegister | 注册轨迹设备 | 骑手、手机号、deviceId |
courierReport | app.transport.courierReport | 上报轨迹 | TMS 要求的坐标和设备字段 |
courierConfig | app.transport.courierConfig | 查询骑手配置 | sid |
updateStoreLocation | app.transport.updateStoreLocation | 更新门店坐标 | 门店和坐标字段 |
updateContactLocation | app.transport.updateContactLocation | 更新修理厂坐标 | 客户和坐标字段 |
6.2 PC 商家自配送接口
| HTTP 逻辑路径 | 方法 | 请求重点 | 成功结果 |
|---|---|---|---|
/sale/SelfDelivery/create | POST | saInvoiceId、delieverId、remark | delivery_no、saInvoiceId |
/sale/SelfDelivery/cancel | POST | saInvoiceId、cancelReason | TMS 删除结果 |
/sale/SelfDelivery/detail | POST | saInvoiceId | TMS 配送详情 |
6.3 PC 三方配送接口
| HTTP 逻辑路径 | 方法 | 说明 |
|---|---|---|
/sale/ThirdDelivery/canDelivery | POST | 查询站点是否在 Redis 白名单 |
/sale/ThirdDelivery/updateStoreAddress | POST | 同步门店地址到运力中心并保存本地 |
/sale/ThirdDelivery/updateContactAddr | POST | 更新客户省市区、地址和经纬度 |
/sale/ThirdDelivery/updateContactAddrInfo | POST | 更新收货人姓名和手机号 |
/sale/ThirdDelivery/inquiry | POST | 根据出库单询价 |
/sale/ThirdDelivery/create | POST | 使用 inquiryNo 和运力能力下单 |
/sale/ThirdDelivery/detail | POST | 按出库单查配送详情和轨迹 URL |
/sale/ThirdDelivery/cancelCheck | POST | 查询能否取消及违约金 |
/sale/ThirdDelivery/cancelReasons | POST | 查询可选取消原因 |
/sale/ThirdDelivery/cancel | POST | 执行三方取消并回退出库状态 |
/sale/ThirdDelivery/outCancelCheck | POST | 撤销出库前校验配送单 |
/sale/ThirdDelivery/backConfirm | POST | 三方退回到店后确认收货 |
6.4 AI 控制台接口
| 路径 | 作用 | 注意点 |
|---|---|---|
/aiConsole/Courier/lists | 查询配送员轨迹列表 | 数据来自 StaffService |
/aiConsole/Courier/pushTmsTransportCreate | 手工补推 TMS | 接受完整 jsonData,属于高风险运维入口 |
/aiConsole/Courier/getTransportByTmsNo | 按 transNo 查详情 | 用于补推后确认 |
/aiConsole/Courier/getTransportReceiverList | 查询收货人配送单 | 要求 sid |
7. 请求与响应示例
示例只展示业务字段,不包含域名、Cookie、Token 和生产数据。
7.1 创建商家自配送
POST /sale/SelfDelivery/create
Content-Type: application/json
{
"saInvoiceId": 928001,
"delieverId": 3102,
"remark": "到店前电话联系"
}
{
"status": "success",
"data": {
"delivery_no": "KZPS202607160000001",
"saInvoiceId": 928001
}
}
7.2 APP 查询待配送
{
"apiCode": "transportList",
"sid": "9999",
"senderCode": "211",
"courierCode": "1175",
"transportStatus": "10",
"offset": 1,
"limit": 20
}
待配送响应被拆成:
| 字段 | 含义 |
|---|---|
myDeliverList | 当前骑手已经关联的待配送单 |
otherDeliverList | 未分配或分配给其他骑手的待配送单 |
pageDeliveryList | 配送中/已完成时的分页列表 |
totalPages | TMS 返回总页数 |
totalCounts | TMS 返回总记录数 |
7.3 APP 接单
{
"apiCode": "transportTaking",
"sid": 9999,
"uid": 1175,
"outShipmentNo": "XS9999202607160001",
"transNo": "KZPS202607160000001"
}
7.4 轨迹设备注册与上报
{
"apiCode": "courierReportRegister",
"sid": 9999,
"courierCode": "1175",
"courierName": "配送员A",
"courierMobile": "13800000000",
"deviceId": "device-example-001"
}
{
"apiCode": "courierReport",
"sid": "9999",
"courierCode": "1175",
"deviceId": "device-example-001",
"localType": "GAODE",
"localX": 30.000001,
"localY": 120.000001,
"reportTime": "2026-07-16 14:30:00"
}
轨迹上报字段由 DGJ 原样转发给 TMS,最终契约应以当前 TMS 文档为准;仓库内 TransportSer::courierReport() 没有二次字段校验。
7.5 三方配送询价
POST /sale/ThirdDelivery/inquiry
Content-Type: application/json
{
"sid": 9999,
"saInvoiceId": 928001
}
核心响应结构:
{
"customerAddress": {
"contactName": "客户A",
"mobile": "13800000000",
"latitude": "30.000001",
"longitude": "120.000001"
},
"storeAddress": {
"storeNumber": "STORE001",
"linkName": "门店联系人",
"latitude": "30.100001",
"longitude": "120.100001"
},
"saInvoiceId": 928001,
"inquiry": {
"items": [
{
"channel": "imdada",
"channelName": "达达",
"inquiryNo": "INQ-EXAMPLE",
"deliveryAmt": 12.50,
"balanceAmt": 100.00,
"expiredTime": 1784185200,
"recommend": 1,
"code": 0
}
]
},
"expiredTime": 1784185200
}
7.6 三方配送下单
POST /sale/ThirdDelivery/create
Content-Type: application/json
{
"sid": 9999,
"saInvoiceId": 928001,
"inquiryNo": "INQ-EXAMPLE",
"capacityIdList": ["CAPACITY-A"],
"remark": "易碎品"
}
{
"status": "success",
"data": {
"deliveryNo": "SPS202607160000001"
}
}
7.7 三方状态 MQ 示例
{
"deliveryNo": "SPS202607160000001",
"deliveryStatus": "30",
"backStatus": "00",
"courierName": "骑手A",
"courierMobile": "13800000000"
}
7.8 TMS 自配送状态 MQ 示例
{
"sid": "9999",
"bizType": "STATUS",
"transNo": "KZPS202607160000001",
"status": "20",
"completeStatus": "NOTHING",
"courierCode": "1175",
"courierName": "配送员A",
"courierMobile": "13800000000",
"details": [
{
"itemCode": "10326742",
"itemName": "示例商品",
"unitName": "个",
"qty": 1
}
]
}
8. 核心表和分片规则
8.1 销售配送表
| 常量/逻辑表 | 物理表 | 作用 | 关键字段 |
|---|---|---|---|
SCM_SA_INVOICE | t_scm_sa_invoice_0_{sid % N} | 销售出库主单 | id、sid、billNo、srcOrderNo、billStatus、delieverId、delivery_no、storeId、buId |
SCM_SA_INVOICE_INFO | 销售出库明细分表 | 配送商品明细 | iid、invId、skuId、qty |
SCM_THIRD_PARTY_DELIVERY_ORDER | t_scm_third_party_delivery_order | 三方配送本地订单快照 | delivery_order_no、渠道、状态、骑手、收发地址、sid |
SCM_SA_INVOICE_DELIVERY_RELATION | t_sa_invoice_delivery_relation | 出库单到配送单的桥接 | sa_invoice_no、sa_invoice_id、delivery_no、delivery_id、store_id、sid |
关系表名称带“三方”,但当前代码也用它保存 KZPS 商家自配送关系。因此更准确的理解是“销售出库与配送关系表”。
erDiagram
SCM_SA_INVOICE ||--|{ SCM_SA_INVOICE_INFO : contains
SCM_SA_INVOICE ||--o{ SA_INVOICE_DELIVERY_RELATION : links
THIRD_PARTY_DELIVERY_ORDER ||--o{ SA_INVOICE_DELIVERY_RELATION : referenced_by
SCM_SA_INVOICE {
bigint id
bigint sid
string billNo
int billStatus
bigint delieverId
string delivery_no
}
THIRD_PARTY_DELIVERY_ORDER {
bigint id
bigint sid
string delivery_order_no
string delivery_channel
string delivery_status
string back_status
}
SA_INVOICE_DELIVERY_RELATION {
bigint id
bigint sid
bigint sa_invoice_id
string sa_invoice_no
bigint delivery_id
string delivery_no
bigint store_id
}
8.2 采购履约表
| 常量 | 物理表 | 用途 | 关键字段 |
|---|---|---|---|
SCM_PO_ORDER | t_scm_po_order | 采购主单 | id、billNo、billStatus、orderType、sid |
SCM_PO_ORDER_INFO | 采购明细分表 | 采购商品行 | id、iid、invId、skuId、qty |
SCM_PO_ORDER_EXT | t_scm_po_order_ext | 急件自提扩展 | iid、pick_connect、pick_mobile、pick_address、pick_time |
SCM_PO_OUT_INFO | 采购出库流水分表 | 调拨/WMS 出库事实 | srcOrderOutId、srcOrderEntryId、deliveryCode、boxNo、express_no、wmsNo、toArriveTime |
8.3 索引要求
仓库没有发现建表 DDL,以下是根据查询模式推导的生产索引要求,必须在数据库环境确认,不能把它们当成已存在事实。
| 表 | 建议唯一/普通索引 | 原因 |
|---|---|---|
| 配送订单表 | 唯一 delivery_order_no | MQ 回调只按配送单号查询,无 sid |
| 配送关系表 | 唯一 (sid, sa_invoice_no, delivery_no, is_delete) 或等价约束 | 防止外部重试产生重复关系 |
| 配送关系表 | 索引 (delivery_no, sid, is_delete) | 所有状态回调的反查入口 |
| 配送关系表 | 索引 (sa_invoice_no, sid, is_delete) | 出库详情和撤销查询 |
| 采购扩展表 | 唯一 iid | insertPickInfo() 按采购单一对一更新/插入 |
| 采购出库流水 | 唯一或强索引 (sid, srcOrderOutId) | 调拨出库消息以行号幂等 |
9. Redis 数据
| Redis Key | 类型 | Field/Value | 用途 | 主数据吗 |
|---|---|---|---|---|
HASH_THIRD_PARTY_DELIVERY_WHITE_LIST | Hash | field=sid | 是否开放三方配送 | 否,功能开关 |
APP_TRANSPORT_SWITCH_STORE | Hash | field=uid,value=storeId | APP 配送页当前门店 | 否,界面偏好 |
TMS_WMS_NO_SYNC_HASH | Hash | field=sid,value=JSON | 暂存早于 DGJ 出库流水到达的运单映射 | 否,补偿队列 |
9.1 白名单判断
ThirdDeliverySer::canDelivery() 执行:
HGET {PREFIX}hash_third_party_delivery_white_list {sid}
值只要在 PHP 中为真就开放。若运营配置写入字符串 0、空字符串或删除 Field,页面都会视为未开放。
9.2 APP 门店切换
HGET {PREFIX}app_transport_store {uid}
HSET {PREFIX}app_transport_store {uid} {storeId}
当前 store.php 会先按账号的 storeDefault + storeLever 查询可用门店,但写入 switchStoreId 前没有在方法内再次判断它是否属于可用集合。入口层或调用方是否限制,需要联调确认。
9.3 运单延迟补偿
sequenceDiagram
participant TMS as TMS MQ
participant C as ExpressNoSyncNotify
participant DB as 采购出库流水
participant R as Redis Hash
participant J as ExpressNoSupplement
TMS->>C: expressNo -> outExpressNo
C->>DB: 按 express_no 查询
alt 已有出库流水
C->>DB: 更新 wmsNo
else 消息早到
C->>R: HSET sid JSON映射
end
J->>R: HGETALL
J->>DB: 再按 express_no 查询
J->>DB: 更新 wmsNo
J->>R: 写回未匹配项
10. 三套状态机
10.1 销售出库状态
| 值 | 枚举 | 业务含义 |
|---|---|---|
0 | BILL_STATUS_RE_OUTED | 老销售退货兼容的已出库 |
1 | BILL_STATUS_OUTED | 已出库/待配送 |
2 | BILL_STATUS_SENDIND | 派送中 |
3 | BILL_STATUS_ARRIVE | 已送达 |
4 | BILL_STATUS_CHECK | 已对账 |
5 | BILL_STATUS_OVER | 已核销 |
6 | BILL_STATUS_CANCELED | 已撤销 |
stateDiagram-v2
[*] --> 已出库
已出库 --> 派送中: 接单/三方建单
派送中 --> 已送达: TMS完成回调
派送中 --> 已出库: 取消/退回
已送达 --> 已对账: 财务对账
已对账 --> 已核销: 收款核销
已对账 --> 已送达: 撤销对账并按配送状态恢复
已出库 --> 已撤销: 撤销出库
10.2 TMS 商家自配送状态
| 值 | 含义 | 对应 DGJ 出库状态 |
|---|---|---|
10 | 待揽收 | 通常 1 已出库 |
20 | 配送中 | 2 派送中 |
90 | 已送达 | 3 已送达 |
99 | 已取消 | 恢复 1 已出库,但消费者当前不处理该值 |
stateDiagram-v2
[*] --> 待揽收: 创建 KZPS
待揽收 --> 配送中: 骑手接单/取件
配送中 --> 已送达: 到达并签收
待揽收 --> 已取消: 删除配送任务
配送中 --> 已取消: 中止配送
10.3 三方配送状态
| 值 | 含义 | DGJ 回调动作 |
|---|---|---|
10 | 待接单 | 只更新配送订单状态 |
20 | 待取货 | 只更新配送订单状态 |
21 | 骑手到店 | 只更新配送订单状态 |
30 | 配送中 | 只更新配送订单状态;建单时已把出库改为 2 |
90 | 已完成 | 出库改为 3 已送达 |
98 | 已退回 | 出库改为 1 已出库 |
99 | 已取消 | 出库改为 1 已出库 |
退回状态另有一套维度:
| 字段 | 值 | 含义 |
|---|---|---|
back_type | 00 | 无退回 |
back_type | 10 | 收货方拒收 |
back_type | 20 | 妥投异常 |
back_status | 00 | 无 |
back_status | 10 | 返回中 |
back_status | 20 | 已返回 |
stateDiagram-v2
[*] --> 待接单
待接单 --> 待取货
待取货 --> 骑手到店
骑手到店 --> 配送中
配送中 --> 已完成
待接单 --> 已取消
待取货 --> 已取消
配送中 --> 已退回: 拒收/妥投异常
已退回 --> [*]: 门店确认退回
10.4 状态转换不是简单同值映射
| 来源 | 来源值 | 目标值 | 代码位置 |
|---|---|---|---|
| 自配送 MQ | 20 | 出库 2 | ExpressNoSyncNotify::selfTransportStatus |
| 自配送 MQ | 90 | 出库 3 | 同上 |
| 三方 MQ | 90 | 出库 3 | thirdPartyDeliveryChangeStatus |
| 三方 MQ | 98/99 | 出库 1 | 同上 |
| 撤销对账查询 TMS | 10/20/90/99 | 出库 1/2/3/1 | InvSaService::resetSaInvoiceStatus |
TransportSer::transportStatusChange() 会保护 4 已对账、5 已核销、6 已撤销 不被配送回调覆盖,但对 1/2/3 没有单调性判断,因此乱序消息仍可能把 3 已送达 回退到 2 派送中。
11. 商家自配送创建主流程
入口:SelfDelivery::create() -> SelfDeliverySer::create()。
11.1 前置条件
- Session 中存在合法
sid。 saInvoiceId对应的销售出库单存在。- 传入配送员时,员工存在且已关联“配送员”角色。
- 配送员员工必须关联可用管理员账号,才能得到 TMS
courierCode=adminUid。 - 门店、客户默认地址、销售出库明细能够查询。
11.2 创建步骤
sequenceDiagram
actor U as PC操作人
participant C as SelfDelivery Controller
participant S as SelfDeliverySer
participant DB as DGJ MySQL
participant T as TMS
U->>C: saInvoiceId, delieverId, remark
C->>S: 注入 sid/uid/uname
S->>DB: 查询销售出库单
S->>DB: 校验员工与配送员身份
alt 已有 KZPS
S->>T: update(outOrderNo,outShipmentNo,courier)
else 无 KZPS
S->>DB: 查询门店/客户/商品/配送时限
S->>T: create(完整收发货和商品信息)
T-->>S: KZPS配送单号
end
S->>DB: 开事务
S->>DB: 更新 invoice.delivery_no/delieverId
S->>DB: 不存在时插入配送关系
S->>DB: 提交
S-->>U: delivery_no
11.3 发给 TMS 的字段来源
| TMS 字段 | DGJ 来源 |
|---|---|
sourceSystem | 固定 DGJ |
senceCode | 固定 tramgr_create_service_shipment |
sid | 当前服务站 |
senderCode | 出库单 storeId |
senderName | 服务站名称 + 门店名称 |
senderPerson/Mobile | 门店联系人、电话 |
senderLocation | 门店高德经纬度 |
receiverCode | 客户 contactId |
receiverName | 客户名称 |
receiverPerson/Mobile | 默认收货地址联系人、电话 |
receiverAddress/Location | 客户默认地址和高德经纬度 |
courier | 配送员姓名、手机号、管理员 UID |
outOrderNo | 出库单 srcOrderNo |
outShipmentNo | 出库单 billNo |
detailList | 出库明细 + 物料缓存中的标准名、单位 |
receiverDuration | 客户-门店配送时限;异常时默认 30 分钟 |
11.4 更新已有配送单
若销售出库单已保存 KZPS,再次创建不会新建 TMS 任务,而是调用 transportUpdate() 更新骑手。
这意味着“创建”接口同时承担“更新配送员”的语义。调用方必须根据返回和当前 delivery_no 判断是首次创建还是修改。
12. APP 配送列表与选店
12.1 门店范围
APP 先从管理员账号读取:
storeDefault + storeLever -> 去重 -> 过滤已删除/禁用门店
当前选店写入 Redis APP_TRANSPORT_SWITCH_STORE[uid]。
12.2 列表分支
flowchart TD
A["transportList"] --> V{"transportStatus"}
V -->|"10 待揽收"| W["固定拉取最多200条"]
W --> L["补齐门店/客户经纬度"]
L --> M["拆 myDeliverList"]
L --> O["拆 otherDeliverList"]
V -->|"20 配送中"| D["按 offset/limit 请求 TMS"]
V -->|"90 已完成"| F["按 offset/limit 请求 TMS"]
D --> P["pageDeliveryList"]
F --> P
12.3 列表格式化
TransportSer::formatList() 会扫描 transportDetail.itemCode,批量查询商品分类,再将 TMS 明细格式化为 DGJ 页面所需结构。
待配送列表还会在 TMS 未保存坐标时,用 DGJ 门店和客户默认地址补齐坐标。
12.4 已知分页语义
配送中和已完成分支中:
page.number = intval(offset)
page.size = intval(limit)
这里的 offset 实际是“页码”,不是 SQL 偏移量。前端改分页组件时不能直接传 (page-1)*limit。
13. 配送员身份和改派
配送员不是普通员工字段,而是三层关系:
flowchart LR
STAFF["员工 Staff"] --> ROLE["员工角色关系 Courier Role"]
STAFF --> ACCOUNT["adminAccount"]
ACCOUNT --> ADMIN["管理员账号 uid"]
ADMIN --> TMS["TMS courierCode"]
| 检查 | 失败表现 |
|---|---|
| 员工未删除且未禁用 | “送货人账号不可用” |
| 员工具有配送员角色 | 无法在 APP 配送 |
员工 adminAccount 不为空 | 无 TMS 骑手编码 |
| 对应管理员状态可用 | adminUid 为空或身份校验失败 |
改派入口 transportCancel 接收:
{
"sid": 9999,
"outShipmentNo": "XS9999202607160001",
"changeCourier": {
"courierCode": 1188
},
"cancelReason": "改由其他配送员配送"
}
前端传入的 changeCourier.courierCode 实际是管理员 UID。服务会反查员工 ID,并更新销售出库单 delieverId。
14. 接单、到达和签收
14.1 接单
TransportSer::transportTaking() 的顺序是:
- 校验当前
uid是否有配送员身份。 - 按
outShipmentNo查询销售出库单。 - 更新
delieverId。 - 将出库状态改为
2 派送中。 - 调用 TMS
transport/taking。
sequenceDiagram
actor R as 骑手
participant A as APP API
participant D as DGJ DB
participant T as TMS
R->>A: transportTaking
A->>D: 校验员工/账号/角色
A->>D: 写 delieverId
A->>D: billStatus=2
A->>T: taking
T-->>A: 接单结果
14.2 到达和签收
transportArrive() 和 transportReceipt() 主要是 TMS 透传,DGJ 本地状态依赖后续 tmsprod_self_transport_status MQ 回调推进。
因此接口返回成功但页面状态暂未变化时,应先判断是短暂异步延迟,还是 MQ 消费失败。
14.3 重要一致性风险
接单流程先改 DGJ,再调用 TMS,并且没有事务包围外部调用。如果 TMS 接单失败,DGJ 可能已经显示 派送中 且写入配送员。
建议修复方向:
- 优先调用 TMS,成功后再更新本地;或
- 为本地写入增加
taking_pending/taking_failed操作记录;或 - 失败时显式回滚
delieverId和出库状态。
15. 轨迹链路
sequenceDiagram
participant APP as 骑手APP
participant DGJ as DGJ API
participant TMS as TMS
participant MAP as 配送地图/AI控制台
APP->>DGJ: courierReportRegister(deviceId)
DGJ->>TMS: 注册设备和骑手
loop 配送过程中
APP->>DGJ: courierReport(经纬度,时间)
DGJ->>TMS: 原样转发轨迹
end
MAP->>DGJ: 查询骑手/配送详情
DGJ->>TMS: courier/list 或 transport/getno
TMS-->>MAP: 当前坐标和任务
15.1 坐标约定
| 字段 | 含义 |
|---|---|
LocalX | 纬度 Latitude |
LocalY | 经度 Longitude |
localType | GAODE 或 BAIDU |
项目中发货方和收货方明确按高德坐标组装。若 APP 上报百度坐标却标成高德,地图会出现位置偏移。
15.2 轨迹失败检查顺序
courierReportRegister是否成功。deviceId是否稳定且与骑手一致。- APP 是否持续调用
courierReport。 - 坐标系和值域是否正确。
- TMS 是否保存并在查询接口返回。
- 地图页是否使用正确
courierCode/transNo。
16. 三方配送准入和地址准备
16.1 准入条件
三方配送入口是否显示由 Redis 白名单决定。业务下单还依赖:
| 数据 | 必需字段 |
|---|---|
| 门店 | storeNumber、linkName、linkMobile、linkAddress、经纬度 |
| 客户默认地址 | 联系人、手机号、详细地址、经纬度 |
| 客户 | 客户编码/名称 |
| 出库单 | billNo、数量、金额、门店、客户 |
16.2 更新门店地址
updateStoreAddress() 先调用运力中心 store/update,再保存 DGJ 门店。
sequenceDiagram
actor U as 操作人
participant D as DGJ
participant T as 三方物流中心
participant DB as DGJ DB
U->>D: 联系人/电话/地址/经纬度
D->>DB: 查询门店和服务站名称
D->>T: storeUpdate
T-->>D: 成功
D->>DB: updateStoreInfo
外部更新成功、本地更新失败时会不一致,当前没有补偿事务。
16.3 更新客户地址
updateContactAddr():
- 调用
ContactSer::saveAddressCode()处理地址编码。 - 校验省、区和三级编码、详细地址、经纬度、客户 ID。
- 事务内新增或更新默认地址。
- 提交后调用
syncContactToSaas()。
注意:SAAS 同步发生在本地提交之后;同步失败时 catch 会调用 rollback,但数据库事务已提交,无法真正回滚。
17. 三方询价流程
17.1 参数组装
ThirdDeliverySer::inquiry() 按销售出库单查询门店和客户地址,向运力中心传递:
| 类别 | 字段 |
|---|---|
| 业务单 | orderNo、totalQty、orderAmt |
| 收货方 | 姓名、手机号、客户编码、完整地址、经纬度 |
| 发货方 | 门店编码、联系人、电话、地址、经纬度 |
| 业务控制 | 默认重量 5、小费 0、操作人 |
17.2 报价选择
运力中心返回 items 后,DGJ 会:
- 取所有报价最小
expiredTime作为整体过期时间。 - 取最小
deliveryAmt。 - 若
deliveryAmt > balanceAmt,标记code=-1、message=余额不足。 - 最低配送费的报价标记
recommend=1。
flowchart TD
A["读取出库单"] --> B{"门店和客户地址完整?"}
B -->|否| E["当前代码进入空报价风险分支"]
B -->|是| C["调用 v2/inquiry"]
C --> D["遍历运力 items"]
D --> F{"配送费 > 余额?"}
F -->|是| G["code=-1 余额不足"]
F -->|否| H["可选择"]
G --> I["按最低配送费标推荐"]
H --> I
17.3 当前空地址缺陷
地址不完整时,代码构造的是一个“数组中的默认报价”,但后面固定读取 $inquiryDataTmp['items']。这两种结构不一致,随后 min(array_column(...)) 也无法处理空列表。
正确行为应是明确返回“请先完善门店/客户地址”,而不是继续计算报价。
18. 三方配送下单和落库
18.1 切换配送方式限制
若销售出库单 delivery_no 以 KZPS 开头,三方下单直接拒绝:
若要变更为三方配送,请先在出库单详情,取消商家自配送
反向从 SPS 切换到自配送时,也应先取消三方配送并确认状态已恢复。
18.2 下单步骤
sequenceDiagram
actor U as 操作人
participant DGJ as ThirdDeliverySer
participant TMS as 三方物流中心
participant O as 配送订单表
participant R as 配送关系表
participant I as 销售出库分表
U->>DGJ: inquiryNo, capacityIdList, saInvoiceId
DGJ->>I: 查询出库单
DGJ->>TMS: v2/create
TMS-->>DGJ: deliveryNo/channel/thirdDeliveryNo/fetchCode
DGJ->>DGJ: 开本地事务
DGJ->>O: 插入三方配送快照
DGJ->>I: billStatus=2
DGJ->>R: 插入出库配送关系
DGJ->>DGJ: 提交
18.3 本地配送订单保存内容
| 字段组 | 内容 |
|---|---|
| 单号 | DGJ 聚合配送单号、三方平台单号、询价单号 |
| 渠道 | delivery_channel、delivery_channel_name |
| 发货 | 门店地址、联系人、电话 |
| 收货 | 客户详细地址、联系人、电话 |
| 操作 | created_by、备注、sid |
18.4 事务边界风险
运力中心 create 在本地事务之前执行。若外部下单成功而本地插入失败,会产生:
TMS 有 SPS 单号
DGJ 配送订单表无记录
DGJ 关系表无记录
销售出库单仍可能显示待配送
当前代码没有自动按 inquiryNo 或出库单号补偿落库。建议至少增加:
- 外部请求幂等键;
- 外部成功响应持久化日志;
- 按出库单查询 TMS 并重建本地关系的补偿任务;
- 配送订单号和关系唯一索引。
19. 三方取消、撤销出库和改派
19.1 普通取消
flowchart TD
A["cancelCheck"] --> B{"允许取消?"}
B -->|否| X["阻止或要求强确认"]
B -->|是且有违约金| C["展示违约金/取消原因"]
B -->|是且无违约金| D["直接允许"]
C --> E["cancel"]
D --> E
E --> F["调用三方 v2/cancel"]
F --> G["本地配送状态=99"]
G --> H["销售出库状态=1"]
19.2 撤销出库前判断
outCancelCheck() 返回三类结果:
allowCancelStatus | 含义 | 页面建议 |
|---|---|---|
1 | 可取消且无违约金 | 可继续 |
2 | 可取消但存在进行中配送/违约信息 | 先取消配送 |
3 | 已完成、已取消、已退回或外部不允许取消 | 强提示后由业务决定 |
19.3 本地与外部顺序
三方取消先调用外部,再分别更新本地配送订单和出库状态,未使用统一事务。如果第二步失败,外部已取消但 DGJ 仍显示派送中。
19.4 商家自配送取消有两种语义
| 入口 | TMS 动作 | DGJ 动作 |
|---|---|---|
TransportSer::transportCancel | 中止/改派配送员 | 更新 delieverId,不直接清空 delivery_no |
SelfDeliverySer::cancel | 删除配送任务 | 清空 delivery_no,出库恢复 1 |
排查“取消失败”时先确认页面调用的是哪个入口。
20. 三方退回流程
三方配送拒收或妥投异常时,MQ 会把 deliveryStatus 更新为 98 已退回,并可携带 backStatus。
sequenceDiagram
participant T as 三方物流/TMS
participant MQ as RabbitMQ
participant C as ExpressNoSyncNotify
participant DB as DGJ DB
participant U as 门店操作人
T->>MQ: deliveryStatus=98, backStatus=10/20
MQ->>C: thirdPartyDeliveryChangeStatus
C->>DB: 更新配送/退回状态
C->>DB: 销售出库恢复已出库
U->>T: callback/confirm
T-->>U: 确认退回完成
backConfirm() 当前只调用外部确认接口,没有同步修改本地 back_status。本地最终状态依赖外部再次回调,若外部不回调,页面可能长期停留旧状态。
21. 急件自提采购流程
急件自提订单类型:
PoOrderEnums::ORDERTYPE_SELF_PICK_ORDER = 30-Cxx-70
21.1 下单前校验
PoOrderSer::selfPickCheck():
- 商品列表不能为空。
- 只支持快准车服供货主体,不支持撮合商物料。
- 按急件自提订单类型查询供给仓库存。
- 逐商品校验请求数量不超过允许数量。
21.2 自提信息回调
Routing Key:dispatchcenter_self_pick_up_notify。
{
"orderCode": "CG9999202607160001",
"customerCode": "9999",
"contact": "仓库联系人",
"contactNumber": "13800000000",
"selfPickUpAddress": "示例自提仓地址",
"selfPickUpTime": "2026-07-16 16:00:00"
}
sequenceDiagram
participant DC as 调拨中心
participant MQ as RabbitMQ
participant C as DispatchCenterNotify
participant PO as 采购主单
participant EXT as 采购扩展表
DC->>MQ: self_pick_up_notify
MQ->>C: 消费消息
C->>PO: 按 orderCode 查询
C->>C: 校验 orderType=30-Cxx-70
alt 已有扩展行
C->>EXT: update pick_* by iid
else 无扩展行
C->>EXT: insert iid + pick_*
end
21.3 页面显示
采购详情读取 pick_address/pick_connect/pick_mobile/pick_time。回调尚未到达时显示“请于付完款5分钟后查看”。
insertPickInfo() 按 iid 先查后写,应用层具备重复消息覆盖能力,但并发重复插入仍依赖数据库唯一索引保证。
22. TMS 状态回调
消费者:ExpressNoSyncNotify::consume(),Destination:tmsprod_notify。
| Routing Key | 回调方法 | 业务 |
|---|---|---|
tmsprod_self_transport_status | selfTransportStatus | 商家自配送状态 |
tmsprod_third_party_delivery_change_status | thirdPartyDeliveryChangeStatus | 三方配送状态 |
tmsprod_out_expressno_relation | callback | 采购运单映射 |
tmsprod_waybill_prearrvied_time | syncArrvveTime | 预计到货、自动签收时间 |
tmsprod_express_split_carton_no | expressNoUpdate | 拆箱后更新运单 |
tmsprod_third_pickup_order_status | thirdReturnPickupOrderStatus | 售后上门取件状态 |
22.1 商家自配送回调
flowchart TD
A["收到 transNo/sid/status"] --> B["关系表按 delivery_no+sid 查询"]
B --> C{"存在未撤销出库单?"}
C -->|否| X["记录日志并 ACK"]
C -->|是| D{"TMS status"}
D -->|20| E["出库=2 派送中"]
D -->|90| F["出库=3 已送达"]
D -->|其他| X
E --> G["尝试更新本地配送订单状态"]
F --> G
G --> H["ACK"]
当前消费者不处理 10 和 99,因此取消后的恢复主要依赖同步取消接口,而不是 MQ。
22.2 三方回调
处理步骤:
- 只按
deliveryNo查询配送订单。 - 更新
delivery_status、可选back_status、骑手姓名和电话。 - 状态
90时将关联出库单改为3。 - 状态
98/99时将关联出库单改为1。 - 无论异常与否,
finally返回 ACK。
22.3 ACK 策略
两个状态回调在多数异常场景都会 ACK,意味着 RabbitMQ 不会自动重试。可靠性依赖:
- TMS 是否会主动重复推送;
- 是否有人工
resend; - 是否有状态巡检;
- 日志和钉钉告警是否及时发现。
这些外部运行机制需要在部署环境确认。
23. 调拨中心采购出库流程
Destination:DEST_DISPATCHCENTER_NOTIFY。
23.1 消息过滤
dispatchCenterDeliveryStockOut() 只保留:
outOrderCode非空;sourceChannel为1或4;- DGJ 能查到对应采购单;
- 采购状态为待发货、配送中或已完成。
23.2 行级落库
sequenceDiagram
participant DC as 调拨中心
participant C as DispatchCenterNotify
participant PO as 采购主单
participant PI as 采购明细
participant OUT as 采购出库流水
DC->>C: delivery_stock_out(details)
C->>C: 按 outOrderCode 分组
C->>PO: 查询采购单和状态
C->>PI: 按 outOrderRowCode 查询商品
C->>OUT: 按 stockRowId 查重
loop 每个新出库行
C->>OUT: 组装数量/金额/箱号/运单/仓库
end
C->>PO: 首次出库改为配送中(4)
C->>OUT: 批量插入
23.3 幂等键
外部 stockRowId 保存为 srcOrderOutId。重复消息先查询已存在行并跳过,因此它是采购出库回调的业务幂等键。
23.4 售后延迟队列
若相关售后采购单仍在“已提交/审核中”,消费者把消息发到 20 秒延迟队列,最多重试 3 次,之后告警并 ACK。
flowchart TD
A["采购出库消息"] --> B{"有关联售后且仍审核中?"}
B -->|否| C["正常落采购出库流水"]
B -->|是| D{"retryTimes > 2?"}
D -->|否| E["发20秒延迟消息并ACK"]
E --> A
D -->|是| F["告警并ACK,不再自动重试"]
24. 运单、箱号和预计到货
24.1 三种标识
| 字段 | 来源 | 含义 |
|---|---|---|
boxNo | 调拨/WMS | 箱号 |
express_no | TMS/调拨 | 快准侧运单号 |
wmsNo | 外部映射 | 三方承运运单号或 WMS 号 |
24.2 预计到货
调拨中心 dispatchcenter_logistice_info_notify 按采购明细行聚合;同一 outOrderLineId 多条时间取较晚的 planReceiptTime,再调用 PoOrderReceiveSer::timeAddOrUpdate()。
TMS waybill_prearrvied_time 则按配送单列表更新采购出库流水 toArriveTime,并保存 autoSignTime 扩展信息。
24.3 消息早到
运单映射消息可能早于采购出库流水。代码用 Redis 暂存,再由 ExpressNoSupplement::expressNoDelaySync() 补偿。
当前补偿代码有一个需要验证的问题:构造 ['express_no' => ['in', $expressNosStr]] 时传入的是拼接后的字符串,不是原始数组;这可能与 mix_where 的预期不一致,需在本地/预发用真实查询验证。
25. 撤销对账与配送状态恢复
销售出库撤销对账时,不能一律恢复为 3 已送达,而是根据当前配送状态判断。
flowchart TD
A["撤销对账"] --> B{"delivery_no前缀"}
B -->|KZPS| C["向TMS查询 transportStatus"]
B -->|SPS| D["查本地三方 delivery_status"]
B -->|其他| E{"历史上是否有配送关系?"}
C --> F["10=>1,20=>2,90=>3,99=>1"]
D --> F
E -->|无且原状态4| G["恢复1 已出库"]
E -->|有/未知| H["保持默认3"]
25.1 三方状态映射缺口
resetSaInvoiceStatus() 复用自配送的状态表:10/20/90/99。但三方配送的“配送中”是 30,不是 20,另有 21 和 98。
结果是三方单处于 30 配送中 或 98 已退回 时,撤销对账可能无法得到正确恢复状态。
建议按配送类型使用两张显式映射表。
26. 数据流总图
flowchart TB
subgraph Sale["销售侧"]
SA["销售订单"] --> INV["销售出库单"]
INV --> MODE{"配送方式"}
MODE -->|自配送| KZPS["TMS KZPS"]
MODE -->|三方| SPS["TMS SPS"]
KZPS --> REL["配送关系表"]
SPS --> TDO["三方配送订单表"]
TDO --> REL
REL --> INV
end
subgraph App["骑手履约"]
KZPS --> TAKE["接单"]
TAKE --> REPORT["轨迹"]
REPORT --> SIGN["到达/签收"]
end
subgraph Purchase["采购侧"]
PO["采购订单"] --> DC["调拨中心"]
DC --> OUT["采购出库流水"]
DC --> PICK["急件自提扩展"]
DC --> ETA["预计到货/运单"]
end
KZPS -.MQ.-> INV
SPS -.MQ.-> INV
27. 商家自配送排查手册
27.1 出库单没有 KZPS
按顺序检查:
- 出库单是否存在且
sid正确。 - 配送员工是否存在、启用并绑定管理员账号。
- 门店、客户默认地址是否完整。
- 出库明细商品是否能从物料缓存读取名称和单位。
Services/transport日志中的“创建配送单参数/结果”。- TMS
transport/create是否超时或返回业务错误。 - 外部已创建时,是否本地事务失败导致未保存
delivery_no。
27.2 骑手看不到任务
| 检查项 | 说明 |
|---|---|
| APP 当前门店 | Redis 选店是否与出库单 storeId 一致 |
TMS senderCode | 创建时来自门店 ID |
| 骑手 UID | TMS courierCode 是管理员 UID,不是员工 ID |
| 状态筛选 | 待配送只查 10 |
isSelf | 配送中/已完成列表会传该筛选 |
| TMS 任务 | 按 transNo 直接查询确认 |
27.3 接单后 DGJ 派送中但 TMS 未接单
这是当前调用顺序可产生的已知中间态。查询操作时点日志,确认 transportTaking 在本地更新后是否抛出 TMS 错误,再人工回退或重试,不能直接重复创建配送单。
27.4 已签收但 DGJ 未送达
- TMS 是否产生
tmsprod_self_transport_status且status=90。 - 消费者是否启动。
- 关系表是否有
(delivery_no,sid)。 - 关系指向的出库单是否已撤销。
- 出库单是否已是
4/5/6,被保护逻辑跳过。
28. 状态异常排查决策树
flowchart TD
A["页面状态不符合预期"] --> B{"delivery_no前缀"}
B -->|KZPS| C["查TMS自配送详情"]
B -->|SPS| D["查三方配送订单"]
B -->|空| E["尚未建配送或已取消清空"]
C --> F{"TMS状态正确?"}
D --> F
F -->|否| G["查发起接口/外部运力"]
F -->|是| H["查MQ消息和消费者日志"]
H --> I{"关系表存在?"}
I -->|否| J["外部成功、本地落库失败或关系丢失"]
I -->|是| K{"出库状态4/5/6?"}
K -->|是| L["保护状态,不会被配送覆盖"]
K -->|否| M["检查乱序消息/映射缺口"]
28.1 乱序判断
同一配送单如果先收到 90,后收到历史 20/30:
- 三方回调会先将出库改为
3; - 后到的三方
30不改出库,所以相对安全; - 自配送后到
20会把出库从3回退到2。
当前代码没有比较事件时间、版本号或状态优先级。是否由 MQ 分区保证有序需要环境确认。
29. 三方配送排查手册
29.1 页面没有三方入口
HGET {PREFIX}hash_third_party_delivery_white_list {sid}
同时检查 application/controllers/scm/InvSa.php 是否把 thirdDeliveryOpen 传给页面。
29.2 询价报错
| 现象 | 重点检查 |
|---|---|
| 无报价/代码警告 | 门店或客户经纬度为空触发结构缺陷 |
| 余额不足 | 每个报价 deliveryAmt > balanceAmt |
| 地址不识别 | 三级行政区编码、详细地址、坐标 |
| 操作人为空 | user_id 无法查到管理员 |
| 超时 | Provider 超时为 5 秒 |
29.3 外部有 SPS、本地无记录
- 按出库单号调用三方
detail/orderno。 - 查
Services或 Provider 请求日志取得deliveryNo。 - 查本地配送订单表。
- 查关系表和销售出库
delivery_no。 - 不要直接再次下单,先确认外部接口是否幂等。
29.4 取消后仍派送中
- 外部
cancel是否成功。 - 本地配送订单是否
99。 - 关系表能否反查出库单。
- 出库单是否
4/5/6被保护。 - 是否有后到的状态回调再次覆盖配送订单状态。
30. 一致性故障矩阵
| 故障点 | 外部状态 | 本地状态 | 当前自动恢复 | 建议 |
|---|---|---|---|---|
| 自配送创建外部成功、本地失败 | 有 KZPS | 无单号/关系 | 无明确补偿 | 按出库单查询 TMS 重建 |
| 三方创建外部成功、本地失败 | 有 SPS | 无订单/关系 | 无明确补偿 | 幂等查询 + 补偿落库 |
| 接单本地成功、TMS 失败 | 未接单 | 出库已派送中 | 无 | 失败回滚或待确认状态 |
| 三方取消外部成功、本地失败 | 已取消 | 仍派送中 | 可能靠 MQ | 状态巡检 |
| 门店外部更新成功、本地失败 | 新地址 | 旧地址 | 无 | 重试本地或反向同步 |
| 客户本地更新成功、SAAS 失败 | 新地址 | SAAS 旧地址 | 取决于同步重试 | Outbox |
| MQ 消费异常后 ACK | 新状态 | 旧状态 | Broker 不重试 | DLQ/补偿扫描 |
| 运单消息早到 | 已有映射 | 无出库流水 | Redis 半小时补偿 | 修复查询参数并监控积压 |
quadrantChart
title 配送一致性风险优先级
x-axis 低发生概率 --> 高发生概率
y-axis 低业务影响 --> 高业务影响
quadrant-1 优先治理
quadrant-2 重点监控
quadrant-3 常规观察
quadrant-4 快速修复
"外部建单成功本地失败": [0.55, 0.92]
"接单本地先改状态": [0.65, 0.72]
"MQ异常仍ACK": [0.45, 0.85]
"运单消息早到": [0.78, 0.45]
"APP选店越权写入": [0.25, 0.55]
31. 急件自提排查手册
31.1 无法下单
检查 selfPickCheck() 返回的逐商品错误:
- 是否包含撮合商物料;
- 快准车服供给仓是否有
allowQty; - 请求数量是否超库存;
skuId与货主编码是否正确。
31.2 支付后仍无自提地址
- 采购类型是否确为
30-Cxx-70。 - 调拨中心是否发出
dispatchcenter_self_pick_up_notify。 - 消息的
orderCode是否为 DGJ 采购单号。 customerCode是否与采购单sid一致;当前服务只取值但查询订单未显式按 sid。t_scm_po_order_ext是否有对应iid。- 消费日志
DispatchCenterNotify/selfPickUpNotify是否报“非急件自提订单”。
31.3 重复自提消息
正常情况下同一 iid 更新覆盖。若出现两行,检查数据库是否缺少 iid 唯一索引,以及并发消息是否同时通过“先查为空”。
32. 采购物流排查手册
32.1 采购单未变配送中
| 检查 | 预期 |
|---|---|
details[].outOrderCode | 有 DGJ 采购单号 |
sourceChannel | 1 或 4 |
| 采购状态 | 3 待发货、4 配送中 或 5 已完成 |
outOrderRowCode | 能查到采购明细 ID |
stockRowId | 未在采购出库流水中出现 |
32.2 重复采购出库
按 srcOrderOutId=stockRowId 查询。应用会跳过已存在行;若仍重复,检查:
- 两条消息是否使用不同
stockRowId表示同一物理出库; - 数据库是否缺唯一约束;
- 分片
sid是否错误导致查重查错表。
32.3 运单/预计到货为空
依次查询:
- 采购出库流水是否已生成;
boxNo、express_no、wmsNo哪个为空;- TMS 运单映射消息是否消费;
- Redis
TMS_WMS_NO_SYNC_HASH[sid]是否积压; - 半小时补偿任务是否运行;
waybill_prearrvied_time是否包含正确deliveryNos。
33. 常用 SQL
生产只执行只读查询;销售出库和采购流水为分片表,先按 sid 计算实际后缀。以下表名中的 {n} 必须替换。
33.1 查销售出库配送状态
SELECT id, sid, billNo, srcOrderNo, billStatus, delieverId,
delivery_no, storeId, buId, modifyTime, isDelete
FROM t_scm_sa_invoice_0_{n}
WHERE sid = :sid
AND billNo = :sa_invoice_no;
33.2 查配送关系
SELECT id, sid, sa_invoice_id, sa_invoice_no, delivery_id,
delivery_no, store_id, is_delete
FROM t_sa_invoice_delivery_relation
WHERE sid = :sid
AND (sa_invoice_no = :sa_invoice_no OR delivery_no = :delivery_no)
ORDER BY id DESC;
33.3 查三方配送订单
SELECT id, sid, delivery_order_no, third_party_order_no,
delivery_channel, delivery_channel_name, delivery_status,
back_type, back_status, delivery_person,
delivery_person_phone, inquiry_no, is_delete
FROM t_scm_third_party_delivery_order
WHERE delivery_order_no = :delivery_no;
33.4 查重复关系
SELECT sid, sa_invoice_no, delivery_no, COUNT(*) AS cnt
FROM t_sa_invoice_delivery_relation
WHERE is_delete = 0
GROUP BY sid, sa_invoice_no, delivery_no
HAVING COUNT(*) > 1;
33.5 查急件自提
SELECT po.id, po.sid, po.billNo, po.billStatus, po.orderType,
ext.pick_connect, ext.pick_mobile, ext.pick_address, ext.pick_time
FROM t_scm_po_order po
LEFT JOIN t_scm_po_order_ext ext ON ext.iid = po.id
WHERE po.billNo = :po_order_no;
33.6 查采购出库流水
SELECT id, sid, srcOrderNo, srcOrderEntryId, srcOrderOutId,
billNo, boxNo, express_no, wmsNo, qty,
warehouseCode, warehouseName, toArriveTime, createTime
FROM t_scm_po_out_info_0_{n}
WHERE sid = :sid
AND (srcOrderNo = :po_order_no OR express_no = :express_no)
ORDER BY createTime, id;
33.7 查同一外部行是否重复
SELECT sid, srcOrderOutId, COUNT(*) AS cnt, SUM(qty) AS total_qty
FROM t_scm_po_out_info_0_{n}
WHERE sid = :sid
AND srcOrderOutId IN (:stock_row_ids)
GROUP BY sid, srcOrderOutId
HAVING COUNT(*) > 1;
34. Redis、MQ 和日志排查
34.1 Redis 只读命令
redis-cli HGET '<prefix>hash_third_party_delivery_white_list' '<sid>'
redis-cli HGET '<prefix>app_transport_store' '<uid>'
redis-cli HGET '<prefix>tms_wms_no_sync_hash' '<sid>'
redis-cli HLEN '<prefix>tms_wms_no_sync_hash'
不要在生产直接执行 DEL、HDEL 或批量改白名单。运单补偿 Hash 中可能包含仍待数据库落单的数据。
34.2 MQ 检查点
| Destination | Routing Key | 消费者 |
|---|---|---|
tmsprod_notify | tmsprod_self_transport_status | ExpressNoSyncNotify |
tmsprod_notify | tmsprod_third_party_delivery_change_status | ExpressNoSyncNotify |
tmsprod_notify | tmsprod_out_expressno_relation | ExpressNoSyncNotify |
tmsprod_notify | tmsprod_waybill_prearrvied_time | ExpressNoSyncNotify |
DEST_DISPATCHCENTER_NOTIFY | dispatchcenter_delivery_stock_out | DispatchCenterNotify |
DEST_DISPATCHCENTER_NOTIFY | dispatchcenter_self_pick_up_notify | DispatchCenterNotify |
34.3 日志关键字
rg -n "创建配送单|创建配送单失败|开始取件|配送单取消失败" application/Services/Transport
rg -n "第三方配送单状态变更|TMS自配送单状态变更|自配送单和出库单关联关系不存在" application/controllers/tasks/ExpressNoSyncNotify.php
rg -n "急件自提消息|出库开始|行号:.*重复|设置预计到货时间" application/controllers/tasks/DispatchCenterNotify.php
rg -n "同步数据|express_no|TMS_WMS_NO_SYNC_HASH" application/controllers/tasks/ExpressNoSyncNotify.php application/controllers/tasks/ExpressNoSupplement.php
34.4 代码定位命令
rg -n "transportCreate|transportTaking|transportArrive|transportReceipt|transportStatusChange" application/Services/Transport application/service/api/app/transport
rg -n "inquiry|create|cancelCheck|outCancelCheck|backConfirm" application/Services/SaOrders/ThirdDeliverySer.php application/controllers/sale/ThirdDelivery.php
rg -n "ORDERTYPE_SELF_PICK_ORDER|selfPickCheck|insertPickInfo" application
rg -n "TMSPROD_SELF_TRANSPORT_STATUS|TMSPROD_THIRD_PARTY_DELIVERY_CHANGE_STATUS|TYPE_DISPATCHCENTER_DELIVERY_STOCK_OUT" application
35. 已确认风险清单
| 级别 | 风险 | 证据 | 影响 |
|---|---|---|---|
| P0 | 自配送和三方建单都先调用外部、后落本地 | 两个 create() 的调用顺序 | 外部有单、本地无单 |
| P0 | 自配送接单先改本地状态、后调用 TMS | transportTaking() | TMS 失败但 DGJ 已派送中 |
| P0 | 状态回调异常仍 ACK | 两个 MQ 回调的 finally | 消息丢失且无 Broker 重试 |
| P1 | 三方询价空地址的默认结构与后续读取不匹配 | inquiryDataTmp 数字下标 vs ['items'] | 空地址时警告/异常 |
| P1 | 三方撤销对账复用自配送状态映射 | resetSaInvoiceStatus() | 30/98 恢复错误 |
| P1 | 出库 1/2/3 无状态版本或单调保护 | transportStatusChange() | 自配送乱序可回退已送达 |
| P1 | 三方取消外部与本地更新无统一事务 | ThirdDeliverySer::cancel() | 取消状态不一致 |
| P1 | 关系表应用层先查后插 | SelfDeliverySer::create() | 并发重复关系依赖唯一索引 |
| P1 | 三方回调只按配送单号查订单 | getThirdDeliveryOrderByNo() | 隐含单号全局唯一要求 |
| P2 | APP 选店写入未在方法内校验授权集合 | store::service() | 可能写入无权限门店偏好 |
| P2 | 轨迹上报原样透传,缺字段校验 | courierReport() | 错坐标/设备字段延后暴露 |
| P2 | backConfirm() 不更新本地退回状态 | 外部调用后直接返回 | 依赖后续回调 |
| P2 | 自配送回调尝试更新三方配送订单表 | selfTransportStatus() | 通常无对应行,更新无效且语义混乱 |
| P2 | 运单补偿 in 条件疑似传拼接字符串 | ExpressNoSupplement | 补偿可能长期不命中 |
36. 建议治理方案
36.1 可靠建单
flowchart LR
REQ["创建配送请求"] --> OP["本地操作单 CREATED"]
OP --> EXT["带幂等键调用TMS"]
EXT -->|成功| SAVE["事务保存配送单+关系+出库状态"]
EXT -->|超时| QUERY["按业务单号查询TMS"]
QUERY -->|存在| SAVE
QUERY -->|不存在| RETRY["可安全重试"]
SAVE --> DONE["操作单 DONE"]
36.2 状态事件版本化
每条配送状态事件建议至少保存:
| 字段 | 用途 |
|---|---|
delivery_no | 聚合键 |
event_id | 消息幂等键 |
event_time | 判断乱序 |
status | 外部状态 |
status_rank | 单调比较 |
raw_payload | 审计与重放 |
consume_result | 成功/失败/忽略原因 |
36.3 状态映射拆分
不要再让 KZPS 和 SPS 共用一个数值映射表。分别建立:
selfDeliveryToInvoiceStatus
thirdDeliveryToInvoiceStatus
并为取消、退回、撤销对账分别定义合法目标状态。
37. 回归测试清单
37.1 商家自配送
- [ ] 无配送员创建,TMS 能生成待揽收单。
- [ ] 指定合法配送员创建,员工 ID 与管理员 UID 映射正确。
- [ ] 员工未设置配送员角色时明确拦截。
- [ ] 已有 KZPS 再次操作只更新骑手,不重复建单。
- [ ] 创建成功后出库单和关系表都保存同一
delivery_no。 - [ ] TMS 成功、本地模拟失败后可以查到并补偿。
- [ ] APP 待配送、配送中、已完成列表筛选正确。
- [ ] 接单、改派、到达、签收完整闭环。
- [ ] 轨迹设备注册与连续上报可在地图查看。
- [ ] 删除自配送后清空
delivery_no,出库恢复已出库。
37.2 三方配送
- [ ] 白名单开/关决定页面入口。
- [ ] 门店地址缺失时明确提示,不进入空报价计算。
- [ ] 客户地址缺失、坐标缺失、手机号缺失逐项提示。
- [ ] 达达/美团多报价时最低价推荐正确。
- [ ] 余额不足报价不可选。
- [ ] 询价过期后下单有明确错误。
- [ ] 已有 KZPS 时禁止直接创建 SPS。
- [ ] 下单后配送订单、关系、出库状态一致。
- [ ] 待接单、待取货、骑手到店、配送中、完成状态显示正确。
- [ ] 取消前校验、违约金、取消原因流程完整。
- [ ] 取消和已退回后出库恢复已出库。
- [ ] 退回确认后本地状态最终收敛。
37.3 MQ 与乱序
- [ ] 同一三方状态重复投递不产生重复关系或错误状态。
- [ ] 自配送
90后再投20不应回退已送达。 - [ ] 关系不存在时消息进入待补偿,而不是静默永久 ACK。
- [ ] 消费数据库异常时可以重试或进入 DLQ。
- [ ] 已对账、已核销、已撤销不被配送回调覆盖。
- [ ] 撤销对账对 KZPS 的
10/20/90/99恢复正确。 - [ ] 撤销对账对 SPS 的
10/20/21/30/90/98/99恢复正确。
37.4 急件自提与采购物流
- [ ] 撮合商物料被急件自提拦截。
- [ ] 供给仓库存不足被逐商品拦截。
- [ ] 自提回调首次插入和重复更新都正确。
- [ ] 并发重复消息不产生两条采购扩展记录。
- [ ] 调拨出库重复
stockRowId不重复落流水。 - [ ] 售后审核中消息按 20 秒延迟并限制重试次数。
- [ ] 箱号、快准运单、三方运单映射正确。
- [ ] 运单消息早到时进入 Redis,补偿后清除已成功项。
- [ ] 多条预计到货时间按当前业务规则取较晚值。
37.5 兼容业务
- [ ] 普通销售出库、销售退货、撤销出库不受影响。
- [ ] 已对账和已核销财务状态不被覆盖。
- [ ] AI IM 工作台发起三方配送与销售出库页面一致。
- [ ] 多门店账号切店后只看到授权门店配送单。
- [ ] 老数据无
delivery_no时页面仍显示“无配送”。
38. 发布和运行检查
配送功能依赖常驻 CLI 消费者和外部系统。发布后至少确认:
| 检查项 | 验证方式 |
|---|---|
ExpressNoSyncNotify 消费进程 | 进程管理器状态、最新消费日志 |
DispatchCenterNotify 消费进程 | 进程状态、采购测试消息 |
| 20 秒延迟队列 | 队列声明成功且 DLX Routing 正确 |
| 运单补偿任务 | 调度平台最近运行时间和 Redis 积压 |
| TMS HTTP | 自配送详情只读调用成功 |
| 三方物流 HTTP | 白名单站点询价测试成功 |
| Redis | 三个 Key 可访问且前缀正确 |
| 数据库分片 | 测试 sid 命中正确销售/采购分表 |
不要用生产真实出库单测试创建、取消、签收。优先使用预发专用站点和可回收订单。
39. 监控指标建议
| 指标 | 建议告警条件 |
|---|---|
| 配送创建外部成功、本地失败数 | >0 立即告警 |
| 无关系的 TMS 状态消息 | 连续 5 分钟 >0 |
| 状态消费异常后 ACK 数 | >0 立即告警 |
| Redis 运单补偿待处理数 | 超过两个调度周期仍增长 |
| 出库已送达后回退派送中数 | >0 立即告警 |
| 三方询价 5 秒超时率 | 5 分钟超过基线 |
| 三方余额不足率 | 作为运营指标观察 |
| 急件自提付款后无地址时长 | 超过约定 SLA |
| 调拨出库重复行消息数 | 异常突增告警 |
40. 证据、推断与待确认项
40.1 已由代码确认
- 自配送单前缀
KZPS,三方配送前缀SPS。 - APP 自配送状态为
10/20/90/99。 - 三方状态为
10/20/21/30/90/98/99。 - 销售出库状态为
1 已出库、2 派送中、3 已送达、4 已对账、5 已核销、6 已撤销。 - 同一配送关系表同时保存 KZPS 和 SPS。
- 两种建单均先调用外部,再落本地。
- TMS 状态通过
tmsprod_notify消费。 - 急件自提通过调拨中心消息写采购扩展表。
- 采购出库按
stockRowId/srcOrderOutId做应用层幂等。 - 运单消息早到时进入 Redis Hash 等待补偿。
40.2 根据查询模式推断
delivery_order_no应全局唯一,否则三方回调可能串站。- 配送关系应有组合唯一索引,否则并发创建可重复。
- MQ 最好按
delivery_no保序,否则自配送状态可能回退。 - 运单补偿任务注释称每半小时运行,但实际调度需查部署平台。
40.3 待环境确认
- 两张配送表的真实 DDL、索引和字段长度。
SCM_SA_INVOICE_SUB_NUM在各环境的实际值。- TMS create/cancel 是否支持业务幂等键。
tmsprod_notify的队列分区、顺序和重试策略。- 消费进程由 Supervisor、容器还是调度平台托管。
ExpressNoSupplement::expressNoDelaySync的真实调度频率。- 三方渠道当前是否仍同时启用达达和美团。
- 轨迹保存周期、隐私脱敏和查询权限。
- APP 统一分发器的环境实际 URL 与鉴权头。
41. 一页式排查卡
第一步:拿到 sid + 业务单号 + delivery_no + 问题发生时间。
第二步:识别链路。
KZPS -> 商家自配送
SPS -> 三方配送
CG + 30-Cxx-70 -> 急件自提
采购单 + 箱号/运单 -> 调拨/WMS履约
第三步:核对主事实。
销售:销售出库分表 + 配送关系 + TMS/三方详情
采购:采购主单 + 采购扩展 + 采购出库流水
第四步:核对异步链。
tmsprod_notify / dispatchcenter_notify
消费日志、ACK策略、关系是否存在、消息是否乱序
第五步:判断一致性方向。
外部有、本地无 -> 建单落库补偿
外部新、本地旧 -> MQ/消费问题
外部旧、本地新 -> 同步接口本地先写或人工修改
两边一致、页面旧 -> 查询缓存/筛选/门店上下文
第六步:修复后按链路完整回归,不只改一张表。
42. 相关文档
04_销售_出库_配送与收款.md:销售主链与收款关系。15_销售完整排查手册.md:销售订单、出库、退货整体排查。16_库存一致性排查手册.md:出库与库存事实核对。23_外部系统对接清单.md:TMS、调拨中心、WMS 边界。13_MQ回调与补偿任务地图.md:MQ Destination、Routing Key 和补偿任务。24_本地开发联调和日志定位.md:消费者启动与日志定位。25_高风险公共文件影响面.md:修改InvSaService、MQ 公共逻辑的回归要求。
43. 最终结论
配送域的主线不是“创建一张配送单”,而是:
业务单据
-> 选择履约模式
-> 外部系统创建任务
-> 本地保存可追踪关系
-> 骑手/仓库执行
-> MQ 推进状态
-> 到达/签收/退回
-> 对账核销或逆向恢复
维护这部分代码时必须同时守住三件事:
- 单号关系完整:没有
销售出库单 <-> 配送单或采购行 <-> 外部出库行的关系,后续回调无法落地。 - 状态转换有序:TMS、三方和 DGJ 的数值含义不同,不能直接复用映射,也不能忽略乱序。
- 跨系统结果可补偿:数据库事务无法覆盖 HTTP 和 MQ。外部成功、本地失败不是偶发小问题,而是必须有明确恢复路径的正常分布式故障场景。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| 自提 | sale/SelfDelivery/App transport | 出库单、自提人、核销码、时间 | SelfDelivery Controller/App API | SelfDeliverySer | out invoice + pickup ID | 待自提、核销和签收状态 |
| 自有骑手 | sale/ThirdDelivery/AI Courier | 出库单、地址、骑手、费用 | Delivery/Courier Controller | TransportSer/TmsProvider | delivery order ID | 创建配送并分配骑手 |
| 三方配送 | 销售 Service/ThirdDelivery | 出库单、物流商、地址、包裹 | Controller/Service | ThirdDeliverySer/ThirdLogisticsProvider | local+external delivery ID | 三方下单和物流状态 |
| 回调/补单 | Dispatch/Express MQ、supplement task | delivery/out invoice、event、运单号 | DispatchCenterNotify、ExpressNoSyncNotify、ExpressNoSupplement | 配送 Service | 出库/配送/外部运单号 | 回写接单、取货、签收、取消与运单 |
日志证据矩阵
| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 发起配送 | Controller/TransportSer | request_id、出库单、delivery ID、mode | 本地关系保存,Provider 调用发起 | 地址/状态/重复下单失败 | local delivery ID 进入 Provider | | 外部下单 | Tms/ThirdLogistics Provider | external request/order ID、HTTP/业务码 | 对方受理并返回运单/配送号 | timeout、外部成功本地未存 | 外部 ID 用于回调 | | 状态回调 | DispatchCenterNotify | message ID、event、local/external ID | 状态单向推进并 ACK | 乱序、重复、关系缺失 | ID 查配送关系与出库单 | | 运单补偿 | Express task | task batch、out invoice、external ID | 补齐缺失运单且重跑 0 变化 | 扫描过宽、覆盖正确运单 | 批次清单回查关系 |
环节数据变更台账
| 步骤 | 代码位置 | 事务 | 读取事实 | 写入表/缓存/MQ | 字段或数量变化 | 回查证据 |
|---|---|---|---|---|---|---|
| 建本地配送 | Self/ThirdDelivery/Transport Service | 本地事务 | 出库状态、地址、已有关系 | 配送主表、SCM_SA_INVOICE_DELIVERY_RELATION | insert;status pending;mode/fee 写入 | 出库单+local delivery ID |
| 调外部 | Tms/ThirdLogistics Provider | DB 外部边界 | 配送快照 | 外部配送系统 | 本地 pending,获得 external ID;超时不可直接重建 | request/external ID、对方回执 |
| 接单配送 | Dispatch Consumer | 单消息事务 | 当前状态、事件时序 | 配送/出库状态、骑手/运单字段 | pending -> accepted/picked/delivering/signed/canceled | message ID、状态时间 |
| 自提核销 | SelfDeliverySer | 核销事务 | 核销码、待自提状态 | 自提/出库状态 | wait_pickup -> signed;同码仅一次 | pickup ID、核销人/时间 |
| 补偿 | ExpressNoSupplement | 小批幂等 | 对方已存在、本地缺失的运单 | 本地关系/运单字段 | null -> external no;已有值不覆盖 | batch、out invoice、重复运行 0 变化 |
子模块追踪:self-delivery-create 商家自配送创建
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 创建配送 | 销售出库选择商家自配送 | invoice billNo、address、fee、mode | application/controllers/sale/SelfDelivery.php -> application/Services/Transport/SelfDeliverySer.php | 出库可配送态、地址、已有关系和骑手配置 | 配送本地事务 insert 主单/关系,status none -> pending | request ID + invoice/local delivery ID | 重复出库键返回既有配送;创建失败不推进出库配送态 |
| 创建回查 | 页面无配送单/关系 | invoice/local delivery ID | application/models/saOrders/ThirdPartyDeliveryRelationModel.php | 配送主表、出库关系和状态 | 查询只读 不写 | invoice + local delivery + relation count | 只有主单时补关系/领域撤销,不重复创建配送单 |
子模块追踪:self-delivery-rider 配送员接单、改派与到达
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 接单改派 | 骑手接单/运营改派/到达 | delivery ID、rider ID、action、version | application/controllers/aiConsole/Courier.php -> application/Services/Transport/SelfDeliverySer.php | 当前配送态、原骑手、骑手可用性和版本 | 配送本地事务 pending -> accepted/arrived,rider old -> new | request ID + delivery/rider + action/version | 非法迁移/旧版本零写入;改派保留历史 |
| 状态回查 | 骑手端和后台不同步 | delivery ID、event time | application/KzData/Enums/TransportEnums.php | 状态历史、当前骑手和消息顺序 | 查询只读 不写 | delivery + rider + status timeline | 乱序不回退到达态;只补事件/缓存,不重建配送 |
子模块追踪:self-delivery-sign 自配送签收与轨迹
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 轨迹上报 | 骑手位置/状态更新 | delivery/rider、coordinate、timestamp | application/Services/Transport/SelfDeliverySer.php | 当前任务归属、坐标时间和配送态 | 轨迹事务/外部地图边界 append;核心订单状态按事件单向推进 | request ID + delivery/rider + timestamp | 过旧坐标不覆盖新轨迹;敏感位置按权限访问 |
| 签收 | 骑手/客户确认签收 | delivery/invoiceNo、proof/sign code | application/controllers/sale/SelfDelivery.php | delivering/arrived、签收证据和重复键 | 配送本地事务 old -> signed,写签收时间;后置订单通知 commit 后 | request ID + delivery/invoice + signer/time | 重复签收 0 副作用;通知失败只补消息 |
子模块追踪:third-delivery-quote 三方配送准入与询价
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 准入询价 | 选择三方配送前计算服务与费用 | sid、address/coordinate、goods/weight | application/Providers/OpsCenter/ThirdLogistics/ThirdLogisticsProvider.php | 站点准入、地址、货物、承运商和本地配置 | 外部查询事务外,本地业务 DB 不写;返回 quote snapshot | request ID + sid + quote/provider code | timeout/无服务明确不可选;不伪造价格或准入 |
| 报价复核 | 下单价与询价价不同 | quote ID、delivery request | application/Services/SaOrders/ThirdDeliverySer.php | quote 有效期、请求货物地址和最终费用 | 查询只读;下单时重新询价/校验并保存最终 fee | quote/request ID + old/final fee | 过期报价不直接使用;差异由用户/规则确认 |
子模块追踪:third-delivery-create 三方配送下单与本地落库
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 本地建单 | 出库确认三方配送 | invoiceNo、local delivery ID、quote/address | application/controllers/sale/ThirdDelivery.php -> application/Services/SaOrders/ThirdDeliverySer.php | 出库态、quote、已有关系和地址 | 本地事务 insert delivery/relation,status none -> pending | request ID + invoice/local delivery + fee | 本地失败不调用外部;重复 invoice 返回既有关系 |
| 外部下单 | pending 配送提交承运商/TMS | local/external delivery ID | application/Providers/OpsCenter/Tmsprod/TmsProvider.php | 本地配送快照和外部受理态 | 外部调用事务外;受理后本地 pending -> created、保存 external ID | request ID + local/external IDs + codes | timeout 按本地业务键查外部,禁止重复外部单 |
子模块追踪:third-delivery-cancel 三方取消、改派与异常退回
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 取消改派 | 用户/运营取消或换承运商 | local/external delivery、action/reason | application/Services/SaOrders/ThirdDeliverySer.php | 当前外部/本地态、可取消规则和费用 | 外部调用事务外;确认后本地事务 old -> canceled/reassigned | request ID + both IDs + action/result | timeout 先查原单终态;未取消成功不得建新外部单 |
| 异常退回 | 承运失败/退回回调 | message ID、delivery/invoice、event | application/controllers/tasks/DispatchCenterNotify.php | 当前配送态、出库/库存和退回关系 | 回调本地事务推进 failed/returned;库存反向按独立领域事务 | message ID + delivery/invoice + event | 状态成功不代表库存已返;分别补偿状态和库存 |
子模块追踪:urgent-pickup 采购急件自提
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 创建自提 | 采购急件选择自提 | po billNo、pickup ID/code、contact | application/Services/Transport/TransportSer.php | 采购状态、已发/收量、提货信息和重复关系 | 自提本地事务 insert,status none -> wait_pickup | request ID + po/pickup IDs | 不允许状态零写入;核销码日志脱敏 |
| 自提核销 | 提货人确认取货 | pickup ID/code、operator | application/Services/Transport/SelfDeliverySer.php | wait_pickup、码有效性和已核销态 | 核销本地事务 wait_pickup -> signed/picked,同码一次 | request ID + pickup/po + operator/time | 重复码 0 变化;后置采购状态缺失只补通知 |
子模块追踪:tms-callback TMS 状态回调
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 回调消费 | TMS 接单/取件/运输/签收/取消 | message ID、event、external/local delivery | application/controllers/tasks/DispatchCenterNotify.php | 关系、当前状态、事件版本/时间 | 单消息本地事务 status old -> mapped new;ACK 在 commit 后 | message ID + event + both IDs | 未知/乱序不回退;重复 0 副作用 |
| 下游推进 | 签收/取消后更新出库/来源单 | delivery/invoice/source IDs | application/Services/Transport/TransportSer.php | 配送终态、出库和来源当前态 | 单回调事务或后置消息单向推进;外部边界另记 | three IDs + affected rows/routing | 主配送已成只补来源状态/MQ,不重放 TMS 业务 |
子模块追踪:transfer-delivery 调拨中心采购出库
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 调拨出库 | 调拨中心回传采购发货 | message ID、po/transferNo、SKU、qty | application/controllers/tasks/DispatchCenterNotify.php | 采购可发量、已有 out info、调拨关系 | 回调本地事务 outQty +n、采购状态推进,库存按业务类型处理 | message ID + po/transfer + SKU | 重复/超发 0 增量;不要将销售配送逻辑套用采购 |
| 履约对账 | 出库后采购/调拨状态不平 | po/transfer/delivery IDs | application/models/scm/PoOrderExtModel.php | 采购扩展、调拨单、配送和已发/收量 | 查询只读 不写 | all IDs + qty/status timeline | 只补缺失关系/状态;库存事实单独验收 |
子模块追踪:express-supplement 运单、箱号与预计到货补偿
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 异步同步 | 外部运单/箱号消息 | message ID、invoice/delivery、express/box/ETA | application/controllers/tasks/ExpressNoSyncNotify.php | 本地关系、已有字段、外部版本 | 单消息本地事务 null/old -> external latest,不覆盖更新版本 | message ID + invoice/delivery + expressNo hash | 重复/旧消息 0 变化;字段敏感时脱敏日志 |
| 定时补偿 | 扫描外部有、本地缺失记录 | batch、invoice IDs | application/controllers/tasks/ExpressNoSupplement.php | 外部查询结果、本地空字段和任务水位 | 每单本地事务只补 null 字段;已有值不覆盖,重跑 0 变化 | task + batch + invoice + before/after | 小批执行;外部不确定/冲突进入人工核对 |