15 KiB
运输管理 · 固定必备证书槽位 — 设计文档
日期: 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 确认)
- 固定必备槽位:打开编辑弹窗时,系统预置必备证书行,名称锁定不可改、不可删。
- 必备三项必填:每个必备槽位的 证号、有效期、附件 均必填。
- 可增补其他证件:除必备行外,仍可「添加其他证书」(名称可选/可输,可删)。
- 山峡自有车强制:仅
vehicleType=self的车辆强制主车/挂车必备;承运商等非山峡车辆不强制。 - 人员类型:人员主数据新增类型——驾驶员、押运员、驾驶员兼押运员;按类型预置必备证书。
- 历史数据:一律阻断——只要保存(含仅改备注),必备证书未补齐则拒绝。
与 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 | 人员必备按类型 | 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:
- 若
pl_vehicle_personnel中该人仅有role=driver→driver - 若仅有
role=escort→escort - 若同时存在 driver 与 escort 关联 →
both - 若无关联记录 → 默认
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 传入对应requiredCertNamesvehicleType=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(aliaspersonnelType)。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 展示人员类型、按钮图标等)。
七、验收标准
- 山峡自有车新增:打开弹窗即见主车 3 个必备空槽;填挂车号后出现挂车 4 槽。
- 必备槽位:名称不可编辑、不可删除;证号/有效期/附件任一缺失则无法保存。
- 可添加「道路运输证」等额外证书并正常保存。
- 承运商车辆在「全部车辆信息」中无必备槽位(或有不阻断),缺证仍可保存。
- 人员页必选类型;三类人员必备槽位正确;改类型后槽位同步。
- 历史自有车/人员:仅改备注时若缺必备证,保存返回明确错误。
- 山峡自有车:驾驶员类型不可选为押运员 role(及反向);both 均可。
- 后端 duplicate 校验:直接调 API 缺必备证返回 400。
python scripts/check_field_consistency.py通过(personnel_type 字段)。- 迁移脚本可重复执行;现有
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 人天 |
十、实施任务清单(概要)
- SQL:
personnel_type列 + 迁移推断脚本 - Backend:Model / Schema /
_row_to_dict/ 校验服务 / vehicle & personnel API - Frontend utils:统一
validateRequiredCertSlots、buildRequiredCertRows - Component:
PlCertSlotFormList - Pages:
self-vehicle、vehicle、personnel - 查看态:人员类型 Tag;缺失 suffix 与必备校验口径一致
- 自测 +
check_field_consistency.py - OpenSpec scenario 增补(可选同 PR)
附录:与现有常数对照
需求与 pl-vehicle-cert.ts / pl-personnel-cert.ts 完全一致,无需调整必备名称列表,仅需 启用 校验并改造 UI 为固定槽位。