Files
shanxia2.0/docs/superpowers/specs/2026-06-29-pl-carrier-attachments-design.md
T
2026-06-29 11:15:39 +08:00

9.4 KiB
Raw Blame History

运输计划 · 承运商资质附件 — 设计文档

日期: 2026-06-29
状态: 已批准
前置: openspec/changes/transport-plan-management/specs/vehicle-driver/spec.md、docs/superpowers/specs/2026-06-19-pl-vehicle-personnel-cert-design.md
页面: web-admin /#/pl/carrier(承运商管理)

目标

在承运商档案中增加三类必填资质/协议附件,并基于运输协议有效期控制承运商车辆是否可用于运输安排:

  1. 营业执照(附件,必填)
  2. 道路经营许可证(附件,必填)
  3. 运输协议(附件,必填 + 协议有效期必填)

协议过期时:承运商展示「协议已过期」;该承运商名下车辆不可被选择用于安排/调整运输。

已确认业务规则

规则 决策
协议过期展示 承运商详情/列表展示 el-tag「协议已过期」
协议过期选车 该承运商名下车辆不出现在安排/计划选车列表;后端 arrange 接口硬校验
合作状态 不自动改为「已终止」
历史数据(无附件的旧承运商) B:旧数据可保留不完整;补全且协议有效前不可安排运输
调整运输(协议已过期) B:承运商协议过期时禁止任何调整运输(须先更新协议)
附件数量 每类最多 5 个(对齐 CRM 客户证件)
文件类型 JPG / PNG / PDF,单文件 ≤ 20MB

说明:用户对「历史数据」与「调整运输」均回复 B;若仅指其中一项,评审时更正即可。

非目标(本期不做)

  • 营业执照、道路经营许可证的到期日与到期提醒
  • 协议过期自动写入 pl_document_reminder / 提醒中心
  • 协议过期自动变更承运商合作状态
  • 复用 pl_cert 动态证书子表(固定三字段更简单,见方案选型)

方案选型

采用方案 A:承运商表固定字段(非 pl_cert 扩展)

字段 DB 列名 类型 新建必填 说明
营业执照 business_license_urls varchar(2000) ✅ MinIO URL,逗号分隔
道路经营许可证 road_transport_license_urls varchar(2000) ✅ 同上
运输协议 transport_agreement_urls varchar(2000) ✅ 同上
协议有效期 transport_agreement_expire_date date ✅ 仅运输协议

计算字段(API 返回,不落库)

字段 逻辑
qualificationComplete 三类 URL 均非空且 transport_agreement_expire_date 非空
transportAgreementExpired qualificationComplete 且 expire_date < today

数据模型变更

pl_carrier 新增列(位于 status 与 tenant_no 之间)

ALTER TABLE `pl_carrier`
  ADD COLUMN `business_license_urls` varchar(2000) DEFAULT NULL COMMENT '营业执照附件URL,逗号分隔' AFTER `status`,
  ADD COLUMN `road_transport_license_urls` varchar(2000) DEFAULT NULL COMMENT '道路经营许可证附件URL,逗号分隔' AFTER `business_license_urls`,
  ADD COLUMN `transport_agreement_urls` varchar(2000) DEFAULT NULL COMMENT '运输协议附件URL,逗号分隔' AFTER `road_transport_license_urls`,
  ADD COLUMN `transport_agreement_expire_date` date DEFAULT NULL COMMENT '运输协议有效期' AFTER `transport_agreement_urls`;

同步更新:docs/sql/pl_tables.sql、docs/sql/shanxia2.0.sql。


后端设计

Schema(backend/app/schemas/pl/base_data.py)

  • CarrierCreate:四字段均必填(validator)
  • CarrierUpdate:四字段 optional;若传入则按新建同等校验(非空、日期合法)
  • CarrierRead:含四字段 + qualificationComplete + transportAgreementExpired(computed)

校验规则

新建承运商

  • 三类 *_urls 去空白后非空
  • transport_agreement_expire_date 必填、合法 date

更新承运商(历史数据 B)

  • 允许仅更新名称/联系人等,不强制一次性补全附件
  • 一旦传入任一附件/有效期字段,则按完整规则校验

运输资格(安排/选车用)

承运商同时满足方可参与运输:

qualificationComplete == true
AND transportAgreementExpired == false
AND status == '1'(合作中)

API 变更

接口 变更
GET/POST/PUT /transport/carrier 读写新字段;Read 带计算字段
GET /transport/vehicle 新增 query:excludeIneligibleCarrier(默认 false)
为 true 时排除:vehicle_type=carrier 且所属承运商不满足运输资格
POST /transport/order/arrange/{plan_id} 选车后校验承运商运输资格;不通过 → 400

arrange 错误文案(建议)

  • 资质不全:该承运商资质附件不完整,无法安排运输
  • 协议过期:该承运商运输协议已过期,无法安排运输
  • 调整运输且当前计划车辆所属承运商协议过期:该承运商运输协议已过期,请先更新协议后再调整运输(HTTP 400,禁止提交)

辅助函数

建议 app/services/pl_carrier_service.py:

  • carrier_qualification_complete(carrier) -> bool
  • carrier_agreement_expired(carrier, today: date) -> bool
  • carrier_eligible_for_transport(carrier, today: date) -> bool
  • assert_carrier_eligible_for_transport(db, carrier_id, tenant_no) — arrange 与 vehicle 过滤共用

前端设计

1. 承运商列表 / 编辑(views/pl/carrier/index.vue)

  • 弹窗宽度:520px → 720px
  • 新增分区 「资质附件」(参考 CRM CustomerFormDialog 证件区)
  • 组件:AttachmentUploadField × 3
    • accept="image/*,.pdf"
    • folder="pl/carrier/license" / permit / agreement
    • limit=5
  • 运输协议下方:el-date-picker type="date",value-format="YYYY-MM-DD"
  • 新建:四字段 formRules 必填;提交前 hasUploading() 检查
  • 编辑:同字段展示;历史不完整时可保存基础信息,但卡片/查看态标注「资质不全」

卡片列表增强(建议)

  • qualificationComplete === false → el-tag type="warning"「资质不全」
  • transportAgreementExpired === true → el-tag type="danger"「协议已过期」

2. 查看弹窗(pl-carrier-view-dialog)

  • 资质附件:可预览/下载(复用 AttachmentUploadField disabled 或查看态附件列表)
  • 协议有效期 + 过期 Tag

3. 安排运输(pl-plan-arrange-dialog)

  • vehicleApi.list({ vehicleStatus: 'available', excludeIneligibleCarrier: true })
  • 调整运输 B:打开弹窗时若当前计划已安排车辆,且其承运商 transportAgreementExpired 或 !qualificationComplete:
    • 表单项只读或禁用提交
    • 顶部 el-alert type="warning":请先更新承运商资质/协议
  • 提交 arrange 前无需额外前端拦截(后端兜底),但可 preempt 提示

4. 计划表单预选车辆(pl-plan-form-dialog)

  • 与安排运输相同:excludeIneligibleCarrier: true

5. API 类型(api/pl/index.ts)

扩展 Carrier 接口与 VehicleQuery。


OpenSpec 更新

更新 openspec/changes/transport-plan-management/specs/vehicle-driver/spec.md 中 承运商管理:

Scenario: 承运商资质附件维护

  • WHEN 管理员新增承运商
  • THEN 系统 SHALL 要求上传营业执照、道路经营许可证、运输协议,并填写运输协议有效期
  • AND 附件 SHALL 使用 AttachmentUploadField 卡片化布局,URL 逗号分隔存库

Scenario: 协议过期阻断运输

  • WHEN 承运商运输协议有效期早于当前日期
  • THEN 系统 SHALL 展示「协议已过期」
  • AND 该承运商名下车辆 SHALL NOT 出现在安排运输与计划预选车辆列表
  • AND 安排/调整运输接口 SHALL 拒绝使用该承运商车辆
  • AND 协议过期时 SHALL 禁止调整运输,直至更新协议

Scenario: 历史承运商兼容

  • WHEN 存在未补全资质附件的历史承运商
  • THEN 系统 SHALL 允许保留基础档案
  • AND 在其资质补全且协议有效前 SHALL NOT 允许安排运输

影响范围

层级 文件
SQL docs/sql/pl_tables.sql, docs/sql/shanxia2.0.sql, 迁移脚本
Model backend/app/models/pl_models.py
Schema backend/app/schemas/pl/base_data.py
Service backend/app/services/pl_carrier_service.py(新建)
API backend/app/api/pl/base_data.py, backend/app/api/pl/transport.py
前端 views/pl/carrier/index.vue, pl-carrier-view-dialog, pl-plan-arrange-dialog, pl-plan-form-dialog, api/pl/index.ts
OpenSpec vehicle-driver/spec.md

验收标准

  1. 新建承运商:缺任一附件或协议有效期 → 前端/后端均拦截
  2. 历史承运商无附件:可编辑保存基础字段;其车辆不在安排运输选车列表
  3. 补全资质且协议未过期:车辆恢复可选
  4. 协议过期:承运商展示「协议已过期」;车辆不可选;arrange 返回 400
  5. 已安排计划 + 承运商后过期:打开「调整运输」不可提交,提示先更新协议
  6. 自有车辆(vehicle_type=self)不受承运商规则影响
  7. 查看弹窗可预览/下载三类附件

工作量估算

约 2 人日(含 DDL、CRUD、表单/查看、选车过滤、arrange 校验、联调)。