日期: 2026-07-05
状态: 已实现(2026-07-05)
前置:docs/superpowers/specs/2026-07-04-pl-hazard-cert-ready-design.md(计划级「证书齐备」人工确认,已实现)
界面模式: P06 复杂业务列表 + 弹窗表单(延续/#/pl/plan-cert危化品证上传页)
| 环节 | 行为 |
|---|---|
| 危化品识别 | 以 CRM 产品主数据 isToxic / isExplosive 为准(计划产品行冗余快照) |
| 证书存储 | pl_plan_product.purchase_cert_url(购买证)、transport_cert_url(界面称「合法使用证明」) |
| 上传页 UI | 所有危化品产品统一展示「购买证 + 合法使用证明」两个上传位 |
| 安排运输 | 仅校验 pl_plan.hazard_cert_ready 人工确认标志,不强制 URL 非空 |
证书类型应 按产品危化属性区分,而非所有危化品统一双证:
| 产品属性 | 所需证书 |
|---|---|
| 仅易制毒 | 购买证 + 运输证 |
| 仅易制爆 | 合法使用证明 |
| 易制毒 + 易制爆(双属性) | 购买证 + 运输证 + 合法使用证明(三种) |
transport_cert_url 在库表 comment 与 UI 中均标注为「合法使用证明」,与易制毒所需的「运输证」语义冲突。legal_use_cert_url,三字段语义固定、互不混用。transport_cert_url。hazard_cert_ready,不因 URL 缺失阻断(D4/D10);列表展示缺失状态供操作员自查。| # | 决策 | 说明 |
|---|---|---|
| D1 | 三字段独立存储 | purchase_cert_url(购买证)、transport_cert_url(运输证)、legal_use_cert_url(合法使用证明) |
| D2 | 按属性决定所需证种 | 仅毒 → 购买证+运输证;仅爆 → 合法使用证明;毒+爆 → 三种都要 |
| D3 | 上传不强制齐全 | 延续 D3/D10:保存时允许 0~N 个附件;不恢复 URL 硬性 gate 安排运输 |
| D4 | 安排运输仍查齐备标志 | 不新增 URL 非空校验;hazard_cert_ready 逻辑不变 |
| D5 | 仅易制毒历史 transport 当运输证 | 旧 UI 虽标「合法使用证明」,仅易制毒产品行的 transport_cert_url 迁移时 原样保留,语义改为运输证 |
| D6 | 仅易制爆历史 transport → legal_use | 仅易制爆产品行:transport_cert_url 迁入 legal_use_cert_url 后清空 transport_cert_url |
| D7 | 双属性历史 transport → legal_use | 毒+爆双属性产品行:旧第二槽位在 UI 中为「合法使用证明」→ transport_cert_url 迁入 legal_use_cert_url;transport_cert_url 置空(运输证需补传) |
| D8 | 上传弹窗与查看结构一致 | 采用「产品卡片 + 动态表单项」布局,避免固定列表格无法容纳 1~3 证 |
| D9 | CRM 标识为准 | 所需证种以计划产品行 is_toxic / is_explosive(及 CRM enrichment)判定;实现时上传页继续 enrichPlanProductsHazardFromCrm |
pl_plan_product 字段语义(变更后)| 字段 | 类型 | 说明 | 适用属性 |
|---|---|---|---|
purchase_cert_url |
varchar(500), nullable | 购买证 URL | 易制毒(含双属性) |
transport_cert_url |
varchar(500), nullable | 运输证 URL(comment 从「合法使用证明」更正) | 易制毒(含双属性) |
legal_use_cert_url |
varchar(500), nullable | 合法使用证明 URL(新增) | 易制爆(含双属性) |
DDL 示例:
ALTER TABLE `pl_plan_product`
ADD COLUMN `legal_use_cert_url` varchar(500) DEFAULT NULL COMMENT '合法使用证明URL' AFTER `transport_cert_url`;
ALTER TABLE `pl_plan_product`
MODIFY COLUMN `transport_cert_url` varchar(500) DEFAULT NULL COMMENT '运输证URL';
同步写入 docs/sql/pl_tables.sql、docs/sql/shanxia2.0.sql,增量脚本置于 backend/scripts/migrate_pl_plan_product_legal_use_cert_url.sql。
# 伪代码
def required_cert_keys(is_toxic, is_explosive):
keys = []
if is_toxic:
keys += ["purchase_cert_url", "transport_cert_url"]
if is_explosive:
keys += ["legal_use_cert_url"]
return keys
| is_toxic | is_explosive | 所需字段 |
|---|---|---|
| 1 | 0 | purchase + transport |
| 0 | 1 | legal_use |
| 1 | 1 | purchase + transport + legal_use |
| 0 | 0 | (非危化品,不涉及) |
pl_plan.hazard_cert_ready 保留2026-07-04 三字段与齐备标志 正交:齐备仍为计划级人工确认;产品变更(D7)仍自动撤销齐备。本变更 不修改 齐备 API 与安排运输 gate 逻辑。
脚本:backend/scripts/migrate_pl_plan_product_legal_use_cert_url.sql(DDL + DML 同文件或拆分)。
以计划产品行快照 is_toxic / is_explosive 为准(与线上一致):
| 场景 | purchase_cert_url | transport_cert_url | legal_use_cert_url |
|---|---|---|---|
仅易制毒 (toxic=1, explosive=0) |
不变 | 不变(D5:视为运输证) | NULL |
仅易制爆 (toxic=0, explosive=1) |
不变(通常为空) | 清空 | = 原 transport_cert_url |
双属性 (toxic=1, explosive=1) |
不变 | 清空 | = 原 transport_cert_url(D7:旧第二槽为合法使用证明) |
| 非危化品 | 不涉及 | 不涉及 | 不涉及 |
-- 仅易制爆:搬迁合法使用证明
UPDATE pl_plan_product
SET legal_use_cert_url = transport_cert_url,
transport_cert_url = NULL
WHERE is_toxic = 0 AND is_explosive = 1
AND transport_cert_url IS NOT NULL AND transport_cert_url != '';
-- 双属性:旧 transport 作合法使用证明,运输证待补传
UPDATE pl_plan_product
SET legal_use_cert_url = transport_cert_url,
transport_cert_url = NULL
WHERE is_toxic = 1 AND is_explosive = 1
AND transport_cert_url IS NOT NULL AND transport_cert_url != '';
-- 仅易制毒:无 DML(transport 原样保留为运输证)
| 文件 | 改动 |
|---|---|
backend/app/models/pl_models.py |
新增 legal_use_cert_url;更新 transport_cert_url comment |
backend/app/schemas/pl/plan.py |
PlanProductRead/Create/Update、ProductCertUpdateItem 增加 legalUseCertUrl;更新字段 description |
现有:
PATCH /api/transport/plan/{id}/product-certs
Body: { products: [{ id, purchaseCertUrl?, transportCertUrl? }] }
扩展为:
{
"products": [
{
"id": "…",
"purchaseCertUrl": "…",
"transportCertUrl": "…",
"legalUseCertUrl": "…"
}
]
}
_row_to_dict / 计划详情 / 列表 products 须回显 legalUseCertUrl。pl_plan_service.py 创建/更新计划产品时:
legal_use_cert_url;无变更:仍调用 validate_hazard_cert_ready_for_transport,不检查 URL。
web-admin/src/api/pl/index.ts:
PlanProduct 增加 legalUseCertUrl?: string;updateProductCerts 请求体增加该字段。web-admin/src/composables/usePlToxicProductCertCheck.ts 扩展:
export type HazardCertSlotKey = 'purchaseCertUrl' | 'transportCertUrl' | 'legalUseCertUrl'
export interface HazardCertSlot {
key: HazardCertSlotKey
label: string
}
export function getRequiredCertSlots(product: HazardFlagProduct): HazardCertSlot[]
export function isProductCertSlotFilled(product: PlanProduct, key: HazardCertSlotKey): boolean
export function isProductCertComplete(product: PlanProduct): boolean
export function findMissingHazardCerts(products: PlanProduct[]): PlanProduct[]
export function buildMissingHazardCertMessage(products: PlanProduct[]): string
findMissingHazardCerts 重写:按 getRequiredCertSlots 检查对应 key 是否有 URL(仅用于列表提示/标红,不阻断运输)。
标签映射:
| key | label |
|---|---|
purchaseCertUrl |
购买证 |
transportCertUrl |
运输证 |
legalUseCertUrl |
合法使用证明 |
web-admin/src/components/business/pl-plan-cert-upload-dialog/index.vue
| 项 | 改动 |
|---|---|
| 标题 | 「上传易制毒/易制爆证书」→ 可保留或改为「上传危化品证书」 |
| 提示 | 按类型说明所需证种;保留「可先证书齐备再补传」 |
| 表单项 | v-for 产品 × getRequiredCertSlots(product) 动态渲染 AttachmentUploadField |
| 布局 | 毒 2 项、爆 1 项、毒+爆 3 项;仍用 form-grid-2,三证时第三项可 grid-column: 1 / -1 或三列自适应 |
| 保存 | 提交三字段;非所需槽位传空或不传(后端忽略) |
web-admin/src/components/business/pl-plan-hazard-cert-view-table/index.vue
方案(D8):改为与上传弹窗一致的 产品卡片 + 只读附件,每产品展示其 getRequiredCertSlots 对应项;弃用固定「购买证 | 合法使用证明」两列表格。
| 组件 | 使用页面 | 改动 |
|---|---|---|
pl-plan-list-cert-cell/index.vue |
危化品证上传列表「计划附件」列 | 按槽位动态展示「有/无」 |
pl-plan-list-product-cell/index.vue |
运输计划列表产品列(show-cert-status) |
同上;缺失 --missing 按类型判定 |
| 页面 | 改动 |
|---|---|
views/pl/plan-cert/index.vue |
证书齐备确认框文案:说明分类型所需证种 |
pl-plan-view-tabs/index.vue |
「危化品证书信息」区块经子组件生效 |
pl-plan-arrange-dialog/index.vue |
内嵌查看经子组件生效 |
pl-plan-archive-dialog/index.vue |
同上 |
| 文件 | 改动 |
|---|---|
pl-plan-form-dialog/index.vue |
编辑/复制计划时透传 legalUseCertUrl |
usePlContractToPlan.ts |
类型透传 |
hazmatCertNo、车辆/人员证书、证件到期提醒 document-reminder;hazard_cert_ready API 与安排运输 gate。┌─ 产品 A ─────────────── [易制毒] ─┐
│ 购买证 [上传] │
│ 运输证 [上传] │
└───────────────────────────────────┘
┌─ 产品 B ─────────────── [易制爆] ─┐
│ 合法使用证明 [上传] │
└───────────────────────────────────┘
┌─ 产品 C ──────── [易制毒] [易制爆] ─┐
│ 购买证 [上传] │
│ 运输证 [上传] │
│ 合法使用证明 [上传] │
└─────────────────────────────────────┘
| 2026-07-04 决策 | 本变更 |
|---|---|
| D3 上传 0~2 任意组合 | 演进为 0~3,按属性上限 |
| D4 安排运输只查齐备 | 不变 |
| D10 零附件可齐备 | 不变 |
| D11 齐备后可补传 | 不变;补传支持第三字段 |
| 列表展示有/无 | 增强:按类型展示 1~3 种证的有无 |
| 层 | 文件/模块 |
|---|---|
| SQL | docs/sql/pl_tables.sql、docs/sql/shanxia2.0.sql |
| 迁移 | backend/scripts/migrate_pl_plan_product_legal_use_cert_url.sql |
| Model | backend/app/models/pl_models.py |
| Schema | backend/app/schemas/pl/plan.py |
| Service | backend/app/services/pl_plan_service.py |
| API | backend/app/api/pl/plan.py(product-certs、_row_to_dict) |
| FE API | web-admin/src/api/pl/index.ts |
| FE Composable | usePlToxicProductCertCheck.ts |
| FE 组件 | pl-plan-cert-upload-dialog、pl-plan-hazard-cert-view-table、pl-plan-list-cert-cell、pl-plan-list-product-cell |
| FE 页面 | plan-cert/index.vue(文案) |
| 文档 | docs/运输计划管理-需求说明文档.md;openspec/changes/transport-plan-management/specs/transport-plan/spec.md |
| 风险 | 缓解 |
|---|---|
| 双属性历史数据缺运输证 | D7 迁移说明 + 列表标红缺失;操作员补传 |
| 仅易制毒历史 transport 实为误传合法使用证明 | 产品已确认按运输证保留(D5);若有个案由操作员重传 |
| 字段 comment 与旧文档不一致 | 同步更新需求文档与 OpenSpec |
| 前端多处硬编码双证文案 | 统一走 getRequiredCertSlots,禁止组件内重复规则 |
pl_plan_product 存在 legal_use_cert_url;transport_cert_url comment 为「运输证」transport → legal_use;仅毒 transport 不变findMissingHazardCerts 按类型判定缺失,列表标红正确hazardCertReady;未齐备时提示不变实现前须按 superpowers 流程编写 Implementation Plan:
docs/superpowers/plans/2026-07-05-pl-hazard-cert-by-type.md
建议任务顺序:DDL + 迁移 → 后端 Schema/API → composable → 上传弹窗 → 查看/列表组件 → 文案与文档。