# 运输计划一车多卸重构 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将运输计划从「1 供方 + 1 需方」升级为「1 装货段 + N 送货段」,并贯通归档、地磅、司机/现场链接、打印与历史迁移。 **Architecture:** 在 `pl_plan` 保留计划级主数据;新增 `pl_plan_load` / `pl_plan_delivery` 及段产品子表;链接与地磅按 `stop_type`+`stop_id` 关联;打印与归档按段规则聚合。详见 `docs/superpowers/specs/2026-08-26-pl-multi-stop-redesign-design.md`。 **Tech Stack:** FastAPI · SQLAlchemy · Pydantic v2 · Vue 3 · Element Plus · MySQL 5.7 · `docs/sql/shanxia2.0.sql` --- ## 文件结构(新建 / 主要修改) | 职责 | 路径 | |------|------| | 装货/送货 ORM | `backend/app/models/pl_models.py` | | 段 CRUD / 归档 | `backend/app/services/pl_plan_service.py`(或拆 `pl_plan_stop_service.py`) | | 计划 API | `backend/app/api/pl/plan.py` | | 安排/地磅 | `backend/app/services/pl_plan_service.py`、`backend/app/api/pl/transport.py` | | 公网司机/现场 | `backend/app/api/public_site.py`(或现有 public 路由) | | DDL | `docs/sql/migrations/2026-08-26-pl-multi-stop.sql` + 合并 `docs/sql/shanxia2.0.sql` | | 迁移脚本 | `backend/scripts/migrate_pl_plan_to_multi_stop.py` | | 计划表单 | `web-admin/src/components/business/pl-plan-form-dialog/` | | 送货段 UI | 新建 `pl-plan-load-section/`、`pl-plan-delivery-section/` | | 选客户打印 | 新建 `pl-plan-delivery-customer-select-dialog/` | | 送提货/入库打印 | `pl-delivery-slip-print-dialog/`、`pl-inbound-slip-print-dialog/` | | 归档弹窗 | `pl-plan-archive-dialog/`、`views/pl/plan-archive/index.vue` | | 类型/API | `web-admin/src/api/pl/index.ts` | --- ## Phase 1:数据库与模型 ### Task 1: DDL — 装货/送货段表 **Files:** - Create: `docs/sql/migrations/2026-08-26-pl-multi-stop.sql` - Modify: `docs/sql/shanxia2.0.sql`(归档后全量快照) - [ ] **Step 1:** 在 migration SQL 中创建 `pl_plan_load`、`pl_plan_load_product`、`pl_plan_delivery`、`pl_plan_delivery_product` - [ ] **Step 2:** `pl_plan` 增加 `freight_remark VARCHAR(512) NULL` - [ ] **Step 3:** `pl_plan_contract` 增加 `stop_id VARCHAR(64) NULL`;`side` 注释扩展 `load`/`delivery` - [ ] **Step 4:** `pl_transport_db_link` 增加 `stop_type VARCHAR(20)`、`stop_id VARCHAR(64)`(可空,兼容旧数据) - [ ] **Step 5:** 在本地库执行 migration 并验证表结构 ### Task 2: SQLAlchemy 模型 **Files:** - Modify: `backend/app/models/pl_models.py` - Modify: `backend/app/schemas/pl/plan.py` - [ ] **Step 1:** 新增 `PlPlanLoad`、`PlPlanLoadProduct`、`PlPlanDelivery`、`PlPlanDeliveryProduct` 类 - [ ] **Step 2:** `PlPlan` 增加 `freight_remark`;`PlPlanContract` 增加 `stop_id` - [ ] **Step 3:** 新增 Pydantic:`PlanLoadBody`、`PlanDeliveryBody`、`PlanStopProductBody`、`*Read` 嵌套进 `PlanWithProducts` - [ ] **Step 4:** 运行 `python -c "from app.models.pl_models import PlPlanLoad"` 确认无导入错误 ### Task 3: 内部客户与地磅方向工具 **Files:** - Create: `backend/app/services/pl_plan_stop_rules.py` - Reuse: `backend/app/services/pl_internal_customer.py` - [ ] **Step 1:** 实现 `resolve_stop_dibang_defaults(customer_name, stop_type) -> (generate_dibang, dibang_direction)` - [ ] **Step 2:** 实现 `warn_qty_imbalance(plan_products, load_products, delivery_products) -> list[str]`(仅返回警告文案) - [ ] **Step 3:** 单元测试(新建 `backend/tests/test_pl_plan_stop_rules.py`)覆盖山峡装货=出库、山峡送货=入库 --- ## Phase 2:计划 CRUD API ### Task 4: 读取计划(含段) **Files:** - Modify: `backend/app/api/pl/plan.py` - Modify: `backend/app/services/pl_plan_service.py` - [ ] **Step 1:** `_row_to_dict` / `get_plan_detail` 联查 load、deliveries、段产品、合同(按 stop_id) - [ ] **Step 2:** `GET /transport/plan/{id}` 返回 `load`、`deliveries[]` - [ ] **Step 3:** 列表 `planApi.list` 增加摘要字段 `loadCustomerName`、`deliveryCustomerSummary`(后端聚合) ### Task 5: 创建/更新计划(全量段) **Files:** - Modify: `backend/app/services/pl_plan_service.py` - Modify: `backend/app/api/pl/plan.py` - [ ] **Step 1:** `create_plan_full` 接受 `load` + `deliveries[]`;事务内写段表 + `pl_plan_product` - [ ] **Step 2:** 校验:必须有 1 条 load、≥1 条 delivery;外部段合同非空 - [ ] **Step 3:** 数量不平衡调用 `warn_qty_imbalance`,响应 `warnings: string[]`(HTTP 200) - [ ] **Step 4:** `update_plan` 全量替换段(软删旧段产品行) - [ ] **Step 5:** 跑 `python scripts/check_field_consistency.py` 并修复差异 ### Task 6: 历史数据迁移 **Files:** - Create: `backend/scripts/migrate_pl_plan_to_multi_stop.py` - [ ] **Step 1:** 对无 `pl_plan_load` 的计划:supplier→load,customer/destination→delivery seq=1 - [ ] **Step 2:** 复制产品行到 load_product / delivery_product;迁移 `pl_plan_contract.side` - [ ] **Step 3:** 已归档计划:把 `inbound_*` 写入 load 产品行,`outbound_*` 写入 delivery 产品行 - [ ] **Step 4:** 干跑模式 `--dry-run` + 日志统计;正式执行前备份库 --- ## Phase 3:前端计划表单 ### Task 7: API 类型与表单数据模型 **Files:** - Modify: `web-admin/src/api/pl/index.ts` - [ ] **Step 1:** 增加 `PlanLoad`、`PlanDelivery`、`PlanStopProduct` 类型 - [ ] **Step 2:** `createFull` / `update` body 含 `load`、`deliveries` - [ ] **Step 3:** 处理响应 `warnings`,`ElMessage.warning` 展示 ### Task 8: 装货段组件(单段) **Files:** - Create: `web-admin/src/components/business/pl-plan-load-section/index.vue` - [ ] **Step 1:** 客户选择 + 合同多选(内部禁用);产品表从计划产品带出 - [ ] **Step 2:** 地址/时间/联系/注意事项(复用地址选择组件) - [ ] **Step 3:** `generateDibang` 随内部客户自动勾选(装货=出库) ### Task 9: 送货段组件(多段) **Files:** - Create: `web-admin/src/components/business/pl-plan-delivery-section/index.vue` - [ ] **Step 1:** `v-for` 送货段卡片;支持增删、排序 - [ ] **Step 2:** 每段:客户、合同、产品行、地址信息、地磅勾选 - [ ] **Step 3:** 「从计划产品同步」按钮(新增段时带出默认行) ### Task 10: 接入 `PlPlanFormDialog` **Files:** - Modify: `web-admin/src/components/business/pl-plan-form-dialog/index.vue` - [ ] **Step 1:** 移除供方/需方区块;接入 load + delivery 组件 - [ ] **Step 2:** 保存/保存并安排:组装 payload;展示 warnings - [ ] **Step 3:** 编辑回填:API 返回的 load/deliveries 写入 formData - [ ] **Step 4:** 复制计划:深拷贝段数据 ### Task 11: 查看弹窗与列表摘要 **Files:** - Modify: `web-admin/src/components/business/pl-plan-view-tabs/index.vue` - Modify: `web-admin/src/views/pl/plan/index.vue` - [ ] **Step 1:** 查看 Tab 增加「装货/送货」分段展示 - [ ] **Step 2:** 列表客户列改为装货/送货摘要 --- ## Phase 4:安排运输与地磅 ### Task 12: 安排弹窗按段地磅 **Files:** - Modify: `web-admin/src/components/business/pl-plan-arrange-dialog/index.vue` - Modify: `backend/app/services/pl_plan_service.py`(`create_transport_for_plan`) - [ ] **Step 1:** 安排弹窗列出各段 `generateDibang` 勾选(可改) - [ ] **Step 2:** 按段调用地磅生成;`pl_transport_db_link.stop_id` 写入 - [ ] **Step 3:** 调整运输:revoke 逻辑按段处理(沿用现有不可作废则阻断) --- ## Phase 5:司机 / 现场链接 ### Task 13: 司机公网页分段上传 **Files:** - Modify: 公网司机页模板/Vue(`miniprogram` 或 `web` 公网页路径,按仓库现有 driver public 页定位) - Modify: `backend/app/api/pl/plan.py` 或 public 路由 - [ ] **Step 1:** 司机页展示装货 + 全部送货段标题 - [ ] **Step 2:** 每段独立附件上传 API:`POST .../driver-feedback` body `{ stopType, stopId, urls }` - [ ] **Step 3:** 管理端 `PlPlanViewTabs` 运输信息 Tab 按段展示司机附件 ### Task 14: 现场公网页(仅内部段) **Files:** - Modify: 现场公网页 + `pl_transport_site_service.py` - [ ] **Step 1:** 单 token 页面仅渲染 `is_internal && generate_dibang` 的段 - [ ] **Step 2:** 每段:数量必填 → 附件;校验顺序 - [ ] **Step 3:** 外部送货段不在现场页出现 --- ## Phase 6:打印 ### Task 15: 送提货单选客户 **Files:** - Create: `web-admin/src/components/business/pl-plan-delivery-customer-select-dialog/index.vue` - Modify: `web-admin/src/views/pl/plan/index.vue`(打印入口) - [ ] **Step 1:** 统计送货段去重 `customer_id`;仅 1 个客户时跳过弹窗 - [ ] **Step 2:** ≥2 客户时弹窗单选;确认后带 `customerId` 打开 `PlDeliverySlipPrintDialog` ### Task 16: 送提货单数据聚合 **Files:** - Modify: `backend/app/api/pl/plan.py`(或 print 专用 endpoint) - Modify: `pl-delivery-slip-print-dialog/index.vue` - Modify: `web-admin/src/utils/pl-delivery-slip-template.ts` - [ ] **Step 1:** 后端按 `customerId` 合并该客户所有送货段产品行 - [ ] **Step 2:** 收货单位 = 客户名;地址/联系人 = **seq 最小**送货段 - [ ] **Step 3:** 模板变量对齐新字段 ### Task 17: 入库单 / 分装单 **Files:** - Modify: `pl-inbound-slip-print-dialog/index.vue` - Modify: `pl-subpack-slip-print-dialog/index.vue` - [ ] **Step 1:** 入库单「送货单位」= `load.customerName` - [ ] **Step 2:** 分装单移除客户相关占位(若有) --- ## Phase 7:归档与运费 ### Task 18: 归档弹窗分段 **Files:** - Modify: `web-admin/src/components/business/pl-plan-archive-dialog/` - Modify: `backend/app/services/pl_plan_service.py`(`archive_plan`) - [x] **Step 1:** 上区:装货表(inbound_*)+ 各送货表(outbound_*) - [x] **Step 2:** 运费区增加 `freightRemark` 输入 - [x] **Step 3:** 提交前校验全部段必填;一次 API 原子归档 - [x] **Step 4:** 库存同步改为按段汇总后写台账 ### Task 19: 运费结算展示备注 **Files:** - Modify: `web-admin/src/views/pl/freight/components/UnsettledPlanPanel.vue` - Modify: `SettlementBillPanel.vue`(结算详情弹窗) - [x] **Step 1:** 列表/详情只读展示 `freightRemark`(计划级) --- ## Phase 8:验收 ### Task 20: 端到端验收清单 - [ ] **Step 1:** 场景 30=10+15+5 建单 → 安排 → 司机/现场 → 归档 → 运费结算(需联调环境手测) - [x] **Step 2:** 送提货单 3 客户选择 + 同客户 2 段合并(单元测试覆盖) - [ ] **Step 3:** 迁移脚本在 staging 数据跑通;旧单打印/归档回归(`--dry-run` 可用,待连库) - [x] **Step 4:** 相关 pytest 通过(`vue-tsc` 全仓既有错误,与本变更无关) - [x] **Step 5:** `./scripts/update-graph.sh`(backend/frontend 已更新) --- ## Spec 覆盖自检 | 需求 | Task | |------|------| | D1 装货 1 段 | Task 5 校验、Task 8 | | D2 送货多段 | Task 9 | | D3 数量告警 | Task 3、5、10 | | D5/D6 内部地磅 | Task 3、8、9、12 | | D7 归档字段 | Task 18 | | D8 整单归档 | Task 18 | | D9/D10 链接 | Task 13、14 | | D11–D13 打印 | Task 15–17 | | D14 运费备注 | Task 1、18、19 | | D15 迁移 | Task 6 | --- ## 建议实施顺序 1. Phase 1–2(可并行前后端类型草案) 2. Phase 3(表单可见进展) 3. Phase 6 打印(业务方易验证) 4. Phase 4–5 安排与链接 5. Phase 7 归档 6. Phase 8 + Task 6 生产迁移 **预估规模:** 大重构,建议分 2~3 个 PR(① 表+API+表单 ② 链接+地磅+打印 ③ 归档+迁移)。