This commit is contained in:
zlwu committed 2026-06-21 09:19:07 +08:00
1 parent c3be9d1dce
commit 299609706c
328 files changed
+144899 -13873

No files matched your search

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-10
@@ -0,0 +1,257 @@
# 运输计划管理系统 - 技术设计
## Context
### 项目背景
安庆山峡化工有限公司已建设 CRM 系统(线索/客户/商机/报价单/合同/产品管理)和地磅系统。现需建设**统一的运输计划管理系统**,将 CRM 合同与后续的运输、地磅、库存等环节串联,形成从合同→计划→运输→地磅→结算→归档的完整业务闭环。
### 技术栈
- **后端**:Python FastAPI + SQLAlchemy + MySQL
- **前端**:Vue 3 + Element Plus + TypeScript
- **对象存储**:MinIO(附件上传)
- **数据库**:MySQL 5.7+
### 约束条件
1. 按需求文档要求,所有计划相关表统一使用 `pl_` 前缀
2. 遵循 shanxia2.0 工程规范(见 `openspec/changes/zkxw-engineering-standards/`)
3. 遵循 UI 规范(Fresh Mint 风格)
4. 数据暂不按归属公司隔离
5. 地磅数据由现场人员手动填写,不对接硬件设备
### 利益相关者
- 计划专员A:发起计划
- 计划专员B:核对订单
- 计划专员C:运费结算确认
- 司机:扫码回传
- 现场工作人员:地磅确认
---
## Goals / Non-Goals
**Goals:**
1. 实现完整的计划管理流程:从合同发起→运输安排→状态流转→归档
2. 支持运输审批流程(会签审批)
3. 实现运费结算功能
4. 实现车辆和司机管理
5. 实现盘存统计和库存预警
6. 遵循工程规范和 UI 规范
**Non-Goals:**
1. 不对接地磅硬件设备(手动填写数据)
2. 不实现复杂的移动端功能(司机端仅扫码回传)
3. 不实现电子签名
4. 不实现多语言支持
5. 不实现操作日志(暂不考虑)
---
## Decisions
### Decision 1: 数据库表命名规范
**选择**:统一使用 `pl_` 前缀
**理由**:
- 与现有 `sys_*`、`biz_*`、`wf_*` 命名体系保持一致
- 便于识别和管理计划相关的表
- 需求文档明确要求
**备选方案**:
- `plan_*` - 与业务语义更贴近,但不符合统一前缀规范
- `transport_*` - 过于局限,无法覆盖库存等其他功能
---
### Decision 2: 审批流程实现方式
**选择**:自定义审批流程(不使用工作流引擎)
**理由**:
- 需求简单:仅支持会签审批(1-2级)
- 不走复杂的工作流引擎
- 审批规则灵活(用户选择审批人)
- 减少系统复杂度
**备选方案**:
- 使用 Warm-Flow 工作流引擎 - 过于复杂,不适合简单审批场景
- 使用独立审批服务 - 增加系统复杂度,当前阶段不必要
---
### Decision 3: 计划与运输的关系设计
**选择**:采用中间关联表实现 N:N 关系
**理由**:
- 需求明确支持:1:1、1:N、N:1 三种场景
- 中间表 `pl_plan_transport` 清晰表达关联关系
- 便于后续扩展和维护
**表结构**:
```
pl_plan_transport
├── plan_id (FK)
├── transport_id (FK)
├── create_time
└── del_flag
```
---
### Decision 4: 库存金额计算方式
**选择**:每批入库单独计算,不使用加权平均
**理由**:
- 需求明确要求:使用入库时的实际价格,每批单独计算
- 财务合规性:每批货物成本清晰可追溯
- 简化计算逻辑
---
### Decision 5: 二维码实现方式
**选择**:URL Scheme + 参数编码
**理由**:
- 简单可靠:二维码包含运输单ID和基本信息
- 无需复杂加密:运输单ID已足够定位
- 便于调试:可通过URL直接访问
**二维码内容**:
```
{base_url}/transport/scan/{transport_id}?code={verify_code}
```
---
### Decision 6: 证件过期提醒实现
**选择**:定时任务每日检查
**理由**:
- 需求明确:过期前30天提醒
- 定时任务简单可靠
- 避免实时检查性能开销
---
## Risks / Trade-offs
### Risk 1: 多系统数据同步
**风险**:CRM 合同变更可能导致已发起计划的数据不一致
**缓解措施**:
- 计划发起时冗余存储合同关键信息
- 明确告知用户:合同变更后需手动同步
- 在计划详情页显示合同信息快照
---
### Risk 2: 并发状态流转冲突
**风险**:多人同时操作同一计划可能导致状态不一致
**缓解措施**:
- 使用乐观锁(version_no 字段)
- 状态变更前检查当前状态
- 前端操作后及时刷新状态
---
### Risk 3: 大量数据统计性能
**风险**:盘存统计涉及大量数据汇总,可能影响性能
**缓解措施**:
- 归档后的数据不会频繁变更,考虑使用物化视图或定时统计表
- 限制查询时间范围
- 大数据量场景考虑分库分表(未来扩展)
---
## Migration Plan
### Phase 1: 数据库变更
1. 创建新表(按依赖顺序):
- `pl_vehicle`、`pl_driver`、`pl_carrier`(基础数据)
- `pl_plan`、`pl_plan_product`(计划主表)
- `pl_transport`、`pl_transport_approval`(运输相关)
- `pl_freight_settlement`(运费结算)
- `pl_inventory`、`pl_inventory_warning`(库存相关)
2. 初始化基础数据(车辆类型、承运商等)
### Phase 2: 后端 API 开发
1. 基础 CRUD API(车辆、司机、承运商)
2. 计划管理 API
3. 运输管理 API
4. 审批流程 API
5. 运费结算 API
6. 盘存统计 API
### Phase 3: 前端页面开发
1. 车辆管理页面
2. 司机管理页面
3. 计划管理页面
4. 运输管理页面
5. 运费结算页面
6. 盘存统计页面
7. 工作台(待办、预警)
### Phase 4: 定时任务
1. 证件过期提醒任务
2. 库存统计定时更新(如采用统计表方案)
### Rollback Strategy
- Phase 1:回滚数据库变更(DROP TABLE)
- Phase 2:删除新增的 API 路由
- Phase 3:删除新增的前端页面和组件
- Phase 4:禁用定时任务
---
## Open Questions
### Q1: 审批人权限控制
**问题**:审批人选择时是否需要过滤有审批权限的用户?
**建议**:暂不限制,所有用户都可被选择;后续按需细化权限
---
### Q2: 通知发送方式
**问题**:系统消息之外是否需要邮件/短信通知?
**建议**:暂仅使用系统消息;后续按需扩展
---
### Q3: 统计报表导出
**问题**:盘存统计报表是否需要导出功能?
**建议**:暂不实现;后续按需补充(见需求文档)
---
### Q4: 框架合同调价函关联
**问题**:框架合同调价后,已发起计划的价格是否需要同步更新?
**建议**:保持计划创建时的价格不变(价格快照);调价仅影响后续新建的计划
@@ -0,0 +1,97 @@
## Why
安庆山峡化工有限公司已建设 CRM 系统(线索/客户/商机/报价单/合同/产品管理)和地磅系统。现需建设**统一的运输计划管理系统**,将 CRM 合同与后续的运输、地磅、库存等环节串联,形成从合同→计划→运输→地磅→结算→归档的完整业务闭环。
## What Changes
### 新增功能模块
1. **计划管理(Plan Management)**
- 支持从 CRM 合同发起计划,自动带入合同基本信息、商品明细、结算方式等
- 支持三种单据类型:直销单、入库单、出库单(根据供应商/目的地自动判断)
- 支持产品属性检查(易制毒/易制爆需上传购买证、运输证)
- 支持跟进状态流转(等客户确认→待采购→待发货/待出库→待收货/待入库→已收货/已入库→待归档→归档完成)
- 支持任意状态下取消计划
2. **运输管理(Transport Management)**
- 支持选择车辆类型(自有/承运商)和具体车辆
- 支持生成二维码供司机/现场人员扫码
- 支持司机扫码回传运输单据和数量
- 支持运输安排状态管理(待安排/已安排)
3. **运输审批(Transport Approval)**
- 支持简单审批流程(会签)
- 提交审批时用户选择审批人,按选择顺序依次审批
- 状态流转:草稿→待执行→执行中→已完成→已取消
- **不允许状态回退**
4. **运费结算(Freight Settlement)**
- 支持按重量或按趟次计算运费
- 支持多选运输单合并生成结算单
- 结算流程:待提交→已提交→审核通过→已确认
- 支持发票信息记录(发票号、开票日期、发票金额)
- 支持汇款凭证上传
5. **车辆与司机管理(Vehicle & Driver Management)**
- 车辆归属唯一性管理(自有 或 承运商)
- 司机档案管理(姓名、联系方式、驾驶证、行驶证、危化品证)
- 证件过期提醒(30天)
6. **盘存统计(Inventory Statistics)**
- 库存台账(按产品汇总库存量和库存金额)
- 出入库明细与汇总报表
- 运输统计(按运输公司/车辆统计趟次和运费)
- 库存预警(按产品设置阈值)
### 数据库变更
- 新增 `pl_plan`(计划主表)
- 新增 `pl_plan_product`(计划产品明细表)
- 新增 `pl_transport`(运输信息表)
- 新增 `pl_transport_approval`(运输审批记录表)
- 新增 `pl_vehicle`(车辆表)
- 新增 `pl_driver`(司机表)
- 新增 `pl_freight_settlement`(运费结算表)
- 新增 `pl_inventory`(库存表)
### API 变更
- 新增 `/api/plan/*`(计划管理相关接口)
- 新增 `/api/transport/*`(运输管理相关接口)
- 新增 `/api/vehicle/*`(车辆管理相关接口)
- 新增 `/api/driver/*`(司机管理相关接口)
- 新增 `/api/freight/*`(运费结算相关接口)
- 新增 `/api/inventory/*`(库存统计相关接口)
## Capabilities
### New Capabilities
- `transport-plan`: 运输计划管理 — 计划创建、编辑、取消、状态流转
- `transport-order`: 运输单管理 — 运输安排、二维码生成、扫码回传
- `transport-approval`: 运输审批 — 审批流程、会签审批、审批记录
- `freight-settlement`: 运费结算 — 结算单生成、发票管理、汇款凭证
- `vehicle-driver`: 车辆司机管理 — 车辆档案、司机档案、证件提醒
- `inventory-statistics`: 盘存统计 — 库存台账、出入库报表、运输统计、库存预警
### Modified Capabilities
- 无(暂无对现有 capability 的需求变更)
## Impact
### 受影响系统
- **后端**:`backend/app/` — 新增计划、运输、车辆、司机、运费、库存相关模块
- **前端**:`web-admin/src/` — 新增计划管理、运输管理、车辆管理等页面
- **数据库**:新增 `pl_*` 系列表
### 外部依赖
- CRM 合同系统(`crm_contract`, `crm_contract_product`)— 读取合同信息
- 地磅系统(`dibang_*`)— 接收出入库数据
- MinIO 对象存储 — 附件上传
### 表前缀规范
按需求文档要求,所有计划相关表统一使用 `pl_` 前缀
@@ -0,0 +1,82 @@
# Freight Settlement (运费结算)
## ADDED Requirements
### Requirement: 运费计算
系统 SHALL 支持两种运费计算方式:按重量(默认)和按趟次。
#### Scenario: 按重量计算(默认)
- **WHEN** 用户选择"按重量"计算方式
- **THEN** 系统 SHALL 默认使用此方式
- **AND** 运费 SHALL 自动计算为:运输数量 × 运输单价
- **AND** 计算结果 SHALL 允许用户手动调整
#### Scenario: 按趟次计算
- **WHEN** 用户选择"按趟次"计算方式
- **THEN** 系统 SHALL 要求用户手动填写运费金额
- **AND** 系统 SHALL NOT 进行自动计算
---
### Requirement: 结算单生成
系统 SHALL 支持多选运输单合并生成结算单。
#### Scenario: 生成结算单
- **WHEN** 用户选择多个运输单
- **AND** 用户点击"生成结算单"
- **THEN** 系统 SHALL 合并选中运输单的运费
- **AND** 系统 SHALL 生成结算单编号
- **AND** 系统 SHALL 显示结算明细供用户确认
---
### Requirement: 结算流程
系统 SHALL 支持运费结算的完整流程。
#### Scenario: 结算流程
| 状态 | 说明 |
|------|------|
| 待提交 | 结算单创建后初始状态 |
| 已提交 | 用户提交结算单 |
| 审核通过 | 计划专员C审核通过 |
| 已确认 | 结算完成 |
#### Scenario: 审核确认
- **WHEN** 结算单状态为"已提交"
- **AND** 计划专员C点击"审核通过"
- **THEN** 系统 SHALL 将状态更新为"审核通过"
- **AND** 系统 SHALL 将状态更新为"已确认"
---
### Requirement: 发票管理
系统 SHALL 支持发票信息记录。
#### Scenario: 发票信息记录
- **WHEN** 用户填写发票信息
- **THEN** 系统 SHALL 记录发票号
- **AND** 系统 SHALL 记录开票日期
- **AND** 系统 SHALL 记录发票金额
---
### Requirement: 汇款凭证
系统 SHALL 支持汇款凭证上传,且需精确对应到结算单。
#### Scenario: 汇款凭证上传
- **WHEN** 用户上传汇款凭证
- **THEN** 系统 SHALL 支持多个文件上传
- **AND** 系统 SHALL 不限制文件类型
- **AND** 系统 SHALL 允许选择对应的结算单
#### Scenario: 汇款凭证关联
- **WHEN** 用户上传汇款凭证
- **THEN** 系统 SHALL 要求选择对应的结算单
- **AND** 系统 SHALL 记录关联关系
- **AND** 系统 SHALL 支持查看关联的结算单详情
@@ -0,0 +1,127 @@
# Inventory Statistics (盘存统计)
## ADDED Requirements
### Requirement: 库存统计口径
系统 SHALL 以归档完成的单据为准进行库存统计。
#### Scenario: 统计范围
- **WHEN** 系统计算库存数据
- **THEN** 系统 SHALL 仅统计归档完成的入库单和出库单
- **AND** 系统 SHALL 排除草稿、审批中、执行中的单据
- **AND** 系统 SHALL 排除已取消的单据
#### Scenario: 直销单排除
- **WHEN** 系统计算库存
- **THEN** 系统 SHALL 排除直销单类型
- **AND** 直销单 SHALL NOT 影响库存数量
---
### Requirement: 库存台账
系统 SHALL 支持按产品汇总当前库存量和库存金额。
#### Scenario: 库存台账展示
- **WHEN** 用户访问库存台账
- **THEN** 系统 SHALL 按产品显示:
- 产品名称
- 产品编号
- 当前库存数量
- 库存单位
- 库存金额(使用入库时的实际价格,每批单独计算)
#### Scenario: 库存金额计算
- **WHEN** 系统计算库存金额
- **THEN** 系统 SHALL 使用入库时的实际价格
- **AND** 系统 SHALL NOT 使用加权平均
- **AND** 每批入库 SHALL 单独计算金额
---
### Requirement: 出入库明细
系统 SHALL 支持按时间范围列出所有入库/出库记录。
#### Scenario: 出入库明细查询
- **WHEN** 用户设置时间范围
- **THEN** 系统 SHALL 显示该范围内的所有入库记录
- **AND** 系统 SHALL 显示该范围内的所有出库记录
- **AND** 每条记录 SHALL 包含:
- 单据编号
- 单据类型(入库/出库)
- 产品信息
- 数量
- 单价
- 金额
- 时间
---
### Requirement: 出入库汇总
系统 SHALL 支持按月/周汇总各产品的入库量、出库量、净变化。
#### Scenario: 按月汇总
- **WHEN** 用户选择"按月"汇总
- **THEN** 系统 SHALL 按月分组显示
- **AND** 系统 SHALL 显示每个产品的:
- 月度入库总量
- 月度出库总量
- 月度净变化
#### Scenario: 按周汇总
- **WHEN** 用户选择"按周"汇总
- **THEN** 系统 SHALL 按周分组显示
- **AND** 系统 SHALL 显示每个产品的:
- 周度入库总量
- 周度出库总量
- 周度净变化
---
### Requirement: 运输统计
系统 SHALL 支持按运输公司/车辆统计运输趟次和运费。
#### Scenario: 按运输公司统计
- **WHEN** 用户选择"按运输公司"统计
- **THEN** 系统 SHALL 显示每个运输公司的:
- 运输趟次
- 总运费
- 平均运费
#### Scenario: 按车辆统计
- **WHEN** 用户选择"按车辆"统计
- **THEN** 系统 SHALL 显示每辆车的:
- 运输趟次
- 总运费
- 运输里程
---
### Requirement: 库存预警
系统 SHALL 支持按产品设置预警阈值并提供预警通知。
#### Scenario: 预警阈值设置
- **WHEN** 管理员为产品设置预警阈值
- **THEN** 系统 SHALL 支持设置:
- 最低库存预警
- 最高库存预警(如适用)
#### Scenario: 预警触发
- **WHEN** 产品库存 ≤ 最低库存预警值
- **THEN** 系统 SHALL 生成库存预警
- **AND** 系统 SHALL 在工作台显示预警列表
- **AND** 预警 SHALL 包含:
- 产品名称
- 当前库存
- 预警阈值
- 预警类型(低于最低/高于最高)
#### Scenario: 预警通知
- **WHEN** 库存预警触发
- **THEN** 系统 SHALL 通过系统消息通知相关人员
- **AND** 通知 SHALL 包含产品信息和当前库存状态
@@ -0,0 +1,59 @@
# PL API JSON 命名(运输计划模块)
本能力补充 `openspec/changes/zkxw-engineering-standards/specs/api-conventions/spec.md` 中「JSON 字段命名」要求,**专用于** `app/api/pl/**` 与 `web-admin/src/api/pl/**`、`web-admin/src/views/pl/**`。
## ADDED Requirements
### Requirement: 运输计划 API JSON 字段 camelCase 对齐
运输计划模块(路由前缀 `/api/transport/**`)的请求体、响应体及 Query 参数中,凡前端 TypeScript 使用 camelCase 的多词字段,后端 Pydantic Schema SHALL 通过 `Field(..., alias="camelCase")` 声明 JSON 别名,并设置 `model_config = {"populate_by_name": True}`(或 `ConfigDict(populate_by_name=True)`)。
路由层访问请求体 SHALL 使用 **Python snake_case 属性名**(如 `payload.carrier_name`),SHALL NOT 使用 `payload.carrierName`。
读接口(`response_model=...Read`)SHALL 为 ORM 列对应的 snake_case 字段配置 alias,使 FastAPI 默认 `response_model_by_alias=True` 输出 camelCase,与 `web-admin/src/api/pl/index.ts` 类型定义一致。
#### Scenario: 承运商新增
- **WHEN** 前端 `POST /api/transport/carrier` 请求体为 `{ "carrierName": "某某物流", "contactName": "张三", "status": "1" }`
- **THEN** `CarrierCreate` SHALL 校验通过(HTTP 非 422)
- **AND** 路由 SHALL 通过 `payload.carrier_name` 写入 `pl_carrier.carrier_name`
#### Scenario: 计划新增含产品明细
- **WHEN** 前端 `POST /api/transport/plan/` 请求体含 `belongsCompany`、`planType`、`planDate` 及 `products[].productId`
- **THEN** `PlanCreate` / `PlanProductCreate` SHALL 校验通过
- **AND** 响应 `PlanRead` / `PlanProductRead` 的 JSON 键 SHALL 为 `planNo`、`createTime` 等 camelCase
#### Scenario: 运输安排
- **WHEN** 前端 `POST /api/transport/order/arrange/{planId}` 请求体为 `{ "vehicleId": "...", "driverId": "...", "freightType": "by_weight" }`
- **THEN** `TransportArrange` SHALL 校验通过
- **AND** 路由 SHALL 使用 `payload.vehicle_id`、`payload.driver_id` 访问字段
#### Scenario: 列表分页 Query
- **WHEN** 前端 GET 列表接口 Query 为 `page=1&pageSize=20&planType=direct`
- **THEN** 路由参数 SHALL 通过 `Query(..., alias="pageSize")` 或 Schema alias 正确解析
- **AND** SHALL NOT 静默忽略 `pageSize` 导致始终使用默认每页 20 条
#### Scenario: 仅 snake_case 无 alias 视为缺陷
- **WHEN** `app/schemas/pl/**` 中 Write/Read Schema 的多词字段仅有 `snake_case` 属性名、无 `alias`
- **THEN** 属于本变更范围内的规范违反;前端发送 camelCase 时将返回 **422 Unprocessable Entity**
- **AND** 修复 SHALL 为后端补 alias,SHALL NOT 要求 web-admin 改为 snake_case
### Requirement: 运输计划 Schema 变更门禁
修改 `app/schemas/pl/**` 或新增 PL 写接口时,提交前 SHALL 运行 `python backend/scripts/check_pl_json_aliases.py`;脚本失败 SHALL 阻塞合并。
Schema 覆盖范围 SHALL 至少包括:
| 路径 | 说明 |
|------|------|
| `app/schemas/pl/__init__.py` | 承运商、车辆、司机 |
| `app/schemas/pl/plan.py` | 计划与产品明细 |
| `app/schemas/pl/transport.py` | 运输、安排、审批 |
| `app/schemas/pl/freight.py` | 运费结算 |
| `app/schemas/pl/inventory.py` | 库存、证件提醒 |
参考实现:`app/schemas/db/weighing.py`(地磅模块 alias 模式)。
@@ -0,0 +1,75 @@
# Transport Approval (运输审批)
## ADDED Requirements
### Requirement: 简单审批流程
系统 SHALL 支持简单审批流程(会签),允许用户选择审批人并按顺序审批。
#### Scenario: 提交审批
- **WHEN** 用户点击"提交审批"
- **THEN** 系统 SHALL 显示审批人选择界面
- **AND** 用户 SHALL 选择审批人(可选择多人)
- **AND** 审批顺序 SHALL 按照用户选择时的排序
#### Scenario: 审批规则
- **WHEN** 用户提交审批
- **THEN** 系统 SHALL 使用会签方式审批(所有人审批通过才算通过)
- **AND** 系统 SHALL 按选择顺序依次审批
- **AND** 每一级审批完成后才能进入下一级
---
### Requirement: 运输计划状态流转
系统 SHALL 支持运输计划的完整状态流转,且不允许状态回退。
#### Scenario: 状态流转(不允许回退)
- **WHEN** 运输计划处于某状态
- **THEN** 系统 SHALL 仅允许向前流转至下一状态
- **AND** 系统 SHALL 禁止回退至已过的状态
- **WHEN** 运输计划状态流转至"已完成"或"已取消"
- **THEN** 系统 SHALL 禁止任何状态变更
#### Scenario: 状态流转详情
| 当前状态 | 下一状态 | 说明 |
|---------|---------|------|
| 草稿 | 待执行/已取消 | 提交审批后进入待执行 |
| 待执行 | 执行中/已取消 | 开始执行后进入执行中 |
| 执行中 | 已完成/已取消 | 执行完成后进入已完成 |
| 已完成 | - | 终态,不可变更 |
| 已取消 | - | 终态,不可变更 |
---
### Requirement: 审批记录
系统 SHALL 记录完整的审批历史。
#### Scenario: 审批记录内容
- **WHEN** 审批人完成审批
- **THEN** 系统 SHALL 记录审批人信息(id, name)
- **AND** 系统 SHALL 记录审批结果(approved/rejected)
- **AND** 系统 SHALL 记录审批意见
- **AND** 系统 SHALL 记录审批时间
- **AND** 系统 SHALL 记录审批耗时
#### Scenario: 审批拒绝处理
- **WHEN** 审批人拒绝审批
- **THEN** 系统 SHALL 将计划状态回退至"草稿"
- **AND** 系统 SHALL 通知申请人审批被拒绝
- **AND** 系统 SHALL 保留审批拒绝记录
---
### Requirement: 审批人选择
系统 SHALL 支持从组织架构或用户列表中选择审批人。
#### Scenario: 选择审批人
- **WHEN** 用户点击"选择审批人"
- **THEN** 系统 SHALL 显示用户选择器
- **AND** 用户 SHALL 能搜索和选择多个用户
- **AND** 用户 SHALL 能调整审批顺序
- **AND** 系统 SHALL 记住用户的选择顺序
@@ -0,0 +1,109 @@
# Transport Order (运输单管理)
## ADDED Requirements
### Requirement: 运输安排
系统 SHALL 支持运输安排,包括选择车辆类型、车辆、司机,填写运输信息。
#### Scenario: 选择自有车辆
- **WHEN** 用户选择车辆类型为"自有"
- **THEN** 系统 SHALL 仅显示自有车辆列表
- **AND** 用户选择具体车辆后
- **THEN** 系统 SHALL 自动带出该车辆的默认司机
#### Scenario: 选择承运商车辆
- **WHEN** 用户选择车辆类型为"承运商"
- **THEN** 系统 SHALL 仅显示承运商车辆列表
- **AND** 用户选择承运商后
- **THEN** 系统 SHALL 仅显示该承运商名下的车辆
- **AND** 用户选择具体车辆后
- **THEN** 系统 SHALL 自动带出该车辆的默认司机
#### Scenario: 运输信息填写
- **WHEN** 用户完成运输安排
- **THEN** 系统 SHALL 生成运输编号(格式:`SXYS######`,每月重置)
- **AND** 系统 SHALL 将运输安排状态从"待安排"更新为"已安排"
---
### Requirement: 车辆归属唯一性
系统 SHALL 确保同一辆车只能属于一种类型(自有 或 承运商)。
#### Scenario: 车辆归属约束
- **WHEN** 管理员新增或编辑车辆
- **THEN** 系统 SHALL 要求选择车辆归属类型(自有/承运商)
- **AND** 系统 SHALL 确保同一辆车只能选择一个归属类型
- **AND** 系统 SHALL 禁止同一车牌号同时属于自有和承运商
---
### Requirement: 二维码生成与分发
系统 SHALL 支持生成二维码供司机/现场人员扫码。
#### Scenario: 二维码生成
- **WHEN** 用户点击"生成二维码"
- **THEN** 系统 SHALL 生成包含运输单信息的二维码
- **AND** 系统 SHALL 显示有效期设置(默认不设期限,用户可手动填写)
- **AND** 系统 SHALL 提供下载二维码图片功能
#### Scenario: 二维码分发
- **WHEN** 用户下载二维码图片
- **THEN** 系统 SHALL 提示用户截图发送给司机/现场人员
- **AND** 二维码 SHALL 包含运输单号、产品信息、目的地等关键信息
---
### Requirement: 司机扫码回传
系统 SHALL 支持司机扫描二维码回传运输信息,无需登录。
#### Scenario: 司机扫码
- **WHEN** 司机扫描运输单二维码
- **THEN** 系统 SHALL 允许无需登录即可访问回传页面
- **AND** 系统 SHALL 允许回传以下信息:
- 运输单据照片
- 运输数量
- 其他信息(备注等)
#### Scenario: 现场人员扫码
- **WHEN** 现场人员扫描二维码
- **THEN** 系统 SHALL 要求登录验证权限
- **AND** 系统 SHALL 显示完整的单据详情供查看
---
### Requirement: 运输与计划关系
系统 SHALL 支持计划与运输的 N:N 关系。
#### Scenario: 1:1 关系
- **WHEN** 一个计划对应一趟运输
- **THEN** 系统 SHALL 允许直接关联
#### Scenario: 1:N 关系(分车装运)
- **WHEN** 一个计划需要多趟运输(分车装运)
- **THEN** 系统 SHALL 允许创建多个运输单关联同一计划
#### Scenario: N:1 关系(拼车)
- **WHEN** 多个计划合并一趟运输(拼车)
- **THEN** 系统 SHALL 允许一个运输单关联多个计划
---
### Requirement: 待入库/待出库单据同步
系统 SHALL 支持待入库/待出库单据的自动同步。
#### Scenario: 单据自动生成
- **WHEN** 计划状态流转至"待入库"或"待出库"
- **THEN** 系统 SHALL 自动生成地磅单
- **AND** 系统 SHALL 通过系统消息通知现场工作人员
- **AND** 系统 SHALL 支持复制文本通过微信发送
#### Scenario: 地磅数据录入
- **WHEN** 现场工作人员确认地磅数据
- **THEN** 系统 SHALL 允许手动填写地磅数据(不对接硬件)
- **AND** 系统 SHALL 允许上传单据和附件
@@ -0,0 +1,190 @@
# Transport Plan (运输计划管理)
## ADDED Requirements
### Requirement: 计划可从 CRM 合同发起
系统 SHALL 支持从 CRM 合同发起运输计划,自动带入合同基本信息、商品明细、结算方式等数据。
#### Scenario: 从合同发起计划
- **WHEN** 用户选择 CRM 合同并点击"发起计划"
- **THEN** 系统 SHALL 自动带入客户信息(customer_id, customer_name)
- **AND** 系统 SHALL 自动带入联系人信息(contact_name, contact_mobile)
- **AND** 系统 SHALL 自动带入甲方/乙方信息(party_a*, party_b*)
- **AND** 系统 SHALL 自动带入合同有效期(start_date, end_date)
- **AND** 系统 SHALL 自动带入结算方式(settlement_method)
- **AND** 系统 SHALL 自动带入商品明细(从 crm_contract_product 读取 product_id, product_name, model, unit, price)
- **AND** 系统 SHALL 自动带入送货方式(delivery_method)
#### Scenario: 合同关联字段映射
| CRM 合同字段 | 计划字段 |
|-------------|---------|
| customer_id | customer_id |
| customer_name | customer_name |
| contact_name | contact_name |
| contact_mobile | contact_mobile |
| party_a* | party_a* |
| party_b* | party_b* |
| start_date | plan_start_date |
| end_date | plan_end_date |
| settlement_method | settlement_method |
---
### Requirement: 单据类型自动判断
系统 SHALL 根据供应商和目的地自动判断单据类型(直销单/入库单/出库单)。
#### Scenario: 判断为入库单
- **WHEN** 目的地 = "安庆山峡" OR 目的地 = "安徽云栋"
- **THEN** 系统 SHALL 将单据类型设置为"入库单"
#### Scenario: 判断为出库单
- **WHEN** 供应商 = "安庆山峡" OR 供应商 = "安徽云栋"
- **THEN** 系统 SHALL 将单据类型设置为"出库单"
#### Scenario: 判断为直销单
- **WHEN** 供应商 ≠ "安庆山峡" AND 供应商 ≠ "安徽云栋" AND 目的地 ≠ "安庆山峡" AND 目的地 ≠ "安徽云栋"
- **THEN** 系统 SHALL 将单据类型设置为"直销单"
---
### Requirement: 产品属性检查
系统 SHALL 在安排运输前检查产品属性,易制毒/易制爆产品必须上传购买证和运输证。
#### Scenario: 易制毒产品需上传证件
- **WHEN** 产品属性包含"易制毒"且用户点击"安排运输"
- **THEN** 系统 SHALL 检查是否已上传购买证
- **AND** 系统 SHALL 检查是否已上传运输证
- **IF** 任一证件缺失
- **THEN** 系统 SHALL 阻止安排运输并提示用户上传缺失证件
#### Scenario: 易制爆产品需上传证件
- **WHEN** 产品属性包含"易制爆"且用户点击"安排运输"
- **THEN** 系统 SHALL 检查是否已上传购买证
- **AND** 系统 SHALL 检查是否已上传运输证
- **IF** 任一证件缺失
- **THEN** 系统 SHALL 阻止安排运输并提示用户上传缺失证件
#### Scenario: 普通产品无需证件
- **WHEN** 产品不包含"易制毒"和"易制爆"属性
- **THEN** 系统 SHALL 允许直接安排运输,无需上传证件
---
### Requirement: 跟进状态流转
系统 SHALL 支持跟进状态的完整流转,并支持任意状态下取消。
#### Scenario: 直销单状态流转
- **WHEN** 直销单状态为"等客户确认"且用户确认要货
- **THEN** 系统 SHALL 将状态流转至"待采购"
- **WHEN** 直销单状态为"待采购"且采购完成
- **THEN** 系统 SHALL 将状态流转至"待发货"
- **WHEN** 直销单状态为"待发货"且货物发出
- **THEN** 系统 SHALL 将状态流转至"待收货"
- **WHEN** 直销单状态为"待收货"且客户确认收货
- **THEN** 系统 SHALL 将状态流转至"待归档"
- **WHEN** 直销单状态为"待归档"且用户点击归档
- **THEN** 系统 SHALL 将状态流转至"归档完成"
#### Scenario: 入库单状态流转
- **WHEN** 入库单状态为"待采购"且采购完成
- **THEN** 系统 SHALL 将状态流转至"待发货"
- **WHEN** 入库单状态为"待发货"且货物到达
- **THEN** 系统 SHALL 将状态流转至"待入库"
- **WHEN** 入库单状态为"待入库"且地磅确认入库
- **THEN** 系统 SHALL 将状态流转至"已入库"
- **WHEN** 入库单状态为"已入库"且用户点击归档
- **THEN** 系统 SHALL 将状态流转至"归档完成"
#### Scenario: 出库单状态流转
- **WHEN** 出库单状态为"等客户确认"且客户确认要货
- **THEN** 系统 SHALL 将状态流转至"待出库"
- **WHEN** 出库单状态为"待出库"且货物出库
- **THEN** 系统 SHALL 将状态流转至"已出库"
- **WHEN** 出库单状态为"已出库"且客户确认收货
- **THEN** 系统 SHALL 将状态流转至"已收货"
- **WHEN** 出库单状态为"已收货"且用户点击归档
- **THEN** 系统 SHALL 将状态流转至"归档完成"
#### Scenario: 任意状态下取消计划
- **WHEN** 用户在任意跟进状态下点击"取消计划"
- **THEN** 系统 SHALL 将计划状态设置为"已取消"
- **AND** 系统 SHALL 保留运输信息并标记为"已取消"
- **AND** 系统 SHALL 禁止该计划继续流转
---
### Requirement: 归档操作
系统 SHALL 支持归档操作,归档后自动更新归档状态和完成状态。
#### Scenario: 归档操作
- **WHEN** 计划处于"待归档"状态且用户点击"归档"
- **THEN** 系统 SHALL 将归档状态从"未归档"更新为"已归档"
- **AND** 系统 SHALL 将完成状态从"否"更新为"是"
- **AND** 系统 SHALL 禁止该计划继续修改
---
### Requirement: 计划编号规则
系统 SHALL 按照规则自动生成计划订单编号。
#### Scenario: 计划编号生成
- **WHEN** 用户新建计划
- **THEN** 系统 SHALL 生成编号格式为 `SXDDHYYYYMMDD######`
- **WHEREIN** `YYYYMMDD` 为当前日期
- **AND** `######` 为当日6位序号(从000001开始,每日重置)
---
### Requirement: 金额计算规则
系统 SHALL 支持入库金额和出库金额的自动计算。
#### Scenario: 入库金额计算
- **WHEN** 用户填写入库数量或修改入库价
- **THEN** 系统 SHALL 自动计算入库金额 = 入库数 × 入库价
- **AND** 入库价默认值为合同价格,支持后续修改
#### Scenario: 出库金额计算
- **WHEN** 用户填写出库数量或修改出库价
- **THEN** 系统 SHALL 自动计算出库金额 = 出库数 × 出库价
---
### Requirement: 通知信息生成
系统 SHALL 按模板生成通知信息,通知信息不可编辑。
#### Scenario: 通知信息模板
- **WHEN** 系统生成通知信息
- **THEN** 系统 SHALL 使用以下模板格式:
```
计划号:{plan_no}
企业名称:{customer_name}
提货单位:{destination}
到货时间:{delivery_date}
药品名称:{product_name}
到货吨位:{quantity}吨
备注:{remark}
```
- **AND** 用户 SHALL NOT 直接编辑通知信息内容
---
### Requirement: Web 管理端按钮须携带图标
运输计划模块 `web-admin/src/views/pl/**`(承运商管理、车辆管理、司机管理、运输计划、运输管理、运费结算、库存台账、证件提醒及 form/detail/arrange 子页)SHALL 遵循 OpenSpec `ui-conventions` 中 **「按钮须携带图标」** Requirement:凡带可见文案的 `el-button` 须在文字前方包含 `<el-icon>`,图标从 `@element-plus/icons-vue` 显式导入。
#### Scenario: 运输计划菜单页面按钮展示
- **WHEN** 用户打开运输计划菜单下任一列表、表单或详情页
- **THEN** 筛选区「搜索」「重置」、工具栏「新增」、操作列「查看/编辑/删除/安排运输/提交/审核/处理」、弹窗与页脚「取消/确定/返回」等带文案按钮 SHALL 均携带语义图标
- **THEN** SHALL NOT 出现仅有文字、无 `<el-icon>` 的按钮
@@ -0,0 +1,74 @@
# Vehicle, Personnel & Certificate(车辆/人员/证书管理)
## ADDED Requirements
### Requirement: 车辆档案管理
系统 SHALL 支持车辆档案的完整管理,含关联人员子表与车辆证书子表。
#### Scenario: 车辆信息维护
- **WHEN** 管理员新增或编辑车辆
- **THEN** 系统 SHALL 记录车牌号(唯一)、车辆类型(自有/承运商)、承运商(类型为承运商时)、载重、车辆状态(可用/维修中/报废)、备注
- **AND** 系统 SHALL 在车辆弹窗内以子表维护关联人员(姓名、手机号非必填、年龄、人员类型、关联备注、默认司机)
- **AND** 系统 SHALL 在车辆弹窗内以子表维护车辆证书(证书名称、证号、有效期、提前提醒天数,默认 30)
#### Scenario: 车辆归属管理
- **WHEN** 管理员编辑车辆归属类型
- **THEN** 系统 SHALL 确保同一辆车只能属于一种类型(自有或承运商)
- **AND** 系统 SHALL 禁止同一车牌号重复
#### Scenario: 车辆关联人员
- **WHEN** 管理员在车辆弹窗维护关联人员子表
- **THEN** 系统 SHALL 支持每车多人,人员类型为司机或押运员
- **AND** 系统 SHALL 确保每辆车最多一名默认司机(仅 role=driver 的行可设为默认)
- **AND** 系统 SHALL 在保存时 upsert 人员主数据(有手机号时按手机号去重,无手机号时按姓名弱匹配)
---
### Requirement: 人员档案管理
系统 SHALL 支持人员(司机、押运员等)档案的独立管理。
#### Scenario: 人员信息维护
- **WHEN** 管理员新增或编辑人员
- **THEN** 系统 SHALL 记录姓名(必填)、手机号(非必填)、年龄、身份证号、在职状态、备注
- **AND** 系统 SHALL 以子表维护人员证书(证书名称、证号、有效期、提前提醒天数,默认 30)
- **AND** 系统 SHALL NOT 在人员表写死驾驶证/行驶证/危化品证等字段
#### Scenario: 人员与车辆绑定
- **WHEN** 管理员在车辆弹窗或人员管理中维护绑定关系
- **THEN** 系统 SHALL 允许同一人员关联多辆车、同一车辆关联多人
- **AND** 系统 SHALL 在运输安排选择车辆后自动带出该车的默认司机
- **AND** 系统 SHALL 在安排运输时司机下拉仅展示该车 role=driver 的关联人员
---
### Requirement: 动态证书管理
系统 SHALL 使用独立证书表管理车辆与人员的各类证书。
#### Scenario: 证书实例维护
- **WHEN** 管理员为车辆或人员添加证书
- **THEN** 系统 SHALL 记录 owner_type(vehicle/personnel)、证书名称、证号、有效期、remind_days(默认 30)、附件
- **AND** 系统 SHALL 允许同一主体持有多个证书,证种可动态增删
- **AND** 证书附件 SHALL 使用 OpenSpec「附件上传卡片化布局」:`AttachmentUploadField` 组件,多附件以逗号分隔 URL 存入 `attachment_urls`;支持上传、下载、图片预览
#### Scenario: 证书到期提醒
- **WHEN** 证书 expire_date 非空且剩余天数 ≤ remind_days
- **THEN** 系统 SHALL 通过定时任务扫描 pl_cert 生成 pl_document_reminder
- **AND** 提醒 SHALL 覆盖车辆与人员证书
- **AND** 提醒列表 SHALL 展示主体类型、主体名称(车牌/姓名)、证书名称、到期日期、剩余天数
---
### Requirement: 承运商管理
系统 SHALL 支持承运商档案管理(不变)。
#### Scenario: 承运商信息维护
- **WHEN** 管理员新增承运商
- **THEN** 系统 SHALL 记录承运商名称、联系人、联系电话、合作状态(合作中/已终止)
#### Scenario: 承运商车辆筛选
- **WHEN** 用户选择承运商后安排运输
- **THEN** 系统 SHALL 仅显示该承运商名下的车辆
@@ -0,0 +1,200 @@
# 运输计划管理系统 - 实施任务清单
## 1. 数据库设计与创建
### 1.1 基础数据表
- [x] 1.1.1 创建承运商表 `pl_carrier`
- [x] 1.1.2 创建车辆表 `pl_vehicle`
- [x] 1.1.3 创建司机表 `pl_driver`
- [x] 1.1.4 创建证件提醒表 `pl_document_reminder`
### 1.2 计划管理表
- [x] 1.2.1 创建计划主表 `pl_plan`
- [x] 1.2.2 创建计划产品明细表 `pl_plan_product`
- [ ] 1.2.3 创建计划变更记录表 `pl_plan_change`
### 1.3 运输管理表
- [x] 1.3.1 创建运输表 `pl_transport`
- [x] 1.3.2 创建计划-运输关联表 `pl_plan_transport`
- [x] 1.3.3 创建运输审批记录表 `pl_transport_approval`
### 1.4 运费结算表
- [x] 1.4.1 创建运费结算表 `pl_freight_settlement`
- [ ] 1.4.2 创建汇款凭证表 `pl_remittance_voucher`
### 1.5 库存管理表
- [x] 1.5.1 创建库存表 `pl_inventory`
- [x] 1.5.2 创建库存预警表 `pl_inventory_warning`
---
## 2. 后端 API 开发
### 2.1 基础数据 API
- [x] 2.1.1 实现承运商 CRUD API (`/api/carrier/*`)
- [x] 2.1.2 实现车辆 CRUD API (`/api/vehicle/*`)
- [x] 2.1.3 实现司机 CRUD API (`/api/driver/*`)
- [x] 2.1.4 实现车辆列表 API(含承运商筛选)
- [x] 2.1.5 实现司机列表 API(含车辆绑定信息)
### 2.2 计划管理 API
- [x] 2.2.1 实现计划列表 API (`/api/plan/list`)
- [x] 2.2.2 实现计划详情 API (`/api/plan/{id}`)
- [x] 2.2.3 实现从合同发起计划 API (`/api/plan/create-from-contract`)
- [x] 2.2.4 实现计划编辑 API (`/api/plan/{id}`, PUT)
- [x] 2.2.5 实现计划取消 API (`/api/plan/{id}/cancel`)
- [x] 2.2.6 实现计划归档 API (`/api/plan/{id}/archive`)
- [x] 2.2.7 实现跟进状态流转 API (`/api/plan/{id}/status`)
- [x] 2.2.8 实现单据类型自动判断逻辑
- [x] 2.2.9 实现产品属性检查逻辑(易制毒/易制爆)
- [x] 2.2.10 实现金额自动计算逻辑(入库/出库金额)
### 2.3 运输管理 API
- [x] 2.3.1 实现运输安排 API (`/api/transport/arrange`)
- [x] 2.3.2 实现运输列表 API (`/api/transport/list`)
- [x] 2.3.3 实现运输详情 API (`/api/transport/{id}`)
- [x] 2.3.4 实现二维码生成 API (`/api/transport/{id}/qrcode`)
- [x] 2.3.5 实现司机扫码回传 API (`/api/transport/scan/feedback`)
- [x] 2.3.6 实现计划-运输关联管理 API (`/api/plan-transport/*`)
### 2.4 审批流程 API
- [x] 2.4.1 实现提交审批 API (`/api/transport/{id}/submit-approval`)
- [x] 2.4.2 实现审批 API (`/api/approval/{id}`)
- [x] 2.4.3 实现审批列表 API (`/api/approval/list`)
- [x] 2.4.4 实现审批详情 API (`/api/approval/{id}/detail`)
### 2.5 运费结算 API
- [x] 2.5.1 实现结算单生成 API (`/api/freight/generate`)
- [x] 2.5.2 实现结算单列表 API (`/api/freight/list`)
- [x] 2.5.3 实现结算单详情 API (`/api/freight/{id}`)
- [x] 2.5.4 实现结算单提交 API (`/api/freight/{id}/submit`)
- [x] 2.5.5 实现结算单审核 API (`/api/freight/{id}/approve`)
- [x] 2.5.6 实现发票信息记录 API (`/api/freight/{id}/invoice`)
- [x] 2.5.7 实现汇款凭证上传 API (`/api/freight/{id}/voucher`)
### 2.6 盘存统计 API
- [x] 2.6.1 实现库存台账 API (`/api/inventory/ledger`)
- [x] 2.6.2 实现出入库明细 API (`/api/inventory/details`)
- [x] 2.6.3 实现出入库汇总 API (`/api/inventory/summary`)
- [x] 2.6.4 实现运输统计 API (`/api/inventory/transport-stats`)
- [x] 2.6.5 实现库存预警列表 API (`/api/inventory/warnings`)
- [x] 2.6.6 实现预警阈值设置 API (`/api/inventory/warning-threshold`)
---
## 3. 前端页面开发
### 3.1 基础数据管理页面
- [x] 3.1.1 实现承运商管理页面 (`/views/transport/carrier/index.vue`)
- [x] 3.1.2 实现车辆管理页面 (`/views/transport/vehicle/index.vue`)
- [x] 3.1.3 实现司机管理页面 (`/views/transport/driver/index.vue`)
- [x] 3.1.4 实现证件到期提醒页面 (`/views/transport/document-reminder/index.vue`)
### 3.2 计划管理页面
- [x] 3.2.1 实现计划列表页面 (`/views/pl/plan/index.vue`)
- [x] 3.2.2 实现计划新增/编辑页面 (`/views/pl/plan/form.vue`)
- [x] 3.2.3 实现计划详情页面 (`/views/pl/plan/detail.vue`)
- [x] 3.2.4 实现运输安排页面 (`/views/pl/plan/arrange.vue`)
### 3.3 运输管理页面
- [x] 3.3.1 实现运输列表页面 (`/views/pl/transport/index.vue`)
### 3.4 运费结算页面
- [x] 3.4.1 实现结算单列表页面 (`/views/pl/freight/index.vue`)
### 3.5 盘存统计页面
- [x] 3.5.1 实现库存台账页面 (`/views/pl/inventory/ledger.vue`)
- [x] 3.5.2 实现证件提醒页面 (`/views/pl/inventory/document-reminder.vue`)
- [x] 3.6 车辆/人员/证书重构(`docs/superpowers/specs/2026-06-19-pl-vehicle-personnel-cert-design.md`)
### 3.7 工作台集成
- [ ] 3.7.1 实现待办任务列表组件 (`PendingTasks.vue`)
- [ ] 3.7.2 实现证件到期提醒组件 (`DocumentReminder.vue`)
- [ ] 3.7.3 实现库存预警提醒组件 (`InventoryWarning.vue`)
---
## 4. 定时任务开发
- [ ] 4.1 实现证件过期提醒定时任务(每日检查)
- [ ] 4.2 实现库存预警检查定时任务(每日检查)
- [ ] 4.3 实现库存统计更新定时任务(可选,如有统计表)
---
## 5. 菜单与权限配置
- [ ] 5.1 配置计划管理菜单
- [ ] 5.2 配置运输管理菜单
- [ ] 5.3 配置审批管理菜单
- [ ] 5.4 配置运费结算菜单
- [ ] 5.5 配置盘存统计菜单
- [ ] 5.6 配置基础数据管理菜单(车辆、司机、承运商)
- [ ] 5.7 配置权限标识(`transport:plan:*`, `transport:vehicle:*` 等)
---
## 6. 路由配置
- [x] 6.1 配置后端 API 路由注册(`app/api/__init__.py`, `app/main.py`)
- [x] 6.2 配置前端路由(`web-admin/src/router/modules/pl.ts`)
---
## 7. 测试与验证
### 7.1 单元测试
- [ ] 7.1.1 编写单据类型判断逻辑测试
- [ ] 7.1.2 编写金额计算逻辑测试
- [ ] 7.1.3 编写审批流程逻辑测试
- [ ] 7.1.4 编写运费计算逻辑测试
### 7.2 API 测试
- [ ] 7.2.1 验证计划 CRUD API
- [ ] 7.2.2 验证运输安排 API
- [ ] 7.2.3 验证审批流程 API
- [ ] 7.2.4 验证运费结算 API
### 7.3 集成测试
- [ ] 7.3.1 验证从合同发起计划完整流程
- [ ] 7.3.2 验证审批流程完整流转
- [ ] 7.3.3 验证运费结算完整流程
- [ ] 7.3.4 验证盘存统计准确性
---
## 8. 文档与培训
- [ ] 8.1 编写用户操作手册
- [ ] 8.2 更新 API 文档
- [ ] 8.3 编写技术设计文档(如有更新)
---
## 9. 部署与上线
- [ ] 9.1 执行数据库变更脚本
- [ ] 9.2 部署后端服务
- [ ] 9.3 部署前端服务
- [ ] 9.4 配置生产环境参数
- [ ] 9.5 验证生产环境功能
@@ -161,7 +161,21 @@ Web 管理端(`web-admin`)与对外 JSON 接口中,**请求体与响应 `d
列表/详情等读接口的 `_row_to_dict`(或等价转换)SHALL 将 ORM 列名转为 camelCase 键名再返回前端。
参考实现:`app/api/admin.py` 中 `TenantCreateBody`;CRM:`app/schemas/crm/customer.py`、`app/schemas/crm/product.py`;路由:`app/api/crm.py`(客户列表/公海池)。
参考实现:`app/api/admin.py` 中 `TenantCreateBody`;CRM:`app/schemas/crm/customer.py`、`app/schemas/crm/product.py`;路由:`app/api/crm.py`(客户列表/公海池);**运输计划**:`app/schemas/pl/**`(`openspec/changes/transport-plan-management/specs/pl-api-json/spec.md`);地磅:`app/schemas/db/weighing.py`。
FastAPI 路由 Query 参数若 Python 参数名为 `page_size`,SHALL 声明 `Query(..., alias="pageSize")`,与前端列表分页一致。
#### Scenario: 运输计划承运商新增 422 回归
- **WHEN** 前端 `POST /api/transport/carrier` 发送 `{ "carrierName": "测试" }`
- **THEN** `CarrierCreate` SHALL 含 `carrier_name = Field(..., alias="carrierName")` 且校验通过
- **AND** SHALL NOT 因缺少 alias 返回 422 `carrier_name Field required`
#### Scenario: 运输计划列表 pageSize Query
- **WHEN** 前端 `GET /api/transport/plan/?page=2&pageSize=50`
- **THEN** 路由 SHALL 使用 `page_size: int = Query(..., alias="pageSize")` 解析为 50
- **AND** SHALL NOT 忽略 `pageSize` 而始终使用默认 20
#### Scenario: 前端提交新增分类
@@ -264,9 +264,9 @@ Web 管理端弹窗 SHALL 遵循规范:表单 `label-position="top"`;遮罩
1. **操作列「查看」按钮**:操作列 SHALL 在首位包含一个「查看」按钮(`<el-icon><View /></el-icon>查看`),点击后弹出只读查看弹窗。
2. **行双击触发**:`el-table` SHALL 监听 `@row-dblclick` 事件,双击任意行时触发与「查看」按钮相同的弹窗(即调用同一 `handleView(row)` 函数)。
3. **条件按钮场景**:当操作列按钮随状态/Tab 变化(如待确认 Tab 显示「确认收款」,已确认 Tab 显示「查看确认信息」),`@row-dblclick` handler SHALL 仅在「查看」类按钮可见时触发对应查看函数,其余状态下可忽略或调用对应主操作函数。
3. **弹窗样式**:查看弹窗 SHALL 使用 `SystemViewDialog` 共享组件(`src/components/business/system-view-dialog/index.vue`),样式对齐待办中心「查看详情」弹窗(无标题栏、hero 渐变头图 + 图标、KV grid 字段展示、关闭按钮)。
4. **字段内容**:查看弹窗 SHALL 展示该行记录的所有业务字段(含状态映射、字典翻译),不展示 `id`、`del_flag` 等技术字段。长文本/备注字段 SHALL 使用 `span: true` 跨两列展示。
5. **区块标题样式**:查看弹窗中各区块标题(`SystemViewDialog` 的 section title,渲染为 `h3`)SHALL 与字段标签(`dt`)形成明显视觉区分。区块标题 SHALL 使用深色文字(`#1e293b`)配合左侧主色竖条装饰(`border-left: 3px solid #10b981`),字号 14px、`font-weight: 600`;字段标签 SHALL 保持为辅助灰色(`#64748b`)、`font-weight: 500`。区块标题样式 SHALL NOT 与字段标签使用相同颜色或相近字重。
4. **弹窗组件**:查看弹窗 SHALL 优先使用 `SystemViewDialog`(`web-admin/src/components/business/system-view-dialog/index.vue`);样式对齐 Hero 渐变头图 + 分区 KV 信息区 + 底部「关闭」按钮(详见下文「查看弹窗 KV 布局与组件选型」)。KV 信息区 SHALL 使用 `ViewKvGrid`(上下堆叠栅格)或 `ViewKvTable`(多组多列表格);**SHALL NOT** 使用 `el-descriptions` 或历史 `.dlg-kv` 布局承载列表行查看详情。
5. **字段内容**:查看弹窗 SHALL 展示该行记录的所有业务字段(含状态映射、字典翻译),不展示 `id`、`del_flag` 等技术字段;KV 跨列/换行规则见「查看弹窗 KV 布局与组件选型」。
6. **区块标题样式**:查看弹窗中各区块标题(如 `SystemViewDialog` 的 section title、`bx` 模块 `.dlg-view-section-title`,渲染为 `h3`/`h4`)SHALL 与字段标签形成明显视觉区分。区块标题 SHALL 使用深色文字(`#1e293b`)配合左侧主色竖条装饰(`border-left: 3px solid #10b981`),字号 14px、`font-weight: 600`;字段标签 SHALL 为 11px、`#64748b`、`font-weight: 400`(由 `ViewKvGrid` 的 `dt` 或 `ViewKvTable` 的 `th` 统一渲染)。区块标题样式 SHALL NOT 与字段标签使用相同颜色或相近字重。
#### Scenario: 新增系统管理列表页
@@ -284,6 +284,169 @@ Web 管理端弹窗 SHALL 遵循规范:表单 `label-position="top"`;遮罩
- **WHEN** PR 修改列表页面且页面包含 `el-table`
- **THEN** 审查者 SHALL 确认 `el-table` 绑定了 `@row-dblclick` 事件,且操作列包含「查看」按钮
### Requirement: 查看弹窗 KV 布局与组件选型
`web-admin` 列表行【查看】弹窗及同类只读详情弹窗的键值(KV)信息区 SHALL 采用下列两种**规范布局之一**,**SHALL NOT** 使用 Element Plus `el-descriptions`,**SHALL NOT** 使用已废弃的 `.dlg-kv` 四列「label|value|label|value」横向布局,**SHALL NOT** 在业务页手写无统一样式的 `<table>` 承载主体 KV。
| 布局 | 说明 | 默认场景 |
|------|------|----------|
| **上下堆叠栅格** | label 在上、value 在下,多列 grid | 字段较多、需 3~4 列密度;`SystemViewDialog` 默认 |
| **多组多列表格** | 每行左 label(`th`)、右 value(`td`);短字段一行可排多组 | 字段较少(通常 ≤10)、需表格对齐感;如报销备用金申请「基础信息」 |
权威视觉细节见 `docs/web-admin-ui-spec-fresh-mint.md` §9.5 / §9.8;交互 Demo 见 `docs/fresh-mint-view-wide-dialog-demos.html`(上下堆叠见主区;**多组多列表格**见 `#table-section`,模式 M~R)。
#### 1. 组件选型
| 场景 | 组件 | 路径 |
|------|------|------|
| 门户 / CRM / 系统管理等(默认) | `SystemViewDialog` | `web-admin/src/components/business/system-view-dialog/index.vue` |
| KV 栅格渲染(上下堆叠,被上述组件复用) | `ViewKvGrid` | `web-admin/src/components/business/system-view-dialog/ViewKvGrid.vue` |
| KV 表格渲染(多组多列,被上述组件复用) | `ViewKvTable` | `web-admin/src/components/business/system-view-dialog/ViewKvTable.vue` |
| 报销管理(`bx` 模块,Hero 自定义壳) | `BxViewKvGrid` / `BxViewKvTable` | `web-admin/src/components/bx/base/BxViewKvGrid.vue`、`BxViewKvTable.vue`(内部分别复用 `ViewKvGrid` / `ViewKvTable`) |
- 新增业务查看弹窗 **SHALL** 优先 `SystemViewDialog` + `ViewField[]` / `ViewSection[]`。
- 字段极多、需 Tab / KPI / 分栏的复杂查看 **SHALL** 采用界面模式 **P10**(独立 `*ViewDialog`,内部仍复用 `ViewKvGrid` / `ViewKvTable` 或 `SystemViewDialog` 的 section 结构)。
- `bx` 模块保留 `.dlg-view-hero` 等既有 Hero 壳时,KV 区 **SHALL** 使用 `BxViewKvGrid` 或 `BxViewKvTable`,**SHALL NOT** 继续手写 `.dlg-kv` 或裸 `<table>`。
#### 2. 弹窗宽度与默认列数
- `SystemViewDialog` 默认宽度 **SHALL** 为 **`1040px`**(`width` prop 可覆盖;合同/报价等宽表场景可用 `calc(100vw - Npx)` 或更大固定宽)。
- 上下堆叠栅格默认列数 **SHALL** 为 **4 列**(`gridColumns: 4`);紧凑模式(`compact`)降为 **2 列**。
- 多组多列表格 **SHALL** 在 **`1040px`** 弹窗内使用;少字段(≤8)可选用 **`720px`~`920px`**(见 Demo Q/R)。
- `bx` 模块 `.dialog-view-xl` 宽度 **SHALL** 与上述一致(`min(1040px, calc(100vw - 32px))`)。
#### 3. KV 单元格结构(上下堆叠栅格)
每个字段渲染为 `<dl>` 内的 `.view-kv` 单元:
- **dt(label)**:上,11px,`#64748b`,`font-weight: 400`
- **dd(value)**:下,13px,`font-weight: 600`,`word-break: break-word`
- **SHALL NOT** 在同一 grid cell 内使用左右并排的 label|value(历史 `el-descriptions` / `.dlg-kv .k/.v` 模式)
#### 4. 四列栅格跨列规则
栅格容器为 `.view-kv-grid.view-kv-grid--cols-4`,`gap: 12px 18px`。跨列逻辑 **SHALL** 由 `web-admin/src/utils/view-field-layout.ts` 与 `ViewField` 属性共同决定:
| 类型 | 每行个数 | 触发条件 | CSS class |
|------|----------|----------|-----------|
| 短值 | **4 个/行** | 默认;显示文本 &lt; 17 字且非长 label 启发式 | (无 span) |
| 长值 | **2 个/行** | `long: true` 或 `span: true`;或自动:文本 ≥17 字、含 ` / ` 路径、label 匹配备注/地址/说明等 | `view-kv-span-2` |
| 整行 | **1 个/行** | `fullSpan: true` 或 `span: 'full'`;或自动:文本 ≥80 字 | `view-kv-span-full` |
常量:`VIEW_KV_LONG_MIN_LEN = 17`,`VIEW_KV_FULL_MIN_LEN = 80`(见 `view-field-layout.ts`)。
响应式:**SHALL** 在 ≤1100px 降为 3 列、≤900px 降为 2 列、≤640px 降为 1 列(`ViewKvGrid.vue` 内 media query);长值在单列下 **SHALL** 占满整行。
#### 5. ViewField 定义约定
类型定义见 `web-admin/src/components/business/system-view-dialog/types.ts`。
```typescript
interface ViewField {
label: string
value?: string | number | boolean | null
items?: string[] // 无编号列表,与 value 二选一
span?: boolean | 'full' // true=长值(2列);'full'=整行
long?: boolean // 显式长值,等同 span:true
fullSpan?: boolean // 显式整行
tagType?: TagType // 状态/类型 → el-tag
money?: boolean // 金额红色 .money-display
}
```
- 空值 **SHALL** 展示为 `—`(`formatViewValue`)。
- 字典/状态 **SHALL** 预先翻译为 label 并视情况设 `tagType`(见「查看弹窗中编码字段展示为可读标签」「状态、结论与类型字段使用 Tag 标签展示」)。
- 金额 **SHALL** 设 `money: true` 并使用 `¥` 前缀(见「价格字段展示规范」)。
#### 6. 弹窗骨架(SystemViewDialog)
标准只读查看弹窗 **SHALL** 按以下顺序组织:
1. **Hero**(`.view-hero`):图标 + 主标题 + 可选 `meta` / `code` + 状态 Tag(hero-actions 插槽可选)
2. **分区**(`.view-section`):区块标题 + `ViewKvGrid` / `ViewKvTable` 或具名插槽(子表、附件、审批流等)
3. **Footer**:仅「关闭」类按钮(带 `Close` 图标),**SHALL NOT** 在查看态混入提交语义
子表、附件、审批历史等 **SHALL** 作为独立 section 或 slot 置于 KV 区之后,**SHALL NOT** 嵌入 KV 栅格 cell 或 KV 表格 `<td>`。
#### 7. 模块辅助工具(推荐)
| 模块 | 工具 | 用途 |
|------|------|------|
| CRM | `web-admin/src/utils/crm/view-fields.ts` | `crmField` / `crmLongField` / `crmDictField` / `crmTagField` 构建 `ViewField` |
| 报销 bx | `web-admin/src/utils/bx/view-kv.ts` | `bxViewKv` / `bxViewKvLong` / `bxViewKvMoney` |
#### 8. KV 表格模式(多组多列)
当业务选用**多组多列表格**展示 KV 时,**SHALL** 使用 `ViewKvTable`(或 `BxViewKvTable`),数据源仍为 `ViewField[]`;跨行/长值逻辑 **SHALL** 复用 `web-admin/src/utils/view-field-layout.ts`(`isViewFieldLong` / `isViewFieldFullSpan` / `buildViewKvTableDualRows` / `buildViewKvTableTripleRows`)。
**模式(`mode` prop)**
| mode | 每行组数 | 列结构 | 典型场景 |
|------|----------|--------|----------|
| `pair` | **1 组** | `th` + `td`(两列) | 少字段、需逐行阅读;Demo **Q** |
| `dual` | **2 组** | `th` + `td` × 2(四列) | 中等字段量;Demo **N** / **R** |
| `triple` | **3 组** | `th` + `td` × 3(六列) | 少字段高密度;Demo 扩展;参考 `bx/advance/apply` 基础信息 |
**行分组规则**
- 短字段 **SHALL** 按 mode 尽可能填满一行(`dual` 最多 2 组、`triple` 最多 3 组);末行不足时 **SHALL** 留空 `th`/`td` 占位,**SHALL NOT** 强行与长字段同组。
- 长值 / 整行字段(`long` / `fullSpan` 或 `view-field-layout.ts` 自动检测)**SHALL** 独占整行:`pair` 为单行 `th`+`td`;`dual` 为 `td colspan="3"`;`triple` 为 `td colspan="5"`。
- 字段顺序 **SHOULD** 将长文本(申请原因、备注等)置于数组末尾,以便短字段先组成完整多组行。
**表格样式(Fresh Mint)**
- 外层 **SHALL** 使用 `.view-kv-table-wrap`(圆角 8px、边框 `#d9e8e2`)。
- 表头单元格 **`th`(label)**:背景 `var(--table-head)`,11px、`#64748b`;**SHALL** 支持换行(`white-space: normal`、`word-break: break-word`、`overflow-wrap: anywhere`),**SHALL NOT** 使用 `white-space: nowrap` 导致长 label(如「实际发放金额(元)」)溢出。
- 数据单元格 **`td`(value)**:13px、`font-weight: 600`;金额 **SHALL** 使用 `.money-display`;状态/类型 **SHALL** 使用 `el-tag`(`ViewKvTableCell`)。
- `triple` 模式 label 列宽约 **10%**、value 列宽约 **23.33%**(三组合计六列);具体比例以 `ViewKvTable.vue` 为准。
**选型建议**
- 字段 **≤8** 且弹窗显空 → 优先 **`pair`** 或 **`triple`** + 适当缩窄弹窗宽度。
- 字段 **9~18** 且需表格感 → 优先 **`dual`**(1040px)。
- 同一弹窗内 **SHALL NOT** 混用 `ViewKvGrid` 与 `ViewKvTable` 于**同一 section**;不同 section 可按需分别选用(如「基础信息」用表格、「扩展信息」用栅格)。
#### 9. 禁止与废弃
- **SHALL NOT** 在新代码中使用 `el-descriptions` 实现列表行【查看】弹窗主体 KV。
- **SHALL NOT** 使用 `.dlg-kv`、`.dlg-kv--triple` 横向 KV(`web-admin/src/styles/bx/index.scss` 内已标记 deprecated,仅兼容旧页)。
- **SHALL NOT** 手写 `<table class="...">` 实现 KV 主体区;**SHALL** 使用 `ViewKvTable` / `BxViewKvTable`。
- 迁移存量页时 **SHALL** 保留 Hero / 子表 / 附件 / 审批流结构,**仅替换** KV 区为 `ViewKvGrid` / `ViewKvTable` / `BxViewKvGrid` / `BxViewKvTable` / `SystemViewDialog`。
#### Scenario: 新增 CRM 列表查看弹窗
- **WHEN** 在 CRM 模块新增带【查看】按钮的列表页
- **THEN** SHALL 使用 `SystemViewDialog`(或独立 `*DetailDialog` 封装该组件)
- **THEN** KV 字段 SHALL 通过 `ViewSection[]` + `ViewField[]` 定义,默认 4 列上下堆叠
- **THEN** SHALL NOT 使用 `el-descriptions`
#### Scenario: 报销模块查看弹窗(上下堆叠)
- **WHEN** 在 `web-admin/src/views/bx/**` 实现或改造查看弹窗,且 KV 区采用上下堆叠栅格
- **THEN** KV 区 SHALL 使用 `BxViewKvGrid`(columns 默认 4)
- **THEN** 弹窗宽度 SHALL 为 1040px 级(`.dialog-view-xl`)
#### Scenario: 报销模块查看弹窗(多组多列表格)
- **WHEN** 在 `web-admin/src/views/bx/**` 的查看弹窗中,某 section(如「基础信息」)采用多组多列表格
- **THEN** SHALL 使用 `BxViewKvTable` + `ViewField[]`,并显式声明 `mode`(`pair` / `dual` / `triple`)
- **THEN** 长 label SHALL 在 `th` 内换行展示,**SHALL NOT** 溢出列宽
- **THEN** 参考实现:`web-admin/src/views/bx/advance/apply/index.vue`(`mode="triple"`)
#### Scenario: 长文本与备注字段
- **WHEN** 查看弹窗包含申请原因、备注、条款等长文本
- **THEN** SHALL 为对应 `ViewField` 设置 `long: true` 或 `fullSpan: true`(或依赖 `view-field-layout.ts` 自动检测)
- **THEN** 4 列栅格下 SHALL 占 2 列或整行,**SHALL NOT** 与短字段同一行挤 4 个
#### Scenario: Code review KV 布局
- **WHEN** PR 新增或修改查看弹窗
- **THEN** 审查者 SHALL 确认 KV 区为 `ViewKvGrid`(上下堆叠 + 声明的 `gridColumns`)或 `ViewKvTable`(多组多列 + 声明的 `mode`)
- **THEN** SHALL 确认未使用 `el-descriptions` / `.dlg-kv` / 手写 KV `<table>` 作为主体字段区
- **THEN** 状态/金额/字典字段 SHALL 符合 Tag、`money`、字典翻译等关联 Requirement
- **THEN** 若使用 `ViewKvTable`,SHALL 确认长 label 可换行、长 value 独占整行
### Requirement: 查看弹窗中编码字段展示为可读标签
`web-admin` 中所有只读查看弹窗(`SystemViewDialog` 及类似组件)中,存储为编码(code / value)的字段 SHALL 展示为对应的人类可读标签(label),SHALL NOT 直接展示原始编码值。这包括但不限于:字典编码→字典标签、状态编码→状态文本、站点类型编码→类型名称等。
@@ -716,6 +879,14 @@ watchDebounced(
| 退订 | `Remove` | 退订、减少持有类操作 |
| 作废 | `Delete` | 作废流水/订单(与删除同图标,可用 `op-warn` 样式区分) |
| 操作流水 / 记录列表 | `List` | 「操作流水」等子列表入口 |
| 返回 | `Close` | 详情页、表单页「返回」按钮(与取消同图标) |
| 选择 / 挑选 | `Select` | 输入框 append「选择」、挑选合同/客户等 |
| 安排运输 | `Van` | 运输计划「安排运输」操作 |
| 归档 / 批量归档 | `FolderChecked` | 计划归档、批量归档 |
| 取消计划 | `CircleClose` | 取消运输计划等业务取消 |
| 二维码 | `Share` | 生成/查看运输二维码 |
| 提交结算 | `Promotion` | 运费结算「提交」 |
| 审核通过 | `CircleCheck` | 运费结算「审核」、证件「处理」等确认类操作 |
#### Scenario: 新增列表页工具栏按钮
@@ -742,6 +913,13 @@ watchDebounced(
- **WHEN** PR 包含新增或修改的 `el-button`
- **THEN** 审查者 SHALL 确认该按钮包含 `<el-icon>` 子元素,且图标组件已从 `@element-plus/icons-vue` 显式导入
#### Scenario: 运输计划模块(web-admin/src/views/pl)
- **WHEN** 实现或修改 `web-admin/src/views/pl/**` 下运输计划菜单页面(承运商管理、车辆管理、司机管理、运输计划、运输管理、运费结算、库存台账、证件提醒及其子页 form/detail/arrange)
- **THEN** 每个带可见文案的 `el-button` SHALL 在文字前方包含 `<el-icon>`,并按上表选用语义图标(如搜索 `Search`、重置 `RefreshLeft`、新增 `Plus`、查看 `View`、安排运输 `Van`、归档 `FolderChecked`、提交 `Promotion`、审核/处理 `CircleCheck`、选择 `Select` 等)
- **THEN** 图标组件 SHALL 在 `<script setup>` 中从 `@element-plus/icons-vue` 显式 `import`,SHALL NOT 依赖隐式全局注册
- **THEN** Code review SHALL 拒绝仅有文字、无 `<el-icon>` 的按钮
#### Scenario: 渠道服务订单与财务确认页
- **WHEN** 实现或修改 `web-admin/src/views/pilot-service/channel-service-order/**` 或 `channel-service-order-finance/**` 中的按钮(含弹窗底栏、表格操作列、行内 link 删除)