15 KiB
运输计划归档 — 设计文档
日期: 2026-06-25
状态: 待审阅
前置:docs/运输计划管理-需求说明文档.md、docs/superpowers/specs/2026-06-21-pl-arrange-transport-refine-design.md、openspec/changes/transport-plan-management/specs/transport-plan/spec.md
界面模式: P06 复杂业务列表 + 弹窗表单;归档弹窗上区编辑、下区只读对照(对齐PlPlanArrangeDialog)
目标
在【运输管理】下新增菜单【运输计划归档】,提供与【运输计划】相同结构的列表与检索;操作列仅 查看 / 编辑 / 归档。归档通过专用弹窗一次性完成:填写产品实际数量 → 调整运费 → 执行归档。原【运输计划】页移除全部归档入口,归档职责集中到本菜单。
已确认决策(2026-06-25)
| # | 决策 | 说明 |
|---|---|---|
| D1 | 列表展示全部计划 | 默认不过滤 archiveStatus;已归档计划仍保留在列表中作为历史可查 |
| D2 | 已归档也可调整 | 已归档行仍显示「查看 / 编辑 / 调整」;「归档」按钮在已归档时文案改为「调整」,打开同一弹窗修改 actualQty 与运费 |
| D2a | 归档页编辑不受归档态限制 | 本菜单内 PlPlanFormDialog 对已归档计划可打开;后端 update_plan 移除「已归档不可修改」阻断(运输计划页仍通过 UI 隐藏已归档行的编辑入口) |
| D3 | 原页移除归档入口 | views/pl/plan/index.vue 去掉「批量归档」;views/pl/plan/detail.vue 去掉「归档」按钮;归档只在新区操作 |
| D4 | 弹窗布局对齐安排运输 | 上区:归档表单;下区:完整 PlPlanViewTabs(计划概况 / 运输信息含司机·现场链接 / 合同信息) |
| D5 | 归档 API 增强 | 单次请求原子完成:写 actual_qty + 同步入库/出库数与金额 + 更新运费 + 改归档/完成状态 |
| D6 | 列表/筛选与运输计划一致 | 列、筛选字段、分页行为与 /#/pl/plan 相同;可额外增加「归档状态」筛选项(展开区) |
非目标(本期不做)
- 跟进状态 12 态完整流转(仍用现有
draft/processing/completed/cancelled简化枚举;归档时将follow_status置为completed) - 归档后允许反归档 / 撤销
- 运费结算单已生成后的运费修订流程(见约束:有关联结算且非草稿则阻断改运费)
- OpenSpec 独立 change 归档(可在实现后补
openspec/changes/条目)
一、页面与路由
1.1 新菜单
| 项 | 值 |
|---|---|
| 菜单名 | 运输计划归档 |
| 路由 path | plan-archive |
| Hash | /#/pl/plan-archive |
| 组件 | web-admin/src/views/pl/plan-archive/index.vue |
| 列表标题 | 运输计划归档(写入 App.vue → panelTitleBarTextRules) |
| menuCode | pl:plan-archive:list |
| 排序 | 紧接「运输计划」之后,order_num=10.5 或 11(危化品证上传顺延) |
1.2 权限按钮
| 权限码 | 用途 |
|---|---|
pl:plan-archive:list |
菜单 + 列表 |
pl:plan-archive:query |
查看弹窗 |
pl:plan-archive:edit |
编辑弹窗(含已归档) |
pl:plan-archive:archive |
打开归档/调整弹窗 + 提交(含已归档调整) |
编辑可复用现有 PlPlanFormDialog + pl:plan:edit 或映射到 pl:plan-archive:edit(实现时二选一,推荐归档页独立权限,后端 update 仍走 /transport/plan/{id})。
1.3 列表行为(D1 / D2)
- 数据范围:调用同一
planApi.list,默认不传archiveStatus(全部)。 - 筛选:与运输计划相同 6 项;展开区增加「归档状态」(未归档 / 已归档 / 全部)。
- 表格列:与
views/pl/plan/index.vue一致(计划编号、日期、类型、客户、产品、证书、跟进/运输状态、金额、车牌、司机、跟进人等)。 - 操作列
min-width约 260(3 个 link 按钮),fixed="right"。
| 条件 | 查看 | 编辑 | 归档 / 调整 |
|---|---|---|---|
followStatus !== 'cancelled' |
✅ | ✅ | ✅(未归档文案「归档」,已归档文案「调整」) |
followStatus === 'cancelled' |
✅ | ❌ | ❌ |
1.4 工具栏
| 按钮 | 规则 |
|---|---|
| 已选 N 项 | 保留 |
| 批量归档 | 保留;仅对选中行中未归档且未作废的执行;已归档跳过并汇总提示 |
| 新增 / 删除 / 证书 / 安排 | 不出现 |
1.5 原运输计划页改动(D3)
| 文件 | 改动 |
|---|---|
views/pl/plan/index.vue |
删除「批量归档」按钮及 handleBatchArchive;删除 FolderChecked 导入(若无他用) |
views/pl/plan/detail.vue |
删除「归档」按钮及 handleArchive |
二、归档弹窗(PlPlanArchiveDialog)
2.1 布局
┌──────────────────────────────────────────────────────────────┐
│ 运输计划归档 · {planNo} [×] │
├──────────────────────────────────────────────────────────────┤
│ ▲ 上区:归档操作(max-height ~42vh,可滚动) │
│ [产品实际数量] PlPlanFormProductTable │
│ showActualQty + editableQty(仅 actualQty) │
│ [运费信息] 复用安排运输区块(只读 hasFreight 来自计划)│
│ [归档说明] 可选 remark(写入 plan.remark 或仅展示) │
├──────────────────────────────────────────────────────────────┤
│ ▼ 下区:计划单查看(flex:1,min-height 280px) │
│ PlPlanViewTabs embedded — 三 Tab 与查看/安排弹窗一致 │
└──────────────────────────────────────────────────────────────┘
│ 取消 │ 确认归档 / 确认保存 │
模式切换:
| 模式 | 标题后缀 | 主按钮 | 提交语义 |
|---|---|---|---|
| 首次归档 | 运输计划归档 · {planNo} |
确认归档 | 写数量/运费 + 置 archived |
| 已归档调整 | 调整归档内容 · {planNo} |
确认保存 | 仅写数量/运费,不改变 archive_status / complete_status |
尺寸与规范:dialog-form-layout、align-center、width: min(1680px, calc(100vw - 80px))、modal-class: order-module-dialog-mask、backdrop-filter: none、表单 label-position="top";底栏 Close / Check 图标间距 6px。
2.2 上区 — 产品实际数量
- 使用
PlPlanFormProductTable:show-actual-qty、editable-qty;计划数量列只读(不传 editable 到 quantity 或单独 propeditableActualQtyOnly)。 - 初始值:
actualQty ?? quantity ?? inboundEstimateQty ?? outboundEstimateQty ?? 0(与usePlPlanViewDetail一致)。 - 校验:每行
actualQty >= 0必填;至少一行有有效数量。 - 提交后后端按
plan_type同步:inbound:inbound_qty = actual_qty,inbound_amount = inbound_qty × inbound_price(价缺省用tax_price)outbound:outbound_qty = actual_qty,outbound_amount = outbound_qty × outbound_pricedirect:仅写actual_qty,重算tax_subtotal若业务需要(按 quantity 单价比例,或保持计划金额不变 — 本期以 actual_qty 落库为主,金额字段按 inbound/outbound 规则)
2.3 上区 — 运费信息
复用 PlPlanArrangeDialog 运费区块 + usePlFreightCalc:
| 字段 | 规则 |
|---|---|
hasFreight |
来自计划,只读 |
distanceKm |
可编辑;预填 transport / plan |
freightType |
by_weight / by_trip |
| 运输量 | by_weight 时默认 actualQty 汇总(sumProductActualQuantities) |
freightPrice / freightAmount |
可编辑;联动计算可手动覆盖 |
hasInvoice |
可编辑 |
hasFreight=0 时显示「本计划无运输费用」,跳过运费写入。
2.4 下区 — 只读对照
- 完整嵌入
PlPlanViewTabs(embedded),含:- 计划概况(含产品 actualQty 展示列)
- 运输信息(车辆、物流、运费、司机链接复制、现场链接复制、回传、地磅)
- 合同信息
- 提交成功后
tabsRef.reload()刷新下区。
2.5 入口与禁用
followStatus !== 'cancelled'均可打开(含已归档)。- 打开时
planApi.get+ 若已安排则读 transport 运费预填;已归档时进入「调整」模式。
三、后端 API
3.1 增强归档接口
方案:扩展 POST /transport/plan/{id}/archive,请求体可选;无 body 时保持向后兼容(仅改状态,供脚本/旧调用),前端归档弹窗必须带 body。
Request PlanArchiveBody:
{
"products": [
{ "id": "计划产品明细ID", "actualQty": 4.7 }
],
"freight": {
"distanceKm": 120.5,
"freightType": "by_weight",
"freightPrice": 50,
"freightAmount": 235,
"hasInvoice": 1
}
}
处理顺序(单事务):
- 校验:计划存在、租户、
follow_status != cancelled - 校验:每个
products[].id属于该计划 - 更新
pl_plan_product.actual_qty;按plan_type写inbound_qty/outbound_qty及金额 - 若
plan.has_freight == 1且 body.freight 存在:- 若存在
plan.transport_id→ 更新pl_transport运费字段 +transport_qty - 更新
plan.freight_amount - 若运输单已纳入非草稿运费结算 → 400 阻断(查
pl_freight_settlement关联)
- 若存在
- 状态分支:
- 若
archive_status != archived:置archive_status=archived、complete_status=yes、follow_status=completed - 若 已归档:跳过状态字段,仅更新业务数据(D2)
- 若
- 写
pl_plan_change:change_type=archive(首次)或archive_adjust(已归档调整) - commit
update_plan(编辑弹窗):移除 archive_status == archived 时的 400 阻断;已归档计划允许通过 PUT 修改计划主表/产品(与归档弹窗互补:表单改计划信息,归档弹窗改 actualQty/运费)。
Response:PlanWithProducts(与 get 一致)
3.2 Schema 新增
文件:backend/app/schemas/pl/plan.py
PlanArchiveProductItem:id,actualQtyPlanArchiveFreightBlock:与 transport arrange 运费字段子集一致PlanArchiveBody:products,freight?
3.3 Service 抽取
在 pl_plan_service.py 新增:
apply_archive_product_quantities(plan, products_payload)— 写 actual + inbound/outboundapply_archive_freight(plan, transport, freight_payload, products)— 运费同步
便于单测与 archive_plan 路由保持薄。
四、前端文件清单
| 文件 | 动作 |
|---|---|
views/pl/plan-archive/index.vue |
新建 — 列表页 |
components/business/pl-plan-archive-dialog/index.vue |
新建 — 归档弹窗 |
composables/usePlPlanList.ts |
新建(推荐) — 从 plan/index 抽取 list/search/pagination |
views/pl/plan/index.vue |
删除批量归档;可选改用 composable |
views/pl/plan/detail.vue |
删除归档按钮 |
router/modules/pl.ts |
新增 plan-archive 路由 |
api/pl/index.ts |
planApi.archive(id, body) 扩展 |
utils/router/menu-icon.ts |
pl:plan-archive:list 图标 |
App.vue |
panelTitleBarTextRules 增加「运输计划归档」 |
docs/sql/shanxia2.0.sql |
sys_menu 插入归档菜单 + 按钮权限 |
backend/app/api/pl/plan.py |
增强 archive_plan;update_plan 允许已归档编辑 |
backend/app/schemas/pl/plan.py |
Archive body schema |
backend/app/services/pl_plan_service.py |
archive 业务函数 |
4.1 组件复用策略
| 组件 | 用途 |
|---|---|
PlPlanViewDialog |
查看 |
PlPlanFormDialog |
编辑 |
PlPlanViewTabs |
归档弹窗下区 |
PlPlanFormProductTable |
归档弹窗 actualQty(可增 prop actualQtyEditableOnly) |
usePlFreightCalc |
归档弹窗运费 |
五、数据库与菜单 SQL
-- 菜单(示例 ID,实现时按现有 PL_* 规范)
INSERT INTO sys_menu (...) VALUES
('PL_PLAN_ARCHIVE', '运输计划归档', 'PL_MENU_ROOT', 'C', 'plan-archive', 'pl/plan-archive/index', 'pl:plan-archive:list', ...);
-- 按钮权限(F 类型,parent=PL_PLAN_ARCHIVE)
-- pl:plan-archive:query | edit | archive
六、安全与约束
| 项 | 规则 |
|---|---|
| 已归档 | 可继续「编辑」与「调整」actualQty/运费;不可反归档(archive_status 不回退为 unarchived) |
| 已作废 | 不可归档 / 不可调整 |
| 运费结算 | 运输单若已在「已提交/审核通过/已确认」结算单中,禁止归档时改运费 |
| 无 transport | has_freight=1 但无 transport_id 时,仅更新 plan.freight_amount,并 warn 日志(或 400 要求先安排运输 — 推荐 400:有运费必须先安排运输) |
| 租户 | 所有读写 tenant_no |
| 事务 | 产品 + 运费 + 归档同一 commit |
七、验收标准
| # | 场景 | 预期 |
|---|---|---|
| V1 | 打开归档列表 | 展示全部计划;筛选与运输计划一致;多「归档状态」筛选项 |
| V2 | 未归档行 | 查看 / 编辑 / 归档 均可用 |
| V3 | 已归档行 | 查看 / 编辑 / 「调整」均可用 |
| V4 | 打开归档/调整弹窗 | 上区 actualQty、运费可编辑;下区三 Tab 完整 |
| V5 | 首次提交归档 | actual_qty、运费、archive/complete 状态一次成功 |
| V6 | 已归档后再次「调整」 | 仅更新 actual_qty/运费/金额;archive_status 保持 archived |
| V6a | 已归档「编辑」 | PlPlanFormDialog 可保存计划主表/产品变更 |
| V7 | 原运输计划页 | 无批量归档、详情无归档按钮 |
| V8 | 批量归档 | 仅处理未归档行;已归档跳过有提示 |
| V9 | 有运费未安排运输 | 归档 400,提示先安排运输 |
| V10 | 按重量运费 | 运输量默认 actualQty 合计,改 actualQty 后运费量联动 |
| V11 | 下区链接 | 司机/现场链接可复制(与安排运输下区一致) |
| V12 | 权限 | 无 pl:plan-archive:archive 不显示归档按钮 |
八、实施分期
| Phase | 内容 |
|---|---|
| P1 | 后端:PlanArchiveBody + service + 增强 archive_plan |
| P2 | 前端:PlPlanArchiveDialog + plan-archive/index.vue + 路由/菜单/API |
| P3 | 原页移除归档入口;抽取 usePlPlanList(可选) |
| P4 | 联调验收 V1–V12 |
九、参考路径
| 说明 | 路径 |
|---|---|
| 运输计划列表 | web-admin/src/views/pl/plan/index.vue |
| 安排运输弹窗(布局基准) | web-admin/src/components/business/pl-plan-arrange-dialog/index.vue |
| 下区三 Tab | web-admin/src/components/business/pl-plan-view-tabs/index.vue |
| 产品表 | web-admin/src/components/business/pl-plan-form-dialog/PlPlanFormProductTable.vue |
| 运费计算 | web-admin/src/composables/usePlFreightCalc.ts |
| 现归档 API | backend/app/api/pl/plan.py → archive_plan |
| 需求 BR-15/32/42 | docs/运输计划管理-需求说明文档.md |