Files
shanxia2.0/docs/superpowers/specs/2026-08-20-pl-plan-contract-complete-design.md
T
2026-08-27 19:38:01 +08:00

9.0 KiB
Raw Blame History

运输计划合同完善 · 设计说明

Demo:docs/fresh-mint-pl-plan-contract-complete-demo.html(方案 B 为采纳交互)
日期:2026-08-20
状态:已实现(待联调验收)

1. 背景与目标

运输计划新增/编辑时,供需双方合同已改为非必填,便于先建计划再补合同。业务上:非内置公司一侧仍应关联合同;内置公司该侧合同可免。

内置公司(合同可免):

  • 安庆市山峡化工有限公司
  • 安徽云栋化工有限公司

判定逻辑复用现有工具:

  • 前端:web-admin/src/utils/pl-internal-customer.ts → isPlInternalCustomerName
  • 后端:backend/app/services/pl_internal_customer.py → is_internal_customer_name

目标: 提供独立的「运输计划合同完善」界面,只列出仍缺合同的未作废计划,并支持在列表行内直接关联已有 CRM 合同(不支持在本页新增/编辑合同)。

2. 已确认决策

项 结论
实现路径 独立菜单页 + 专用列表/保存 API(路径 1)
交互 列表「补充」打开弹窗关联本段合同(已由行内展开改为弹窗,见 docs/fresh-mint-pl-plan-contract-supplement-dialog-demo.html)
计划范围 仅未作废(follow_status != cancelled),含待安排/已安排/运输中等;已归档但未作废的计划仍入列(未采用「排除归档」)
合同数量 每侧可多选(与计划新增/编辑一致)
保存方式 两侧各自「保存」;补完一侧即可落库
菜单角标 要;显示当前缺合同条数(对齐危化品证)
非目标 本页不新增/编辑/审批合同;不改计划其它字段;不做小程序页

3. 信息架构与入口

3.1 菜单

挂在 运输管理 → 运输计划 分组下,与「运输计划」「运输计划归档」并列:

项 值
菜单名 运输计划合同完善
Hash 路由 /#/pl/plan-module/plan-contract
组件 web-admin/src/views/pl/plan-contract/index.vue
menuCode pl:plan-contract:list
更新权限 pl:plan-contract:update

路由注册:web-admin/src/router/modules/pl.ts(plan-module children)。
菜单/权限 SQL:写入 docs/sql(与现有 pl 菜单脚本风格一致)。

3.2 UI 模式

P02 复杂业务列表,参考:

  • web-admin/src/views/pl/plan-cert/index.vue(待办型衍生列表 + 角标)
  • web-admin/src/views/pl/plan/index.vue(列表骨架、筛选、日期快捷)

视觉遵循 docs/web-admin-ui-spec-fresh-mint.md。

页面骨架:

panel
  ├─ panel-top:标题 + 待完善数量
  ├─ filter-block
  ├─ list-toolbar(日期快捷 + 快速搜索 + 刷新;无「新增」)
  ├─ list-table(可展开行)
  └─ pagination

4. 缺口判定规则

对每条未删除、未作废的运输计划:

  1. 供方侧
    • 若 is_internal_customer_name(supplier_name) → 该侧不要求合同(可免)
    • 否则 → 至少 1 条有效供方侧 pl_plan_contract(side=supplier, del_flag=0)
  2. 需方侧
    • 对 customer_name 同理(side=demander)
  3. 入列表条件:供方侧缺口 或 需方侧缺口(任一侧不满足要求)

列表/角标口径必须与上述规则一致(前后端同一工具函数)。

5. 列表与行内展开

5.1 列表列

列 说明
计划编号 主色链接 → 打开现有计划只读查看
计划日期
供方 名称;副文案:已关联 N 份 / 未关联 / 内置可免
需方 同上
合同缺口 Tag:缺供方合同 / 缺需方合同 / 两侧均缺
跟进状态 现有跟进状态 Tag
操作 「展开补充」/「收起」;「查看计划」

5.2 筛选与排序

  • 筛选:计划编号、缺口类型(全部 / 仅缺供方 / 仅缺需方 / 两侧均缺)、供方或需方关键词、计划日期区间
  • 工具栏:日期快捷 + 快速搜索(计划编号/供方/需方)
  • 默认排序:plan_date DESC,其次 create_time DESC
  • 数据权限:与运输计划列表相同(apply_pl_scope)

5.3 行内展开交互

  • 同时仅展开一行;展开另一行时收起当前行
  • 展开区左右两栏:供方合同 | 需方合同
侧状态 UI
待补充 已选合同卡片(可移除)+「选择合同」+ 该侧「保存」
已齐 展示已关联合同;仍允许改选后保存
可免(内置) 灰态说明;无选择器、无保存按钮
  • 「选择合同」复用 ContractSelector(多选;可选状态与计划页一致:已审批 / 执行中);按该侧 customerId 过滤
  • 不提供新建/编辑合同入口
  • 保存只提交该侧合同列表;成功后刷新该行;若两侧均已满足缺口规则,该行从列表移除并刷新角标
  • 有未保存变更时收起:二次确认

组件复用:优先复用 PlPlanContractMultiSelect / ContractSelector 的选择与卡片展示能力。

6. API 设计

建议挂在运输计划模块下(路径示例,实现时可微调前缀,须注册到 app/api + main):

6.1 缺合同分页列表

GET /api/pl/plans/contract-gaps

Query:分页 + planNo、gapType(supplier | demander | both)、客户关键词、planDate 起止、快速搜索等。

固定过滤:del_flag=0、follow_status != cancelled、存在合同缺口(不按 archive_status 排除)。

响应行至少包含:

  • 计划基础字段(编号、日期、供需方 id/name、跟进状态等)
  • gapSides: ("supplier" | "demander")[]
  • supplierContractExempt / demanderContractExempt
  • supplierContracts / demanderContracts(PlanContractLink[],展开回填)

6.2 单侧保存合同

PUT /api/pl/plans/{planId}/contracts/{side}

  • side ∈ supplier | demander
  • Body:{ "contracts": PlanContractLink[] }(可多份;空数组表示清空该侧)

服务端:

  1. 校验计划存在、未作废、租户与数据权限
  2. 校验合同可选(归属该侧客户、状态允许等),复用 assert_contract_selectable_for_plan / validate_plan_party_contracts 中与合同相关的能力;本接口不因「非内置且空列表」直接 422(允许先清空;列表是否仍展示由缺口规则决定)
  3. 加载对侧现有合同 + 本侧请求列表 → 调用现有 replace_plan_contracts 整体替换关联表
  4. 返回:更新后两侧合同 + 最新 gapSides(及豁免标记)

不走完整 PlanUpdate;不修改产品、运输、客户等字段。

6.3 与现有「合同非必填」的关系

计划创建/全量更新已不再强制非内置侧必须有合同。本页是事后补齐通道;主计划编辑页仍可改合同,行为保持一致。

7. 菜单角标

  • menuCode:pl:plan-contract:list
  • 在 backend/app/services/pl_menu_badge_service.py 增加计数函数,口径与列表默认条件一致
  • 加入 SUPPORTED_MENU_CODES;前端侧栏展示数字(对齐 pl:plan-cert:list)

8. 权限

权限码 用途
pl:plan-contract:list 进入页面、拉列表、角标
pl:plan-contract:update 单侧保存合同

「查看计划」复用现有计划查看能力(与 plan-cert 跳转查看一致,通常依赖 pl:plan:list 或已有详情权限)。

9. 前端文件(预期)

文件 职责
web-admin/src/views/pl/plan-contract/index.vue 页面
web-admin/src/router/modules/pl.ts 路由
web-admin/src/api/pl/* list gaps / save side
(可选)小组件 行内展开双侧合同区,避免单文件过大

10. 后端文件(预期)

文件 职责
app/api/pl/plan.py 或独立 router 两个端点
app/services/pl_plan_service.py(或小服务模块) 缺口查询、单侧保存编排
app/schemas/pl/plan.py 请求/响应模型
app/services/pl_menu_badge_service.py 角标
docs/sql/* 菜单与权限

11. 错误与边界

  • 已作废计划:列表不出现;若 ID 直调保存 → 400/422,提示不可修改
  • 合同不属于该侧客户或状态不可选 → 422,明确文案
  • 并发:单侧保存以「读对侧最新 + 写本侧」再 replace_plan_contracts;后写覆盖该次替换结果(可接受;不做乐观锁,除非后续有冲突投诉)
  • 内置侧误传合同:允许关联(不强制拒绝),UI 默认不展示选择器

12. 测试要点

  • 缺口判定:双非内置无合同 → 入列;一侧内置一侧外部无合同 → 仅缺外部侧;双侧内置 → 不入列
  • 单侧保存后对侧合同不变
  • 补齐后行消失、角标减一
  • 作废计划不出现在列表与角标
  • 数据权限用户看不到范围外计划
  • 前端:展开互斥、未保存收起确认、无新建合同入口

13. 非目标(明确不做)

  • 合同 CRUD / 审批
  • 修改运输计划主数据、产品、运输安排
  • 小程序「合同完善」页
  • 主计划列表增加「缺合同」Tab(本设计采用独立页)