运输计划调整 · 全量子数据 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 根因(叠加)
- 旧安排路径:重安排先
revoke 再建;已过磅则整单失败(生产曾未及时加载 upsert 版本)。
- 新 upsert 路径仍偏保守:已关联过磅单时跳过出入库车牌/产品等正文同步。
- 过磅单正文:安排/保存从不回写
vehicle_no / 产品等。
- 段产品换 UUID(见 09-05 文档):
plan_product_id 变化导致 link 对齐困难。
- 月计划:产品/往来变更后未稳定「利旧或新建并改挂」。
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. 分期落地
一期(止血,对应该事故)
- 安排/已安排保存:有出入库 → 强制回写车牌/产品/往来等(含已过磅)。
- 有过磅单 → 同步正文,称重不动。
- 去掉安排前 revoke;去掉「已过磅跳过正文同步」。
- 产品/往来变 → 月计划 find-or-create 改挂。
二期(根治断链)
replace_plan_stops 真·按 id upsert(落地/补齐 2026-09-05)。
- link 匹配键稳定;保存与安排共用 sync。
三期(体验与审计)
- 调整结果摘要(新建/更新/改挂计数)。
- 可选写入
pl_plan_change。
7. 验收场景
- 已安排 + 有出入库、未过磅:改车 → 出入库车牌变,id 不变。
- 已安排 + 有出入库 + 已过磅:改车 → 出入库与过磅单车牌都变,称重不变,出入库 id 不变。
- 新勾选生成地磅 → 新建出入库单。
- 改产品或往来 → 出入库原地更新;月计划利旧或新建并改挂。
- 取消已过磅段地磅勾选 → 阻断。
- 司机原出入库 id / 现场 token 改车后仍可用。
- 重安排不再因「已关联地磅」整单 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 上扩展已过磅改正文、月计划改挂用例