提交
This commit is contained in:
1 parent
493f8d6756
commit
d84f4b944c
406 files changed
+58954
-10376
No files matched your search
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-06-29
|
||||
@@ -0,0 +1,94 @@
|
||||
## Context
|
||||
|
||||
已有 `ApiRequestLogMiddleware` 全量采集 Web API 至 `sys_api_request_log`(path、query、request_body、response_body、user_id、username、create_at 等)。`operlog/list` 将上述数据适配为 RuoYi `OperLogVO`,权限为 admin/super_admin,CRM 详情 Tab 已调用但未按业务实体过滤。
|
||||
|
||||
约束:MySQL 5.7、租户隔离、数据权限、Fresh Mint UI(P01 列表)、中间件与 `ApiCryptoMiddleware` 顺序不变(Crypto 外层、RequestLog 内层,记录明文 JSON)。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 客户可见:姓名、时间、地址、入参、出参
|
||||
- 业务语义可筛、可按 `business_id` 关联单据
|
||||
- 写操作通过 `@oper_log` 声明;未声明接口保留路径推断兜底
|
||||
- 全局操作日志管理页
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 前端 UI 行为埋点(不发 API 的操作)
|
||||
- 独立审计子系统(MQ/ES)
|
||||
- 与 CRM Java 仓 `sys_oper_log` 数据合并
|
||||
- 本期全量 `@oper_log` 覆盖所有写接口
|
||||
- multipart 文件内容入库
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 方案 A + 语义字段(不新建 sys_oper_log)
|
||||
|
||||
在 `sys_api_request_log` 扩展列,中间件继续全量采集;语义由 `@oper_log` + ContextVar 注入。理由:基础设施已存在,改动面可控。
|
||||
|
||||
### D2 语义字段集
|
||||
|
||||
| 列 | 说明 |
|
||||
|----|------|
|
||||
| `tenant_no` | 租户隔离 |
|
||||
| `business_title` | 模块,如 `CRM客户管理` |
|
||||
| `business_type` | tinyint,对齐 `sys_oper_type`(0其他 1新增 2修改 3删除 5导出 6导入) |
|
||||
| `business_id` | 业务主键,可空 |
|
||||
| `oper_summary` | 人读摘要,如 `修改客户【杭州某某化工】` |
|
||||
|
||||
展示优先级:`oper_summary` 用于列表摘要;`title`/`businessType` 映射 operlog API 字段。
|
||||
|
||||
### D3 写入链路
|
||||
|
||||
1. 请求进入 → 路由 `@oper_log` 在 handler 执行前/包装器内设置 `ContextVar`
|
||||
2. `call_next` 完成 → 中间件 `finally` 读 ContextVar,与 JWT 字段一并 `_persist_log`
|
||||
3. ContextVar 为空时:`business_title`/`business_type` 由 `oper_log.py` 现有路径规则在**读时**兜底(落库可为 NULL,或中间件落库时同步写推断值——实现取**落库时写推断值**以减少读时计算)
|
||||
|
||||
### D4 请求人姓名
|
||||
|
||||
JWT payload 已有 `real_name`(nick_name)。中间件 `_claims_from_auth` 扩展解析并写入 `create_name`;`username` 仍为 `user_no`。
|
||||
|
||||
### D5 oper_summary 模板
|
||||
|
||||
`{动词}{对象}【{显示名或编号}】`,动词与 `business_type` 一致。装饰器可传 `summary` 或模板参数;Service 层可在执行业务后更新摘要(二期可选)。
|
||||
|
||||
### D6 查询与权限
|
||||
|
||||
- `operlog/list`:DB 层筛选 `tenant_no`、语义字段;数据权限仍按 `user_id`
|
||||
- 管理页菜单:`system:operlog:list`;仅 admin/super_admin(与现 monitor API 一致)
|
||||
- CRM Tab:`business_id` + 可选 `business_title` 精确过滤
|
||||
|
||||
### D7 记录范围(默认)
|
||||
|
||||
- **记录**:经 FastAPI 的业务 API(含 GET 列表)
|
||||
- **跳过**:OPTIONS、/health、/docs、/openapi.json(与现中间件一致)
|
||||
- **不完整**:multipart、超大 body、敏感字段脱敏(保持现逻辑)
|
||||
|
||||
客户若后续要求「仅写操作」,通过 `API_REQUEST_LOG_EXCLUDE_PATHS` 配置扩展(本期仅预留 settings 字段,默认空)。
|
||||
|
||||
### D8 归档
|
||||
|
||||
表 `sys_api_request_log_his` 已存在;本期迁移脚本扩展同名列。定时归档任务记入 tasks 后续项,不阻塞主功能。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| 日志量膨胀 | 后续归档 + 可选排除只读路径 |
|
||||
| 未注解接口语义弱 | 路径推断兜底 + 逐步补注解 |
|
||||
| 响应体含 PII | 保持脱敏;详情仅授权角色可见 |
|
||||
| ContextVar 在子线程丢失 | 落库用 `asyncio.to_thread` 时传入已收集的 dict,不依赖子线程读 ContextVar |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 执行 `docs/sql/migrations/2026-06-29-operation-audit.sql`
|
||||
2. 部署后端(中间件 + 装饰器 + API)
|
||||
3. 部署前端(管理页 + CRM Tab)
|
||||
4. 确认生产 `API_REQUEST_LOG_ENABLED=true`
|
||||
5. 为核心 CRM 写接口补 `@oper_log`
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 日志在线保留天数(默认 90 天归档,待客户确认)
|
||||
- 响应体是否额外脱敏手机号/身份证(默认保持现状,仅 password/token 类)
|
||||
@@ -0,0 +1,51 @@
|
||||
## Why
|
||||
|
||||
客户要求系统中用户操作可追溯:记录请求人姓名、请求时间、接口地址、入参与出参。shanxia2.0 已通过 `ApiRequestLogMiddleware` 写入 `sys_api_request_log`,但请求人多为账号而非姓名、缺少全局管理页、业务语义弱、无法按业务单据精确查询。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 增强现有 API 请求日志
|
||||
|
||||
1. **中间件补齐** — JWT 解析 `real_name` → `create_name`;`tenant_no` 落库;异步写库行为保持
|
||||
2. **业务语义字段** — `business_title`、`business_type`、`business_id`、`oper_summary`(写操作由 `@oper_log` 声明,只读走路径推断兜底)
|
||||
3. **ContextVar + 装饰器** — `app/core/context.py` 扩展;`@oper_log` 供路由/写接口标注模块、类型、业务主键与摘要
|
||||
|
||||
### 查询与管理
|
||||
|
||||
4. **operlog API 增强** — 列表/详情优先读落库语义字段;支持按 `business_id`、`business_title`、`business_type` 筛选
|
||||
5. **操作日志管理页** — `web-admin` 标准列表(P01),`admin` / `super_admin` 可查;详情展示入参/出参
|
||||
6. **CRM 嵌入 Tab 优化** — 客户/合同详情按 `business_id` 查日志,不再拉全量前端过滤
|
||||
|
||||
### 数据库
|
||||
|
||||
7. **表结构扩展** — `sys_api_request_log`、`sys_api_request_log_his` 增加语义列与 `tenant_no`、索引
|
||||
8. **迁移脚本** — `docs/sql/migrations/2026-06-29-operation-audit.sql`;同步 `docs/sql/shanxia2.0.sql`
|
||||
|
||||
### 非本期(文档标注为后续)
|
||||
|
||||
- 定时归档至 `sys_api_request_log_his`
|
||||
- 可配置高频只读 API 排除规则
|
||||
- 全模块 `@oper_log` 覆盖(本期优先 CRM 写接口 + 样例)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `operation-audit`: API 全量请求审计与业务语义操作日志
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- (无独立基线 spec 变更;实现须符合 `zkxw-engineering-standards` 中 api/database/ui/permission conventions)
|
||||
|
||||
## Impact
|
||||
|
||||
- 后端:`api_request_log_middleware.py`、`context.py`、新 `oper_log` 装饰器、`oper_log.py` CRUD、`monitor.py`、ORM、`schemas`
|
||||
- 前端:新 `views/system/operlog/`(或 `monitor/operlog/`)、路由、菜单;CRM 详情 Tab 查询参数
|
||||
- 配置:`API_REQUEST_LOG_ENABLED` 生产须为 true
|
||||
|
||||
## Reference
|
||||
|
||||
- 现有中间件:`backend/app/core/api_request_log_middleware.py`
|
||||
- 现有 operlog 适配:`backend/app/crud/monitor/oper_log.py`
|
||||
- 设计文档:`docs/superpowers/specs/2026-06-29-operation-audit-design.md`
|
||||
- RuoYi 对照:`docs/sql/sx.sql` → `sys_oper_log`
|
||||
@@ -0,0 +1,111 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: API 请求全量采集
|
||||
|
||||
系统 SHALL 通过 `ApiRequestLogMiddleware` 记录经 FastAPI 处理的 API 请求,并写入 `sys_api_request_log`。每条记录 SHALL 包含 HTTP 方法、路径(接口地址)、Query 字符串、请求体、响应体、HTTP 状态码、耗时、IP、User-Agent、请求时间(`create_at`)、用户 ID、用户账号(`user_no`)及用户姓名(`nick_name` → `create_name`)。
|
||||
|
||||
#### Scenario: 记录业务 API 入参与出参
|
||||
|
||||
- **WHEN** 已认证用户调用 JSON 业务接口(如 `POST /api/web/crm/customer/update`)
|
||||
- **THEN** 系统持久化 `path`、`query_string`、`request_body`、`response_body`
|
||||
- **AND** `create_at` 为请求完成时刻
|
||||
- **AND** `create_name` 为 JWT 中的 `real_name`
|
||||
|
||||
#### Scenario: 敏感字段脱敏
|
||||
|
||||
- **WHEN** 请求或响应 JSON 包含 `password`、`token`、`authorization` 等敏感键
|
||||
- **THEN** 落库值 SHALL 为 `***` 而非明文
|
||||
|
||||
#### Scenario: 跳过高噪与健康检查路径
|
||||
|
||||
- **WHEN** 请求为 `OPTIONS` 或路径为 `/health`、`/docs`、`/openapi.json`
|
||||
- **THEN** 系统 SHALL 不写入 `sys_api_request_log`
|
||||
|
||||
#### Scenario: 文件上传不记录文件体
|
||||
|
||||
- **WHEN** 请求 Content-Type 为 `multipart/form-data`
|
||||
- **THEN** `request_body` SHALL 标记为省略说明而非文件二进制内容
|
||||
|
||||
### Requirement: 业务语义字段落库
|
||||
|
||||
系统 SHALL 在 `sys_api_request_log` 存储业务语义列:`tenant_no`、`business_title`、`business_type`、`business_id`、`oper_summary`。写操作接口 SHOULD 通过 `@oper_log` 声明上述字段;未声明时系统 SHALL 使用路径与方法推断规则填充 `business_title` 与 `business_type`(`business_id` 与 `oper_summary` 可为空)。
|
||||
|
||||
#### Scenario: 装饰器声明修改客户
|
||||
|
||||
- **WHEN** 带 `@oper_log(title="CRM客户管理", business_type=2, business_id_param="id", summary="修改客户【{name}】")` 的更新接口被调用且 ContextVar 已设置
|
||||
- **THEN** 落库 `business_title=CRM客户管理`、`business_type=2`
|
||||
- **AND** `business_id` 为请求中的客户 id
|
||||
- **AND** `oper_summary` 为可读摘要文本
|
||||
|
||||
#### Scenario: 只读列表无装饰器
|
||||
|
||||
- **WHEN** 用户调用客户列表 GET 接口且路由无 `@oper_log`
|
||||
- **THEN** 系统仍记录 path 与入参出参
|
||||
- **AND** `business_title` 由路径推断(如 `CRM客户管理`)
|
||||
- **AND** `business_type` 为 `0`(其他)
|
||||
|
||||
#### Scenario: business_type 枚举
|
||||
|
||||
- **WHEN** 系统写入 `business_type`
|
||||
- **THEN** 值 SHALL 属于:0其他、1新增、2修改、3删除、5导出、6导入(与 `sys_oper_type` 字典一致)
|
||||
|
||||
### Requirement: 租户隔离
|
||||
|
||||
操作日志查询 SHALL 按当前用户 `tenant_no` 过滤 `sys_api_request_log.tenant_no`。中间件落库时 SHALL 从 JWT 写入 `tenant_no`。
|
||||
|
||||
#### Scenario: 租户数据不可见
|
||||
|
||||
- **WHEN** 租户 A 的管理员查询操作日志
|
||||
- **THEN** 结果 SHALL 不包含租户 B 的记录
|
||||
|
||||
### Requirement: 操作日志查询 API
|
||||
|
||||
系统 SHALL 提供 `POST /api/web/monitor/operlog/list` 与 `POST /api/web/monitor/operlog/detail`,将日志适配为 RuoYi `OperLogVO` 结构:`operUrl`(path)、`operParam`(request_body)、`jsonResult`(response_body)、`operName`(create_name 优先)、`operTime`、`title`(business_title)、`businessType`。
|
||||
|
||||
#### Scenario: 列表筛选业务实体
|
||||
|
||||
- **WHEN** 请求体包含 `businessId` 与可选 `businessTitle`
|
||||
- **THEN** 返回匹配 `business_id` 的日志分页列表
|
||||
|
||||
#### Scenario: 详情含完整入参出参
|
||||
|
||||
- **WHEN** 管理员请求某条日志详情
|
||||
- **THEN** 响应 SHALL 包含完整 `operParam` 与 `jsonResult`(受库内截断上限约束)
|
||||
|
||||
#### Scenario: 数据权限
|
||||
|
||||
- **WHEN** 非 super_admin 用户查询列表
|
||||
- **THEN** 结果 SHALL 受数据权限约束(按操作人 `user_id` 范围)
|
||||
|
||||
#### Scenario: 访问控制
|
||||
|
||||
- **WHEN** 非 admin/super_admin 用户调用 operlog API
|
||||
- **THEN** 系统 SHALL 拒绝访问
|
||||
|
||||
### Requirement: 操作日志管理页
|
||||
|
||||
web-admin SHALL 提供操作日志标准列表页(界面模式 P01),支持按模块、操作类型、操作人、状态、时间范围筛选;详情展示接口地址、入参、出参与摘要。按钮与表格 SHALL 符合 Fresh Mint 与 `web-admin-crud-dialog` 规范。
|
||||
|
||||
#### Scenario: 管理员查看日志列表
|
||||
|
||||
- **WHEN** 已授权的 admin 用户打开操作日志页
|
||||
- **THEN** 展示分页列表,列含模块、类型、操作人、时间、摘要、状态
|
||||
- **AND** 可打开详情查看 `operParam` 与 `jsonResult`
|
||||
|
||||
### Requirement: CRM 业务详情操作日志 Tab
|
||||
|
||||
CRM 客户详情与合同详情中的操作日志 Tab SHALL 按 `business_id` 调用 operlog 列表,不再拉取全量日志后前端过滤。
|
||||
|
||||
#### Scenario: 客户详情日志
|
||||
|
||||
- **WHEN** 用户在某客户详情打开「操作日志」Tab
|
||||
- **THEN** 仅展示 `business_id` 等于该客户 id 的日志
|
||||
|
||||
### Requirement: 配置与生产开关
|
||||
|
||||
`API_REQUEST_LOG_ENABLED` 在生产环境 SHALL 为 `true`。body 截断上限 SHALL 保持可配置(`API_REQUEST_LOG_MAX_BODY`、`API_REQUEST_LOG_MAX_BODY_BYTES`)。
|
||||
|
||||
#### Scenario: 关闭采集
|
||||
|
||||
- **WHEN** `API_REQUEST_LOG_ENABLED=false`
|
||||
- **THEN** 中间件 SHALL 不写入数据库
|
||||
@@ -0,0 +1,42 @@
|
||||
## 1. 数据库
|
||||
|
||||
- [x] 1.1 创建 `docs/sql/migrations/2026-06-29-operation-audit.sql`(扩展 `sys_api_request_log` / `_his`:`tenant_no`、`business_title`、`business_type`、`business_id`、`oper_summary` + 索引)
|
||||
- [x] 1.2 同步 `docs/sql/shanxia2.0.sql`
|
||||
|
||||
## 2. 后端 · 采集层
|
||||
|
||||
- [x] 2.1 扩展 `app/core/context.py`:`oper_log_meta` ContextVar(title/type/id/summary)→ `oper_log_context.py`
|
||||
- [x] 2.2 新增 `app/core/oper_log_decorator.py`:`@oper_log(...)` 装饰器
|
||||
- [x] 2.3 更新 `api_request_log_middleware.py`:解析 `real_name`、`tenant_no`;落库语义字段;推断兜底
|
||||
- [x] 2.4 更新 `SysApiRequestLog` / `SysApiRequestLogHis` ORM
|
||||
|
||||
## 3. 后端 · 查询层
|
||||
|
||||
- [x] 3.1 更新 `oper_log.py`:优先读落库语义列;列表支持 `businessId`、`businessTitle`、`tenant_no` DB 筛选
|
||||
- [x] 3.2 更新 `schemas/monitor/operlog.py` 与 `monitor.py` 请求体字段
|
||||
- [x] 3.3 更新 `api_request_log_crud.py` 历史表字段(列表仍走 operlog API)
|
||||
|
||||
## 4. 后端 · 注解覆盖(首期)
|
||||
|
||||
- [x] 4.1 CRM 客户写接口 `@oper_log`(增删改、公海、转移等)
|
||||
- [x] 4.2 CRM 合同写接口 `@oper_log`(新增/修改/删除/导出/作废)
|
||||
- [x] 4.3 配置项预留 `API_REQUEST_LOG_EXCLUDE_PATHS`(默认空)
|
||||
|
||||
## 5. 前端
|
||||
|
||||
- [x] 5.1 `api/monitor/operlog` 类型与查询参数扩展
|
||||
- [x] 5.2 操作日志列表页 `views/system/operlog/index.vue`(P01)+ 详情弹窗
|
||||
- [x] 5.3 路由 `router/modules/sqxp.ts` + 菜单 SQL(`system:operlog:list`)
|
||||
- [x] 5.4 CRM `CustomerDetailDialog`、`contract/detail` Tab 改为 `businessId` 查询
|
||||
|
||||
## 6. 验收与文档
|
||||
|
||||
- [ ] 6.1 冒烟:写操作落库姓名/语义/入参出参;管理页筛选;客户详情 Tab(需执行迁移并重启后端)
|
||||
- [x] 6.2 默认配置 `API_REQUEST_LOG_ENABLED=true`(`config.py`)
|
||||
- [x] 6.3 `./scripts/update-graph.sh backend`(frontend 待有 import 变更后可再跑)
|
||||
|
||||
## 7. 后续(非本期阻塞)
|
||||
|
||||
- [ ] 7.1 定时任务:热数据归档至 `sys_api_request_log_his`
|
||||
- [ ] 7.2 运输计划 / 报销等模块 `@oper_log` 全覆盖
|
||||
- [ ] 7.3 响应体 PII 扩展脱敏规则
|
||||
@@ -63,11 +63,32 @@
|
||||
|
||||
### Requirement: 承运商管理
|
||||
|
||||
系统 SHALL 支持承运商档案管理(不变)。
|
||||
系统 SHALL 支持承运商档案管理,含资质附件与运输协议有效期校验。
|
||||
|
||||
#### Scenario: 承运商信息维护
|
||||
- **WHEN** 管理员新增承运商
|
||||
- **THEN** 系统 SHALL 记录承运商名称、联系人、联系电话、合作状态(合作中/已终止)
|
||||
- **AND** 系统 SHALL 要求上传营业执照、道路经营许可证、运输协议,并填写运输协议有效期
|
||||
- **AND** 附件 SHALL 使用 `AttachmentUploadField` 卡片化布局,URL 逗号分隔存库
|
||||
|
||||
#### Scenario: 协议过期阻断运输
|
||||
- **WHEN** 承运商运输协议有效期早于当前日期
|
||||
- **THEN** 系统 SHALL 展示「协议已过期」
|
||||
- **AND** 该承运商名下车辆 SHALL NOT 出现在安排运输与计划预选车辆列表
|
||||
- **AND** 安排/调整运输接口 SHALL 拒绝使用该承运商车辆
|
||||
- **AND** 协议过期时 SHALL 禁止调整运输,直至更新协议
|
||||
|
||||
#### Scenario: 历史承运商兼容
|
||||
- **WHEN** 存在未补全资质附件的历史承运商
|
||||
- **THEN** 系统 SHALL 允许保留基础档案
|
||||
- **AND** 在其资质补全且协议有效前 SHALL NOT 允许安排运输
|
||||
|
||||
#### Scenario: 承运商审核
|
||||
|
||||
- **WHEN** 管理员审核承运商
|
||||
- **THEN** 系统 SHALL 支持将待审核承运商标记为已通过
|
||||
- **AND** 仅审核通过且资质齐全、协议有效的承运商车辆可用于运输安排
|
||||
- **AND** 修改资质附件后 SHALL 将审核状态重置为待审核
|
||||
|
||||
#### Scenario: 承运商车辆筛选
|
||||
- **WHEN** 用户选择承运商后安排运输
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-06-29
|
||||
@@ -0,0 +1,76 @@
|
||||
## Context
|
||||
|
||||
山峡化工 web-admin 运输管理(PL)已有 ETC/加油充值申请与发放(`pl_vehicle_recharge_apply` / `pl_vehicle_recharge_record`),采用双菜单、无审批、1:0~1 关联、发放看板 UX。客户新增 **车辆维保** 需求,业务形态相似但申请字段更丰富,且需在选车时提醒未过预计取车日的待处理维保。
|
||||
|
||||
工程约束:租户隔离、数据权限、软删除、Fresh Mint UI、静态路由(`VITE_ACCESS_MODE=frontend`)。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 交付维保申请页 + 处理登记页,业务规则与客户确认一致
|
||||
- 复用 ETC 模块架构,降低回归与工期
|
||||
- 选车维保提醒 API + 前端弹窗
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 审批流、车辆状态自动联动、报表、小程序
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 双菜单平行模块(非抽象框架)
|
||||
|
||||
**决策**:新建 `pl_vehicle_maintenance_*` 表与独立 Service/API/页面,复制 ETC 模式。
|
||||
**理由**:仅第二个类似场景,抽象成本高;独立模块便于维保特有规则(提醒、日期校验)演进。
|
||||
**备选**:通用 ApplyDisburse 基类 — 拒绝(过度设计)。
|
||||
|
||||
### D2 处理完成 = 申请 status processed
|
||||
|
||||
**决策**:不设 `is_processed` 字段;创建/更新处理记录且关联申请时,申请置 `processed`。
|
||||
**理由**:客户选 A,与 ETC「发放即完成」一致。
|
||||
**备选**:独立布尔 — 拒绝。
|
||||
|
||||
### D3 金额可选
|
||||
|
||||
**决策**:`amount` NULL 允许;非 NULL 时 > 0。
|
||||
**理由**:客户选 B,可先登记处理人与时间。
|
||||
|
||||
### D4 维保类型 varchar + UI allow-create
|
||||
|
||||
**决策**:库存自由文本;字典仅作下拉预设(保养/维修/年检)。
|
||||
**理由**:客户要求可手填;避免每次自定义类型写 dict 表。
|
||||
|
||||
### D5 选车提醒
|
||||
|
||||
**决策**:查询 `pending` 且 `(expected_pickup_date IS NULL OR today <= expected_pickup_date)` 的申请;`today > expected_pickup_date` 不提醒;取车时间为空仍提醒。
|
||||
**理由**:客户明确规则;不依赖 `vehicle_status`。
|
||||
|
||||
### D6 单号前缀
|
||||
|
||||
**决策**:申请 `SXWB`,处理 `SXWC`。
|
||||
**理由**:与 SXSQ/SXCL 命名风格一致,WB=维保,WC=维保处理。
|
||||
|
||||
### D7 处理看板 API
|
||||
|
||||
**决策**:`GET .../process-board`,申请 LEFT JOIN 处理(同 ETC `disburse-board`)。
|
||||
**理由**:UX 已验证;行内处理/编辑。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| 与 ETC 代码重复 | 接受;后续若第三模块再抽公共层 |
|
||||
| 多条 pending 提醒信息过多 | 弹窗列表展示全部待提醒项 |
|
||||
| 预计取车日为空长期 pending 持续提醒 | 符合客户确认;业务侧应填取车日或作废 |
|
||||
| 静态路由遗漏导致菜单不可见 | 同步 `pl.ts` + DB 菜单 |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 执行 `docs/sql/migrations/2026-06-29-pl-vehicle-maintenance.sql`
|
||||
2. 部署后端 + 前端
|
||||
3. 为相关角色授权菜单(脚本含 super_admin + 拥有 PL_VEHICLE 的角色)
|
||||
4. 回滚:隐藏菜单;表保留(软删即可)
|
||||
|
||||
## Open Questions
|
||||
|
||||
- (无 — 客户已确认 V1 范围)
|
||||
@@ -0,0 +1,58 @@
|
||||
## Why
|
||||
|
||||
客户需要在运输管理模块中管理车辆维保全流程:业务人员提交维保申请,车管/财务登记处理结果与费用。现有 **ETC/加油** 已验证「申请 + 登记」双菜单模式,维保场景字段更多且需选车提醒,宜平行扩展而非改造 ETC 模块。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 新增功能模块
|
||||
|
||||
1. **车辆维保申请**
|
||||
- 车牌、申请人、申请时间、维保类型(预设 + 自定义)、维保开始/预计取车时间、内容、店铺地址、附件、备注
|
||||
- 无审批;提交后状态为「待处理」
|
||||
- 选车时弹窗提醒该车未过期(预计取车日前)的待处理维保信息
|
||||
|
||||
2. **车辆维保处理登记**
|
||||
- 看板列表合并展示申请与处理信息
|
||||
- 待处理行内「处理」;已处理可编辑/撤销
|
||||
- 支持无申请补录
|
||||
- 处理字段:金额(可选)、处理人、处理时间、附件
|
||||
|
||||
### 数据库变更
|
||||
|
||||
- 新增 `pl_vehicle_maintenance_apply`
|
||||
- 新增 `pl_vehicle_maintenance_process`
|
||||
- 新增字典 `pl_maintenance_type`、`pl_maintenance_apply_status`
|
||||
- 新增菜单与按钮权限(运输管理下)
|
||||
|
||||
### API 变更
|
||||
|
||||
- 新增 `/api/transport/vehicle-maintenance-apply/*`
|
||||
- 新增 `/api/transport/vehicle-maintenance-process/*`
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `vehicle-maintenance`: 车辆维保申请与处理登记 — 双表 CRUD、状态联动、选车提醒、处理看板
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- (无)
|
||||
|
||||
## Impact
|
||||
|
||||
### 受影响系统
|
||||
|
||||
- **后端**:`backend/app/models/pl_models.py`、`services/`、`api/pl/`、`schemas/pl/`
|
||||
- **前端**:`web-admin/src/views/pl/`、`api/pl/index.ts`、`router/modules/pl.ts`、`utils/pl-display.ts`
|
||||
- **数据库**:`docs/sql/migrations/`、`docs/sql/pl_tables.sql`
|
||||
|
||||
### 外部依赖
|
||||
|
||||
- `pl_vehicle` — 车牌校验
|
||||
- `sys_user` — 申请人/处理人
|
||||
- MinIO — 附件
|
||||
|
||||
### 参考实现
|
||||
|
||||
- `pl_vehicle_recharge_*` / `vehicle-recharge-apply` / `vehicle-recharge-record`
|
||||
@@ -0,0 +1,127 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 车辆维保申请提交
|
||||
|
||||
系统 SHALL 允许用户在「车辆维保申请」页创建维保申请,包含车牌号、申请人、申请时间、维保类型、维保开始时间、预计取车时间(可空)、维保内容、维保店铺地址、附件与备注。申请提交后状态 SHALL 为 `pending`(待处理),且 SHALL NOT 触发审批流。
|
||||
|
||||
#### Scenario: 成功创建申请
|
||||
|
||||
- **WHEN** 用户填写必填字段并提交新建申请
|
||||
- **THEN** 系统生成唯一申请单号(前缀 `SXWB` + 日期 + 6 位序号)
|
||||
- **AND** 申请状态为待处理
|
||||
- **AND** 申请人默认为当前登录用户且可修改
|
||||
|
||||
#### Scenario: 车牌不存在
|
||||
|
||||
- **WHEN** 用户提交的车牌号不在 `pl_vehicle` 中
|
||||
- **THEN** 系统拒绝创建并返回错误提示
|
||||
|
||||
#### Scenario: 日期顺序非法
|
||||
|
||||
- **WHEN** 申请时间晚于维保开始时间,或开始时间晚于预计取车时间(取车时间有值时)
|
||||
- **THEN** 系统拒绝保存并提示日期顺序错误
|
||||
|
||||
### Requirement: 车辆维保申请维护
|
||||
|
||||
系统 SHALL 允许用户对状态为 `pending` 的申请进行编辑、作废与软删除。已处理或已作废的申请 SHALL NOT 允许编辑。已关联处理记录的申请 SHALL NOT 允许删除。
|
||||
|
||||
#### Scenario: 编辑待处理申请
|
||||
|
||||
- **WHEN** 用户编辑状态为待处理的申请并保存
|
||||
- **THEN** 系统更新申请字段并保持状态为待处理
|
||||
|
||||
#### Scenario: 作废申请
|
||||
|
||||
- **WHEN** 用户对待处理申请执行作废
|
||||
- **THEN** 申请状态变为 `cancelled`(已作废)
|
||||
|
||||
### Requirement: 维保类型输入
|
||||
|
||||
系统 SHALL 在申请表单提供维保类型下拉,预设选项为保养、维修、年检,且 SHALL 允许用户输入自定义类型文本。自定义类型 SHALL 以文本形式存储,SHALL NOT 强制写入字典表。
|
||||
|
||||
#### Scenario: 选择预设类型
|
||||
|
||||
- **WHEN** 用户从下拉选择「保养」
|
||||
- **THEN** 申请记录的维保类型保存为「保养」
|
||||
|
||||
#### Scenario: 输入自定义类型
|
||||
|
||||
- **WHEN** 用户在下拉中输入并确认自定义类型「四轮定位」
|
||||
- **THEN** 申请记录的维保类型保存为「四轮定位」
|
||||
|
||||
### Requirement: 选车维保提醒
|
||||
|
||||
系统 SHALL 在用户选择车牌时查询该车牌待处理的维保申请,并在满足提醒条件时弹出维保信息提醒。提醒条件为:申请状态为 `pending`,且(预计取车时间为空 **或** 当前日期小于等于预计取车时间)。当当前日期大于预计取车时间时,系统 SHALL NOT 弹出提醒。
|
||||
|
||||
#### Scenario: 存在未过期待处理申请时提醒
|
||||
|
||||
- **WHEN** 用户选择某车牌且该车牌存在待处理申请,预计取车日为今天或未来
|
||||
- **THEN** 系统弹窗展示该申请的维保类型、时间、内容、店铺地址等信息
|
||||
|
||||
#### Scenario: 超过预计取车日后不提醒
|
||||
|
||||
- **WHEN** 用户选择某车牌且该车牌存在待处理申请,但预计取车日早于今天
|
||||
- **THEN** 系统不弹出维保提醒
|
||||
|
||||
#### Scenario: 预计取车时间为空仍提醒
|
||||
|
||||
- **WHEN** 用户选择某车牌且该车牌存在待处理申请,预计取车时间为空
|
||||
- **THEN** 系统弹窗展示维保信息直至该申请被处理或作废
|
||||
|
||||
### Requirement: 车辆维保处理登记
|
||||
|
||||
系统 SHALL 在「车辆维保处理登记」页提供处理看板,合并展示申请信息与处理信息。用户 SHALL 能够对待处理申请执行「处理」登记,登记字段包括金额(可选,有值时须大于 0)、处理人、处理时间、附件与备注。提交处理登记后,关联申请状态 SHALL 变为 `processed`(已处理)。
|
||||
|
||||
#### Scenario: 关联申请的处理登记
|
||||
|
||||
- **WHEN** 用户对某待处理申请执行处理并提交
|
||||
- **THEN** 系统创建处理记录(单号前缀 `SXWC`)
|
||||
- **AND** 关联申请状态变为已处理
|
||||
- **AND** 同一申请 SHALL NOT 存在第二条有效处理记录
|
||||
|
||||
#### Scenario: 金额为空允许提交
|
||||
|
||||
- **WHEN** 用户提交处理登记未填写金额
|
||||
- **THEN** 系统允许保存且处理记录金额为空
|
||||
|
||||
#### Scenario: 金额非法
|
||||
|
||||
- **WHEN** 用户填写金额小于等于 0
|
||||
- **THEN** 系统拒绝保存
|
||||
|
||||
### Requirement: 无申请补录
|
||||
|
||||
系统 SHALL 允许用户在处理登记页创建不关联申请单的处理记录(无申请补录),此时 `apply_id` SHALL 为空。
|
||||
|
||||
#### Scenario: 无申请补录
|
||||
|
||||
- **WHEN** 用户通过「无申请补录」创建处理记录且不选择申请单
|
||||
- **THEN** 系统保存处理记录且 `apply_id` 为空
|
||||
|
||||
### Requirement: 处理记录撤销与编辑
|
||||
|
||||
系统 SHALL 允许编辑已存在的处理记录(含补填金额)。删除处理记录时,若其关联申请,系统 SHALL 将申请状态恢复为 `pending`。
|
||||
|
||||
#### Scenario: 删除处理释放申请
|
||||
|
||||
- **WHEN** 用户删除某条关联申请的处理记录
|
||||
- **THEN** 处理记录软删除
|
||||
- **AND** 原关联申请状态恢复为待处理
|
||||
|
||||
### Requirement: 租户与权限隔离
|
||||
|
||||
所有维保申请与处理数据的查询与写入 SHALL 按当前用户租户过滤。列表与详情 SHALL 应用数据权限范围。删除 SHALL 为软删除(`del_flag=1`)。
|
||||
|
||||
#### Scenario: 跨租户不可见
|
||||
|
||||
- **WHEN** 用户 A 所属租户与维保数据租户不一致
|
||||
- **THEN** 用户 A 无法查询或修改该数据
|
||||
|
||||
### Requirement: Web 管理端 UI 规范
|
||||
|
||||
维保申请与处理登记页面 SHALL 遵循 Fresh Mint 规范:标准列表面板、弹窗表单 `label-position="top"`、状态字段使用 `el-tag`、操作列 `fixed="right"`。菜单 SHALL 注册于运输管理静态路由与数据库菜单。
|
||||
|
||||
#### Scenario: 菜单可访问
|
||||
|
||||
- **WHEN** 已授权用户登录并展开运输管理
|
||||
- **THEN** 可见「车辆维保申请」与「车辆维保处理登记」菜单项
|
||||
@@ -0,0 +1,33 @@
|
||||
## 1. 数据库与菜单
|
||||
|
||||
- [x] 1.1 创建迁移 `docs/sql/migrations/2026-06-29-pl-vehicle-maintenance.sql`(两表 + 字典 + 菜单 + 按钮权限 + 角色授权)
|
||||
- [x] 1.2 同步 `docs/sql/pl_tables.sql` 追加表定义
|
||||
|
||||
## 2. 后端模型与 Schema
|
||||
|
||||
- [x] 2.1 在 `pl_models.py` 新增 `PlVehicleMaintenanceApply` / `PlVehicleMaintenanceProcess`
|
||||
- [x] 2.2 新增 `schemas/pl/vehicle_maintenance.py`(Create/Update/Read/Board/Reminder)
|
||||
|
||||
## 3. 后端 Service 与 API
|
||||
|
||||
- [x] 3.1 实现 `pl_vehicle_maintenance_service.py`(单号 SXWB/SXWC、校验、CRUD、状态联动、提醒查询、process-board)
|
||||
- [x] 3.2 新增 `api/pl/vehicle_maintenance.py` 并注册路由
|
||||
- [x] 3.3 运行字段一致性检查(如适用)
|
||||
|
||||
## 4. 前端 API 与展示工具
|
||||
|
||||
- [x] 4.1 在 `api/pl/index.ts` 增加类型与 API 方法
|
||||
- [x] 4.2 在 `pl-display.ts` 增加维保类型/申请状态 Tag 映射
|
||||
|
||||
## 5. 前端页面
|
||||
|
||||
- [x] 5.1 实现 `vehicle-maintenance-apply/index.vue`(列表 + 弹窗 + 选车提醒 + 用户/车牌下拉)
|
||||
- [x] 5.2 实现 `vehicle-maintenance-process/index.vue`(处理看板 + 行内处理/补录)
|
||||
- [x] 5.3 在 `router/modules/pl.ts` 注册静态路由
|
||||
- [x] 5.4 在 `App.vue` 注册 panel 标题竖条规则(如需要)
|
||||
|
||||
## 6. 验收
|
||||
|
||||
- [x] 6.1 冒烟:申请 CRUD、作废、选车提醒(含取车日边界)
|
||||
- [x] 6.2 冒烟:处理登记、无申请补录、撤销处理、金额可选
|
||||
- [x] 6.3 更新 graphify 图谱
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-06-29
|
||||
@@ -0,0 +1,56 @@
|
||||
## Context
|
||||
|
||||
已有 ETC/加油(申请→发放看板)与车辆维保(申请→处理看板)两套实现。尿素申请含 **计划金额 + 入库数量**,发放为 **实付金额**,语义上更接近 ETC 的「待发放/已发放」,但申请字段更丰富。
|
||||
|
||||
约束:MySQL 5.7、租户隔离、Fresh Mint UI、frontend 静态路由、软删除。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** 双菜单 CRUD、1:0~1 关联、无申请补录、发放金额默认带出申请金额、角色菜单幂等迁移。
|
||||
|
||||
**Non-Goals:** 审批、库存系统、尿素主数据、选车提醒、车辆状态联动。
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1 平行复制 ETC 模块
|
||||
|
||||
新建 `pl_vehicle_urea_*` 表与 Service,发放页复用 `disburse-board` 模式。理由:与 ETC 文案/状态一致,工期可控。
|
||||
|
||||
### D2 双金额字段
|
||||
|
||||
- 申请表 `apply_amount`(计划,必填)
|
||||
- 发放表 `amount`(实付,必填)
|
||||
- 发放弹窗默认 `amount = apply_amount`,允许修改。
|
||||
|
||||
### D3 状态 disbursed
|
||||
|
||||
不设 `is_processed`;发放成功 → 申请 `disbursed`。
|
||||
|
||||
### D4 单位与数量
|
||||
|
||||
`unit` varchar + UI 预设「吨桶」+ allow-create;`quantity` decimal(10,2) 必填 > 0。
|
||||
|
||||
### D5 单号
|
||||
|
||||
`SXNS`(尿素申请)、`SXUF`(尿素发放)。
|
||||
|
||||
### D6 角色菜单 id
|
||||
|
||||
super_admin 用固定 id `PL_Urea_RM_SA_*`;其他角色用 `MD5(CONCAT(role_id, ':', menu_id))`,避免 `UUID()` 1062。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 风险 | 缓解 |
|
||||
|------|------|
|
||||
| 申请/发放金额不一致难对账 | 看板同时展示两列金额 |
|
||||
| 与 ETC 代码重复 | 接受;第三模块后再抽象 |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 执行 `2026-06-29-pl-vehicle-urea.sql`
|
||||
2. 部署后端与前端
|
||||
3. 重新登录刷新菜单
|
||||
|
||||
## Open Questions
|
||||
|
||||
- (无)
|
||||
@@ -0,0 +1,41 @@
|
||||
## Why
|
||||
|
||||
客户需要在运输管理中记录车辆尿素入库申请与财务发放,流程与 ETC/加油一致,但申请侧包含入库时间、单位、数量及计划金额,发放侧登记实付金额。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 新增功能
|
||||
|
||||
1. **车辆尿素申请** — 车牌、申请人、申请/入库时间、单位(默认吨桶可自定义)、入库数、申请金额、附件、备注
|
||||
2. **车辆尿素发放登记** — 发放看板、行内发放、无申请补录;发放金额(必填,默认带出申请金额)、处理人、发放时间、附件
|
||||
|
||||
### 数据库
|
||||
|
||||
- `pl_vehicle_urea_apply` / `pl_vehicle_urea_disburse`
|
||||
- 字典 `pl_urea_unit`、`pl_urea_apply_status`
|
||||
- 菜单与角色授权(运输管理下 order_num 35/36)
|
||||
|
||||
### API
|
||||
|
||||
- `/api/transport/vehicle-urea-apply/*`
|
||||
- `/api/transport/vehicle-urea-disburse/*`(含 `disburse-board`)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `vehicle-urea`: 车辆尿素申请与发放登记
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- (无)
|
||||
|
||||
## Impact
|
||||
|
||||
- 后端 `pl_models`、新 Service/API
|
||||
- 前端两页 + `api/pl/index.ts` + `pl-display.ts` + `router/modules/pl.ts`
|
||||
- SQL 迁移(角色菜单用确定性 id)
|
||||
|
||||
## Reference
|
||||
|
||||
- `pl_vehicle_recharge_*`、`vehicle-recharge-apply`、`vehicle-recharge-record`
|
||||
@@ -0,0 +1,74 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 车辆尿素申请提交
|
||||
|
||||
系统 SHALL 允许用户在「车辆尿素申请」页创建申请,包含车牌、申请人、申请时间、入库时间、单位、入库数、申请金额、附件与备注。提交后状态为 `pending`(待发放),且无审批流。
|
||||
|
||||
#### Scenario: 成功创建申请
|
||||
|
||||
- **WHEN** 用户填写全部必填项并提交
|
||||
- **THEN** 系统生成申请单号(前缀 `SXNS`)
|
||||
- **AND** 状态为待发放
|
||||
- **AND** 申请人默认为当前登录用户且可修改
|
||||
|
||||
#### Scenario: 金额与数量校验
|
||||
|
||||
- **WHEN** 申请金额或入库数小于等于 0
|
||||
- **THEN** 系统拒绝保存
|
||||
|
||||
#### Scenario: 日期顺序非法
|
||||
|
||||
- **WHEN** 申请时间晚于入库时间
|
||||
- **THEN** 系统拒绝保存
|
||||
|
||||
### Requirement: 尿素单位输入
|
||||
|
||||
系统 SHALL 提供单位下拉,默认选项包含「吨桶」,且 SHALL 允许用户输入自定义单位文本。
|
||||
|
||||
#### Scenario: 自定义单位
|
||||
|
||||
- **WHEN** 用户输入自定义单位并保存
|
||||
- **THEN** 单位以文本形式存入申请记录
|
||||
|
||||
### Requirement: 车辆尿素发放登记
|
||||
|
||||
系统 SHALL 在发放登记页提供看板列表,对待发放申请执行「发放」。发放字段包括发放金额(必填且大于 0)、处理人、发放时间、附件。提交后关联申请状态 SHALL 变为 `disbursed`(已发放)。
|
||||
|
||||
#### Scenario: 发放金额默认带出
|
||||
|
||||
- **WHEN** 用户对某待发放申请执行发放并打开弹窗
|
||||
- **THEN** 发放金额字段默认等于该申请的申请金额
|
||||
- **AND** 用户可修改后再提交
|
||||
|
||||
#### Scenario: 计划与实付可不同
|
||||
|
||||
- **WHEN** 用户修改默认发放金额后提交
|
||||
- **THEN** 系统保存用户填写的发放金额
|
||||
- **AND** 申请记录中的申请金额保持不变
|
||||
|
||||
### Requirement: 无申请补录
|
||||
|
||||
系统 SHALL 支持不关联申请单的发放补录,`apply_id` 可为空。
|
||||
|
||||
#### Scenario: 无申请补录
|
||||
|
||||
- **WHEN** 用户通过无申请补录创建发放且未选择申请
|
||||
- **THEN** 系统保存发放记录且 `apply_id` 为空
|
||||
|
||||
### Requirement: 发放撤销
|
||||
|
||||
删除发放记录时,若其关联申请,系统 SHALL 将申请状态恢复为 `pending`。
|
||||
|
||||
#### Scenario: 撤销发放
|
||||
|
||||
- **WHEN** 用户删除关联申请的发放记录
|
||||
- **THEN** 申请恢复为待发放
|
||||
|
||||
### Requirement: 租户与 UI 规范
|
||||
|
||||
数据 SHALL 租户隔离并应用数据权限;状态字段使用 Tag 展示;菜单挂于运输管理静态路由。
|
||||
|
||||
#### Scenario: 菜单可见
|
||||
|
||||
- **WHEN** 已授权用户登录
|
||||
- **THEN** 可见车辆尿素申请与发放登记菜单
|
||||
@@ -0,0 +1,23 @@
|
||||
## 1. 数据库与菜单
|
||||
|
||||
- [x] 1.1 创建 `docs/sql/migrations/2026-06-29-pl-vehicle-urea.sql`(确定性 role_menu id)
|
||||
- [x] 1.2 同步 `docs/sql/pl_tables.sql`
|
||||
|
||||
## 2. 后端
|
||||
|
||||
- [x] 2.1 ORM `PlVehicleUreaApply` / `PlVehicleUreaDisburse`
|
||||
- [x] 2.2 Schema `vehicle_urea.py`
|
||||
- [x] 2.3 Service `pl_vehicle_urea_service.py`
|
||||
- [x] 2.4 API `vehicle_urea.py` + 路由注册
|
||||
|
||||
## 3. 前端
|
||||
|
||||
- [x] 3.1 `api/pl/index.ts` + `pl-display.ts`
|
||||
- [x] 3.2 `vehicle-urea-apply/index.vue`
|
||||
- [x] 3.3 `vehicle-urea-disburse/index.vue`(发放默认带出申请金额)
|
||||
- [x] 3.4 `router/modules/pl.ts`
|
||||
|
||||
## 4. 验收
|
||||
|
||||
- [x] 4.1 申请/发放/补录/撤销冒烟
|
||||
- [x] 4.2 graphify 更新
|
||||
Reference in new issue
Block a user