本文把 DGJ2 中“省市区、客户地址、服务站与仓库地址、订单地址快照、退货地址、门店配送时效、自配送、三方配送、采购承运商”统一梳理为地址主数据、业务地址快照和履约执行三层。

阅读后应能回答:

  1. 地址编码、地址名称、详细地址和经纬度分别用于什么。
  2. 修改客户地址后,历史采购/销售订单是否应跟着变化。
  3. 服务站、门店、仓库、客户和快准退货地址为什么不能混用。
  4. 配送时效优先从哪里取,分钟和小时怎样统一。
  5. 自配送和美团/达达三方配送的状态、单号和本地关系有什么差异。
  6. 采购急件的承运商时效来自哪里。
  7. 省市区校准、Redis 缓存和直辖市兼容如何工作。
  8. 地址错误、配送不生成、状态不同步、退货寄错时如何排查。

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.phpTMS 自配送创建、列表、接单、送达、签收、取消
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.phpWMS 运单详情
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_storageDGJ 仓库地址、编码、经纬度
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 名称反查的动态补区行为

若地图解析得到的区县名称不在本地表:

  1. 找省、市名称对应编码。
  2. 查该市下最大区县 ID。
  3. 若最大 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():

  1. 读取本地所有地区,以 ID 建 map。
  2. 调 OPS /opscenter/orgmgr/dgj/region/listAll。
  3. 递归比较新增、修改和本地多余记录。
  4. 事务删除、批量更新、批量新增。
  5. 删除 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):

  1. 从主账号取服务站 province。
  2. 在 t_sys_region 找省份对应区域类型。
  3. 无区域时返回默认环境 1。
  4. 有 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):

  1. 查联系人地址列表。
  2. 没地址抛“请完善客户地址”。
  3. 取第一条地址,构造采购订单地址结构。
  4. 若传 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 字段
codelocationNo
addraddress
provinceCodeprovince_code
cityCodecity_code
regionCodecounty_code
localXlongitude
localYlatitude

创建快准仓时同时创建默认货位和不良品货位。

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_ADDRESSprovince => 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:

  1. 查询全部 kzcp_address。
  2. 每个地址写独立 hash。
  3. 查询省份映射。
  4. 批量写省份 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 单号

前缀类型
KZPSTMS 商家自配送单
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():

  1. 校验销售出库单存在且可配送。
  2. 获取发货门店地址和坐标。
  3. 获取客户默认地址和坐标。
  4. 获取客户与门店配送时效,转为 receiptDuration。
  5. 获取销售出库商品明细。
  6. 构造 TMS 请求。
  7. 创建成功后保存配送单号/更新出库状态。
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服务作用
transportStoreapp.transport.store配送门店切换
transportListapp.transport.transportList待揽收/配送中/已完成列表
transportCancelListapp.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():

  1. 校验当前账号是有效配送员工。
  2. 用员工资料覆盖 courierName/courierMobile。
  3. 查销售出库单。
  4. 更新出库单 delieverId。
  5. 更新销售出库状态为配送中。
  6. 调 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():

  1. 必须有 sid + outShipmentNo。
  2. 验证销售出库单存在。
  3. 校验操作员工身份。
  4. 调 TMS 取消。
  5. 若请求带 changeCourier,把新 admin uid 映射为 staff id。
  6. 更新销售出库单 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_noDGJ/物流中心配送号
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():

  1. 调三方物流中心取消。
  2. 本地配送状态设 99。
  3. 查所有关联销售出库单。
  4. 将出库单状态恢复为已出库。
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():

  1. 查本地配送单。
  2. 获取操作人。
  3. 调三方 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["核对前端字段映射"]

检查:

  1. 先确认页面应读主地址还是快照。
  2. 对比 code 与 name。
  3. 查直辖市中间层。
  4. 查 30 天 Redis 缓存。
  5. 查是否主表更新但 SAAS/OPS 未同步。

41. 排查 SOP:地图位置不对

  1. 查地址文本是否最新。
  2. 查经纬度是否为空、0 或仍是旧值。
  3. 确认坐标系/地图类型 GAODE 或 BAIDU。
  4. 确认 X 是经度、Y 是纬度的外部契约。
  5. 用地图打开坐标,核对真实位置。
  6. 查 updateStoreLocation/updateContactLocation 是否成功。
  7. 查 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:三方配送远程有单、本地无单

  1. 用 inquiryNo 查询价。
  2. 用三方返回 deliveryNo 查物流中心。
  3. 查 t_scm_third_party_delivery_order。
  4. 查 t_sa_invoice_delivery_relation。
  5. 查创建时本地事务异常。
  6. 确认销售出库状态是否被改为配送中。
  7. 使用补建关系工具前,确认不会再次向三方下单。

这类问题的恢复目标是补本地事实,不是重放远程 create。


45. 排查 SOP:采购退货地址不对

  1. 查服务站主账号 province/areaCode 使用了哪个字段。
  2. 查 HASH_AREA_RETURN_ADDRESS 是否有省份映射。
  3. 无映射时确认是否静默用了 ID 1。
  4. 查 HASH_KZ_ADDRESS{id} 是否是旧内容。
  5. 对比数据库两张地址表。
  6. 执行缓存刷新前,确认旧 hash field 是否需要清理。
  7. 查采购退货单/物流记录保存的地址快照。

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-14TMS 创建异常被吞并返回空上层误判成功结构化错误返回
R46-15接单先改本地状态再调 TMSTMS 失败本地已配送中调整顺序或补偿
R46-16三方下单远程成功后才开本地事务远程孤儿单幂等查询与补建
R46-17三方取消远程与本地非原子状态分叉状态机 + 补偿任务
R46-18配送退回不自动等于库存退货实物回站但库存未增加明确退回收货单据
R46-19App 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 地址快照链

  1. 创建客户地址 A。
  2. 下采购单并保存地址快照 A。
  3. 将客户当前地址改为 B。
  4. 历史采购单仍显示 A。
  5. 新采购单显示 B。
  6. 验证 SAAS 同步。

49.2 自配送链

  1. 完善门店和客户地址/坐标。
  2. 设置客户门店时效。
  3. 销售出库创建 KZPS 配送单。
  4. 配送员 App 接单、轨迹、到达、签收。
  5. 验证销售详情状态和签收图。

49.3 三方配送链

  1. 查询美团/达达运力。
  2. 使用询价号创建 SPS 配送单。
  3. 验证本地主单和出库关系。
  4. 取消并验证出库恢复。
  5. 模拟拒收退回并执行回站确认。

50. 上线回归矩阵

修改模块必回归
AreaSer/AreaCalibrate省市区、直辖市、缓存、历史地址
客户地址采购、销售、配送、SAAS 同步
StorageSer仓库、默认仓、货位、地图坐标
退货地址采购退货、各省映射、缓存刷新
ContactStoreRelSer门店绑定、分钟/小时时效、机器人展示
TransportSerApp 列表、接单、到达、签收、取消
ThirdDeliverySer询价、创建、取消、退回、出库撤销
采购承运商急件、自提、截单、揽收时间
服务站区域指导价、供货、首配、活动、灰度

51. 待环境确认项

  1. t_sys_area_address 是否允许物理删除,历史地址如何保留名称。
  2. 地区校准生产调度频率、最近差异和负责人。
  3. ALL_REGIONS 是否由其他任务同步清理。
  4. 地址名称反查自动新增区县是否仍有线上流量。
  5. 客户地址“第一条”是否由 Model 默认按 isDefault 排序。
  6. 仓库地理编码失败时是否保留旧坐标或清空。
  7. TMS 的 X/Y 与 longitude/latitude 精确契约及坐标系。
  8. 自配送创建、接单、签收的下游幂等键。
  9. TMS 状态到 SaInvoiceEnums::billStatus 的完整映射。
  10. 三方物流回调入口、签名、重试和本地状态更新任务。
  11. 三方远程孤儿单是否已有补偿扫描。
  12. 配送退回后的库存入库业务入口。
  13. 客户默认配送时效所在配置表的最终字段和单位枚举。
  14. t_sys_carrier 当前是否仍作为有效承运商时效源。
  15. 急件承运商 collectTimes 的时区、日期和截单规则。
  16. 退货默认地址 ID 1 对应哪个仓及适用省份。
  17. 服务站区域变更后需刷新哪些价格/供货/活动缓存。

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/ConsumerService/Provider汇合点最终业务事实
区域/地址维护basedata/Area/Region/DeliveryAddr区划码、文本、经纬度、联系人BaseData ControllerArea/Region/Storage Servicearea/address ID主数据地址和坐标
地址校准tasks/AreaCalibrate/KzAddress地址 ID、批次、外部地理结果CLI taskArea/ReturnAddress Serviceaddress ID补齐标准区划/坐标,不改历史快照
采购物流PoOrderSer收货地址、仓库、承运要求采购入口Logistics ProviderPO + address snapshot采购地址快照和预计时效
销售配送Self/ThirdDelivery/App transport出库单、地址快照、坐标、配送方式Delivery ControllerTransport/ThirdDelivery Serviceinvoice+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 -> newaddress ID、操作人、更新时间
订单快照PoOrderSer/SaOrder Service订单事务下单时主地址SCM_PO_ORDER_ADDRESS/销售地址快照snapshot insert;后续主地址改变不回写历史order+snapshot 内容
计算时效Logistics Provider外部只读边界快照区划/坐标、商品仓库外部请求/本地估时字段预计承运商/时效 null -> valuerequest ID、输入快照、返回码
创建配送Transport/ThirdDelivery Service本地+外部非原子出库、地址快照、配送方式delivery/关系表、外部 TMSpending + external ID;不修改库存事实invoice+delivery IDs
校准/取消Task/Delivery cancel受控事务主数据或外部履约最终态主地址/配送状态/MQ只校准主数据;取消状态单向推进并同步外部batch/message、历史快照 diff=0

子模块追踪:region-tree 行政区划树与缓存

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
区划查询页面按父级加载省市区树parent/code、level、versionapplication/controllers/basedata/Region.php -> application/Services/BaseData/RegionSer.php区划主表、层级、有效状态和缓存查询只读;cache miss 事务外 none -> tree snapshotrequest ID + parent/code + DB/cache version空缓存与无子级区分;错误树只清目标版本缓存
维护刷新区划数据/状态更新region ID/code、parent、statusapplication/Services/System/RegionSer.php原节点、父链、子节点和引用主数据本地事务 name/parent/status old -> new;commit 后清树缓存request ID + region + changed fields循环父链/有引用删除零写入;历史订单快照不回写

子模块追踪:region-calibrate 省市区校准任务

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
dry-run扫描文本与区划码不一致主地址batch、address IDs、scopeapplication/controllers/tasks/AreaCalibrate.php地址文本、现区划、标准树和置信结果查询只读;输出可校准/歧义/失败清单task + batch + counts + address IDs歧义/低置信不自动写;先小批人工抽样
校准写入确认主地址区划batch、address ID、before hashapplication/Services/Area/AreaSer.php当前仍等于 baseline、标准 region IDs每地址本地事务 region/text old -> calibratedbatch + address + before/after + rows只改主数据;订单历史快照 diff 必须 0,重跑 0 变化

子模块追踪:contact-address 客户、供应商与服务站地址

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
保存地址基础资料新增/编辑联系人地址owner type/ID、address ID、region/text/coordinatesapplication/controllers/basedata/Area.php -> application/Services/Area/AreaSer.php所有权、区划、重复地址、默认项和权限地址本地事务 insert/update,默认关系 old -> newrequest ID + owner/address + changed fields非法区划/越权零写入;敏感地址日志摘要化
仓库关联联系人/服务站地址关联仓库contact/store/storage IDsapplication/Services/Storage/ContactStoreRelSer.php主地址、仓库、原关系和站点范围关系本地事务 old set -> newrequest ID + relation IDs + count全量替换失败回滚;历史订单继续用原快照

子模块追踪:order-address-snapshot 采购销售订单地址快照

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建快照采购/销售下单order/source billNo、address ID/versionapplication/Services/PoOrders/PoOrderSer.php -> application/models/orders/PoOrderAddressModel.php下单时主地址、联系人、区划和坐标订单本地事务 insert immutable snapshot none -> order addressrequest ID + order/address snapshot ID主地址无效整单拒绝/按规则;后续主地址改不回写
历史回查订单地址与当前资料不同order/snapshot/address IDsapplication/models/orders/PoOrderAddressModel.php订单快照、当前主地址和创建时间查询只读 不写order + snapshot/current diff差异通常合法历史;只有创建时错误才走订单领域更正流程

子模块追踪:storage-geocode 仓库地址与地理编码

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
保存仓库地址仓库维护文本/区划storage ID、address/regionapplication/Services/Storage/StorageSer.php仓库、站点、原地址/坐标和引用主数据本地事务 address/region old -> newrequest ID + storage + changed fields在途配送不自动换历史地址;校验失败零写入
地理编码地址变更后解析坐标storage/address ID、request IDapplication/Services/Area/AreaSer.php最新文本、旧坐标和外部编码结果外部查询事务外;确认后本地事务 lat/lng old -> newrequest ID + storage/address + provider codetimeout 保留旧坐标/待校准,不写假坐标

子模块追踪:return-address 快准退货地址与缓存

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
维护退货地址配置快准退货仓/联系人return address ID、scope/sid、fieldsapplication/Services/KzAddress/ReturnAddressSer.php原配置、范围、区划和默认项配置本地事务 old -> new;commit 后缓存失效request ID + address/scope + version有进行中退货继续用已保存快照;敏感字段脱敏
查询缓存采购售后获取退货地址sid/order type、cache keyapplication/controllers/tasks/KzAddress.phpDB 有效配置、缓存版本和订单快照查询只读;cache miss none -> snapshotrequest/task ID + sid/key + address IDDB 正确只补缓存;无配置明确失败,不猜默认地址

子模块追踪:delivery-timeliness 承运商与配送时效

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
时效询价下单/配送选择承运商origin/destination snapshot、goods、carrierapplication/Providers/OpsManager/Wms/LogisticsProvider.php两端区划/坐标、货物、承运商准入和服务时间外部查询事务外;本地业务 DB 不写,返回 quote/ETArequest ID + address snapshot IDs + carrier/codetimeout/无服务明确不可选;ETA 不作为履约成功
时效回查页面预计到货异常delivery/order、quote ID、ETAapplication/Services/Transport/TransportSer.php询价输入快照、外部结果和实际运单查询只读;创建配送时固化 ETA null -> valuedelivery + quote + input/result地址主数据后变不重算历史;需改派时重新询价

子模块追踪:self-delivery-address 自配送创建与地址使用

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
创建自配送销售出库选择商家配送invoiceNo、order address snapshot、rider/feeapplication/controllers/sale/SelfDelivery.php -> application/Services/Transport/SelfDeliverySer.php出库态、不可变地址快照、配送范围和已有关系配送本地事务 insert,保存 address snapshot/coordinates、status pendingrequest ID + invoice/delivery + snapshot ID不直接读取后来变更的客户地址;重复 invoice 幂等
签收回查骑手导航/签收地址异常delivery/snapshot/current addressapplication/Services/Transport/SelfDeliverySer.php配送保存地址、订单快照和轨迹查询只读;历史配送地址不写delivery + snapshot diff + sign time创建时错走配送更正/改派,不改订单主地址历史

子模块追踪:third-delivery-address 三方询价、下单与取消

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
询价下单三方配送 quote/createinvoiceNo、address snapshot、quote IDapplication/controllers/sale/ThirdDelivery.php -> application/Services/SaOrders/ThirdDeliverySer.php出库、地址快照、quote 有效期和已有外部单本地配送事务保存地址/quote none -> pending;外部下单事务外request ID + invoice/local/external + quotetimeout 按本地单查外部;禁止用当前地址重复建单
取消改派取消或地址错误改派local/external delivery、reason/new snapshotapplication/Services/SaOrders/ThirdDeliverySer.php外部当前态、可取消规则和原地址外部取消确认后本地事务 old -> canceled;新单独立创建request ID + old/new delivery IDs原单未取消不得新建;费用/退款独立对账

子模块追踪:purchase-logistics 采购急件与退货物流

环节入口/触发请求/业务键代码链路读取事实写入与字段变化日志证据异常与补偿
急件物流采购急件自提/配送po billNo、address snapshot、mode/ETAapplication/Providers/PurchaseService/LogisticsProvider.php采购态、发收量、供需地址和已有物流本地关系事务 none -> pending;外部物流事务外request ID + po/logistics IDs + codetimeout 按 po/外部单回查;不重复生成物流
退货物流采购退货提交地址/运单return billNo、return address snapshot、expressapplication/models/orders/PoReturnOrderLogisticsModel.php退货态、可退量、退货地址配置和已有关系退货物流本地事务 insert/update old -> shipped/closedrequest/message + return/logistics + address ID使用创建时退货地址快照;物流完成与退款分别验收