2026-07-12-pl-required-cert-slots-design.md 15 KB

运输管理 · 固定必备证书槽位 — 设计文档

日期: 2026-07-12
状态: 已实现(2026-07-12)
前置: docs/superpowers/specs/2026-06-19-pl-vehicle-personnel-cert-design.md(动态多证、首期不配置必检)
界面模式: P01 标准列表 + 弹窗表单(/#/pl/vehicle、/#/pl/self-vehicle、/#/pl/personnel)

背景与问题

现状(2026-06-19 上线后)

环节 行为
证书录入 用户点击「添加证书」→ 手选/手输证书名称 → 填写证号、有效期 → 上传附件
前端校验 仅有证书行时「证书名称不能为空」;不校验必备证种齐全
必备常数 pl-vehicle-cert.ts / pl-personnel-cert.ts 已定义必备名称,但 validateVehicleRequiredCerts 已 @deprecated
缺失提示 查看态标题可显示「(证书缺失)」;保存不阻断
人员类型 pl_personnel 无人员类型字段;司机/押运员角色仅在车辆关联子表 pl_vehicle_personnel.role
承运商车辆 「全部车辆信息」含自有 + 承运商;必备缺失检查仅对 vehicleType=self

业务诉求(2026-07-12 确认)

  1. 固定必备槽位:打开编辑弹窗时,系统预置必备证书行,名称锁定不可改、不可删。
  2. 必备三项必填:每个必备槽位的 证号、有效期、附件 均必填。
  3. 可增补其他证件:除必备行外,仍可「添加其他证书」(名称可选/可输,可删)。
  4. 山峡自有车强制:仅 vehicleType=self 的车辆强制主车/挂车必备;承运商等非山峡车辆不强制。
  5. 人员类型:人员主数据新增类型——驾驶员、押运员、驾驶员兼押运员;按类型预置必备证书。
  6. 历史数据:一律阻断——只要保存(含仅改备注),必备证书未补齐则拒绝。

与 2026-06-19 设计的关系

2026-06-19 明确「首期不配置必检规则、安排运输不硬性拦截证书」。本次变更:

  • 升级 车辆/人员主数据保存时的证书完整性要求(仅山峡自有车 + 全部人员主数据)。
  • 不变更 运输计划安排运输时对 hazard_cert_ready 的既有逻辑(危化品计划级证书另案)。
  • 沿用 pl_cert 动态多证表结构,不新增证书实例表。

已确认设计

# 决策 说明
D1 必备槽位预置 + 锁定 必备行 certName 只读、不可删除;额外行可自由增删
D2 必备字段:证号 + 有效期 + 附件 提醒天数维持默认 30,不作为必备条件
D3 仅山峡自有车强制车辆证 vehicleType=self 时校验主车 3 证、有挂车号时挂车 4 证;carrier 不校验
D4 人员类型三选一 driver 驾驶员 / escort 押运员 / both 驾驶员兼押运员;人员主数据必填
D5 人员必备按类型 driver、both → 身份证+驾驶证+从业资格证;escort → 身份证+从业资格证 已由 2026-07-13-pl-personnel-cert-split-design.md 替代:驾/押从业证拆分,both 须 4 证
D6 从业资格证别名 「危化品从业资格证」等等价匹配「从业资格证」 已废止;押运员侧仅 押运员证 可匹配 押运员从业资格证
D7 保存一律阻断 历史数据缺必备证时,任何字段保存均须先补齐
D8 前后端双校验 前端 UX + 后端 API 400,防绕过
D9 关联人员角色兼容 车辆子表 role 保留;保存山峡自有车时校验人员类型与 role 可匹配(见 §三)
D10 挂车动态 无 trailerNo 不要求挂车槽位;填写挂车号后立即要求 4 项必备

一、必备证书清单

1.1 山峡自有车辆(vehicleType=self)

部位 条件 必备证书(各需证号+有效期+附件)
主车 始终 行驶证、营运证、保险单
挂车 trailerNo 非空 行驶证、营运证、保险单、罐检报告

常数来源:PL_SELF_VEHICLE_HEAD_REQUIRED_CERT_NAMES、PL_SELF_VEHICLE_TRAILER_REQUIRED_CERT_NAMES(web-admin/src/utils/pl-vehicle-cert.ts)。

1.2 承运商 / 非自有车辆(vehicleType=carrier)

  • 不预置必备行(或预置但不阻断,推荐:不预置、不校验,与现网自由录入一致)。
  • 用户仍可手动添加任意证书。

1.3 人员(pl_personnel.personnel_type)

personnel_type 展示文案 必备证书
driver 驾驶员 身份证、驾驶证、驾驶员从业资格证
escort 押运员 身份证、押运员从业资格证
both 驾驶员兼押运员 身份证、驾驶证、驾驶员从业资格证、押运员从业资格证

常数来源:PL_PERSONNEL_DRIVER_REQUIRED_CERT_NAMES、PL_PERSONNEL_ESCORT_REQUIRED_CERT_NAMES。

1.4 「证书已填」判定

必备槽位 同时满足:

certName  匹配目标名称(从业资格证含别名规则)
certNo    非空 trim
expireDate 非空
attachmentUrls 非空 trim(至少一个 URL)

二、数据模型

2.1 pl_personnel 新增字段

字段 类型 必填 说明
personnel_type varchar(20) ✅(新数据;迁移见 §2.3) driver / escort / both

DDL 示例:

ALTER TABLE `pl_personnel`
  ADD COLUMN `personnel_type` varchar(20) NOT NULL DEFAULT 'both'
  COMMENT '人员类型 driver-驾驶员 escort-押运员 both-驾驶员兼押运员'
  AFTER `id_card_no`;

同步:docs/sql/pl_tables.sql、docs/sql/shanxia2.0.sql;增量脚本 backend/scripts/migration_20260712_pl_personnel_type.sql。

2.2 字典(可选增强)

dict_type 值
pl_personnel_type driver-驾驶员, escort-押运员, both-驾驶员兼押运员

列表 Tag、表单下拉可与字典对齐;必备规则仍硬编码于前后端 util,不依赖运行时字典配置(YAGNI)。

2.3 历史数据迁移 · 人员类型

按优先级推断 personnel_type:

  1. 若 pl_vehicle_personnel 中该人仅有 role=driver → driver
  2. 若仅有 role=escort → escort
  3. 若同时存在 driver 与 escort 关联 → both
  4. 若无关联记录 → 默认 both(必备要求最宽,避免误标 escort 导致缺驾驶证仍可通过)

迁移后运营需对人员主数据做一次补录/核对(D7 保存阻断会驱动补齐)。

2.4 证书表

pl_cert 无结构变更。必备/额外均存同一表;必备行通过 cert_name +(车辆)vehicle_part 识别。

可选:必备行写入时设 order_num 靠前,额外行在后。


三、业务规则

3.1 车辆保存(POST/PUT /transport/vehicle)

if vehicle_type == 'self':
    校验主车必备 3 证齐全(证号+有效期+附件)
    if trailer_no 非空:
        校验挂车必备 4 证齐全
    校验关联人员(见 3.2)
else:
    跳过车辆必备校验(仅校验 certName 非空等基础项,若有证书行)

「山峡自有车辆信息」页固定 vehicleType=self,与「全部车辆信息」中自有行逻辑一致。

3.2 山峡自有车 · 关联人员

保存时除人员主数据证书外,额外校验:

检查项 规则
类型与 role 匹配 escort 类型不可关联为 driver;driver 类型不可关联为 escort;both 均可
角色必备证书 关联行的 role=driver 时,该人员证书须满足 driver 必备;role=escort 时满足 escort 必备

说明:人员主数据按 personnel_type 校验一次;若 both 且仅关联为 escort,主数据仍须驾驶证(因类型为 both)。若未来需放宽,可另开变更。

3.3 人员保存(POST/PUT /transport/personnel)

  • personnel_type 必填。
  • 按类型校验必备证书(D5)。
  • 任意字段更新(含仅 remark)均执行完整必备校验(D7)。

3.4 挂车号动态

  • 编辑中 trailerNo 从空变为非空:前端立即展示挂车 4 个必备空槽;保存时校验。
  • 从非空变为空:挂车必备校验跳过;已有挂车 vehicle_part=trailer 证书行保存策略——保留数据但不阻断(挂车号清空后不再校验挂车必备;证书行仍随 certList 提交,后端按现有 upsert 逻辑处理)。

四、前端设计

4.1 组件策略

方案:扩展或替换 PlCertFormList → PlCertSlotFormList(推荐新组件,原组件供承运商等非强制场景复用)。

模式 props 行为
required requiredCertNames, vehiclePart?, strictFields=true 预置必备行、锁定名称、校验三字段
free 无 required 等同现有 PlCertFormList

PlVehicleCertPartTabs:

  • vehicleType=self → 各 part 传入对应 requiredCertNames
  • vehicleType=carrier → 子组件 mode=free

4.2 页面改动

页面 变更
pl/self-vehicle/index.vue 证书区 required;保存前 validateSelfVehicleCerts
pl/vehicle/index.vue 依 formData.vehicleType 切换 required/free;查看态缺失 suffix 逻辑保持
pl/personnel/index.vue 表单加「人员类型」;证书区按类型 required;列表加类型列 Tag

4.3 表单 · 人员类型

人员类型*  [ el-select ]
  - 驾驶员 (driver)
  - 押运员 (escort)
  - 驾驶员兼押运员 (both)
  • 新增默认:both 或空(须必选,无默认值更安全 → 无默认,必选)。
  • 修改类型:重新计算必备槽位;已有同名校验数据保留,新增必备行补空槽。

4.4 UI 分区

┌─ 人员证书 ──────────────────────────────────────┐
│ ▎必备证书                                      │
│   身份证    证号*  有效期*  附件*   🔒          │
│   驾驶证    ...                                 │
│ ▎其他证书                                      │
│   [+ 添加其他证书]                              │
└─────────────────────────────────────────────────┘

必备区与额外区可用分组标题或表格分段区分;必备行删除按钮隐藏。

4.5 校验与错误提示

  • 保存失败:ElMessage.warning + 可选 ElMessageBox.alert 列出缺失项。
  • 文案示例:主车「行驶证」缺少:证号、附件。
  • 长表单:scroll-to-error 滚动至证书区首个缺失槽。

五、后端设计

5.1 新增服务 · pl_cert_required_service.py(或并入 pl_cert_service.py)

def get_required_vehicle_cert_names(vehicle_type, trailer_no, part) -> list[str]
def get_required_personnel_cert_names(personnel_type) -> list[str]
def validate_required_certs(cert_rows, required_names, *, part_label=None) -> None  # raises HTTPException 400
def validate_personnel_type_role_compat(personnel_type, role) -> None

5.2 API 接入点

路由 校验时机
POST/PUT .../vehicle vehicle_type=self 时校验 cert_list + 关联人员
POST/PUT .../personnel 始终校验 personnel_type + cert_list

错误响应示例:

{
  "detail": "必备证书未齐全:主车「保险单」缺少有效期、附件;人员「张三」类型为驾驶员,缺少驾驶证"
}

5.3 Schema

  • PersonnelBase / PersonnelCreate / PersonnelUpdate / PersonnelRead 增加 personnel_type(alias personnelType)。
  • VehicleCreate/Update 无需新字段;沿用 vehicle_type。

六、OpenSpec / 工程规范

建议在 openspec/changes/transport-plan-management/ 或新 change 中增补 Scenario:

  • 山峡自有车保存 SHALL 阻断缺失必备车辆证。
  • 承运商车辆保存 SHALL NOT 因缺失必备车辆证阻断。
  • 人员保存 SHALL 按 personnel_type 阻断缺失必备人员证。

交叉核对:openspec/changes/zkxw-engineering-standards/specs/ui-conventions/spec.md(Tag 展示人员类型、按钮图标等)。


七、验收标准

  1. 山峡自有车新增:打开弹窗即见主车 3 个必备空槽;填挂车号后出现挂车 4 槽。
  2. 必备槽位:名称不可编辑、不可删除;证号/有效期/附件任一缺失则无法保存。
  3. 可添加「道路运输证」等额外证书并正常保存。
  4. 承运商车辆在「全部车辆信息」中无必备槽位(或有不阻断),缺证仍可保存。
  5. 人员页必选类型;三类人员必备槽位正确;改类型后槽位同步。
  6. 历史自有车/人员:仅改备注时若缺必备证,保存返回明确错误。
  7. 山峡自有车:驾驶员类型不可选为押运员 role(及反向);both 均可。
  8. 后端 duplicate 校验:直接调 API 缺必备证返回 400。
  9. python scripts/check_field_consistency.py 通过(personnel_type 字段)。
  10. 迁移脚本可重复执行;现有 pl_cert 数据不丢失。

八、风险与缓解

风险 缓解
历史大量缺证导致无法保存 D7 为业务明确要求;上线前导出缺证清单,运营批量补录
人员类型迁移不准 迁移规则 §2.3 + 列表展示类型供人工修正
组件复杂度上升 新组件 PlCertSlotFormList,承运商继续用旧组件
车辆页切换类型 self↔carrier 切换为 carrier 时隐藏必备槽位且不校验;切回 self 时重新注入必备槽并校验
both 类型关联 escort 仍要驾驶证 设计已说明;若业务异议可后续改为「按 role 动态必备」

九、工作量估算

模块 预估
DB + 迁移 + Schema 0.5 天
后端必备校验服务 + API 0.5 天
PlCertSlotFormList + 车辆 tabs 接入 1 天
人员页类型字段 + 证书接入 0.5 天
三页联调 + 历史场景自测 0.5~1 天
合计 约 3~3.5 人天

十、实施任务清单(概要)

  1. SQL:personnel_type 列 + 迁移推断脚本
  2. Backend:Model / Schema / _row_to_dict / 校验服务 / vehicle & personnel API
  3. Frontend utils:统一 validateRequiredCertSlots、buildRequiredCertRows
  4. Component:PlCertSlotFormList
  5. Pages:self-vehicle、vehicle、personnel
  6. 查看态:人员类型 Tag;缺失 suffix 与必备校验口径一致
  7. 自测 + check_field_consistency.py
  8. OpenSpec scenario 增补(可选同 PR)

附录:与现有常数对照

需求与 pl-vehicle-cert.ts / pl-personnel-cert.ts 完全一致,无需调整必备名称列表,仅需 启用 校验并改造 UI 为固定槽位。