From f8d0d10b2c42d457344b7ba3e535a9be5dad166b Mon Sep 17 00:00:00 2001 From: zlwu Date: Sat, 5 Sep 2026 18:08:55 +0800 Subject: [PATCH] docs: add design for stable plan stop IDs and in-place link reuse. Captures brainstorming decisions so edits after arrange no longer break dibang/driver/site associations by regenerating stop UUIDs. Co-authored-by: Cursor --- ...05-pl-stop-stable-id-reuse-links-design.md | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-05-pl-stop-stable-id-reuse-links-design.md diff --git a/docs/superpowers/specs/2026-09-05-pl-stop-stable-id-reuse-links-design.md b/docs/superpowers/specs/2026-09-05-pl-stop-stable-id-reuse-links-design.md new file mode 100644 index 000000000..79db4decb --- /dev/null +++ b/docs/superpowers/specs/2026-09-05-pl-stop-stable-id-reuse-links-design.md @@ -0,0 +1,200 @@ +# 运输计划段次稳定 ID + 外围单据就地复用 + +**日期:** 2026-09-05 +**状态:** 待审阅(brainstorming 已确认方案 3) +**触发案例:** 生产 `PO20260904024` — 库内已有地磅单,详情「送货」段显示「暂无关联地磅单」 +**相关:** `2026-08-26-pl-multi-stop-redesign-design.md`、`2026-08-30-pl-plan-save-sync-dibang-date-design.md`、`2026-06-20-pl-arrange-dibang-design.md` + +--- + +## 1. 背景与根因 + +### 1.1 现象 + +- `pl_transport_db_link` / `db_inbound_outbound` / `db_weighing_slip` 均存在且有效。 +- 详情「送货信息段次」地磅卡片为空文案「暂无关联地磅单」。 + +### 1.2 根因 + +1. `replace_plan_stops` 对送货段采用 **整段软删 + `generate_uuid()` 再建**;装货段因 `uk_pl_plan_load_plan` 已部分 upsert。 +2. `pl_transport_db_link.stop_id` / `plan_product_id` **不随保存改挂**,仍指向已软删段/产品行。 +3. 前端 `resolveStopDibangRows` 按 **当前有效段 `id`** 过滤 → 匹配失败。 +4. 已安排后「调整运输」会先 `PUT`(段已换新 ID),再 sync 地磅: + - **未过磅**:可作废重建,展示偶发「修好」,但出入库/批次号易换新。 + - **已过磅**:`revoke_regeneratable_dibang_for_transport` 阻断重建 → 关联悬空且无法自动修复(`PO20260904024` 即此路径)。 + +### 1.3 产品诉求(已确认) + +- 段次级稳定身份(方案 B):保存不换段身份。 +- 技术取方案 **3**:稳定键 = 段/产品行主键;前端回传旧 `id`,后端 upsert。 +- 安排后的段次新增、段内信息调整:司机链接、现场链接、地磅相关单据 **尽量复用旧 ID**,只改可变内容,避免重复生成。 + +--- + +## 2. 目标与非目标 + +### 2.1 目标 + +| # | 目标 | +|---|------| +| G1 | 装/送货段与计划产品行主键在编辑保存中 **尽量不变**(有 id 则 upsert) | +| G2 | 已有 `pl_transport_db_link` / 出入库 / 过磅单:**默认不重建**;可变字段就地更新 | +| G3 | 司机/现场 token:**默认不 rotate**;旧链接持续有效 | +| G4 | 详情按段展示地磅不再因「保存换 UUID」丢失 | +| G5 | 历史悬空 link(含 `PO20260904024`)可修复 | + +### 2.2 非目标 + +- 改造地磅过磅流程、净重计算。 +- 已过磅单据自动作废重建。 +- 复制计划带走源单地磅/token(复制仍全新)。 +- 本期不做「作废并重发链接」完整 UI(可留扩展点)。 + +--- + +## 3. 核心模型 + +``` +pl_plan.id(稳定) +├── pl_plan_load.id ← 保持 upsert +├── pl_plan_delivery.id ← 改为按 payload.id upsert +├── pl_plan_product.id ← 有 id 则保留 +└── pl_transport(安排后) + ├── driver_access_token / site_access_token ← 默认不 rotate + └── pl_transport_db_link(stop_id, plan_product_id) + → db_inbound_outbound(就地更新;仅新段新建) + → db_weighing_slip(只读关联,永不因保存重建) +``` + +司机/现场仍为 **1 个/运输单**(多停靠设计 D9/D10);段变更后同一链接内按稳定 `stop_id` 分区展示即可。 + +--- + +## 4. 保存 / 调整运输语义 + +### 4.1 段保存:upsert 替代整段删建 + +| 实体 | 现状 | 目标 | +|------|------|------| +| `pl_plan_load` | 已原地更新 | 保持 | +| `pl_plan_delivery` | 每次新建 UUID | payload 带本计划有效/`可复活` 的 `id` → **UPDATE**;无 `id` → **INSERT**;未出现在 payload 的旧有效段 → **软删** | +| 段产品行 / `pl_plan_product` | 常随重建 | 有 `id` / `planProductId` 则保留并更新字段 | + +**写入契约** + +- `PlanLoadBody` / `PlanDeliveryBody` 增加可选 `id`(alias 保持 camelCase)。 +- 前端编辑回传段 `id`、产品行 `id` / `planProductId`;「新增送货段」不带 `id`;删除段 = 不放入 payload。 +- 后端校验:回传 `id` 必须属于当前 `plan_id`(含软删行可复活);否则 422。 + +### 4.2 仅保存 vs 调整运输 + +**PUT 更新计划(含 upsert 段)** + +- 未安排:只写计划/段,不碰地磅/token。 +- 已安排: + 1. 对已有有效 link:按稳定 `stop_id` **就地更新**出入库可变字段(见 §5)。 + 2. `generateDibang=1` 且该段尚无有效 link → **新建**出入库 + link。 + 3. 取消 `generateDibang`:已过磅 → **阻断**;未过磅且可作废 → 软删 link + 出入库,月计划回减贡献量(不删月计划行)。 + +**调整运输(rearrange)** + +- 继续复用同一 `pl_transport` 行。 +- 司机/现场 token:**默认不 rotate**。 +- 地磅:取消「能作废则整批 revoke 再建」主路径;与 PUT 相同的 **按段 upsert / 就地更新**。 + +### 4.3 复制计划 + +- 新 `plan_id`、新段 id、新产品行 id。 +- **不**复制 `pl_transport_db_link`、出入库、token。 +- 复制后需重新安排才生成外围单据。 + +### 4.4 历史脏数据 + +- 运维脚本:将有效 link 的 `stop_id` / `plan_product_id` 改挂到当前 `del_flag=0` 的对应段/产品(同计划内:同客户 + 同 CRM `product_id` 优先;单段内部送货可唯一匹配)。 +- 含生产案例 `PO20260904024`。 +- 可选迁移期前端兜底(仅同计划单内部送货且 link 的 stop 已删):不作为正式长期逻辑。 + +--- + +## 5. 地磅就地更新字段矩阵 + +分水岭:**该出入库是否已有有效过磅单**(`db_weighing_slip` 未删且未作废);可作废状态集沿用:`待入库` / `待出库` / `待三方填写` / `待处理`。 + +| 字段 | 未过磅且可作废 | 已过磅或不可改 | +|------|----------------|----------------| +| `date`(含跨月改挂月计划) | 可更新(沿用 `sync_linked_dibang_dates_for_plan`) | 不改 + `warnings` | +| `partner_company` | 可按段客户规则重算更新 | 不改 + `warnings` | +| `vehicle_no` | 可随运输车牌更新 | 不改 + `warnings` | +| `quantity` / link.`contributed_qty` | 可按段产品计划量更新并调整月计划贡献 | 不改 + `warnings` | +| `product` / 规格 / `business_type` | 段 `productId` 变更 → **阻断保存** | 同左 | +| `batch_no`、出入库主键、过磅单 | **永不因保存/调整重建** | 同左 | +| remark 中计划编号文案 | 可校正为当前 `plan_no` | 可校正文案 | + +### 5.1 阻断 vs 警告 + +| 场景 | 行为 | +|------|------| +| 已过磅段取消 `generateDibang` | HTTP 400,文案含批次号 | +| 未过磅可作废,取消勾选 | 软删 link + 出入库;月计划回减贡献量 | +| 已过磅仍改日期/往来/车牌/数量 | 计划保存成功;字段跳过 + `warnings[]` | +| 已有 link 的段更换 CRM `productId` | 阻断 | +| 新段勾选地磅 | 新建出入库 + link(新批次号) | + +--- + +## 6. 司机 / 现场链接 + +| 动作 | 行为 | +|------|------| +| 首次安排 | 生成 driver / site token(现网一致) | +| 之后保存 / 调整运输 | **不 rotate** | +| 扩展(本期可不做 UI) | 显式「作废并重发链接」才 rotate | + +--- + +## 7. 实现落点 + +| 区域 | 变更 | +|------|------| +| `backend/app/services/pl_plan_stop_service.py` | `replace_plan_stops` → upsert 送货段与段产品;计划产品行保留 id | +| `backend/app/schemas/pl/plan.py` | `PlanLoadBody` / `PlanDeliveryBody` 可选 `id` | +| `web-admin` 计划表单提交 | 回传段/产品 id;新增段不带 id | +| `backend/app/services/pl_dibang_bridge_service.py` | 新增/改造 `upsert_stop_dibang_links`;调整路径不再整批 revoke 再建 | +| `backend/app/api/pl/transport.py` | rearrange 默认不 rotate token;调用 upsert | +| 运维 | 历史 link 改挂脚本;修复 `PO20260904024` | +| 单测 | 见 §8 | + +详情展示:`resolveStopDibangRows` 继续按 `stopType + stopId` 即可(段 id 稳定后自然对齐)。 + +--- + +## 8. 测试要点 + +1. 安排 → 改送货地址/时间 → 保存或调整:段 id、出入库 id、批次号不变;详情仍显示地磅。 +2. 安排并过磅 → 再改地址:计划可存;出入库关键字段不强制改;详情仍有地磅。 +3. 过磅后取消地磅勾选:保存失败并提示。 +4. 多送货段:仅一段勾地磅;编辑另一段不影响该 link。 +5. 新增第二段并勾地磅:只新建一条出入库;旧段旧单保留。 +6. 调整运输:司机/现场旧链接仍可用(token 未变)。 +7. 复制计划:无地磅 link;源单不受影响。 +8. 历史修复后 `PO20260904024` 类数据:送货段可见磅单。 + +--- + +## 9. 已确认决策摘要 + +| # | 决策 | +|---|------| +| D1 | 段次级稳定身份;稳定键 = 段/产品行主键 | +| D2 | 前端回传旧 id;后端 upsert(方案 3) | +| D3 | 外围单据默认复用旧 ID,就地改内容 | +| D4 | 已过磅取消地磅勾选 → 阻断 | +| D5 | 调整运输默认不 rotate 司机/现场 token | +| D6 | 复制计划不带走地磅/token | + +--- + +## 10. 开放扩展(非本期必做) + +- 管理端「作废并重发司机/现场链接」按钮。 +- 更复杂的多段同客户匹配启发式(正式逻辑以稳定 id + 修复脚本为准)。