本文把 DGJ2 中“省市区、客户地址、服务站与仓库地址、订单地址快照、退货地址、门店配送时效、自配送、三方配送、采购承运商”统一梳理为地址主数据、业务地址快照和履约执行三层。
阅读后应能回答:
- 地址编码、地址名称、详细地址和经纬度分别用于什么。
- 修改客户地址后,历史采购/销售订单是否应跟着变化。
- 服务站、门店、仓库、客户和快准退货地址为什么不能混用。
- 配送时效优先从哪里取,分钟和小时怎样统一。
- 自配送和美团/达达三方配送的状态、单号和本地关系有什么差异。
- 采购急件的承运商时效来自哪里。
- 省市区校准、Redis 缓存和直辖市兼容如何工作。
- 地址错误、配送不生成、状态不同步、退货寄错时如何排查。
1. 业务目标
| 目标 | 业务意义 |
|---|---|
| 地址标准化 | 用统一省市区编码表达地点 |
| 地址可执行 | 除文本外保存经纬度,支持路径、距离和骑手定位 |
| 历史可追溯 | 业务单保存下单时地址快照 |
| 配送可编排 | 根据发货门店、收货客户、时效和运力创建配送单 |
| 退货可路由 | 按服务站省份选择快准退货地址 |
| 价格与供货区域 | 省市区影响指导价、方案、供货和首配 |
| 外部一致性 | OPS、TMS、三方物流和 DGJ 对地址编码有一致契约 |
2. 三层地址模型
| 层次 | 代表对象 | 特征 |
|---|---|---|
| 地址主数据 | 省市区、客户地址、仓库地址 | 可修改,代表当前资料 |
| 业务地址快照 | 采购订单地址、配送单发收地址 | 创建后应冻结,代表历史事实 |
| 履约执行数据 | 配送单、轨迹、签收、运单 | 记录谁在何时送到哪里 |
flowchart TD
M["地址主数据"] -->|"下单时复制"| S["业务地址快照"]
S -->|"创建配送"| E["履约执行地址"]
M -->|"后续修改"| N["新的主数据"]
N -.->|"不应覆盖"| S
历史单据展示应优先使用业务快照,而不是实时回查当前客户地址。
3. 地址的六个组成部分
| 组成 | 示例含义 | 常见字段 |
|---|---|---|
| 省编码 | 行政区划编码 | provinceCode、province_code |
| 市编码 | 行政区划编码 | cityCode、city_code、eparchy |
| 区县编码 | 行政区划编码 | countyCode、regionCode |
| 省市区名称 | 展示文本 | province/city/county |
| 详细地址 | 街道、门牌、园区 | address、addr、linkAddress |
| 经纬度 | 地图定位 | longitude/latitude、localX/localY |
flowchart LR
C["行政区划编码"] --> ID["稳定匹配/区域规则"]
N["省市区名称"] --> UI["页面展示"]
A["详细地址"] --> TXT["人工识别"]
L["经纬度"] --> MAP["距离/轨迹/配送"]
文本一样不代表编码一样;编码正确也不代表经纬度正确。四类信息都要分别校验。
4. 核心代码地图
4.1 省市区与区域
| 文件 | 责任 |
|---|---|
application/Services/Area/AreaSer.php | 地区树、子区域、名称转编码、直辖市兼容 |
application/Services/BaseData/RegionSer.php | 省市区 Redis 树缓存和旧 JS 适配 |
application/Services/System/RegionSer.php | 省份到发布大区/灰度环境 |
application/controllers/basedata/Area.php | 地区页面入口 |
application/controllers/basedata/Region.php | 区域入口 |
application/controllers/tasks/AreaCalibrate.php | 从 OPS 组织中心校准省市区 |
4.2 地址
| 文件 | 责任 |
|---|---|
application/controllers/basedata/DeliveryAddr.php | 客户/供应商收货地址维护 |
application/Services/Storage/StorageSer.php | 仓库地址、默认仓、经纬度更新 |
application/Services/KzAddress/ReturnAddressSer.php | 快准退货地址按省份缓存 |
application/controllers/tasks/KzAddress.php | 刷新退货地址缓存 |
application/Services/PoOrders/PoOrderSer.php | 客户地址校验与采购单地址快照 |
application/models/orders/PoOrderAddressModel.php | 采购订单地址快照 |
4.3 配送与物流
| 文件 | 责任 |
|---|---|
application/Services/Transport/TransportSer.php | TMS 自配送创建、列表、接单、送达、签收、取消 |
application/Services/Transport/SelfDeliverySer.php | 销售出库转自配送单 |
application/Services/SaOrders/ThirdDeliverySer.php | 三方询价、下单、取消、退回 |
application/controllers/sale/SelfDelivery.php | 自配送页面/API |
application/controllers/sale/ThirdDelivery.php | 三方配送页面/API |
application/service/api/app/transport/* | 配送员 App 接单、轨迹、到达、签收 |
application/Services/Storage/ContactStoreRelSer.php | 客户与门店关系及配送时效 |
4.4 采购承运商
| 文件 | 责任 |
|---|---|
application/Services/PoOrders/PoOrderSer.php | 急件仓库、截单时间、承运商揽收时间 |
application/Providers/PurchaseService/LogisticsProvider.php | 采购物流服务 |
application/Providers/OpsManager/Wms/LogisticsProvider.php | WMS 运单详情 |
application/models/orders/PoReturnOrderLogisticsModel.php | 采购退货物流记录 |
5. 核心表
5.1 地区与地址
| 表 | 用途 |
|---|---|
t_sys_area_address | 行政区划树,id/parent_id/type/name |
t_sys_region | 省份与大区/发布环境关系 |
t_bs_contact_addr | 客户/供应商当前地址 |
t_scm_po_order_address | 采购订单地址快照 |
t_bs_area_return_address | 省份到快准退货地址 ID |
kzcp_address | 快准退货/仓库地址正文 |
t_bs_storage | DGJ 仓库地址、编码、经纬度 |
t_bs_store | 门店/分仓的联系人、地址和经纬度 |
5.2 配送
| 表 | 用途 |
|---|---|
t_scm_third_party_delivery_order | 三方配送本地订单 |
t_sa_invoice_delivery_relation | 销售出库单与三方配送单关系 |
t_bs_contact_store_rel | 客户与门店绑定及专属时效 |
| 客户配置表 | 默认配送时效回退 |
t_sys_carrier | 历史/本地承运商时效信息 |
t_scm_po_return_order_logistics | 采购退货运单 |
erDiagram
AREA_ADDRESS ||--o{ AREA_ADDRESS : parent_child
CONTACT ||--o{ CONTACT_ADDR : owns
CONTACT_ADDR ||--o{ PO_ORDER_ADDRESS : snapshots
STORAGE ||--o{ CONTACT_STORE_REL : serves
CONTACT ||--o{ CONTACT_STORE_REL : bound_to
SA_INVOICE ||--o{ DELIVERY_RELATION : delivered_by
THIRD_DELIVERY_ORDER ||--o{ DELIVERY_RELATION : includes
AREA_RETURN_ADDRESS }o--|| KZCP_ADDRESS : points_to
6. 行政区划树
t_sys_area_address 以树结构保存:
省(type=1)
-> 市(type=2)
-> 区县(type=3)
AreaSer::getSettleList() 和 getSettleWithNoKey() 将平铺列表转换为树。
flowchart TD
CN["中国/根"] --> P["省"]
P --> C["市"]
C --> D["区县"]
6.1 直辖市兼容
北京、天津、上海、重庆在标准行政区划中通常存在“市辖区”中间层。getAddressRegeoCode() 会将直辖市的 city 统一映射到 市辖区,避免地图返回“北京市/北京市/朝阳区”与表结构不一致。
flowchart LR
A["北京市"] --> B["市辖区"]
B --> C["朝阳区"]
6.2 名称反查的动态补区行为
若地图解析得到的区县名称不在本地表:
- 找省、市名称对应编码。
- 查该市下最大区县 ID。
- 若最大 ID 合法,使用
maxId+1自动插入新区。
这是兼容逻辑,不是权威行政编码生成器。自增出的 ID 可能与官方编码不同,应尽快通过正式校准替换。
7. 省市区缓存
| Redis key | 内容 | TTL |
|---|---|---|
ALL_REGIONS | 标准地区树 | 30 天 |
ALL_REGIONS_FOR_ADPTING | 为老 JS 调整 type/parent 的列表 | 30 天 |
sequenceDiagram
participant U as 页面
participant R as RegionSer
participant C as Redis
participant DB as t_sys_area_address
U->>R: 获取地区树
R->>C: GET key
alt 命中
C-->>R: JSON
else 未命中
R->>DB: getList
R->>R: list_to_tree/兼容转换
R->>C: SET 30天
end
R-->>U: 地区数据
地区主表更新后必须让所有相关缓存失效。只删旧 JS 适配 key,标准 ALL_REGIONS 仍可能继续返回旧数据。
8. 省市区校准任务
tasks/AreaCalibrate::calibrate():
- 读取本地所有地区,以 ID 建 map。
- 调 OPS
/opscenter/orgmgr/dgj/region/listAll。 - 递归比较新增、修改和本地多余记录。
- 事务删除、批量更新、批量新增。
- 删除
ALL_REGIONS_FOR_ADPTING缓存。
flowchart TD
A["OPS 权威地区树"] --> C["递归比较"]
B["DGJ 本地地区表"] --> C
C --> D["insert"]
C --> E["update"]
C --> F["delete"]
D --> T["事务保存"]
E --> T
F --> T
T --> R["清理地区缓存"]
任务对名称包含北京/天津/上海/重庆的节点做过滤并避免删除,说明直辖市历史数据有兼容包袱。执行前必须导出删除清单。
9. 服务站与发布区域
System\RegionSer::getStationEnv(sid):
- 从主账号取服务站
province。 - 在
t_sys_region找省份对应区域类型。 - 无区域时返回默认环境
1。 - 有 Redis 灰度配置时,可按区域或 sid 返回灰度环境
9。
flowchart TD
S["sid"] --> P["主账号 province"]
P --> R["t_sys_region 区域类型"]
R --> G{"灰度配置命中?"}
G -->|"是"| E9["环境 9"]
G -->|"否"| E["区域默认环境"]
区域在这里不仅是报表维度,也可能影响代码/配置灰度。
10. 客户/供应商地址
t_bs_contact_addr 同时服务客户和供应商地址场景,典型字段:
| 字段 | 含义 |
|---|---|
contactId | 客户/供应商 ID |
contactName、mobile | 收货联系人 |
provinceCode/cityCode/countyCode | 行政编码 |
province/city/county | 名称快照 |
address | 详细地址 |
longitude/latitude | 地图坐标 |
isDefault | 默认地址 |
flowchart LR
C["客户/供应商"] --> A1["地址 1 默认"]
C --> A2["地址 2"]
C --> A3["地址 3"]
10.1 地址更新与 SAAS 同步
ThirdDeliverySer::updateContactAddr() 在本地事务中新增/更新地址,提交后再调用 ContactSer::syncContactToSaas()。
sequenceDiagram
participant U as 用户
participant D as DGJ
participant DB as 地址表
participant S as SAAS
U->>D: 更新省市区/详细地址/经纬度
D->>DB: 本地事务保存
DB-->>D: commit
D->>S: syncContactToSaas
本地提交后 SAAS 同步失败会形成短期或长期不一致,需要独立补偿。
11. 地址编码和名称一致性
合法地址应满足:
provinceCode 对应 province
cityCode 的 parent 是 provinceCode,且名称对应 city
countyCode 的 parent 是 cityCode,且名称对应 county
经纬度落在地址附近
flowchart TD
A["地址记录"] --> B{"编码层级正确?"}
B -->|"否"| X["区域匹配错误"]
B -->|"是"| C{"编码与名称一致?"}
C -->|"否"| Y["展示/规则冲突"]
C -->|"是"| D{"经纬度合理?"}
D -->|"否"| Z["距离/配送错误"]
D -->|"是"| OK["可执行地址"]
不要只拼接字符串判断地址正确。
12. 采购订单地址快照
PoOrderSer::checkContactAddr(contactId, orderId):
- 查联系人地址列表。
- 没地址抛“请完善客户地址”。
- 取第一条地址,构造采购订单地址结构。
- 若传
orderId且已有快照,则保留快照记录 ID 和po_id。
sequenceDiagram
participant U as 采购下单
participant P as PoOrderSer
participant CA as contact_addr
participant OA as po_order_address
U->>P: contactId
P->>CA: 查询当前地址
alt 无地址
P-->>U: 请完善客户地址
else 有地址
P->>P: 复制编码、名称、联系人、详细地址
P->>OA: 保存订单地址快照
end
订单后续展示应调用 getContactOrderAddr(orderId) 优先读 t_scm_po_order_address,不应实时回查客户地址。
13. 仓库地址
StorageSer 管理的仓库地址字段:
address
province_code / city_code / county_code
longitude / latitude
phone
isDefault
13.1 快准仓创建
外部仓库资料映射:
| 外部字段 | DGJ 字段 |
|---|---|
code | locationNo |
addr | address |
provinceCode | province_code |
cityCode | city_code |
regionCode | county_code |
localX | longitude |
localY | latitude |
创建快准仓时同时创建默认货位和不良品货位。
flowchart TD
A["外部仓库资料"] --> B["t_bs_storage"]
B --> C["默认货位"]
B --> D["不良品货位"]
B --> E["门店/位置同步"]
13.2 默认仓唯一性
新建或更新仓库为默认时,事务内先把同站其他可采购快准仓 isDefault=0,再设置目标仓。
数据库若没有唯一约束,并发更新仍可能出现多个默认仓。
14. 仓库地址地理编码
StorageSer::updateLocation() 会对仓库地址调用地图能力,补齐/更新位置。代码中明确有“地址解析失败”待处理分支。
sequenceDiagram
participant U as 仓库维护
participant S as StorageSer
participant DB as t_bs_storage
participant M as 高德地图
U->>S: 省市区 + address
S->>DB: 保存地址字段
S->>M: 地理编码
alt 成功
M-->>S: longitude/latitude
S->>DB: 更新坐标
else 失败
M-->>S: 空/异常
S->>S: 记录或保留旧坐标
end
地址文本更新而坐标未更新,会导致 TMS 发货点仍在旧位置。
15. 快准退货地址
两层数据:
| 数据 | 作用 |
|---|---|
t_bs_area_return_address | 省份编码 -> 地址 ID |
kzcp_address | 地址 ID -> 完整地址内容 |
Redis:
| key | 内容 |
|---|---|
HASH_AREA_RETURN_ADDRESS | province => address_id |
HASH_KZ_ADDRESS{id} | 单个地址全部字段 |
getReturnAddr(province) 找不到省份映射时固定回退地址 ID 1。
flowchart TD
P["服务站 province"] --> H["HASH_AREA_RETURN_ADDRESS"]
H --> I{"有 address_id?"}
I -->|"否"| D["默认 id=1"]
I -->|"是"| A["目标 address_id"]
D --> K["HASH_KZ_ADDRESS{id}"]
A --> K
K --> R["采购退货寄件地址"]
静默回退可能把未配置省份的退货寄到错误仓,应在业务页面标记“使用默认退货地址”。
16. 退货地址缓存刷新
php index.php tasks/KzAddress flushCache:
- 查询全部
kzcp_address。 - 每个地址写独立 hash。
- 查询省份映射。
- 批量写省份 hash。
当前 hMSet 不会自动删除已从数据库移除的旧 field,完整刷新前应考虑先清 key 或比较差异。
17. 配送时效来源
ContactStoreRelSer::getDeliveryTimeLimit(contactId, storeId) 的优先级:
客户 + 门店关系的 time_limit
-> 客户配置 delivery_time
-> 0
统一返回分钟。
flowchart TD
A["contactId + storeId"] --> B{"存在启用的门店关系时效?"}
B -->|"是"| C["读取 time_limit/unit"]
B -->|"否"| D{"客户默认时效存在?"}
D -->|"是"| E["读取 delivery_time/unit"]
D -->|"否"| Z["返回 0"]
C --> F{"单位是小时?"}
E --> F
F -->|"是"| G["*60 并四舍五入"]
F -->|"否"| H["按分钟四舍五入"]
17.1 输入约束
| 单位 | 校验 |
|---|---|
| 分钟 | 1~60 正整数 |
| 小时 | 1~60,允许一位小数的业务描述 |
时效值为空或非数字返回 0。0 是“未配置”,不是承诺 0 分钟送达。
18. 时效展示与机器人报价
机器人询价会读取客户/门店关系和客户配置,再格式化时效文本。显示开关还受客户配置:
show_order_delivery_warehouse。show_delivery_time_estimate。delivery_time、delivery_time_unit。
flowchart LR
R["客户门店关系"] --> T["配送分钟数"]
C["客户默认配置"] --> T
S["展示开关"] --> UI["报价/订单显示"]
T --> UI
查询到 0 时,前端应展示“暂无时效”而非“预计 0 分钟”。
19. 自配送单号和状态
19.1 单号
| 前缀 | 类型 |
|---|---|
KZPS | TMS 商家自配送单 |
SPS | 三方配送单 |
19.2 TMS 自配送状态
| 状态 | 中文 |
|---|---|
10 | 待揽收 |
20 | 配送中 |
90 | 已送达 |
99 | 已取消 |
stateDiagram-v2
[*] --> WaitPickup: 10 创建
WaitPickup --> Delivering: 20 接单/取件
Delivering --> Delivered: 90 签收
WaitPickup --> Cancelled: 99 取消
Delivering --> Cancelled: 99 中止(取决于TMS规则)
20. 自配送创建请求
TransportSer::transportCreate() 的关键结构:
{
"sourceSystem": "DGJ",
"senceCode": "tramgr_create_service_shipment",
"receiptDuration": 30,
"sid": "<sid>",
"senderCode": "<门店ID>",
"senderName": "<门店名称>",
"senderPerson": "<发货人>",
"senderPersonMobile": "<发货电话>",
"senderLocation": {
"senderLocalType": "GAODE",
"senderLocalX": 120.1,
"senderLocalY": 30.2
},
"receiverCode": "<客户ID>",
"receiverName": "<客户名称>",
"receiverPerson": "<收货人>",
"receiverPersonMobile": "<收货电话>",
"receiverProvinceCode": "330000",
"receiverCityCode": "330100",
"receiverRegionCode": "330110",
"receiverAddress": "<详细地址>",
"receiverLocation": {
"receiverLocalType": "GAODE",
"receiverLocalX": 120.09,
"receiverLocalY": 30.34
},
"outOrderNo": "<销售订单号>",
"outShipmentNo": "<销售出库单号>",
"detailList": [
{"itemCode": "<invId>", "itemName": "<商品名>", "unitName": "个", "qty": 1}
]
}
代码注释样例中经纬度键名和常见 X/Y 语义可能存在混淆,联调必须用真实地图点验证,不要仅凭字段名判断。
21. 销售出库转自配送
SelfDeliverySer::create():
- 校验销售出库单存在且可配送。
- 获取发货门店地址和坐标。
- 获取客户默认地址和坐标。
- 获取客户与门店配送时效,转为
receiptDuration。 - 获取销售出库商品明细。
- 构造 TMS 请求。
- 创建成功后保存配送单号/更新出库状态。
sequenceDiagram
participant U as 服务站
participant S as SelfDeliverySer
participant DB as DGJ
participant T as TMS
U->>S: 对出库单创建自配送
S->>DB: 出库单/门店/客户地址/时效/商品
S->>S: 构造 sender/receiver/detail
S->>T: transportCreate
alt TMS 成功
T-->>S: KZPS 配送单号
S->>DB: 更新出库单配送字段
else TMS 异常
T-->>S: 异常
S-->>U: 创建失败/空结果
end
TransportSer::transportCreate() 捕获异常后返回空字符串,调用方必须显式判断,不能把空配送号当成功。
22. 配送员 App 接口字典
appapis.php 的逻辑 API code:
| API code | 服务 | 作用 |
|---|---|---|
transportStore | app.transport.store | 配送门店切换 |
transportList | app.transport.transportList | 待揽收/配送中/已完成列表 |
transportCancelList | app.transport.transportCancelList | 中止配送列表 |
transportCourierStatusCount | 状态统计 | 配送员各状态数量 |
transportTaking | 接单 | 绑定配送员并推进出库状态 |
transportArrive | 到达 | 上报到达收货点 |
transportReceipt | 签收 | 完成配送 |
transportCancel | 取消 | 中止或更换配送员 |
courierReportRegister | 设备注册 | 绑定配送设备 |
courierReport | 轨迹上报 | 上传经纬度轨迹 |
courierConfig | 骑手配置 | 获取配送参数 |
updateStoreLocation | 更新门店位置 | 补发货点坐标 |
updateContactLocation | 更新客户位置 | 补收货点坐标 |
23. 配送列表请求
POST /app-api
Content-Type: application/json
{
"apiCode": "transportList",
"sid": 1001,
"senderCode": "<门店ID>",
"courierCode": "<配送员ID>",
"transportStatus": "20",
"offset": 1,
"limit": 10
}
代码注释明确:前端 offset 实际传的是页码,不是 SQL offset。
flowchart TD
A["transportStatus"] --> B{"10/20/90?"}
B -->|"10"| C["待揽收列表,可筛本人"]
B -->|"20"| D["配送中列表"]
B -->|"90"| E["已完成列表"]
C --> F["补门店/客户本地地址"]
D --> F
E --> F
TMS 距离字段以米返回,DGJ 列表格式化为一位小数 km 字符串。
24. 接单、到达、签收
24.1 接单
transportTaking():
- 校验当前账号是有效配送员工。
- 用员工资料覆盖
courierName/courierMobile。 - 查销售出库单。
- 更新出库单
delieverId。 - 更新销售出库状态为配送中。
- 调 TMS 接单。
sequenceDiagram
participant A as 配送员App
participant D as DGJ
participant T as TMS
A->>D: transportTaking
D->>D: 校验员工并更新出库配送员
D->>D: 出库单状态=配送中
D->>T: 接单
T-->>D: 结果
D-->>A: 接单结果
本地状态更新和 TMS 接单不是同一个数据库事务,TMS 失败时可能需要回退本地配送员/状态。
24.2 到达和签收
transportArrive()透传 TMS 到达。transportReceipt()透传签收,并由相关销售服务更新配送状态/签收信息。- 销售详情通过
getTransportGetoutshipmentnos()聚合 TMS 状态、描述和签收图。
25. 自配送取消和更换配送员
transportCancel():
- 必须有
sid + outShipmentNo。 - 验证销售出库单存在。
- 校验操作员工身份。
- 调 TMS 取消。
- 若请求带
changeCourier,把新 admin uid 映射为 staff id。 - 更新销售出库单
delieverId。
flowchart TD
A["取消请求"] --> B["校验出库单/操作人"]
B --> C["TMS transportCancel"]
C --> D{"是否更换配送员?"}
D -->|"是"| E["admin uid -> staff id"]
D -->|"否"| F["delieverId=0"]
E --> G["更新出库单"]
F --> G
删除销售出库单时,transportDelete() 只对 delivery_no 以 KZPS 开头的自配送单调用 TMS 删除。
26. 三方配送能力
当前枚举支持:
| code | 名称 |
|---|---|
meituan | 美团 |
imdada | 达达 |
三方状态:
| 状态 | 中文 |
|---|---|
10 | 待接单 |
20 | 待取货 |
21 | 骑手到店 |
30 | 配送中 |
90 | 已完成 |
98 | 已退回 |
99 | 已取消 |
stateDiagram-v2
[*] --> WaitAccept: 10
WaitAccept --> WaitPickup: 20
WaitPickup --> AtStore: 21
AtStore --> Delivering: 30
Delivering --> Completed: 90
WaitAccept --> Cancelled: 99
WaitPickup --> Cancelled: 99
Delivering --> Callback: 98 异常退回
退回原因:00 无、10 收货方拒收、20 妥投异常;退回状态:00 无、10 返回中、20 已返回。
27. 三方配送询价
ThirdDeliverySer::inquiry() 需要:
- 销售出库单。
- 发货门店完整地址、联系人、经纬度。
- 客户收货地址、联系人、经纬度。
- 商品件数/重量等询价信息。
sequenceDiagram
participant U as 服务站
participant D as ThirdDeliverySer
participant DB as 地址/出库单
participant L as 三方物流中心
U->>D: 询价(saInvoiceId)
D->>DB: 门店和客户地址
alt 地址或坐标缺失
D-->>U: 先完善地址
else 完整
D->>L: pickup + receiver + 商品
L-->>D: inquiryNo + 运力报价
D-->>U: 美团/达达可选运力
end
询价结果的 inquiryNo 是创建三方配送单的重要幂等/关联编号,不应跨地址或跨出库单复用。
28. 创建三方配送
逻辑请求示例:
POST /sale/thirdDelivery/create
Content-Type: application/json
{
"sid": 1001,
"saInvoiceId": 12345,
"inquiryNo": "<询价单号>",
"capacityIdList": "<选择的运力ID>",
"remark": "尽快配送",
"user_id": 100,
"user_name": "操作人"
}
28.1 创建时序
sequenceDiagram
participant U as 服务站
participant D as ThirdDeliverySer
participant L as 三方物流中心
participant DB as DGJ
U->>D: create
D->>DB: 查销售出库单
D->>D: 若已有 KZPS 则拒绝切换
D->>L: inquiryNo + capacityId
L-->>D: SPS deliveryNo + 渠道单号
D->>DB: 事务写三方配送主单
D->>DB: 销售出库状态=配送中
D->>DB: 写出库-配送关系
D-->>U: deliveryNo
远程三方下单发生在本地事务之前。远程成功而本地写库失败时,三方已产生骑手单,本地却没有关系,必须有按 deliveryNo/inquiryNo 补建机制。
29. 三方配送本地快照
t_scm_third_party_delivery_order 保存:
| 字段 | 说明 |
|---|---|
delivery_order_no | DGJ/物流中心配送号 |
delivery_channel | 美团/达达 code |
delivery_channel_name | 展示名称 |
third_party_order_no | 渠道原始运单号 |
pickup_address/contact/phone | 发货快照 |
receive_address/contact/phone | 收货快照 |
inquiry_no | 询价关联 |
delivery_status | 本地配送状态 |
t_sa_invoice_delivery_relation 允许一张配送单关联一个或多个销售出库单,排查时不能只查主单。
30. 三方配送取消
ThirdDeliverySer::cancel():
- 调三方物流中心取消。
- 本地配送状态设
99。 - 查所有关联销售出库单。
- 将出库单状态恢复为已出库。
sequenceDiagram
participant U as 用户
participant D as DGJ
participant L as 三方物流
participant DB as 本地表
U->>D: cancel(deliveryNo)
D->>L: cancel
L-->>D: 取消结果
D->>DB: delivery_status=99
D->>DB: 关联出库单恢复已出库
远程取消与本地更新没有包在同一事务中。远程失败、本地是否仍继续更新要看 Provider 抛错契约;必须联调验证。
31. 撤销销售出库前的配送检查
outCancelCheck():
返回 allowCancelStatus | 含义 |
|---|---|
1 | 可取消,无违约金 |
2 | 可取消但有进行中配送/违约提示,应先取消配送 |
3 | 配送已完成/已取消/已退回或渠道不允许,提示仍要撤销 |
flowchart TD
A["撤销销售出库"] --> B{"有三方配送单?"}
B -->|"否"| OK["按普通出库撤销"]
B -->|"是"| C{"本地终态?"}
C -->|"完成/取消/退回"| W["强提示状态3"]
C -->|"进行中"| D["调用三方 cancelCheck"]
D --> E["状态1/2/3"]
出库撤销和配送取消是两个业务动作,不能只撤销库存单而留下骑手继续配送。
32. 三方异常退回
配送异常可能进入 98 已退回,服务站还需执行 backConfirm():
- 查本地配送单。
- 获取操作人。
- 调三方
callbackConfirm确认退回收货。
flowchart LR
D["配送中"] --> E["拒收/妥投异常"]
E --> R["返回中"]
R --> B["已退回 98"]
B --> C["服务站 backConfirm"]
配送退回不等于销售退货入库。若商品重新入库,还应有库存业务单据支撑。
33. 采购急件承运商时效
PoOrderSer::getSpeedPick() 从供给库存服务返回仓库能力:
| 字段 | 含义 |
|---|---|
warehouseAddress | 供给仓地址 |
warehousePerson/Mobile | 仓库联系人 |
warehouseCode/Name | 仓库编码与名称 |
lastInterceptFlag/Time | 最晚截单标志与时间 |
carrierCurrent | 当前承运信息 |
carrierHistory | 历史承运信息 |
carriers[].collectTimes | 各承运商揽收时间 |
代码将所有 collectTimes 合并去重为页面 carriers。
flowchart TD
A["急件商品+供应商"] --> B["KzInventory getSpeedInventory"]
B --> C["供给仓库存"]
B --> D["仓库地址/截单时间"]
B --> E["承运商揽收时间"]
C --> F["是否可下单"]
D --> F
E --> F
这里的时效是供给仓/承运商能力,不是客户门店配送时效,两者不能混用。
34. 采购订单截止时间
getOrderDeadLine():
- 从订单服务取
lastFullEndTimeStr、lastEndTimeStr、bizFlag。 - 从本地
poDeadLineModel取poEndTime。 - 原 6 小时 Redis 缓存代码已注释,当前可能每次远程查询。
flowchart LR
O["订单服务 speedStatus"] --> D["最后截单时间"]
L["本地 po_dead_line"] --> D
D --> UI["采购页面展示"]
远程与本地截止时间不一致时,要明确页面字段的优先级和业务含义。
35. 采购退货物流
采购退货通常使用服务站省份选择快准退货地址,并记录:
- 退货单号。
- 物流公司/承运商。
- 运单号
express_no。 - 寄件地址与收件地址。
- 物流节点。
WMS 物流详情可通过 LogisticsProvider::getLogisticsDetail(expressNo) 查询,返回节点和承运商品牌。
sequenceDiagram
participant U as 服务站
participant R as ReturnAddressSer
participant P as 采购退货
participant W as WMS物流
U->>R: province
R-->>U: 快准退货地址
U->>P: 提交退货物流/运单号
P->>W: 查询运单详情
W-->>P: carrier + nodes
36. 区域对价格和供货的影响
省市区不仅用于地址:
| 业务 | 使用区域 |
|---|---|
| 区域指导价 | 方案关联省份、服务站省份 |
| 快速报价 | 服务站 province/eparchy |
| 首配方案 | 市优先、省次之、全国兜底 |
| 最低采购规则 | 城市 eparchy |
| 活动范围 | 运营省份关系 |
| 商品供货区域 | Item Center 供应区域变更 |
| 发布灰度 | 省份 -> t_sys_region |
flowchart TD
P["服务站 province/city"] --> G["指导价方案"]
P --> F["供货/首配方案"]
P --> M["最低采购规则"]
P --> A["活动范围"]
P --> R["发布区域"]
修改服务站省市区属于跨域高风险变更,不是单纯展示资料修改。
37. 地址快照原则
| 场景 | 应使用当前地址 | 应使用历史快照 |
|---|---|---|
| 新建订单选地址 | 是 | 否 |
| 历史订单详情 | 否 | 是 |
| 重新发起配送 | 需用户确认 | 默认用原配送快照 |
| 复制订单 | 重新选择/确认 | 不能盲复制失效地址 |
| 售后退货 | 当前退货配置 | 原销售收货地址仅用于追溯 |
flowchart TD
A["业务动作"] --> B{"新业务还是历史展示?"}
B -->|"新业务"| C["读取当前主地址并确认"]
B -->|"历史展示"| D["读取订单/配送快照"]
38. 常用只读 SQL
38.1 查地区层级
SELECT d.id AS county_code, d.name AS county,
c.id AS city_code, c.name AS city,
p.id AS province_code, p.name AS province
FROM t_sys_area_address d
JOIN t_sys_area_address c ON c.id = d.parent_id
JOIN t_sys_area_address p ON p.id = c.parent_id
WHERE d.id = <countyCode>;
38.2 查客户地址
SELECT id, sid, contactId, contactName, mobile,
provinceCode, province, cityCode, city,
countyCode, county, address,
longitude, latitude, isDefault
FROM t_bs_contact_addr
WHERE sid = <sid>
AND contactId = <contactId>
ORDER BY isDefault DESC, id;
38.3 查采购订单地址快照
SELECT id, po_id, sid, contact_id, name, mobile,
province_code, province, city_code, city,
county_code, county, address, deferred_day_date
FROM t_scm_po_order_address
WHERE po_id = <采购订单ID>;
38.4 查仓库地址
SELECT id, sid, locationNo, name, type, isDefault,
province_code, city_code, county_code,
address, longitude, latitude, phone
FROM t_bs_storage
WHERE sid = <sid>
AND isDelete = 0
ORDER BY type, isDefault DESC, id;
38.5 查省份退货地址映射
SELECT r.province, r.address_id,
a.id, a.name, a.province AS address_province, a.addr
FROM t_bs_area_return_address r
LEFT JOIN kzcp_address a ON a.id = r.address_id
WHERE r.province = '<provinceCode>';
38.6 查客户门店时效
SELECT id, sid, contact_id, store_id, status,
time_limit, time_limit_unit, creator, editor
FROM t_bs_contact_store_rel
WHERE sid = <sid>
AND contact_id = <contactId>
AND store_id = <storeId>;
38.7 查三方配送和出库关系
SELECT d.delivery_order_no, d.delivery_channel,
d.third_party_order_no, d.delivery_status,
d.pickup_address, d.receive_address, d.inquiry_no,
r.sa_invoice_no, r.sa_invoice_id, r.store_id
FROM t_scm_third_party_delivery_order d
LEFT JOIN t_sa_invoice_delivery_relation r
ON r.delivery_no = d.delivery_order_no
WHERE d.sid = <sid>
AND (d.delivery_order_no = '<配送单号>'
OR r.sa_invoice_no = '<销售出库单号>');
38.8 查采购退货运单
SELECT *
FROM t_scm_po_return_order_logistics
WHERE sid = <sid>
AND (order_no = '<采购退货单号>' OR express_no = '<运单号>')
ORDER BY id DESC;
39. 常用代码检索
rg -n "SYS_AREA_ADDRESS|ALL_REGIONS|AreaCalibrate" application
rg -n "provinceCode|cityCode|countyCode|longitude|latitude" application/Services application/service
rg -n "PoOrderAddress|checkContactAddr|getContactOrderAddr" application
rg -n "ReturnAddressSer|HASH_AREA_RETURN_ADDRESS|HASH_KZ_ADDRESS" application
rg -n "getDeliveryTimeLimit|time_limit_unit|delivery_time_unit" application
rg -n "TRANSPORT_STATUS_|transport(Create|Taking|Arrive|Receipt|Cancel)" application
rg -n "ThirdDeliveryEnums|inquiryNo|delivery_order_no|delivery_status" application
rg -n "carrierCurrent|carrierHistory|collectTimes|lastInterceptTime" application
40. 排查 SOP:地址显示错误
flowchart TD
A["地址显示错误"] --> B{"历史单还是当前资料?"}
B -->|"历史单"| C["查业务地址快照"]
B -->|"当前资料"| D["查 contact/storage 主表"]
C --> E["核对编码与名称"]
D --> E
E --> F{"地区缓存是否旧?"}
F -->|"是"| G["刷新相关 Redis key"]
F -->|"否"| H["核对前端字段映射"]
检查:
- 先确认页面应读主地址还是快照。
- 对比 code 与 name。
- 查直辖市中间层。
- 查 30 天 Redis 缓存。
- 查是否主表更新但 SAAS/OPS 未同步。
41. 排查 SOP:地图位置不对
- 查地址文本是否最新。
- 查经纬度是否为空、0 或仍是旧值。
- 确认坐标系/地图类型
GAODE或BAIDU。 - 确认 X 是经度、Y 是纬度的外部契约。
- 用地图打开坐标,核对真实位置。
- 查
updateStoreLocation/updateContactLocation是否成功。 - 查 TMS 配送单保存的是创建时坐标还是实时坐标。
flowchart LR
A["文本地址"] --> G["地理编码"]
G --> L["经纬度"]
L --> T["TMS配送快照"]
T --> M["地图展示/距离"]
42. 排查 SOP:自配送单未生成
| 检查项 | 可能问题 |
|---|---|
| 销售出库单 | 不存在、状态不可配送 |
| 发货门店 | 地址/联系人/坐标缺失 |
| 客户地址 | 默认地址/手机号/坐标缺失 |
| 时效 | 0 本身不一定阻断,但需确认 TMS 契约 |
| 商品明细 | 空、数量异常 |
| TMS Provider | 超时、返回空、业务失败 |
| 本地保存 | TMS 成功但更新配送号失败 |
TransportSer::transportCreate() 吞异常并返回空,必须查“创建配送单失败”日志和请求中的 outOrderNo。
43. 排查 SOP:配送状态不同步
flowchart TD
A["状态不一致"] --> B["按 outShipmentNo 查 DGJ 出库"]
B --> C["按 delivery_no 查 TMS/三方"]
C --> D["查最后一次操作:接单/到达/签收/取消"]
D --> E{"远程成功?"}
E -->|"否"| F["重试外部动作"]
E -->|"是"| G{"本地更新成功?"}
G -->|"否"| H["补本地状态/关系"]
G -->|"是"| I["查页面缓存/查询映射"]
同时核对销售出库 billStatus 和配送系统 transportStatus,它们不是同一套枚举。
44. 排查 SOP:三方配送远程有单、本地无单
- 用
inquiryNo查询价。 - 用三方返回
deliveryNo查物流中心。 - 查
t_scm_third_party_delivery_order。 - 查
t_sa_invoice_delivery_relation。 - 查创建时本地事务异常。
- 确认销售出库状态是否被改为配送中。
- 使用补建关系工具前,确认不会再次向三方下单。
这类问题的恢复目标是补本地事实,不是重放远程 create。
45. 排查 SOP:采购退货地址不对
- 查服务站主账号
province/areaCode使用了哪个字段。 - 查
HASH_AREA_RETURN_ADDRESS是否有省份映射。 - 无映射时确认是否静默用了 ID 1。
- 查
HASH_KZ_ADDRESS{id}是否是旧内容。 - 对比数据库两张地址表。
- 执行缓存刷新前,确认旧 hash field 是否需要清理。
- 查采购退货单/物流记录保存的地址快照。
46. 已识别代码风险
| 编号 | 风险 | 后果 | 建议 |
|---|---|---|---|
| R46-01 | 名称反查缺区时用 maxId+1 自动造编码 | 非官方编码污染 | 改为权威编码映射/待审核区 |
| R46-02 | 地区缓存 30 天 | 主表更新后长期旧数据 | 统一版本化失效 |
| R46-03 | 校准任务只显式删适配 key | 标准树仍可能旧 | 清全部地区缓存 |
| R46-04 | 校准任务批量删除本地多余地区 | 历史地址编码失去名称 | 软下线并保留历史 |
| R46-05 | 直辖市过滤逻辑特殊 | 新旧数据层级不一致 | 契约测试四直辖市 |
| R46-06 | 客户地址本地提交后再同步 SAAS | 跨系统不一致 | outbox/重试状态 |
| R46-07 | 下单取地址列表第一条 | 不一定是真正默认地址 | 显式按 isDefault |
| R46-08 | 地址文本更新后地理编码可能失败 | 旧坐标继续配送 | 坐标状态和人工确认 |
| R46-09 | 默认仓只靠事务更新 | 并发可能多个默认仓 | 唯一约束/条件更新 |
| R46-10 | 退货省份无配置静默回退 ID 1 | 退货寄错仓 | 页面告警并阻断高风险场景 |
| R46-11 | 地址缓存 hMSet 不删除旧 field | 删除配置仍残留 | 刷新前清 key 或差异删除 |
| R46-12 | 时效 0 同时表示缺失 | UI 可能显示 0 分钟 | 返回 configured 标识 |
| R46-13 | 时间单位由整数枚举隐式表达 | 小时/分钟误用 | 输出标准分钟和原值 |
| R46-14 | TMS 创建异常被吞并返回空 | 上层误判成功 | 结构化错误返回 |
| R46-15 | 接单先改本地状态再调 TMS | TMS 失败本地已配送中 | 调整顺序或补偿 |
| R46-16 | 三方下单远程成功后才开本地事务 | 远程孤儿单 | 幂等查询与补建 |
| R46-17 | 三方取消远程与本地非原子 | 状态分叉 | 状态机 + 补偿任务 |
| R46-18 | 配送退回不自动等于库存退货 | 实物回站但库存未增加 | 明确退回收货单据 |
| R46-19 | App offset 实际是页码 | 分页调用方误解 | 字段改名/兼容层 |
| R46-20 | 坐标 X/Y 字段语义易混 | 地图点反转 | API 契约和真实点测试 |
| R46-21 | 采购承运商时效与门店配送时效同名 | 产品/研发混用 | 明确命名和独立模型 |
| R46-22 | 服务站区域修改影响价格/供货/灰度 | 跨域回归遗漏 | 区域变更事件清单 |
47. 监控建议
| 指标 | 目的 |
|---|---|
| 地址编码名称不一致数 | 主数据质量 |
| 空/零经纬度地址数 | 配送可执行性 |
| 地图解析失败率 | 地理编码稳定性 |
| 默认地址/默认仓重复数 | 唯一性 |
| 退货地址默认 ID 1 回退次数 | 省份配置缺失 |
| TMS 创建返回空次数 | 自配送失败 |
| TMS 与销售出库状态差异 | 状态同步 |
| 三方远程有单本地无关系数 | 孤儿单 |
| 配送时效未配置客户数 | 履约承诺完整性 |
| 地区校准新增/修改/删除数量 | 权威数据变更风险 |
48. 单元测试清单
48.1 地区与地址
- 普通省市区名称反查。
- 四个直辖市名称反查。
- 不存在区县不应静默生成错误官方编码。
- 地区校准后两个缓存都失效。
- 客户默认地址明确排序。
- 采购订单地址快照不随主地址变化。
- 仓库地址修改后坐标同步。
- 省份无退货地址时有明确提示。
48.2 时效
- 客户门店专属分钟时效优先。
- 客户门店专属小时时效正确乘 60。
- 无关系时回退客户配置。
- 两层都无配置返回“未配置”。
- 非数字、负数、超范围拒绝。
48.3 配送
- 自配送创建、接单、到达、签收、取消。
- TMS 创建失败不写空配送号。
- 接单 TMS 失败回退本地状态。
- 三方询价缺地址/坐标阻断。
- 三方创建远程成功、本地失败可补建。
- 三方取消和销售出库状态对称恢复。
- 异常退回确认不直接修改库存。
49. 集成回归场景
49.1 地址快照链
- 创建客户地址 A。
- 下采购单并保存地址快照 A。
- 将客户当前地址改为 B。
- 历史采购单仍显示 A。
- 新采购单显示 B。
- 验证 SAAS 同步。
49.2 自配送链
- 完善门店和客户地址/坐标。
- 设置客户门店时效。
- 销售出库创建 KZPS 配送单。
- 配送员 App 接单、轨迹、到达、签收。
- 验证销售详情状态和签收图。
49.3 三方配送链
- 查询美团/达达运力。
- 使用询价号创建 SPS 配送单。
- 验证本地主单和出库关系。
- 取消并验证出库恢复。
- 模拟拒收退回并执行回站确认。
50. 上线回归矩阵
| 修改模块 | 必回归 |
|---|---|
AreaSer/AreaCalibrate | 省市区、直辖市、缓存、历史地址 |
| 客户地址 | 采购、销售、配送、SAAS 同步 |
StorageSer | 仓库、默认仓、货位、地图坐标 |
| 退货地址 | 采购退货、各省映射、缓存刷新 |
ContactStoreRelSer | 门店绑定、分钟/小时时效、机器人展示 |
TransportSer | App 列表、接单、到达、签收、取消 |
ThirdDeliverySer | 询价、创建、取消、退回、出库撤销 |
| 采购承运商 | 急件、自提、截单、揽收时间 |
| 服务站区域 | 指导价、供货、首配、活动、灰度 |
51. 待环境确认项
t_sys_area_address是否允许物理删除,历史地址如何保留名称。- 地区校准生产调度频率、最近差异和负责人。
ALL_REGIONS是否由其他任务同步清理。- 地址名称反查自动新增区县是否仍有线上流量。
- 客户地址“第一条”是否由 Model 默认按
isDefault排序。 - 仓库地理编码失败时是否保留旧坐标或清空。
- TMS 的 X/Y 与 longitude/latitude 精确契约及坐标系。
- 自配送创建、接单、签收的下游幂等键。
- TMS 状态到
SaInvoiceEnums::billStatus的完整映射。 - 三方物流回调入口、签名、重试和本地状态更新任务。
- 三方远程孤儿单是否已有补偿扫描。
- 配送退回后的库存入库业务入口。
- 客户默认配送时效所在配置表的最终字段和单位枚举。
t_sys_carrier当前是否仍作为有效承运商时效源。- 急件承运商
collectTimes的时区、日期和截单规则。 - 退货默认地址 ID 1 对应哪个仓及适用省份。
- 服务站区域变更后需刷新哪些价格/供货/活动缓存。
52. 一页式地址与履约脉络
flowchart TD
A["OPS权威省市区"] --> B["DGJ地区表/Redis"]
B --> C["服务站/客户/仓库主地址"]
C --> D["新业务选择地址"]
D --> E["订单地址快照"]
C --> F["经纬度解析"]
E --> G{"履约方式"}
F --> G
G -->|"自配送"| H["TMS KZPS"]
G -->|"三方配送"| I["美团/达达 SPS"]
H --> J["接单/轨迹/到达/签收"]
I --> K["询价/下单/取消/退回"]
C --> L["客户门店时效"]
L --> H
C --> M["区域指导价/供货/首配/灰度"]
C --> N["按省份选择采购退货地址"]
53. 结论
地址问题要按三类事实分开:
当前资料事实:客户/仓库现在在哪里
业务快照事实:下单或发货当时约定送到哪里
配送执行事实:骑手实际从哪里取、送到哪里、何时签收
标准排查路径:
业务单号
-> 地址快照
-> 当前主地址
-> 省市区编码与名称
-> 经纬度和地图坐标系
-> 客户门店时效
-> KZPS/SPS 配送单
-> TMS/三方状态与轨迹
-> DGJ 销售出库状态
只修地址文本不修坐标,会继续送错位置;只修客户主地址不修业务快照,会篡改历史解释;只取消销售出库不取消配送单,会让库存和实物履约分离。所有变更必须同时评估主数据、业务快照和外部履约状态。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| 区域/地址维护 | basedata/Area/Region/DeliveryAddr | 区划码、文本、经纬度、联系人 | BaseData Controller | Area/Region/Storage Service | area/address ID | 主数据地址和坐标 |
| 地址校准 | tasks/AreaCalibrate/KzAddress | 地址 ID、批次、外部地理结果 | CLI task | Area/ReturnAddress Service | address ID | 补齐标准区划/坐标,不改历史快照 |
| 采购物流 | PoOrderSer | 收货地址、仓库、承运要求 | 采购入口 | Logistics Provider | PO + address snapshot | 采购地址快照和预计时效 |
| 销售配送 | Self/ThirdDelivery/App transport | 出库单、地址快照、坐标、配送方式 | Delivery Controller | Transport/ThirdDelivery Service | invoice+delivery ID | 创建配送并推进履约 |
日志证据矩阵
| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 | | --- | --- | --- | --- | --- | --- | --- | | 地址保存 | Controller/Area Service | request_id、address/area ID、sid | 文本、区划、坐标 commit | 区划失效、坐标缺失、联系人错误 | address ID 进入订单快照 | | 校准 | AreaCalibrate/KzAddress task | task batch、address ID、provider request ID | 标准码/坐标更新且计数闭合 | 误改历史快照、外部结果低置信 | address ID 比 before/after | | 时效/承运商 | Logistics/Transport Provider | PO/invoice、region code、external request ID | 返回可用承运商/时效或受理配送 | 超时、区域不支持、文本坐标矛盾 | external ID 进入回调 | | 配送回调 | Dispatch/Delivery Service | delivery/invoice、event、message ID | 状态推进且地址快照不变 | 外部取消本地未取消、送错坐标 | 单号回查配送关系/快照 |
环节数据变更台账
| 步骤 | 代码位置 | 事务 | 读取事实 | 写入表/缓存/MQ | 字段或数量变化 | 回查证据 |
|---|---|---|---|---|---|---|
| 维护主地址 | Area/Region/DeliveryAddr Service | 主数据事务 | 原文本/区划/坐标 | BS_AREA/地址/仓库联系人表 | text/region/lat/lng old -> new | address ID、操作人、更新时间 |
| 订单快照 | PoOrderSer/SaOrder Service | 订单事务 | 下单时主地址 | SCM_PO_ORDER_ADDRESS/销售地址快照 | snapshot insert;后续主地址改变不回写历史 | order+snapshot 内容 |
| 计算时效 | Logistics Provider | 外部只读边界 | 快照区划/坐标、商品仓库 | 外部请求/本地估时字段 | 预计承运商/时效 null -> value | request ID、输入快照、返回码 |
| 创建配送 | Transport/ThirdDelivery Service | 本地+外部非原子 | 出库、地址快照、配送方式 | delivery/关系表、外部 TMS | pending + external ID;不修改库存事实 | invoice+delivery IDs |
| 校准/取消 | Task/Delivery cancel | 受控事务 | 主数据或外部履约最终态 | 主地址/配送状态/MQ | 只校准主数据;取消状态单向推进并同步外部 | batch/message、历史快照 diff=0 |
子模块追踪:region-tree 行政区划树与缓存
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 区划查询 | 页面按父级加载省市区树 | parent/code、level、version | application/controllers/basedata/Region.php -> application/Services/BaseData/RegionSer.php | 区划主表、层级、有效状态和缓存 | 查询只读;cache miss 事务外 none -> tree snapshot | request ID + parent/code + DB/cache version | 空缓存与无子级区分;错误树只清目标版本缓存 |
| 维护刷新 | 区划数据/状态更新 | region ID/code、parent、status | application/Services/System/RegionSer.php | 原节点、父链、子节点和引用 | 主数据本地事务 name/parent/status old -> new;commit 后清树缓存 | request ID + region + changed fields | 循环父链/有引用删除零写入;历史订单快照不回写 |
子模块追踪:region-calibrate 省市区校准任务
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| dry-run | 扫描文本与区划码不一致主地址 | batch、address IDs、scope | application/controllers/tasks/AreaCalibrate.php | 地址文本、现区划、标准树和置信结果 | 查询只读;输出可校准/歧义/失败清单 | task + batch + counts + address IDs | 歧义/低置信不自动写;先小批人工抽样 |
| 校准写入 | 确认主地址区划 | batch、address ID、before hash | application/Services/Area/AreaSer.php | 当前仍等于 baseline、标准 region IDs | 每地址本地事务 region/text old -> calibrated | batch + address + before/after + rows | 只改主数据;订单历史快照 diff 必须 0,重跑 0 变化 |
子模块追踪:contact-address 客户、供应商与服务站地址
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 保存地址 | 基础资料新增/编辑联系人地址 | owner type/ID、address ID、region/text/coordinates | application/controllers/basedata/Area.php -> application/Services/Area/AreaSer.php | 所有权、区划、重复地址、默认项和权限 | 地址本地事务 insert/update,默认关系 old -> new | request ID + owner/address + changed fields | 非法区划/越权零写入;敏感地址日志摘要化 |
| 仓库关联 | 联系人/服务站地址关联仓库 | contact/store/storage IDs | application/Services/Storage/ContactStoreRelSer.php | 主地址、仓库、原关系和站点范围 | 关系本地事务 old set -> new | request ID + relation IDs + count | 全量替换失败回滚;历史订单继续用原快照 |
子模块追踪:order-address-snapshot 采购销售订单地址快照
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 创建快照 | 采购/销售下单 | order/source billNo、address ID/version | application/Services/PoOrders/PoOrderSer.php -> application/models/orders/PoOrderAddressModel.php | 下单时主地址、联系人、区划和坐标 | 订单本地事务 insert immutable snapshot none -> order address | request ID + order/address snapshot ID | 主地址无效整单拒绝/按规则;后续主地址改不回写 |
| 历史回查 | 订单地址与当前资料不同 | order/snapshot/address IDs | application/models/orders/PoOrderAddressModel.php | 订单快照、当前主地址和创建时间 | 查询只读 不写 | order + snapshot/current diff | 差异通常合法历史;只有创建时错误才走订单领域更正流程 |
子模块追踪:storage-geocode 仓库地址与地理编码
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 保存仓库地址 | 仓库维护文本/区划 | storage ID、address/region | application/Services/Storage/StorageSer.php | 仓库、站点、原地址/坐标和引用 | 主数据本地事务 address/region old -> new | request ID + storage + changed fields | 在途配送不自动换历史地址;校验失败零写入 |
| 地理编码 | 地址变更后解析坐标 | storage/address ID、request ID | application/Services/Area/AreaSer.php | 最新文本、旧坐标和外部编码结果 | 外部查询事务外;确认后本地事务 lat/lng old -> new | request ID + storage/address + provider code | timeout 保留旧坐标/待校准,不写假坐标 |
子模块追踪:return-address 快准退货地址与缓存
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 维护退货地址 | 配置快准退货仓/联系人 | return address ID、scope/sid、fields | application/Services/KzAddress/ReturnAddressSer.php | 原配置、范围、区划和默认项 | 配置本地事务 old -> new;commit 后缓存失效 | request ID + address/scope + version | 有进行中退货继续用已保存快照;敏感字段脱敏 |
| 查询缓存 | 采购售后获取退货地址 | sid/order type、cache key | application/controllers/tasks/KzAddress.php | DB 有效配置、缓存版本和订单快照 | 查询只读;cache miss none -> snapshot | request/task ID + sid/key + address ID | DB 正确只补缓存;无配置明确失败,不猜默认地址 |
子模块追踪:delivery-timeliness 承运商与配送时效
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 时效询价 | 下单/配送选择承运商 | origin/destination snapshot、goods、carrier | application/Providers/OpsManager/Wms/LogisticsProvider.php | 两端区划/坐标、货物、承运商准入和服务时间 | 外部查询事务外;本地业务 DB 不写,返回 quote/ETA | request ID + address snapshot IDs + carrier/code | timeout/无服务明确不可选;ETA 不作为履约成功 |
| 时效回查 | 页面预计到货异常 | delivery/order、quote ID、ETA | application/Services/Transport/TransportSer.php | 询价输入快照、外部结果和实际运单 | 查询只读;创建配送时固化 ETA null -> value | delivery + quote + input/result | 地址主数据后变不重算历史;需改派时重新询价 |
子模块追踪:self-delivery-address 自配送创建与地址使用
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 创建自配送 | 销售出库选择商家配送 | invoiceNo、order address snapshot、rider/fee | application/controllers/sale/SelfDelivery.php -> application/Services/Transport/SelfDeliverySer.php | 出库态、不可变地址快照、配送范围和已有关系 | 配送本地事务 insert,保存 address snapshot/coordinates、status pending | request ID + invoice/delivery + snapshot ID | 不直接读取后来变更的客户地址;重复 invoice 幂等 |
| 签收回查 | 骑手导航/签收地址异常 | delivery/snapshot/current address | application/Services/Transport/SelfDeliverySer.php | 配送保存地址、订单快照和轨迹 | 查询只读;历史配送地址不写 | delivery + snapshot diff + sign time | 创建时错走配送更正/改派,不改订单主地址历史 |
子模块追踪:third-delivery-address 三方询价、下单与取消
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 询价下单 | 三方配送 quote/create | invoiceNo、address snapshot、quote ID | application/controllers/sale/ThirdDelivery.php -> application/Services/SaOrders/ThirdDeliverySer.php | 出库、地址快照、quote 有效期和已有外部单 | 本地配送事务保存地址/quote none -> pending;外部下单事务外 | request ID + invoice/local/external + quote | timeout 按本地单查外部;禁止用当前地址重复建单 |
| 取消改派 | 取消或地址错误改派 | local/external delivery、reason/new snapshot | application/Services/SaOrders/ThirdDeliverySer.php | 外部当前态、可取消规则和原地址 | 外部取消确认后本地事务 old -> canceled;新单独立创建 | request ID + old/new delivery IDs | 原单未取消不得新建;费用/退款独立对账 |
子模块追踪:purchase-logistics 采购急件与退货物流
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 急件物流 | 采购急件自提/配送 | po billNo、address snapshot、mode/ETA | application/Providers/PurchaseService/LogisticsProvider.php | 采购态、发收量、供需地址和已有物流 | 本地关系事务 none -> pending;外部物流事务外 | request ID + po/logistics IDs + code | timeout 按 po/外部单回查;不重复生成物流 |
| 退货物流 | 采购退货提交地址/运单 | return billNo、return address snapshot、express | application/models/orders/PoReturnOrderLogisticsModel.php | 退货态、可退量、退货地址配置和已有关系 | 退货物流本地事务 insert/update old -> shipped/closed | request/message + return/logistics + address ID | 使用创建时退货地址快照;物流完成与退款分别验收 |