From 410d2932878c9b670c962f9bd7fc684e546505d2 Mon Sep 17 00:00:00 2001 From: zlwu Date: Sun, 6 Sep 2026 19:28:15 +0800 Subject: [PATCH] docs: add transport plan arrange upsert cascade design. Capture confirmed rules for stable-id updates, dibang inventory/weighing field sync, and monthly plan remap on plan rearrange. Co-authored-by: Cursor --- ...9-06-plan-arrange-upsert-cascade-design.md | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-06-plan-arrange-upsert-cascade-design.md diff --git a/docs/superpowers/specs/2026-09-06-plan-arrange-upsert-cascade-design.md b/docs/superpowers/specs/2026-09-06-plan-arrange-upsert-cascade-design.md new file mode 100644 index 000000000..ad795b88d --- /dev/null +++ b/docs/superpowers/specs/2026-09-06-plan-arrange-upsert-cascade-design.md @@ -0,0 +1,206 @@ +# 运输计划调整 · 全量子数据 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` 上扩展已过磅改正文、月计划改挂用例