Files
shanxia2.0/docs/superpowers/plans/2026-08-26-pl-multi-stop-redesign.md
T
2026-08-27 14:23:18 +08:00

288 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 运输计划一车多卸重构 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+表单 ② 链接+地磅+打印 ③ 归档+迁移)。