2026-09-06-plan-arrange-upsert-cascade-design.md 8.7 KB

运输计划调整 · 全量子数据 Upsert 级联

日期: 2026-09-06
状态: 已确认(brainstorming)
触发案例: 生产 PO20260903001 — 选错车后改车;出入库/过磅单车牌未同步;安排返回「无法随运输安排自动重建」
相关:

  • 2026-09-05-pl-stop-stable-id-reuse-links-design.md(段/产品稳定 ID + link 复用)
  • 2026-08-30-pl-plan-save-sync-dibang-date-design.md
  • 2026-06-20-pl-arrange-dibang-design.md

1. 背景

1.1 现象

  • 计划/运输单已改为新车牌,地磅出入库、过磅单仍为旧车牌。
  • 用户重安排时接口 400:已关联地磅单,无法随运输安排自动重建。
  • 出入库单 id 已发给司机,不能因调整而换新 id。

1.2 根因(叠加)

  1. 旧安排路径:重安排先 revoke 再建;已过磅则整单失败(生产曾未及时加载 upsert 版本)。
  2. 新 upsert 路径仍偏保守:已关联过磅单时跳过出入库车牌/产品等正文同步。
  3. 过磅单正文:安排/保存从不回写 vehicle_no / 产品等。
  4. 段产品换 UUID(见 09-05 文档):plan_product_id 变化导致 link 对齐困难。
  5. 月计划:产品/往来变更后未稳定「利旧或新建并改挂」。

1.3 产品诉求(已确认)

# 决策
D1 整体采用 方案 2:运输计划调整统一 Upsert 契约
D2 有 id 则更新,无 id 则创建;禁止为改业务而删旧建新换主键
D3 出入库单 id 永不因调整而更换(司机已持有)
D4 已有过磅单:强制同步 业务正文(车牌/产品/往来等);称重数字一律不动
D5 新产生的地磅需求(无既有出入库)→ 新建出入库单
D6 出入库产品/往来变更 → 月计划 利旧或新建,并改挂 purchase_plan_id
D7 计划保存与安排/重安排共用同一套下游 sync;主流程成功,下游问题以 warnings 回报(破坏性取消勾选等仍可硬阻断)

2. 目标与非目标

2.1 目标

# 目标
G1 已安排后调整车辆/产品/往来/数量/日期等,关联子数据按 id upsert 完成更新
G2 司机所持出入库 id、运输单 token 在调整后仍有效
G3 已过磅时仍同步出入库与过磅单业务字段;称重不变
G4 新勾选地磅 → 新建出入库;旧关联 → 原地更新
G5 产品/往来变更 → 月计划 find-or-create 并改挂
G6 废除安排前整批 revoke;部署后不得再出现「无法随运输安排自动重建」整单 400

2.2 非目标

  • 不改造地磅硬件/过磅采集流程。
  • 不为「改计划」凭空创建过磅单(过磅单仍由现场过磅产生)。
  • 不自动作废已有过磅单要求重磅(除非未来单独产品决策)。
  • 复制计划仍全新生成下游单据(不带走源单 id)。
  • 运费结算已锁定单据的深度回写(若冲突,警告,本期不强制改结算单)。

3. 总契约:Upsert by ID

请求/关联中存在有效 id  →  UPDATE 业务字段(主键不变)
不存在 id / 不可复用     →  CREATE
禁止:为改业务而 soft-delete + 新 UUID 重建(稳定白名单对象)

3.1 稳定 id 白名单

  • db_inbound_outbound.id(及已发司机依赖的 batch_no / site_access_token 默认不 rotate)
  • pl_transport.id 与司机/现场 *_access_token(默认不 rotate)
  • 已存在的 db_weighing_slip.id(只改正文)
  • 前端回传的 pl_plan_load / pl_plan_delivery / 段产品行 id(有则保留)
  • pl_transport_db_link:更新指向,不换 inventory id

3.2 两条主入口,一个 sync 引擎

PUT update_plan          POST arrange_transport
        \                       /
         \                     /
          v                   v
     计划内核 upsert     运输单 upsert
              \           /
               v         v
         下游 sync orchestrator
         - 地磅出入库(有则改 / 无则建)
         - 过磅单正文(有则改)
         - 月计划利旧或新建并改挂
         - 现场/IO 快照(token 稳定)
入口 职责
保存计划 段/产品/合同等内核 upsert;若已 arranged 触发下游 sync
安排/重安排 运输单有则更新无则创建;勾选地磅走同一 sync;不再先 revoke

4. 字段与对象矩阵

4.1 地磅出入库 db_inbound_outbound

情形 行为
已有 link → inventory UPDATE 同 id
新勾选/新段产品无 inventory CREATE 新出入库 + link
id / batch_no / site_access_token 冻结(默认)
vehicle_no / 产品快照 / partner_company / quantity / date / business_type 强制同步
status 一般不因改车重置

4.2 过磅单 db_weighing_slip

字段类 行为
按 inbound_outbound_id 找到未作废单 UPDATE 正文
vehicle_no / 产品 / 往来 / plan_no / batch_no / business_type 强制同步
gross/tare/net/deduction/*_time 永不改
尚无过磅单 不创建;等现场过磅

4.3 地磅月计划 db_purchase_plan

匹配键:业务类型 + 往来 + 产品(+规格) + 出入库业务日所在月。

结果 行为
找到可复用月计划 出入库改挂该 purchase_plan_id(利旧);数量 delta:旧减新加
找不到 新建月计划并挂靠
仅车牌等变更、产品/往来未变 不改挂;可回写月计划 vehicle_no
过磅单上的 purchase_plan_id / plan_no 随新挂靠同步引用;称重不动

4.4 计划内核 / 运输执行

  • 段与段产品:有 id → upsert(对齐 2026-09-05 文档);消除无故换 plan_product_id。
  • 未出现在 payload 的旧段/产品 → 软删;若已过磅关联 → 硬阻断取消勾选/删除类破坏操作。
  • pl_transport:已有则更新车辆司机等;token 稳定。

5. 错误处理与前端

场景 行为
主数据可保存 200 成功;下游问题进 warnings
部分出入库同步失败 已成功保留;warnings 列清单号与原因
取消已过磅地磅 / 删除已过磅段产品 硬阻断 + 明确提示
部署门禁 安排不得再走整批 revoke;不得返回「无法随运输安排自动重建」整单 400

前端:成功无警告正常提示;有 warnings 则成功+列表;硬阻断用弹窗。


6. 分期落地

一期(止血,对应该事故)

  1. 安排/已安排保存:有出入库 → 强制回写车牌/产品/往来等(含已过磅)。
  2. 有过磅单 → 同步正文,称重不动。
  3. 去掉安排前 revoke;去掉「已过磅跳过正文同步」。
  4. 产品/往来变 → 月计划 find-or-create 改挂。

二期(根治断链)

  1. replace_plan_stops 真·按 id upsert(落地/补齐 2026-09-05)。
  2. link 匹配键稳定;保存与安排共用 sync。

三期(体验与审计)

  1. 调整结果摘要(新建/更新/改挂计数)。
  2. 可选写入 pl_plan_change。

7. 验收场景

  1. 已安排 + 有出入库、未过磅:改车 → 出入库车牌变,id 不变。
  2. 已安排 + 有出入库 + 已过磅:改车 → 出入库与过磅单车牌都变,称重不变,出入库 id 不变。
  3. 新勾选生成地磅 → 新建出入库单。
  4. 改产品或往来 → 出入库原地更新;月计划利旧或新建并改挂。
  5. 取消已过磅段地磅勾选 → 阻断。
  6. 司机原出入库 id / 现场 token 改车后仍可用。
  7. 重安排不再因「已关联地磅」整单 400(正文可同步时)。

8. 与既有文档关系

  • 继承 2026-09-05-pl-stop-stable-id-reuse-links-design.md 的段/产品稳定 ID 与 link 复用目标。
  • 扩展/修正:已过磅时仍强制同步出入库与过磅单业务正文(09-05 中「过磅单只读关联、不因保存重建」仍然成立;但允许改正文字段)。
  • 废除:安排路径依赖 revoke_regeneratable_dibang_for_transport 的整批重建(该函数可保留给作废计划等场景)。

9. 实现触点(供后续 plan)

  • backend/app/services/pl_dibang_bridge_service.py(_update_existing_stop_inventory、月计划改挂、过磅单正文同步)
  • backend/app/api/pl/transport.py(安排不再 revoke)
  • backend/app/api/pl/plan.py(已安排保存触发 sync)
  • backend/app/services/pl_plan_stop_service.py(replace_plan_stops upsert)
  • 测试:在既有 test_pl_dibang_upsert_on_arrange.py 上扩展已过磅改正文、月计划改挂用例