Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2956496fb0 | ||
|
|
c6a22d8a48 | ||
|
|
573f8397a6 | ||
|
|
da487c41d4 | ||
|
|
ee962c97ae |
6
.claude_memory/MEMORY.md
Normal file
6
.claude_memory/MEMORY.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
- [用户角色](user_role.md) — 负责车辆租赁里程考核绩效核算,非技术背景,重视可读性
|
||||||
|
- [输出风格偏好](feedback_style.md) — 对账单风格,人话说明,部门前缀,交替行色
|
||||||
|
- [工作流程偏好](feedback_workflow.md) — 边讨论边改,用具体case验证,高频重新生成
|
||||||
|
- [里程考核规则](project_rules.md) — 完整计算规则:达标/结转/补发/累计/亏损筛选
|
||||||
|
- [项目架构](project_architecture.md) — 3文件架构,18 sheets,数据流
|
||||||
|
- [关键测试车辆](reference_vehicles.md) — 7辆典型车用于验证各类边界场景
|
||||||
17
.claude_memory/feedback_style.md
Normal file
17
.claude_memory/feedback_style.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: 输出风格偏好
|
||||||
|
description: 用户对Excel输出、代码风格、沟通方式的偏好
|
||||||
|
type: feedback
|
||||||
|
---
|
||||||
|
|
||||||
|
Excel输出要对账单风格,不要技术化的判断链。每辆车的发放原因用一句话+关键数字说清楚。
|
||||||
|
|
||||||
|
**Why:** 输出给业务员和管理层看,他们不懂技术术语。
|
||||||
|
|
||||||
|
**How to apply:**
|
||||||
|
- 发放说明列用人话:如"1月多跑9578≥3000,结转(完整月奖金)"
|
||||||
|
- 不要用"cum_qualified"、"floor(excess/target)"等技术词
|
||||||
|
- 每个数字要有来源可追溯
|
||||||
|
- 业务员sheet用部门前缀命名(如"二部-刘念念")
|
||||||
|
- 车辆追踪表多人用独立行+合并单元格,不挤在一个单元格
|
||||||
|
- 交替行底色(白/浅蓝)区分不同车辆
|
||||||
16
.claude_memory/feedback_workflow.md
Normal file
16
.claude_memory/feedback_workflow.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: 工作流程偏好
|
||||||
|
description: 用户对开发流程、沟通方式的偏好
|
||||||
|
type: feedback
|
||||||
|
---
|
||||||
|
|
||||||
|
用户喜欢边讨论边改,通过具体数据case推动规则确认。不喜欢一次性大规划后再实现。
|
||||||
|
|
||||||
|
**Why:** 规则有很多边界情况,只有看到实际数据才能确认逻辑对不对。
|
||||||
|
|
||||||
|
**How to apply:**
|
||||||
|
- 每次改完立即生成Excel让用户验证
|
||||||
|
- 用具体车辆(如粤AGP5797、粤AGP5769)举例说明
|
||||||
|
- 修改后立即"重新生成"是高频操作,保持脚本可随时运行
|
||||||
|
- 用git tag标记版本(V1.0.0 → V3.2.0),方便回退
|
||||||
|
- 用户说"重新生成"就直接 rm + python3 main.py,不需要确认
|
||||||
37
.claude_memory/project_architecture.md
Normal file
37
.claude_memory/project_architecture.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
name: 项目架构和文件结构
|
||||||
|
description: 代码架构、输入输出文件、Excel sheet结构
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
## 代码架构(V3.2.0)
|
||||||
|
- `calc_engine.py` - 计算引擎:规则/读取/分组/结转/补发/累计/亏损读取
|
||||||
|
- `excel_writer.py` - Excel输出:所有sheet生成函数
|
||||||
|
- `main.py` - 入口:按月循环生成独立Excel文件
|
||||||
|
- `generate_q1_summary.py` - 旧版单文件脚本(已被拆分,保留)
|
||||||
|
|
||||||
|
## 输入文件
|
||||||
|
- `租赁任务考核_2026年{1,2,3}月.xlsx` - 月度考核源数据
|
||||||
|
- `{1,2}月.xlsx` - 客户盈亏表(3月待补充)
|
||||||
|
- `里程任务考核_Q1汇总.xlsx` - 全量492辆车辆台账来源
|
||||||
|
|
||||||
|
## 输出文件
|
||||||
|
每月一个独立Excel(18 sheets):
|
||||||
|
1. 考核奖励规则
|
||||||
|
2. 里程明细X月
|
||||||
|
3. X月计算过程
|
||||||
|
4. 车辆考核追踪(全量492辆,多人拆行+合并单元格+交替色)
|
||||||
|
5. X月奖金发放记录(逐条明细+亏损筛选)
|
||||||
|
6. X月汇总(从发放记录生成:考核应发→亏损筛选→最终发放)
|
||||||
|
7-18. 业务员sheets × 12(对账单风格+发放说明)
|
||||||
|
|
||||||
|
## 数据流
|
||||||
|
```
|
||||||
|
源数据 → 里程明细(逐条达标) → 计算过程(分组+判断链)
|
||||||
|
→ 车辆考核追踪(考核维度) → 奖金发放记录(+亏损筛选)
|
||||||
|
→ 月汇总(从发放记录SUM) → 业务员sheets
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why:** 从单文件重构为模块化,支持按月独立核算和多维度展示。
|
||||||
|
|
||||||
|
**How to apply:** 新功能加在对应模块中。计算逻辑改calc_engine,展示改excel_writer,流程改main。
|
||||||
53
.claude_memory/project_rules.md
Normal file
53
.claude_memory/project_rules.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
name: 里程考核绩效核算规则
|
||||||
|
description: 车辆里程考核的完整计算规则,经过多次讨论确认
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心规则(V1.2规则文档 + 讨论确认)
|
||||||
|
|
||||||
|
### 达标判断
|
||||||
|
- 每条考核记录独立判断达标(不合并同车同人多条记录)
|
||||||
|
- 达标条件:实际行驶里程 ≥ 应考核里程
|
||||||
|
|
||||||
|
### 奖金计算
|
||||||
|
- 奖金 = 考核天数/当月天数 × 月奖励金额(始终按天折算)
|
||||||
|
- 不存在"超过整月目标就发全月"的特殊情况
|
||||||
|
|
||||||
|
### 结转(多跑)
|
||||||
|
- floor(多跑里程/月度目标里程) ≥ 1 → 结转,发完整月奖金
|
||||||
|
- 结转占用当月名额,不叠加当月奖励
|
||||||
|
- 结转不依赖目标月的考核记录(无记录也发,视为消耗一个月目标)
|
||||||
|
- 无客户关联的结转不查盈亏,正常发放
|
||||||
|
|
||||||
|
### 补发
|
||||||
|
- 累计全部达标后才补发(严格版:cum_actual ≥ cum_target for ALL months)
|
||||||
|
- 不是"覆盖到哪补到哪"的progressive模式
|
||||||
|
- 亏损月被拦截的奖金永久不发,不补发
|
||||||
|
|
||||||
|
### 累计补发当月
|
||||||
|
- 当月未达标 + 无结转 + 累计全部达标 → 按天折算发当月奖金
|
||||||
|
|
||||||
|
### 亏损筛选
|
||||||
|
- 按客户维度:客户亏损 → 该客户下所有车不发
|
||||||
|
- 补发X月 → 查X月盈亏表(不是当月的)
|
||||||
|
- 未匹配亏损表 → 不发放,标注待人工确认
|
||||||
|
- 无亏损表的月份 → 全部不发放
|
||||||
|
|
||||||
|
### 多人共用车辆
|
||||||
|
- 同车不同销售独立核算
|
||||||
|
- 累计按(车牌号+销售经理)维度,换人后累计重新开始
|
||||||
|
- 单车奖金池12期,跨销售共享
|
||||||
|
|
||||||
|
### 考核奖励规则表
|
||||||
|
| 考核目标 | 月度目标里程 | 奖励金额 |
|
||||||
|
|---------|-----------|---------|
|
||||||
|
| 交投40辆4.5T普货 | 3000km | 150元 |
|
||||||
|
| 交投190辆4.5T冷链车 | 3000km | 150元 |
|
||||||
|
| 羚牛136辆4.5T冷链车 | 5000km | 260元 |
|
||||||
|
| 恒运50辆4.5T普货 | 5000km | 260元 |
|
||||||
|
| 羚牛100辆18T | 6000km | 1000元 |
|
||||||
|
|
||||||
|
**Why:** 规则文档V1.2为基础,多条规则在讨论中逐步确认和修正。
|
||||||
|
|
||||||
|
**How to apply:** 修改计算逻辑前,先对照此规则表确认一致性。
|
||||||
15
.claude_memory/reference_vehicles.md
Normal file
15
.claude_memory/reference_vehicles.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: 关键测试车辆
|
||||||
|
description: 讨论中反复用到的典型车辆案例,用于验证逻辑正确性
|
||||||
|
type: reference
|
||||||
|
---
|
||||||
|
|
||||||
|
| 车牌 | 场景 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| 粤AGP5797 | 1月达标多跑4134(不够结转5000),2月未达标,累计达标 | 验证"累计补发当月" |
|
||||||
|
| 粤AGP5769 | 1月仅1天(84/161未达标),2月未达标,3月大爆发13663 | 验证跨月补发1月+2月+当月 |
|
||||||
|
| 粤AGR7791 | 1月跑12578,多跑9578够结转3个月 | 验证多跑结转 |
|
||||||
|
| 粤AGP5636 | 同人同月2条记录(达标+未达标) | 验证不合并逻辑 |
|
||||||
|
| 粤AGE4080 | 同人同月2条记录,合并后里程误导 | 验证逐条显示✓/✗ |
|
||||||
|
| 粤A03350F | 同车多人(刘念念+董剑煜) | 验证多人拆行+合并单元格 |
|
||||||
|
| 粤AGE8412 | 换销售经理(刘念念→董剑煜) | 验证按(车牌+人)累计 |
|
||||||
7
.claude_memory/user_role.md
Normal file
7
.claude_memory/user_role.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
name: 用户角色
|
||||||
|
description: 用户负责车辆租赁业务的里程考核绩效核算,非技术背景,重视可读性和可追溯性
|
||||||
|
type: user
|
||||||
|
---
|
||||||
|
|
||||||
|
用户负责氢能车辆租赁业务的里程考核和绩效奖金核算工作。非技术背景,对Excel输出的可读性要求很高——需要"旁人打开就能看懂"。偏好对账单风格的展示,不喜欢技术化的判断链。与多个业务部门(一部到六部)的12名销售经理对接。
|
||||||
72
.claude_plans/cuddly-strolling-rivest.md
Normal file
72
.claude_plans/cuddly-strolling-rivest.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# 资产状态多选改造
|
||||||
|
|
||||||
|
## Context
|
||||||
|
当前"总体任务-实时里程"页面的"资产状态"筛选器是单选下拉框,用户需要改为支持多选,以便同时筛选多个资产状态(如同时查看"在库"和"租赁"的车辆)。
|
||||||
|
|
||||||
|
## 后端接口修改建议
|
||||||
|
|
||||||
|
后端 API 端点:`GET /mileage/vehicle/list`
|
||||||
|
|
||||||
|
当前 `storageStatus` 参数为 `string` 类型(单值),需要改为支持多值。**建议两种方案(推荐方案一)**:
|
||||||
|
|
||||||
|
### 方案一:逗号分隔字符串(推荐,改动最小)
|
||||||
|
- 参数名不变:`storageStatus`
|
||||||
|
- 类型不变:`String`
|
||||||
|
- 前端传值:`storageStatus=在库,租赁,自营`
|
||||||
|
- 后端解析:用 `split(",")` 拆分为数组,SQL 查询改为 `WHERE storage_status IN (...)`
|
||||||
|
- **优点**:前后端改动最小,URL 参数简洁,向后兼容(单值时无逗号,行为不变)
|
||||||
|
|
||||||
|
### 方案二:数组参数
|
||||||
|
- 参数名改为:`storageStatuses` 或 `storageStatus[]`
|
||||||
|
- 前端传值:`storageStatuses=在库&storageStatuses=租赁`
|
||||||
|
- 后端用 `@RequestParam List<String> storageStatuses` 接收
|
||||||
|
- **优点**:更 RESTful;**缺点**:前后端改动较大
|
||||||
|
|
||||||
|
### 后端改动清单(方案一)
|
||||||
|
1. Controller 层:参数类型保持 `String storageStatus`
|
||||||
|
2. Service 层:`storageStatus.split(",")` 得到数组
|
||||||
|
3. MyBatis/SQL:将 `WHERE storage_status = #{storageStatus}` 改为:
|
||||||
|
```xml
|
||||||
|
<if test="storageStatusList != null and storageStatusList.size() > 0">
|
||||||
|
AND storage_status IN
|
||||||
|
<foreach collection="storageStatusList" item="item" open="(" separator="," close=")">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
</if>
|
||||||
|
```
|
||||||
|
4. 导出接口 `/mileage/vehicle/export` 同理修改
|
||||||
|
|
||||||
|
## 前端修改计划
|
||||||
|
|
||||||
|
### 涉及文件
|
||||||
|
1. `src/pages/VehicleManagement/index.tsx` — 主页面
|
||||||
|
2. `src/services/vehicleService.ts` — API 服务
|
||||||
|
3. `src/types/mileage.ts` — 类型定义
|
||||||
|
4. `src/pages/H5Mobile/index.tsx` — 移动端页面(同步修改)
|
||||||
|
|
||||||
|
### 修改步骤
|
||||||
|
|
||||||
|
#### Step 1: 修改类型定义 (`src/types/mileage.ts`)
|
||||||
|
- `storageStatus?: string` → `storageStatus?: string | string[]`(兼容多选)
|
||||||
|
|
||||||
|
#### Step 2: 修改 API 服务 (`src/services/vehicleService.ts`)
|
||||||
|
- `getVehicleList` 的 `storageStatus` 类型改为 `string | string[]`
|
||||||
|
- `exportVehicleList` 的 `storageStatus` 类型同样修改
|
||||||
|
- 在传参时,如果是数组则 `join(',')` 转为逗号分隔字符串发送给后端
|
||||||
|
|
||||||
|
#### Step 3: 修改主页面 (`src/pages/VehicleManagement/index.tsx`)
|
||||||
|
- **搜索参数初始化** (L94): `storageStatus: undefined` → 类型改为 `string[] | undefined`
|
||||||
|
- **重置** (L346): 保持 `storageStatus: undefined`
|
||||||
|
- **Select 组件** (L1607-1619): 添加 `mode="multiple"` 属性,启用多选
|
||||||
|
- **导出参数** (L1800): 数组 join(',') 传给导出函数
|
||||||
|
- **loadData** (L192): 在传参前将数组 join(',')
|
||||||
|
|
||||||
|
#### Step 4: 修改移动端页面 (`src/pages/H5Mobile/index.tsx`)
|
||||||
|
- 同步修改 Select 为多选模式
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
1. 打开"总体任务-实时里程"页面,确认资产状态下拉框可多选
|
||||||
|
2. 选择多个状态后点击搜索,检查网络请求参数格式正确(逗号分隔)
|
||||||
|
3. 点击重置,确认多选状态被清空
|
||||||
|
4. 导出 Excel 时确认多选参数正确传递
|
||||||
|
5. 移动端页面同步验证
|
||||||
47
.claude_plans/expressive-soaring-wombat.md
Normal file
47
.claude_plans/expressive-soaring-wombat.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# 修复区域图表"其他"重复问题
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
区域资产分布概览的"按城市"柱状图出现两个"其他"。原因:28 辆运营车辆的 `city` 和 `province` 均为 `null`(GPS 未上报),`resolveCity` 返回"其他",排进 Top 8。然后 Top 8 之外的城市又合并出另一个"其他"。
|
||||||
|
|
||||||
|
## 方案
|
||||||
|
|
||||||
|
不改 `resolveCity`(城市为空归"其他"是合理的)。修改 `/region-chart` 端点的 Top N 合并逻辑:**先把名为"其他"的条目从排序列表中移除,取 Top N 后,将剩余部分(包括原始的"其他")一起合并为最终的"其他"**。
|
||||||
|
|
||||||
|
**文件**: `src/server/routes/vehicles.ts` — `/region-chart` 端点
|
||||||
|
|
||||||
|
### 修改合并逻辑
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/region-chart', async (c) => {
|
||||||
|
const vehicles = await getVehicles();
|
||||||
|
const operating = vehicles.filter((v) => v.status === 'Operating');
|
||||||
|
const groupBy = c.req.query('groupBy') || 'region';
|
||||||
|
const top = Number(c.req.query('top')) || 8;
|
||||||
|
|
||||||
|
const counts = new Map<string, number>();
|
||||||
|
for (const v of operating) {
|
||||||
|
const key = groupBy === 'city' ? resolveCity(v.city, v.province) : mapMacroRegion(v.province, v.city);
|
||||||
|
counts.set(key, (counts.get(key) || 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分离"其他",对非"其他"排序取 Top N,剩余全部合入"其他"
|
||||||
|
const otherCount = counts.get('其他') || 0;
|
||||||
|
counts.delete('其他');
|
||||||
|
|
||||||
|
const sorted = Array.from(counts.entries())
|
||||||
|
.map(([name, value]) => ({ name, value }))
|
||||||
|
.sort((a, b) => b.value - a.value);
|
||||||
|
|
||||||
|
const result = sorted.slice(0, top);
|
||||||
|
const restTotal = sorted.slice(top).reduce((s, item) => s + item.value, 0) + otherCount;
|
||||||
|
if (restTotal > 0) result.push({ name: '其他', value: restTotal });
|
||||||
|
return c.json(result);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
1. `npx tsc --noEmit` 通过
|
||||||
|
2. `curl http://localhost:3000/api/vehicles/region-chart?groupBy=city&top=8` — 只有一个"其他"
|
||||||
|
3. 页面柱状图每个名称唯一
|
||||||
483
.claude_plans/fizzy-petting-shamir.md
Normal file
483
.claude_plans/fizzy-petting-shamir.md
Normal file
@@ -0,0 +1,483 @@
|
|||||||
|
# 还车应结款模块 — 完整实施计划
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
OneOS 需要实现完整的「还车应结款」多角色费用核算模块。当前状态:
|
||||||
|
- **原型**: 3个JSX页面(列表/查看/费用明细),需求文档完整
|
||||||
|
- **老系统**: lingniu_asset_server 有完整实现(4表+多部门审批+能源校验),可参考角色设计和业务流程
|
||||||
|
- **OneOS后端**: ReturnPaymentController/Service 已有骨架,但本质是纯CRUD,**return_payment 系列表尚未建到数据库**
|
||||||
|
- **OneOS前端**: 页面UI完成度高,但100%使用mock数据
|
||||||
|
- **数据库现状**: return_vehicle_task(81条)、delivery_vehicle(32条)、traffic_violation(0条)、accident_info(1条)、energy_account(有数据)、vehicle_check_item(轮胎检查项齐全)
|
||||||
|
|
||||||
|
### 核心目标
|
||||||
|
实现多角色(4个部门)独立填报→提交→审批→生成账单的完整业务流程,各部门只能看到和操作自己的费用区块。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第一阶段:数据库建表 + 角色定义
|
||||||
|
|
||||||
|
### 1.1 角色映射(参照老系统 SettlementDepartment)
|
||||||
|
|
||||||
|
老系统4个结算部门 → OneOS 已有系统角色映射:
|
||||||
|
|
||||||
|
| 结算部门 | depCode | OneOS 角色 | role_key | 职责 |
|
||||||
|
|---------|---------|-----------|----------|------|
|
||||||
|
| 业务服务组 | BUSINESS | 业务服务组(id=3) / 业务服务主管 / 业务经理 | 业务服务组 | 填写违章违约金、保险上浮、ETC费用、租金计算 |
|
||||||
|
| 能源采购组 | ENERGY | **需新建角色: 能源采购组** | energy_group | 填写氢量差、能源费补缴、预付款退费 |
|
||||||
|
| 运维部 | OPERATION | 运维专员 / 运维主管 / 运维助理 | 运维专员 | 填写清洗费、保养维修、车损、证件丢失、轮胎磨损 |
|
||||||
|
| 安全组 | SAFETY | 安全(id=2026890857448787969) | 安全 | 确认违章清单、事故清单 |
|
||||||
|
|
||||||
|
审批角色:
|
||||||
|
| 角色 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| 业务服务主管 / 业务负责人 | 汇总审核,提交总审批 |
|
||||||
|
| 财务 / 财务主管 | 生成账单、付款确认 |
|
||||||
|
| 总经理 | 终审(可选) |
|
||||||
|
|
||||||
|
### 1.2 建表SQL(重新设计,参照老系统+原型需求)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 主表:还车应结款单(一车一单)
|
||||||
|
CREATE TABLE return_settlement (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
return_task_id BIGINT UNSIGNED NOT NULL COMMENT '关联 return_vehicle_task.id',
|
||||||
|
delivery_vehicle_id BIGINT UNSIGNED NOT NULL COMMENT '关联 delivery_vehicle.id',
|
||||||
|
contract_id BIGINT NOT NULL COMMENT '关联 vehicle_lease_contract_info.id',
|
||||||
|
order_detail_id BIGINT COMMENT '关联 vehicle_lease_order_detail.id',
|
||||||
|
contract_code VARCHAR(100) COMMENT '合同编号',
|
||||||
|
project_name VARCHAR(200) COMMENT '项目名称',
|
||||||
|
customer_id BIGINT COMMENT '客户ID',
|
||||||
|
customer_name VARCHAR(200) COMMENT '客户名称',
|
||||||
|
plate_number VARCHAR(50) COMMENT '车牌号',
|
||||||
|
vehicle_id BIGINT UNSIGNED COMMENT '车辆ID',
|
||||||
|
business_dept_name VARCHAR(100) COMMENT '业务部门',
|
||||||
|
business_owner_name VARCHAR(100) COMMENT '业务负责人',
|
||||||
|
delivery_time DATETIME COMMENT '交车时间',
|
||||||
|
return_time DATETIME COMMENT '还车时间',
|
||||||
|
returner_name VARCHAR(100) COMMENT '还车人',
|
||||||
|
-- 保险标识(从合同/服务项推导)
|
||||||
|
has_fragile_insurance TINYINT(1) DEFAULT 0 COMMENT '易损保 0否1是',
|
||||||
|
has_tire_insurance TINYINT(1) DEFAULT 0 COMMENT '轮胎保 0否1是',
|
||||||
|
has_maintenance_insurance TINYINT(1) DEFAULT 0 COMMENT '养护保 0否1是',
|
||||||
|
-- 金额汇总
|
||||||
|
deposit_amount DECIMAL(18,2) DEFAULT 0 COMMENT '保证金总额',
|
||||||
|
pending_settle_amount DECIMAL(18,2) DEFAULT 0 COMMENT '待结算总额',
|
||||||
|
should_refund_amount DECIMAL(18,2) DEFAULT 0 COMMENT '应退还总额',
|
||||||
|
should_pay_amount DECIMAL(18,2) DEFAULT 0 COMMENT '应补缴总额',
|
||||||
|
-- 审批状态(参照老系统)
|
||||||
|
approval_status TINYINT DEFAULT 0 COMMENT '0待提交 1撤回 10待审批 20审批中 30审批完成 40审批驳回',
|
||||||
|
approval_submit_time DATETIME COMMENT '提交审批时间',
|
||||||
|
is_last_vehicle TINYINT(1) DEFAULT 0 COMMENT '是否合同最后一辆车',
|
||||||
|
generated_at DATETIME COMMENT '还车应结款生成时间(15天倒计时起点)',
|
||||||
|
-- 审计字段
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_by BIGINT,
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by BIGINT,
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_return_task (return_task_id),
|
||||||
|
INDEX idx_contract (contract_id),
|
||||||
|
INDEX idx_plate (plate_number),
|
||||||
|
INDEX idx_status (approval_status)
|
||||||
|
) COMMENT '还车应结款主表';
|
||||||
|
|
||||||
|
-- 部门提交状态表(4个部门各一行)
|
||||||
|
CREATE TABLE return_settlement_dept_status (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL COMMENT '关联 return_settlement.id',
|
||||||
|
dep_code VARCHAR(20) NOT NULL COMMENT 'BUSINESS/ENERGY/OPERATION/SAFETY',
|
||||||
|
dep_name VARCHAR(50) COMMENT '部门名称',
|
||||||
|
status TINYINT DEFAULT 0 COMMENT '0待提交 1已提交 2已撤回',
|
||||||
|
submit_by BIGINT COMMENT '提交人ID',
|
||||||
|
submit_by_name VARCHAR(100) COMMENT '提交人姓名',
|
||||||
|
submit_time DATETIME COMMENT '提交时间',
|
||||||
|
total_amount DECIMAL(18,2) DEFAULT 0 COMMENT '部门费用合计',
|
||||||
|
remark VARCHAR(500),
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_by BIGINT,
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by BIGINT,
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_settlement (settlement_id),
|
||||||
|
UNIQUE KEY uk_settlement_dep (settlement_id, dep_code)
|
||||||
|
) COMMENT '部门提交状态';
|
||||||
|
|
||||||
|
-- 业务服务组费用明细
|
||||||
|
CREATE TABLE return_settlement_business_fee (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
seq INT COMMENT '序号',
|
||||||
|
fee_item VARCHAR(200) NOT NULL COMMENT '费用项名称',
|
||||||
|
amount DECIMAL(18,2) DEFAULT 0 COMMENT '金额',
|
||||||
|
remark VARCHAR(500),
|
||||||
|
photos TEXT COMMENT '照片URL JSON数组',
|
||||||
|
attachments TEXT COMMENT '附件URL JSON数组',
|
||||||
|
is_fixed TINYINT(1) DEFAULT 0 COMMENT '是否固定费用项(不可删)',
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_by BIGINT,
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_settlement (settlement_id)
|
||||||
|
) COMMENT '业务服务组费用';
|
||||||
|
|
||||||
|
-- 业务服务组-车辆租金
|
||||||
|
CREATE TABLE return_settlement_rent (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
bill_start_date DATE COMMENT '账单开始日期',
|
||||||
|
vehicle_month_rent DECIMAL(18,2) COMMENT '车辆月租金',
|
||||||
|
received_rent DECIMAL(18,2) DEFAULT 0 COMMENT '本期已收租金',
|
||||||
|
actual_rent DECIMAL(18,2) DEFAULT 0 COMMENT '车辆实际租金',
|
||||||
|
should_refund_rent DECIMAL(18,2) DEFAULT 0 COMMENT '车辆应退租金',
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_by BIGINT,
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by BIGINT,
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_settlement (settlement_id)
|
||||||
|
) COMMENT '车辆租金';
|
||||||
|
|
||||||
|
-- 能源采购组
|
||||||
|
CREATE TABLE return_settlement_energy (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
delivery_hydrogen DECIMAL(10,2) COMMENT '交车氢量(MPa)',
|
||||||
|
return_hydrogen DECIMAL(10,2) COMMENT '还车氢量(MPa)',
|
||||||
|
hydrogen_unit_price DECIMAL(10,2) COMMENT '退还车氢气单价',
|
||||||
|
hydrogen_supplement DECIMAL(18,2) DEFAULT 0 COMMENT '氢量差补缴金额',
|
||||||
|
hydrogen_fee DECIMAL(18,2) DEFAULT 0 COMMENT '氢费补缴金额',
|
||||||
|
electric_fee DECIMAL(18,2) DEFAULT 0 COMMENT '电费补缴金额',
|
||||||
|
prepay_refund DECIMAL(18,2) DEFAULT 0 COMMENT '预付款退费金额',
|
||||||
|
user_balance DECIMAL(18,2) DEFAULT 0 COMMENT '项目预充值余额(快照)',
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_by BIGINT,
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
update_by BIGINT,
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_settlement (settlement_id)
|
||||||
|
) COMMENT '能源采购组费用';
|
||||||
|
|
||||||
|
-- 运维部费用明细
|
||||||
|
CREATE TABLE return_settlement_operation_fee (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
seq INT COMMENT '序号',
|
||||||
|
fee_item VARCHAR(200) NOT NULL COMMENT '费用项',
|
||||||
|
amount DECIMAL(18,2) DEFAULT 0 COMMENT '金额',
|
||||||
|
worry_free_discount DECIMAL(18,2) DEFAULT 0 COMMENT '无忧包减免',
|
||||||
|
remark VARCHAR(500),
|
||||||
|
photos TEXT COMMENT '照片URL JSON数组',
|
||||||
|
attachments TEXT COMMENT '附件URL JSON数组',
|
||||||
|
is_fixed TINYINT(1) DEFAULT 0 COMMENT '是否固定费用项',
|
||||||
|
is_readonly TINYINT(1) DEFAULT 0 COMMENT '是否只读(送车/接车服务费)',
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_by BIGINT,
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_settlement (settlement_id)
|
||||||
|
) COMMENT '运维部费用';
|
||||||
|
|
||||||
|
-- 运维部-轮胎胎纹明细
|
||||||
|
CREATE TABLE return_settlement_tire_tread (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
tire_name VARCHAR(100) COMMENT '轮胎名称(左前轮等)',
|
||||||
|
delivery_depth DECIMAL(6,2) COMMENT '交车胎纹深度(mm)',
|
||||||
|
return_depth DECIMAL(6,2) COMMENT '还车胎纹深度(mm)',
|
||||||
|
depth_diff DECIMAL(6,2) COMMENT '胎纹差(mm)',
|
||||||
|
unit_price DECIMAL(10,2) COMMENT '单价(元/mm)',
|
||||||
|
total_amount DECIMAL(18,2) DEFAULT 0 COMMENT '总金额',
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_settlement (settlement_id)
|
||||||
|
) COMMENT '轮胎胎纹明细';
|
||||||
|
|
||||||
|
-- 安全组-违章快照
|
||||||
|
CREATE TABLE return_settlement_violation (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
source_violation_id BIGINT COMMENT '来源 traffic_violation.id',
|
||||||
|
violation_code VARCHAR(100) COMMENT '违章编码',
|
||||||
|
plate_number VARCHAR(50),
|
||||||
|
violation_behavior VARCHAR(500),
|
||||||
|
violation_time DATETIME,
|
||||||
|
penalty_amount DECIMAL(10,2) DEFAULT 0,
|
||||||
|
payment_status VARCHAR(20) COMMENT '缴费状态',
|
||||||
|
score INT DEFAULT 0 COMMENT '计分值',
|
||||||
|
handle_status VARCHAR(20) COMMENT '处理状态',
|
||||||
|
violation_customer VARCHAR(200),
|
||||||
|
violation_photo VARCHAR(500),
|
||||||
|
remark VARCHAR(500),
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_settlement (settlement_id)
|
||||||
|
) COMMENT '安全组违章快照';
|
||||||
|
|
||||||
|
-- 安全组-事故快照
|
||||||
|
CREATE TABLE return_settlement_accident (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
settlement_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
source_accident_id BIGINT COMMENT '来源 accident_info.id',
|
||||||
|
accident_code VARCHAR(100),
|
||||||
|
plate_number VARCHAR(50),
|
||||||
|
accident_time DATETIME,
|
||||||
|
accident_place VARCHAR(200),
|
||||||
|
accident_type VARCHAR(50),
|
||||||
|
customer_name VARCHAR(200),
|
||||||
|
our_claim_amount DECIMAL(10,2),
|
||||||
|
their_claim_amount DECIMAL(10,2),
|
||||||
|
responsibility VARCHAR(50),
|
||||||
|
accident_status VARCHAR(50),
|
||||||
|
close_time DATE,
|
||||||
|
other_fee DECIMAL(10,2),
|
||||||
|
remark VARCHAR(500),
|
||||||
|
del_flag CHAR(1) DEFAULT '0',
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_settlement (settlement_id)
|
||||||
|
) COMMENT '安全组事故快照';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 新建角色
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 能源采购组角色(系统中尚未有)
|
||||||
|
INSERT INTO `ry-cloud`.sys_role (role_id, role_name, role_key, role_sort, status, del_flag, create_by, create_time)
|
||||||
|
VALUES (2040000000000000001, '能源采购组', 'energy_group', 15, '0', '0', 1, NOW());
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第二阶段:后端核心重构
|
||||||
|
|
||||||
|
### 2.1 文件结构(在 ln-asset-management 内)
|
||||||
|
|
||||||
|
```
|
||||||
|
modules/contract/
|
||||||
|
├── controller/
|
||||||
|
│ └── ReturnSettlementController.java ← 新建,替代 ReturnPaymentController
|
||||||
|
├── service/
|
||||||
|
│ ├── ReturnSettlementService.java ← 接口
|
||||||
|
│ └── impl/ReturnSettlementServiceImpl.java ← 核心实现
|
||||||
|
├── entity/settlement/
|
||||||
|
│ ├── po/
|
||||||
|
│ │ ├── ReturnSettlement.java
|
||||||
|
│ │ ├── ReturnSettlementDeptStatus.java
|
||||||
|
│ │ ├── ReturnSettlementBusinessFee.java
|
||||||
|
│ │ ├── ReturnSettlementRent.java
|
||||||
|
│ │ ├── ReturnSettlementEnergy.java
|
||||||
|
│ │ ├── ReturnSettlementOperationFee.java
|
||||||
|
│ │ ├── ReturnSettlementTireTread.java
|
||||||
|
│ │ ├── ReturnSettlementViolation.java
|
||||||
|
│ │ └── ReturnSettlementAccident.java
|
||||||
|
│ ├── req/ ← 各部门提交请求DTO
|
||||||
|
│ ├── vo/ ← 列表/详情/查看响应DTO
|
||||||
|
│ └── enums/
|
||||||
|
│ ├── SettlementDeptCode.java ← BUSINESS/ENERGY/OPERATION/SAFETY
|
||||||
|
│ ├── DeptSubmitStatus.java ← 待提交/已提交/已撤回
|
||||||
|
│ └── SettlementApprovalStatus.java ← 0/1/10/20/30/40
|
||||||
|
├── mapper/
|
||||||
|
│ └── (9个mapper,对应9张表)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 核心API设计
|
||||||
|
|
||||||
|
```
|
||||||
|
前缀: /asset/return-settlement
|
||||||
|
|
||||||
|
列表与查询:
|
||||||
|
POST /page ← 分页列表(筛选:合同/客户/项目/车牌/还车时间/审批状态)
|
||||||
|
GET /detail/{id} ← 详情(根据角色过滤可见区块)
|
||||||
|
GET /view/{id} ← 只读查看(同详情但全部只读)
|
||||||
|
|
||||||
|
创建(还车完成时自动触发):
|
||||||
|
POST /create-from-return/{returnTaskId} ← 从还车任务创建结算单,自动拉取关联数据
|
||||||
|
|
||||||
|
各部门提交/撤回(需角色鉴权):
|
||||||
|
POST /business/save ← 业务服务组保存
|
||||||
|
POST /business/submit ← 业务服务组提交
|
||||||
|
POST /business/revoke/{id} ← 业务服务组撤回
|
||||||
|
POST /energy/save ← 能源采购组保存
|
||||||
|
POST /energy/submit ← 能源采购组提交
|
||||||
|
POST /energy/revoke/{id} ← 能源采购组撤回
|
||||||
|
POST /operation/save ← 运维部保存
|
||||||
|
POST /operation/submit ← 运维部提交
|
||||||
|
POST /operation/revoke/{id} ← 运维部撤回
|
||||||
|
POST /safety/save ← 安全组保存
|
||||||
|
POST /safety/submit ← 安全组提交
|
||||||
|
POST /safety/revoke/{id} ← 安全组撤回
|
||||||
|
|
||||||
|
审批:
|
||||||
|
POST /submit-review/{id} ← 提交总审批(校验:4组已提交 + 15天倒计时)
|
||||||
|
POST /revoke-review/{id} ← 撤回审批
|
||||||
|
POST /generate-bill/{id} ← 生成账单
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 核心业务逻辑
|
||||||
|
|
||||||
|
**create-from-return 自动拉取数据(关键):**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 从 return_vehicle_task 取:还车氢量、还车里程、接车服务费、还车时间、还车人
|
||||||
|
2. 从 delivery_vehicle 取:交车氢量、交车里程、送车服务费、月租金、保证金、交车时间
|
||||||
|
3. 从 vehicle_lease_contract_info 取:合同编号、项目名称、客户、业务部门/负责人
|
||||||
|
4. 从 vehicle_lease_order_detail 取:月租金(vehicle_rent)、保证金(deposit)、账单开始日
|
||||||
|
5. 从 vehicle_lease_order_service_item 取:推导保险标识
|
||||||
|
- 有"设备损坏金(包含易损件)"→ has_fragile_insurance=1
|
||||||
|
- 有"轮胎磨损费"→ has_tire_insurance=1
|
||||||
|
- (养护保需确认service_item名称)
|
||||||
|
6. 从 vehicle_check_item + delivery_vehicle_check_item 取:交车轮胎胎纹深度
|
||||||
|
7. 从 return_vehicle_check_item 取:还车轮胎胎纹深度
|
||||||
|
8. 从 traffic_violation 按 vehicle_id + 交车~还车时间范围 查:违章记录快照
|
||||||
|
9. 从 accident_info 按 vehicle_id + 交车~还车时间范围 查:事故记录快照
|
||||||
|
10. 从 ln_energy.energy_account 通过 Dubbo 查:客户能源账户余额
|
||||||
|
11. 判断 is_last_vehicle:查同合同下其他 delivery_vehicle 是否都已有还车记录
|
||||||
|
12. 自动初始化4个 dept_status 记录(BUSINESS/ENERGY/OPERATION/SAFETY,状态=待提交)
|
||||||
|
13. 自动初始化固定费用行(业务5项 + 运维10项)
|
||||||
|
14. 自动计算轮胎磨损(胎纹差 * 单价)
|
||||||
|
15. 自动计算证件丢失费(对比交车/还车检查项中的行驶证、营运证等)
|
||||||
|
16. generated_at = NOW()(15天倒计时起点)
|
||||||
|
```
|
||||||
|
|
||||||
|
**角色鉴权逻辑:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 根据当前用户角色判断可见/可操作的部门区块
|
||||||
|
public SettlementDeptCode resolveUserDept(Long userId) {
|
||||||
|
List<String> roleKeys = getUserRoleKeys(userId);
|
||||||
|
if (roleKeys.contains("业务服务组") || roleKeys.contains("业务服务主管")
|
||||||
|
|| roleKeys.contains("业务经理") || roleKeys.contains("业务负责人")
|
||||||
|
|| roleKeys.contains("业务总负责人"))
|
||||||
|
return BUSINESS;
|
||||||
|
if (roleKeys.contains("energy_group"))
|
||||||
|
return ENERGY;
|
||||||
|
if (roleKeys.contains("运维专员") || roleKeys.contains("运维主管")
|
||||||
|
|| roleKeys.contains("运维助理") || roleKeys.contains("运维总负责人"))
|
||||||
|
return OPERATION;
|
||||||
|
if (roleKeys.contains("安全"))
|
||||||
|
return SAFETY;
|
||||||
|
if (roleKeys.contains("财务") || roleKeys.contains("财务主管") || roleKeys.contains("财务总监"))
|
||||||
|
return null; // 财务可见全部,但不可编辑
|
||||||
|
if (roleKeys.contains("superadmin") || roleKeys.contains("总经理"))
|
||||||
|
return null; // 全部可见
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**费用汇总计算(服务端计算,不依赖前端):**
|
||||||
|
|
||||||
|
```
|
||||||
|
businessTotal = SUM(business_fee.amount) + rent.should_refund_rent
|
||||||
|
energyTotal = hydrogen_supplement + hydrogen_fee + electric_fee - prepay_refund
|
||||||
|
operationTotal = SUM(operation_fee.amount)
|
||||||
|
pendingSettle = businessTotal + energyTotal + operationTotal
|
||||||
|
shouldRefund = max(0, deposit - pendingSettle)
|
||||||
|
shouldPay = max(0, pendingSettle - deposit)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第三阶段:前端对接
|
||||||
|
|
||||||
|
### 3.1 替换mock为真实API
|
||||||
|
|
||||||
|
修改文件:
|
||||||
|
- `apps/web-antd/src/api/financial/return-payment/index.ts` — 所有API函数指向新后端端点
|
||||||
|
- `apps/web-antd/src/views/financial/returnPayment/index.vue` — 列表页调真实API
|
||||||
|
- `apps/web-antd/src/views/financial/returnPayment/detail.vue` — 详情页调真实API
|
||||||
|
|
||||||
|
### 3.2 角色可见性控制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 根据当前用户角色控制UI区块
|
||||||
|
const userDeptCode = computed(() => {
|
||||||
|
// 从用户信息中获取角色,映射到部门
|
||||||
|
const roles = userStore.getRoles;
|
||||||
|
if (roles.includes('业务服务组') || roles.includes('业务服务主管') ...) return 'BUSINESS';
|
||||||
|
if (roles.includes('energy_group')) return 'ENERGY';
|
||||||
|
if (roles.includes('运维专员') || roles.includes('运维主管') ...) return 'OPERATION';
|
||||||
|
if (roles.includes('安全')) return 'SAFETY';
|
||||||
|
return 'ALL'; // 财务/管理员
|
||||||
|
});
|
||||||
|
|
||||||
|
// 业务服务组区块:仅业务角色可编辑,其他角色不可见或只读
|
||||||
|
const canEditBusiness = computed(() => userDeptCode.value === 'BUSINESS' || userDeptCode.value === 'ALL');
|
||||||
|
const canEditEnergy = computed(() => userDeptCode.value === 'ENERGY' || userDeptCode.value === 'ALL');
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 关键交互保持原型一致
|
||||||
|
|
||||||
|
- 保留15天倒计时(后端校验 + 前端展示)
|
||||||
|
- 保留4组折叠/展开
|
||||||
|
- 保留Popover费用明细弹出
|
||||||
|
- 保留轮胎磨损hover弹窗
|
||||||
|
- 保留无忧包减免联动(根据保险标识启用/禁用)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 第四阶段:Dubbo接口(跨服务数据)
|
||||||
|
|
||||||
|
### 4.1 ln-energy 暴露接口
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 在 ln-energy 中新建
|
||||||
|
public interface RemoteEnergySettlementService {
|
||||||
|
/** 查询客户/项目的能源账户余额 */
|
||||||
|
BigDecimal getAccountBalance(Long customerId);
|
||||||
|
|
||||||
|
/** 查询客户ETC未缴费用合计 */
|
||||||
|
BigDecimal getUnpaidEtcFee(Long customerId, String plateNumber,
|
||||||
|
LocalDate startDate, LocalDate endDate);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 ln-asset-management 调用
|
||||||
|
|
||||||
|
在 ReturnSettlementServiceImpl 中通过 `@DubboReference` 注入调用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实施顺序
|
||||||
|
|
||||||
|
| 步骤 | 内容 | 依赖 |
|
||||||
|
|------|------|------|
|
||||||
|
| S1 | 执行建表SQL + 新建能源采购组角色 | 无 |
|
||||||
|
| S2 | 后端Entity/Mapper/Service骨架 | S1 |
|
||||||
|
| S3 | create-from-return 自动数据拉取 | S2 |
|
||||||
|
| S4 | 4组 save/submit/revoke API | S2 |
|
||||||
|
| S5 | 列表/详情/查看 API + 角色鉴权 | S4 |
|
||||||
|
| S6 | 前端 mock→API 切换 + 角色可见性 | S5 |
|
||||||
|
| S7 | submit-review + 15天校验 | S4 |
|
||||||
|
| S8 | Dubbo 接口(能源余额/ETC费用) | S5 |
|
||||||
|
| S9 | 生成账单/导出 | S7 |
|
||||||
|
| S10 | 端到端测试 | S6-S9 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证计划
|
||||||
|
|
||||||
|
1. **建表验证**: 连接数据库确认9张表已创建
|
||||||
|
2. **数据拉取验证**: 用已有还车记录调 create-from-return,确认自动填充的数据完整性
|
||||||
|
3. **角色隔离验证**: 分别用不同角色登录,确认只能看到/操作自己的费用区块
|
||||||
|
4. **费用计算验证**: 修改各部门费用后,确认汇总金额正确
|
||||||
|
5. **提交流程验证**: 4组依次提交→倒计时结束→提交审核→审批
|
||||||
|
6. **前端联调**: 列表筛选、详情展示、Popover、轮胎弹窗、15天倒计时
|
||||||
|
|
||||||
|
### 关键文件清单
|
||||||
|
|
||||||
|
**后端(修改/新建):**
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/contract/controller/ReturnSettlementController.java`
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/contract/service/ReturnSettlementService.java`
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/contract/service/impl/ReturnSettlementServiceImpl.java`
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/contract/entity/settlement/` (9个PO + req/vo/enums)
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/contract/mapper/` (9个mapper)
|
||||||
|
- `ln-asset-management/src/main/resources/db/migration/` (建表SQL)
|
||||||
|
|
||||||
|
**前端(修改):**
|
||||||
|
- `ln-one-os-web/apps/web-antd/src/api/financial/return-payment/index.ts`
|
||||||
|
- `ln-one-os-web/apps/web-antd/src/views/financial/returnPayment/index.vue`
|
||||||
|
- `ln-one-os-web/apps/web-antd/src/views/financial/returnPayment/detail.vue`
|
||||||
|
- `ln-one-os-web/apps/web-antd/src/types/return-payment.ts`
|
||||||
|
|
||||||
|
**跨服务(新建):**
|
||||||
|
- `ln-energy/src/main/java/.../api/RemoteEnergySettlementService.java`
|
||||||
123
.claude_plans/functional-jingling-dijkstra.md
Normal file
123
.claude_plans/functional-jingling-dijkstra.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# 羚牛 BI 报表服务实现计划
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
基于 AI Studio 生成的前端原型(`-V1.0`),构建连接真实 MySQL 数据库的 BI 报表服务。前端复用 React+Vite+Tailwind 代码,后端使用 Hono + TypeScript,替换 mock 数据为真实数据库查询结果。
|
||||||
|
|
||||||
|
**数据库**: `192.168.130.111:3306/lingniu_prod3` (root/lingniu#2024)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ln-bi/
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── vite.config.ts
|
||||||
|
├── .env # 数据库连接配置
|
||||||
|
├── src/
|
||||||
|
│ ├── server/
|
||||||
|
│ │ ├── index.ts # Hono 服务入口
|
||||||
|
│ │ ├── db.ts # MySQL 连接池(mysql2)
|
||||||
|
│ │ ├── routes/
|
||||||
|
│ │ │ └── vehicles.ts # 车辆数据 API 路由
|
||||||
|
│ │ └── types.ts # 后端类型定义
|
||||||
|
│ ├── App.tsx # 前端主组件(改造自 -V1.0)
|
||||||
|
│ ├── api.ts # 前端 API 调用层
|
||||||
|
│ ├── types.ts # 前端共享类型
|
||||||
|
│ ├── main.tsx
|
||||||
|
│ └── index.css
|
||||||
|
└── index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现步骤
|
||||||
|
|
||||||
|
### Step 1: 项目初始化
|
||||||
|
|
||||||
|
1. 在 `ln-bi/` 初始化项目,安装依赖:
|
||||||
|
- **前端**: react, react-dom, lucide-react, motion, tailwindcss, vite, @vitejs/plugin-react, @tailwindcss/vite
|
||||||
|
- **后端**: hono, @hono/node-server, mysql2, dotenv
|
||||||
|
- **开发**: typescript, tsx, @types/node, @types/react, @types/react-dom, concurrently
|
||||||
|
2. 配置 `tsconfig.json`、`vite.config.ts`(含 API 代理到后端)
|
||||||
|
3. 创建 `.env` 文件(数据库连接信息)
|
||||||
|
4. 配置 `package.json` scripts:
|
||||||
|
- `dev:server` — tsx 启动后端
|
||||||
|
- `dev:client` — vite 启动前端
|
||||||
|
- `dev` — concurrently 同时启动前后端
|
||||||
|
|
||||||
|
### Step 2: 后端 — 数据库连接与查询
|
||||||
|
|
||||||
|
**关键文件**: `src/server/db.ts`, `src/server/routes/vehicles.ts`
|
||||||
|
|
||||||
|
1. 创建 MySQL 连接池(mysql2/promise)
|
||||||
|
2. 实现主查询 API `GET /api/vehicles`,执行用户提供的 SQL,返回所有营运车辆数据
|
||||||
|
3. 后端对原始数据做聚合计算,返回前端需要的结构:
|
||||||
|
- `GET /api/vehicles/summary` — 总览统计(总资产、运营数、库存数等)
|
||||||
|
- `GET /api/vehicles/list` — 车辆列表(支持分页/过滤)
|
||||||
|
- `GET /api/vehicles/by-type` — 按车型分组汇总
|
||||||
|
- `GET /api/vehicles/by-batch` — 按批次分组汇总(如果数据库有批次字段)
|
||||||
|
- `GET /api/vehicles/inventory-analysis` — 库存分析(按区域分布)
|
||||||
|
|
||||||
|
**数据映射**(SQL 字段 → 前端字段):
|
||||||
|
| SQL 字段 | 前端字段 | 说明 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 车牌号 | plateNumber | 车牌 |
|
||||||
|
| 车辆型号Label | type | 车型分类(4.5T/18T/49T等) |
|
||||||
|
| 车辆品牌Label + 车辆型号Label | model | 品牌车型组合 |
|
||||||
|
| 省/市 | location | 区域(映射为嘉兴/广东/北京/新疆/其他) |
|
||||||
|
| 车辆租赁状态Label | status | Operating/Inventory/Abnormal |
|
||||||
|
| 车辆归属状态Label | ownership | Self/Leased/Public/Hanging |
|
||||||
|
| 合同编码 | batch | 批次信息(或从其他字段推断) |
|
||||||
|
|
||||||
|
**注意**: 省/市需映射为前端的 5 大区域(嘉兴、广东、北京、新疆、其他)。归属状态、租赁状态需根据字典值映射为英文枚举。
|
||||||
|
|
||||||
|
### Step 3: 后端 — Hono 服务
|
||||||
|
|
||||||
|
**关键文件**: `src/server/index.ts`
|
||||||
|
|
||||||
|
1. 创建 Hono app,挂载车辆路由
|
||||||
|
2. 添加 CORS 中间件(开发时前端在不同端口)
|
||||||
|
3. 监听端口 3001
|
||||||
|
4. 错误处理中间件
|
||||||
|
|
||||||
|
### Step 4: 前端改造
|
||||||
|
|
||||||
|
**关键文件**: `src/App.tsx`, `src/api.ts`
|
||||||
|
|
||||||
|
1. 从 `-V1.0/src/App.tsx` 复制前端代码
|
||||||
|
2. 创建 `src/api.ts` — 封装 fetch 调用后端 API
|
||||||
|
3. 改造 `App.tsx`:
|
||||||
|
- 删除 `MOCK_VEHICLES` 和 `SUMMARY` 常量
|
||||||
|
- 删除 `getProcessedData`、`getProcessedDataByBatch`、`getInventoryAnalysisData` 纯前端聚合函数(改为后端聚合)
|
||||||
|
- 添加 `useEffect` + `useState` 从 API 获取数据
|
||||||
|
- 添加 loading 状态和错误处理
|
||||||
|
- 保留所有 UI 组件和交互逻辑不变
|
||||||
|
4. 保留主题切换、展开/折叠、模态框等交互功能
|
||||||
|
5. 车牌号弹窗改为调用 API 按条件查询
|
||||||
|
|
||||||
|
### Step 5: Vite 代理配置
|
||||||
|
|
||||||
|
在 `vite.config.ts` 中配置 `/api` 代理到后端 `http://localhost:3001`,开发时无需 CORS。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 暂不实现(后续补充)
|
||||||
|
|
||||||
|
以下统计指标当前 SQL 未覆盖,先用 0 或占位:
|
||||||
|
- 本周新增 / 本周移除
|
||||||
|
- 待交车数量
|
||||||
|
- 本周交付 / 本周归还 / 本周替换
|
||||||
|
- 批次信息(需确认数据库中对应字段)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证方案
|
||||||
|
|
||||||
|
1. `npm run dev` 同时启动前后端
|
||||||
|
2. 访问 `http://localhost:3000` 查看页面
|
||||||
|
3. 检查浏览器 Network 面板确认 API 调用正常
|
||||||
|
4. 对比前端显示数据与数据库查询结果是否一致
|
||||||
|
5. 测试交互功能:主题切换、展开折叠、车牌号弹窗、按型号/批次视图切换
|
||||||
275
.claude_plans/glistening-jingling-squid.md
Normal file
275
.claude_plans/glistening-jingling-squid.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
# 还车费用核算模块 — 完整实施计划
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
还车费用核算(ReturnSettlement)模块框架已搭建,但与需求文档(`docs/还车费用核算-业务流程与时序.md`)对比存在多个 Gap:审批流转未对接 Warm-Flow、驳回重置未实现、无生成账单接口、无权限/状态校验、全量 `Map<String,Object>` 传参(已有强类型 VO/Req 未使用)。本计划按"先重构后补功能"策略分阶段实施。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0: 代码质量重构(强类型替换 Map)
|
||||||
|
|
||||||
|
**目标:** 消除运行时字段错误风险,为后续所有功能奠定基础。
|
||||||
|
|
||||||
|
### 0.1 Controller 签名替换
|
||||||
|
|
||||||
|
| 当前 | 替换为 |
|
||||||
|
|------|--------|
|
||||||
|
| `page(@RequestParam Map<String, Object>)` → `Map` | `page(ReturnSettlementQuery)` → `IPage<SettlementListVO>` |
|
||||||
|
| `getDetail(Long id)` → `Map` | → `SettlementDetailVO` |
|
||||||
|
| `saveBusiness(@RequestBody Map)` | `@RequestBody @Valid SaveBusinessReq` |
|
||||||
|
| `saveEnergy(@RequestBody Map)` | `@RequestBody @Valid SaveEnergyReq` |
|
||||||
|
| `saveOperation(@RequestBody Map)` | `@RequestBody @Valid SaveOperationReq` |
|
||||||
|
| `saveSafety(@RequestBody Map)` | `@RequestBody @Valid SaveSafetyReq` |
|
||||||
|
|
||||||
|
submit 方法与 save 使用相同 Req 类。
|
||||||
|
|
||||||
|
### 0.2 Service 接口签名同步
|
||||||
|
|
||||||
|
`ReturnSettlementService` 中所有方法签名从 `Map` 改为强类型。
|
||||||
|
|
||||||
|
### 0.3 ServiceImpl 内部重构
|
||||||
|
|
||||||
|
- `convertToListVO` → 返回 `SettlementListVO`
|
||||||
|
- `getDetail` → 构建 `SettlementDetailVO` 而非手动 put Map
|
||||||
|
- `saveBusinessData` / `saveEnergyData` / `saveOperationData` → 接收强类型 Req
|
||||||
|
- 删除 `getStr()`, `getBigDecimal()`, `getInt()` 工具方法
|
||||||
|
|
||||||
|
### 0.4 VO 字段补齐
|
||||||
|
|
||||||
|
`SettlementDetailVO` 内嵌类需补充字段:
|
||||||
|
- `DeptStatusVO` 增加 `status` (Integer, 0/1/2 三态) + `totalAmount` (BigDecimal)
|
||||||
|
- `BusinessFeeVO` 增加 `id`, `seq`, `isFixed`
|
||||||
|
- `OperationFeeVO` 增加 `id`, `seq`, `isFixed`, `isReadonly`
|
||||||
|
- `TireTreadVO` 增加 `id`
|
||||||
|
|
||||||
|
### 0.5 N+1 查询优化
|
||||||
|
|
||||||
|
`page()` 方法中批量查所有 settlementId 对应的 deptStatus,构建 `Map<Long, List<DeptStatus>>` 缓存,避免列表每条记录单独查询。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 编译通过
|
||||||
|
- Postman 调用 page、detail、save、submit 接口,JSON 结构与前端兼容
|
||||||
|
- 前端页面功能无回归
|
||||||
|
|
||||||
|
### 关键文件
|
||||||
|
|
||||||
|
- `ReturnSettlementController.java`
|
||||||
|
- `ReturnSettlementService.java`
|
||||||
|
- `ReturnSettlementServiceImpl.java`
|
||||||
|
- `SettlementDetailVO.java` / `SettlementListVO.java`
|
||||||
|
- `SaveBusinessReq.java` / `SaveEnergyReq.java` / `SaveOperationReq.java` / `SaveSafetyReq.java`
|
||||||
|
- `ReturnSettlementQuery.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: 权限与状态校验
|
||||||
|
|
||||||
|
**目标:** 堵住安全漏洞。
|
||||||
|
|
||||||
|
### 1.1 权限校验
|
||||||
|
|
||||||
|
新增公共方法:
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void assertDeptPermission(SettlementDeptCode requiredDept) {
|
||||||
|
String userDept = resolveUserDept();
|
||||||
|
if (userDept != null && !requiredDept.getCode().equals(userDept)) {
|
||||||
|
throw new ServiceException("您没有权限操作" + requiredDept.getName() + "的数据");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在所有 save/submit/revoke 方法开头调用。
|
||||||
|
|
||||||
|
### 1.2 审批状态前置校验
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void assertEditableStatus(Long settlementId) {
|
||||||
|
ReturnSettlement s = assertSettlementExists(settlementId);
|
||||||
|
if (s.getApprovalStatus() >= SettlementApprovalStatus.PENDING_APPROVAL.getCode()) {
|
||||||
|
throw new ServiceException("结算单已进入审批流程,无法编辑");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 部门提交状态校验
|
||||||
|
|
||||||
|
- submit 校验当前部门状态不为 SUBMITTED(防重复提交)
|
||||||
|
- revoke 校验状态为 SUBMITTED
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 不同角色账号跨部门操作 → 403
|
||||||
|
- 审批中状态下 save → 被拦截
|
||||||
|
|
||||||
|
### 关键文件
|
||||||
|
|
||||||
|
- `ReturnSettlementServiceImpl.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Warm-Flow 审批对接
|
||||||
|
|
||||||
|
**目标:** submitReview 启动工作流,审批完成/驳回通过 Listener 回调。
|
||||||
|
|
||||||
|
### 2.1 submitReview 对接
|
||||||
|
|
||||||
|
注入 `RemoteWorkflowService`(`@DubboReference`),submitReview 中调用 `startCompleteTask` 启动流程。
|
||||||
|
|
||||||
|
- businessId 格式:`"ReturnSettlement_" + id`
|
||||||
|
- flowCode:`returnSettlement`(需在 Warm-Flow 后台配置)
|
||||||
|
- 状态直接写 `APPROVING(20)`(startCompleteTask 完成第一个任务后已在审批中)
|
||||||
|
|
||||||
|
### 2.2 revokeReview 对接
|
||||||
|
|
||||||
|
调用 `remoteWorkflowService.cancelProcessApply` 撤销流程实例。
|
||||||
|
|
||||||
|
### 2.3 新增 Dubbo 远程接口
|
||||||
|
|
||||||
|
在 `ln-asset-api` 新增 `RemoteReturnSettlementService`:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface RemoteReturnSettlementService {
|
||||||
|
boolean updateApprovalStatus(Long settlementId, Integer approvalStatus);
|
||||||
|
boolean resetDeptStatuses(Long settlementId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在 `ln-asset-management` 实现 `RemoteReturnSettlementServiceImpl`。
|
||||||
|
|
||||||
|
### 2.4 新增 Warm-Flow Listener
|
||||||
|
|
||||||
|
参照 `ContractStatusChangeListener.java` 模式:
|
||||||
|
|
||||||
|
- `SettlementApprovalListener.java` — 审批通过 → `updateApprovalStatus(id, 30)`
|
||||||
|
- `SettlementRejectListener.java` — 审批驳回 → `updateApprovalStatus(id, 40)` + `resetDeptStatuses(id)`
|
||||||
|
|
||||||
|
### 2.5 审批驳回重置逻辑
|
||||||
|
|
||||||
|
`resetDeptStatuses`:4个部门状态全部重置为 `PENDING(0)`,清空 submitBy/submitByName/submitTime,主表 approvalStatus 置为 `PENDING_SUBMIT(0)`。
|
||||||
|
|
||||||
|
### 审批状态机
|
||||||
|
|
||||||
|
```
|
||||||
|
0(待提交) --submitReview--> 20(审批中) --approve--> 30(审批完成)
|
||||||
|
--reject--> 40(驳回) --reset--> 0(待提交)
|
||||||
|
<--revokeReview-- 20(审批中) --> 1(撤回)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 启动微服务集群 → 创建结算单 → 4部门填报 → submitReview → 工作流待办出现
|
||||||
|
- 审批通过 → approvalStatus = 30
|
||||||
|
- 审批驳回 → approvalStatus = 40 + 4个 deptStatus 回到 0
|
||||||
|
- revokeReview → 工作流实例撤销
|
||||||
|
|
||||||
|
### 关键文件
|
||||||
|
|
||||||
|
- `ReturnSettlementServiceImpl.java`
|
||||||
|
- 新增 `RemoteReturnSettlementService.java` (ln-asset-api)
|
||||||
|
- 新增 `RemoteReturnSettlementServiceImpl.java` (ln-asset-management)
|
||||||
|
- 新增 `SettlementApprovalListener.java` (ln-cloud/ruoyi-workflow)
|
||||||
|
- 新增 `SettlementRejectListener.java` (ln-cloud/ruoyi-workflow)
|
||||||
|
- 参照 `ContractStatusChangeListener.java`
|
||||||
|
- 参照 `RemoteContractService.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: 生成账单
|
||||||
|
|
||||||
|
**目标:** 审批完成后财务可生成账单。
|
||||||
|
|
||||||
|
### 3.1 Controller 新增端点
|
||||||
|
|
||||||
|
```java
|
||||||
|
@PostMapping("/generate-bill/{id}")
|
||||||
|
public R<Long> generateBill(@PathVariable Long id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Service 实现
|
||||||
|
|
||||||
|
1. 校验 approvalStatus == 30
|
||||||
|
2. 校验权限(仅 superadmin/财务)
|
||||||
|
3. 幂等检查(ReturnSettlement 主表增加 `bill_id` 字段)
|
||||||
|
4. 从结算单构建 `Bills` 对象(参照 `BillGenerateService.createBills`)
|
||||||
|
5. 保存 Bills,回写 bill_id
|
||||||
|
|
||||||
|
### 3.3 前端补充
|
||||||
|
|
||||||
|
`return-payment/index.ts` 新增 `generateBill(id)` API。列表页增加"生成账单"按钮(仅 approvalStatus=30 + 财务角色可见)。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 审批完成的结算单调用 generate-bill → Bills 表有记录
|
||||||
|
- 非完成状态调用 → 错误
|
||||||
|
- 重复调用 → 幂等
|
||||||
|
|
||||||
|
### 关键文件
|
||||||
|
|
||||||
|
- `ReturnSettlementController.java`
|
||||||
|
- `ReturnSettlementService.java` / `ReturnSettlementServiceImpl.java`
|
||||||
|
- `ReturnSettlement.java` (新增 bill_id 字段)
|
||||||
|
- 前端 `return-payment/index.ts`
|
||||||
|
- 参照 `BillsServiceImpl.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: 能源余额拉取 + 租金自动计算
|
||||||
|
|
||||||
|
**目标:** 补充业务计算逻辑,独立于 Phase 1-3,可并行。
|
||||||
|
|
||||||
|
### 4.1 能源余额
|
||||||
|
|
||||||
|
注入 `RemoteEnergyAccountService`(`@DubboReference`),在 `createInitialEnergy` 中拉取 userBalance。调用失败降级为 ZERO + warn 日志。
|
||||||
|
|
||||||
|
### 4.2 租金自动计算
|
||||||
|
|
||||||
|
在 `createInitialRent` 中:`actualRent = (月租金 / 30) * (交车日到还车日天数)`,业务组 save 时可手动覆盖。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
|
||||||
|
- 创建结算单 → energy.userBalance 正确
|
||||||
|
- rent.actualRent 正确计算
|
||||||
|
- ln-energy 不可用时降级不阻断
|
||||||
|
|
||||||
|
### 关键文件
|
||||||
|
|
||||||
|
- `ReturnSettlementServiceImpl.java`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: 低优先级
|
||||||
|
|
||||||
|
### 5.1 倒计时字段
|
||||||
|
|
||||||
|
VO 增加 `deadline` (Date) + `remainingDays` (Integer)。
|
||||||
|
|
||||||
|
### 5.2 导出 Excel
|
||||||
|
|
||||||
|
新增 `GET /return-settlement/export`。
|
||||||
|
|
||||||
|
### 5.3 无忧包减免后端校验
|
||||||
|
|
||||||
|
saveOperationData 中校验 worryFreeDiscount > 0 时对应保险标识为 1。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 依赖关系
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 0 (重构) ─────┐
|
||||||
|
├──► Phase 1 (权限+状态) ──► Phase 2 (审批) ──► Phase 3 (账单)
|
||||||
|
Phase 4 (能源+租金) ─┘ (可与 Phase 1/2 并行)
|
||||||
|
|
||||||
|
Phase 5 (低优先级) — 独立
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新增文件清单
|
||||||
|
|
||||||
|
| 文件 | 位置 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `RemoteReturnSettlementService.java` | ln-asset-api | Dubbo 远程接口 |
|
||||||
|
| `RemoteReturnSettlementServiceImpl.java` | ln-asset-management | Dubbo 实现 |
|
||||||
|
| `SettlementApprovalListener.java` | ln-cloud/ruoyi-workflow | 审批通过监听 |
|
||||||
|
| `SettlementRejectListener.java` | ln-cloud/ruoyi-workflow | 审批驳回监听 |
|
||||||
599
.claude_plans/lucky-churning-comet.md
Normal file
599
.claude_plans/lucky-churning-comet.md
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
# 车辆租赁合同模块实施计划
|
||||||
|
|
||||||
|
## Context(背景)
|
||||||
|
|
||||||
|
### 为什么需要这个变更
|
||||||
|
车辆租赁业务是 OneOS 系统的核心业务模块,需要管理与客户签订的车辆租赁合同,包括合同信息、车辆订单、服务项目、被授权人、审批流程等。该模块需要与 BPM 工作流引擎深度集成,实现合同的审批流转。
|
||||||
|
|
||||||
|
### 问题或需求
|
||||||
|
1. **业务需求**:管理车辆租赁合同的全生命周期(创建→审批→执行→到期→续签/终止)
|
||||||
|
2. **审批流程**:4级审批(业务部主管→事业部主管→财务部→法务部)
|
||||||
|
3. **复杂关联**:合同关联客户、车辆、服务项目、被授权人、附件等多个实体
|
||||||
|
4. **特殊业务**:续签合同、转正式合同、变更为三方合同、新增车辆等
|
||||||
|
5. **状态管理**:审批状态 × 合同状态的复杂状态机
|
||||||
|
|
||||||
|
### 预期结果
|
||||||
|
构建一个完整的车辆租赁合同管理模块,支持合同的 CRUD、审批流程、特殊业务操作,并与现有的客户管理、车辆管理、BPM 工作流模块无缝集成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构设计
|
||||||
|
|
||||||
|
### 整体架构
|
||||||
|
```
|
||||||
|
Controller (REST API)
|
||||||
|
↓
|
||||||
|
Service (业务逻辑)
|
||||||
|
↓
|
||||||
|
├── Mapper (数据访问)
|
||||||
|
├── BpmProcessInstanceApi (流程启动)
|
||||||
|
└── FileApi (附件上传)
|
||||||
|
↓
|
||||||
|
BpmProcessInstanceStatusEventListener (流程状态监听)
|
||||||
|
↓
|
||||||
|
Service (更新合同状态)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模块结构
|
||||||
|
```
|
||||||
|
yudao-module-asset/yudao-module-asset-server/
|
||||||
|
├── controller/admin/contract/
|
||||||
|
│ └── ContractController.java
|
||||||
|
├── service/contract/
|
||||||
|
│ ├── ContractService.java
|
||||||
|
│ ├── ContractServiceImpl.java
|
||||||
|
│ └── listener/
|
||||||
|
│ └── ContractBpmListener.java
|
||||||
|
├── dal/
|
||||||
|
│ ├── dataobject/contract/
|
||||||
|
│ │ ├── ContractDO.java
|
||||||
|
│ │ ├── ContractVehicleDO.java
|
||||||
|
│ │ ├── ContractVehicleServiceDO.java
|
||||||
|
│ │ ├── ContractAuthorizedDO.java
|
||||||
|
│ │ ├── ContractAttachmentDO.java
|
||||||
|
│ │ └── ContractChangeHistoryDO.java
|
||||||
|
│ └── mysql/contract/
|
||||||
|
│ ├── ContractMapper.java
|
||||||
|
│ ├── ContractVehicleMapper.java
|
||||||
|
│ ├── ContractVehicleServiceMapper.java
|
||||||
|
│ ├── ContractAuthorizedMapper.java
|
||||||
|
│ ├── ContractAttachmentMapper.java
|
||||||
|
│ └── ContractChangeHistoryMapper.java
|
||||||
|
├── controller/admin/contract/vo/
|
||||||
|
│ ├── ContractBaseVO.java
|
||||||
|
│ ├── ContractSaveReqVO.java
|
||||||
|
│ ├── ContractRespVO.java
|
||||||
|
│ ├── ContractPageReqVO.java
|
||||||
|
│ ├── ContractDetailRespVO.java
|
||||||
|
│ ├── ContractVehicleVO.java
|
||||||
|
│ ├── ContractVehicleServiceVO.java
|
||||||
|
│ └── ContractAuthorizedVO.java
|
||||||
|
├── convert/contract/
|
||||||
|
│ └── ContractConvert.java
|
||||||
|
└── enums/
|
||||||
|
├── ContractTypeEnum.java
|
||||||
|
├── ContractApprovalStatusEnum.java
|
||||||
|
└── ContractStatusEnum.java
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据库表设计
|
||||||
|
|
||||||
|
### 1. asset_contract(合同主表)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `asset_contract` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
|
||||||
|
-- 合同基本信息
|
||||||
|
`contract_code` VARCHAR(50) NOT NULL COMMENT '合同编码',
|
||||||
|
`contract_type` TINYINT NOT NULL DEFAULT 1 COMMENT '合同类型(1=试用 2=正式)',
|
||||||
|
`project_name` VARCHAR(200) NOT NULL COMMENT '项目名称',
|
||||||
|
`start_date` DATE NOT NULL COMMENT '生效日期',
|
||||||
|
`end_date` DATE NOT NULL COMMENT '结束日期',
|
||||||
|
`payment_method` VARCHAR(50) COMMENT '付款方式',
|
||||||
|
`payment_cycle` VARCHAR(50) COMMENT '付款周期',
|
||||||
|
`signing_company` VARCHAR(200) COMMENT '签约公司(乙方)',
|
||||||
|
`delivery_province` VARCHAR(50) COMMENT '交车省份',
|
||||||
|
`delivery_city` VARCHAR(50) COMMENT '交车城市',
|
||||||
|
`delivery_location` VARCHAR(255) COMMENT '交车地点',
|
||||||
|
`remark` VARCHAR(1000) COMMENT '备注',
|
||||||
|
|
||||||
|
-- 甲方客户信息(关联 asset_customer)
|
||||||
|
`customer_id` BIGINT NOT NULL COMMENT '客户ID',
|
||||||
|
`customer_name` VARCHAR(200) NOT NULL COMMENT '客户名称(冗余)',
|
||||||
|
|
||||||
|
-- 丙方客户信息(三方合同,可选)
|
||||||
|
`third_party_enabled` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否三方合同',
|
||||||
|
`third_party_customer_id` BIGINT COMMENT '丙方客户ID',
|
||||||
|
`third_party_name` VARCHAR(200) COMMENT '丙方名称',
|
||||||
|
|
||||||
|
-- 业务信息
|
||||||
|
`business_dept_id` BIGINT NOT NULL COMMENT '业务部门ID',
|
||||||
|
`business_manager_id` BIGINT NOT NULL COMMENT '业务负责人ID',
|
||||||
|
|
||||||
|
-- 审批状态
|
||||||
|
`approval_status` TINYINT NOT NULL DEFAULT 0 COMMENT '审批状态(0=草稿 1=审批中 2=审批通过 3=审批拒绝 4=已撤回)',
|
||||||
|
`bpm_instance_id` VARCHAR(64) COMMENT 'BPM流程实例ID',
|
||||||
|
|
||||||
|
-- 合同状态
|
||||||
|
`contract_status` TINYINT NOT NULL DEFAULT 0 COMMENT '合同状态(0=草稿 1=待生效 2=进行中 3=已到期 4=已终止 5=已续签)',
|
||||||
|
`effective_time` DATETIME COMMENT '实际生效时间',
|
||||||
|
`terminate_time` DATETIME COMMENT '终止时间',
|
||||||
|
`terminate_reason` VARCHAR(500) COMMENT '终止原因',
|
||||||
|
|
||||||
|
-- 续签信息
|
||||||
|
`renewed_contract_id` BIGINT COMMENT '续签后的新合同ID',
|
||||||
|
`original_contract_id` BIGINT COMMENT '原合同ID(如果是续签合同)',
|
||||||
|
|
||||||
|
-- 系统字段
|
||||||
|
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_contract_code` (`contract_code`, `deleted`),
|
||||||
|
INDEX `idx_customer_id` (`customer_id`),
|
||||||
|
INDEX `idx_approval_status` (`approval_status`),
|
||||||
|
INDEX `idx_contract_status` (`contract_status`),
|
||||||
|
INDEX `idx_business_dept` (`business_dept_id`),
|
||||||
|
INDEX `idx_start_date` (`start_date`),
|
||||||
|
INDEX `idx_end_date` (`end_date`),
|
||||||
|
INDEX `idx_create_time` (`create_time`),
|
||||||
|
INDEX `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车辆租赁合同表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. asset_contract_vehicle(车辆租赁订单)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `asset_contract_vehicle` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`contract_id` BIGINT NOT NULL COMMENT '合同ID',
|
||||||
|
`vehicle_id` BIGINT COMMENT '车辆ID(关联 asset_vehicle_base)',
|
||||||
|
`brand` VARCHAR(100) NOT NULL COMMENT '品牌',
|
||||||
|
`model` VARCHAR(100) NOT NULL COMMENT '型号',
|
||||||
|
`plate_no` VARCHAR(20) COMMENT '车牌号',
|
||||||
|
`vin` VARCHAR(50) COMMENT 'VIN码',
|
||||||
|
`month_rent` DECIMAL(10,2) NOT NULL COMMENT '月租金(元)',
|
||||||
|
`deposit` DECIMAL(10,2) NOT NULL COMMENT '保证金(元)',
|
||||||
|
`vehicle_status` TINYINT DEFAULT 0 COMMENT '车辆状态(0=待交车 1=已交车 2=已退车)',
|
||||||
|
`actual_delivery_time` DATETIME COMMENT '实际交车时间',
|
||||||
|
`delivery_person` VARCHAR(50) COMMENT '交车人',
|
||||||
|
`remark` VARCHAR(500) COMMENT '备注',
|
||||||
|
|
||||||
|
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_contract_id` (`contract_id`),
|
||||||
|
INDEX `idx_vehicle_id` (`vehicle_id`),
|
||||||
|
INDEX `idx_plate_no` (`plate_no`),
|
||||||
|
INDEX `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同车辆租赁订单表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. asset_contract_vehicle_service(服务项目)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `asset_contract_vehicle_service` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`contract_vehicle_id` BIGINT NOT NULL COMMENT '合同车辆ID',
|
||||||
|
`service_name` VARCHAR(100) NOT NULL COMMENT '服务项目名称',
|
||||||
|
`service_fee` DECIMAL(10,2) NOT NULL COMMENT '服务费用(元)',
|
||||||
|
`effective_date` DATE COMMENT '生效日期',
|
||||||
|
|
||||||
|
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_contract_vehicle_id` (`contract_vehicle_id`),
|
||||||
|
INDEX `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同车辆服务项目表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. asset_contract_authorized(被授权人)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `asset_contract_authorized` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`contract_id` BIGINT NOT NULL COMMENT '合同ID',
|
||||||
|
`name` VARCHAR(50) NOT NULL COMMENT '姓名',
|
||||||
|
`phone` VARCHAR(20) NOT NULL COMMENT '电话',
|
||||||
|
`id_card` VARCHAR(18) NOT NULL COMMENT '身份证号',
|
||||||
|
|
||||||
|
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_contract_id` (`contract_id`),
|
||||||
|
INDEX `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同被授权人表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. asset_contract_attachment(合同附件)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `asset_contract_attachment` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`contract_id` BIGINT NOT NULL COMMENT '合同ID',
|
||||||
|
`attachment_type` TINYINT NOT NULL COMMENT '附件类型(1=合同原件 2=盖章合同)',
|
||||||
|
`file_id` BIGINT NOT NULL COMMENT '文件ID(关联 infra_file)',
|
||||||
|
`file_name` VARCHAR(255) NOT NULL COMMENT '文件名称',
|
||||||
|
`file_url` VARCHAR(500) NOT NULL COMMENT '文件URL',
|
||||||
|
`file_size` BIGINT COMMENT '文件大小(字节)',
|
||||||
|
`upload_time` DATETIME NOT NULL COMMENT '上传时间',
|
||||||
|
`uploader` VARCHAR(64) COMMENT '上传人',
|
||||||
|
|
||||||
|
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`updater` VARCHAR(64) DEFAULT '' COMMENT '更新者',
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_contract_id` (`contract_id`),
|
||||||
|
INDEX `idx_file_id` (`file_id`),
|
||||||
|
INDEX `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同附件表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. asset_contract_change_history(变更历史)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `asset_contract_change_history` (
|
||||||
|
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`contract_id` BIGINT NOT NULL COMMENT '合同ID',
|
||||||
|
`change_type` VARCHAR(50) NOT NULL COMMENT '变更类型(保存/提交审批/审批通过/审批驳回/撤回/终止/续签/转正式/变更三方/新增车辆等)',
|
||||||
|
`change_content` VARCHAR(1000) COMMENT '变更内容',
|
||||||
|
`operator` VARCHAR(64) NOT NULL COMMENT '操作人',
|
||||||
|
`operate_time` DATETIME NOT NULL COMMENT '操作时间',
|
||||||
|
|
||||||
|
`creator` VARCHAR(64) DEFAULT '' COMMENT '创建者',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||||
|
`tenant_id` BIGINT NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_contract_id` (`contract_id`),
|
||||||
|
INDEX `idx_operate_time` (`operate_time`),
|
||||||
|
INDEX `idx_tenant_id` (`tenant_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同变更历史表';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## BPM 集成方案
|
||||||
|
|
||||||
|
### 流程定义
|
||||||
|
- **流程定义 Key**: `rental_contract_approval`
|
||||||
|
- **流程名称**: 车辆租赁合同审批
|
||||||
|
- **审批节点**:
|
||||||
|
1. 业务部主管审批
|
||||||
|
2. 事业部主管审批
|
||||||
|
3. 财务部审批
|
||||||
|
4. 法务部审批(上传盖章合同)
|
||||||
|
|
||||||
|
### 流程变量
|
||||||
|
```java
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
variables.put("contractId", contractId);
|
||||||
|
variables.put("contractCode", contractCode);
|
||||||
|
variables.put("contractType", contractType);
|
||||||
|
variables.put("customerName", customerName);
|
||||||
|
variables.put("projectName", projectName);
|
||||||
|
variables.put("totalAmount", totalAmount);
|
||||||
|
variables.put("businessDeptId", businessDeptId);
|
||||||
|
variables.put("businessManagerId", businessManagerId);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 候选人策略
|
||||||
|
- **业务部主管**: 根据 `businessDeptId` 动态分配(部门主管角色)
|
||||||
|
- **事业部主管**: 固定角色 `BUSINESS_DIRECTOR`
|
||||||
|
- **财务部**: 财务部角色 `FINANCE_DEPT`
|
||||||
|
- **法务部**: 法务部角色 `LEGAL_DEPT`
|
||||||
|
|
||||||
|
### 事件监听器
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class ContractBpmListener {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ContractService contractService;
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
public void onProcessInstanceStatusChange(BpmProcessInstanceStatusEvent event) {
|
||||||
|
// 只处理租赁合同审批流程
|
||||||
|
if (!"rental_contract_approval".equals(event.getProcessDefinitionKey())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long contractId = Long.parseLong(event.getBusinessKey());
|
||||||
|
|
||||||
|
switch (event.getStatus()) {
|
||||||
|
case APPROVE: // 审批通过
|
||||||
|
contractService.handleApprovalApproved(contractId, event.getResult());
|
||||||
|
break;
|
||||||
|
case REJECT: // 审批驳回
|
||||||
|
contractService.handleApprovalRejected(contractId, event.getResult());
|
||||||
|
break;
|
||||||
|
case CANCEL: // 取消/撤回
|
||||||
|
contractService.handleApprovalCancelled(contractId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 接口设计
|
||||||
|
|
||||||
|
### 基础 CRUD 接口
|
||||||
|
```
|
||||||
|
POST /asset/contract/create - 创建合同
|
||||||
|
PUT /asset/contract/update - 更新合同
|
||||||
|
DELETE /asset/contract/delete - 删除合同
|
||||||
|
GET /asset/contract/get - 获取合同详情
|
||||||
|
GET /asset/contract/page - 分页查询合同
|
||||||
|
```
|
||||||
|
|
||||||
|
### 审批相关接口
|
||||||
|
```
|
||||||
|
POST /asset/contract/submit - 提交审批
|
||||||
|
POST /asset/contract/withdraw - 撤回合同
|
||||||
|
GET /asset/contract/approval-history - 审批历史
|
||||||
|
```
|
||||||
|
|
||||||
|
### 特殊业务接口
|
||||||
|
```
|
||||||
|
POST /asset/contract/terminate - 终止合同
|
||||||
|
POST /asset/contract/renew - 续签合同
|
||||||
|
POST /asset/contract/convert-formal - 转正式合同
|
||||||
|
POST /asset/contract/convert-tripartite - 变更为三方合同
|
||||||
|
POST /asset/contract/add-vehicle - 新增车辆
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询接口
|
||||||
|
```
|
||||||
|
GET /asset/contract/change-history - 变更历史
|
||||||
|
GET /asset/contract/vehicle/list - 合同车辆列表
|
||||||
|
GET /asset/contract/authorized/list - 被授权人列表
|
||||||
|
GET /asset/contract/attachment/list - 合同附件列表
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 状态机设计
|
||||||
|
|
||||||
|
### 审批状态(approval_status)
|
||||||
|
```
|
||||||
|
0 - 草稿(DRAFT)
|
||||||
|
1 - 审批中(APPROVING)
|
||||||
|
2 - 审批通过(APPROVED)
|
||||||
|
3 - 审批拒绝(REJECTED)
|
||||||
|
4 - 已撤回(WITHDRAWN)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 合同状态(contract_status)
|
||||||
|
```
|
||||||
|
0 - 草稿(DRAFT)
|
||||||
|
1 - 待生效(PENDING)
|
||||||
|
2 - 进行中(IN_PROGRESS)
|
||||||
|
3 - 已到期(EXPIRED)
|
||||||
|
4 - 已终止(TERMINATED)
|
||||||
|
5 - 已续签(RENEWED)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态流转规则
|
||||||
|
|
||||||
|
**审批状态流转:**
|
||||||
|
```
|
||||||
|
草稿 → 审批中 → 审批通过
|
||||||
|
↓ ↓
|
||||||
|
已撤回 审批拒绝
|
||||||
|
```
|
||||||
|
|
||||||
|
**合同状态流转:**
|
||||||
|
```
|
||||||
|
草稿 → 待生效 → 进行中 → 已到期 → 已续签
|
||||||
|
↓
|
||||||
|
已终止
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态与操作关系矩阵
|
||||||
|
|
||||||
|
| 操作 | 草稿 | 审批中 | 审批通过 | 审批拒绝 | 已撤回 |
|
||||||
|
|------|------|--------|----------|----------|--------|
|
||||||
|
| 编辑 | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||||
|
| 删除 | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||||
|
| 提交审批 | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||||
|
| 撤回 | ✗ | ✓ | ✗ | ✗ | ✗ |
|
||||||
|
| 终止 | ✗ | ✗ | ✓ | ✗ | ✗ |
|
||||||
|
| 续签 | ✗ | ✗ | ✓ | ✗ | ✗ |
|
||||||
|
| 转正式 | ✗ | ✗ | ✓ | ✗ | ✗ |
|
||||||
|
| 变更三方 | ✗ | ✗ | ✓ | ✗ | ✗ |
|
||||||
|
| 新增车辆 | ✗ | ✗ | ✓ | ✗ | ✗ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现分阶段计划
|
||||||
|
|
||||||
|
### 阶段1:基础合同管理(不含审批)
|
||||||
|
|
||||||
|
**目标**: 实现合同的基础 CRUD 功能
|
||||||
|
|
||||||
|
**关键文件**:
|
||||||
|
1. 数据库表创建脚本
|
||||||
|
2. DO 类(6个)
|
||||||
|
3. Mapper 接口(6个)
|
||||||
|
4. VO 类(8个)
|
||||||
|
5. Convert 接口
|
||||||
|
6. Service 接口和实现
|
||||||
|
7. Controller
|
||||||
|
8. 枚举类(3个)
|
||||||
|
|
||||||
|
**功能清单**:
|
||||||
|
- ✓ 创建合同(包含车辆订单、服务项目、被授权人)
|
||||||
|
- ✓ 更新合同
|
||||||
|
- ✓ 删除合同
|
||||||
|
- ✓ 查询合同详情
|
||||||
|
- ✓ 分页查询合同列表
|
||||||
|
- ✓ 查询变更历史
|
||||||
|
|
||||||
|
**验证方式**:
|
||||||
|
- 使用 Postman 测试所有 CRUD 接口
|
||||||
|
- 验证数据库数据正确性
|
||||||
|
- 验证多表关联查询
|
||||||
|
|
||||||
|
### 阶段2:BPM 审批集成
|
||||||
|
|
||||||
|
**目标**: 集成 Flowable 工作流引擎,实现审批流程
|
||||||
|
|
||||||
|
**关键文件**:
|
||||||
|
1. BPM 流程定义文件(BPMN 2.0 XML)
|
||||||
|
2. ContractBpmListener.java(事件监听器)
|
||||||
|
3. 审批相关 Service 方法
|
||||||
|
4. 审批相关 Controller 接口
|
||||||
|
|
||||||
|
**功能清单**:
|
||||||
|
- ✓ 提交审批(启动 BPM 流程)
|
||||||
|
- ✓ 撤回审批
|
||||||
|
- ✓ 监听审批结果(通过/驳回)
|
||||||
|
- ✓ 更新合同审批状态
|
||||||
|
- ✓ 查询审批历史
|
||||||
|
|
||||||
|
**验证方式**:
|
||||||
|
- 创建合同并提交审批
|
||||||
|
- 在 BPM 管理界面审批
|
||||||
|
- 验证合同状态自动更新
|
||||||
|
- 验证审批历史记录
|
||||||
|
|
||||||
|
### 阶段3:特殊业务流程
|
||||||
|
|
||||||
|
**目标**: 实现续签、转正式、变更三方、新增车辆等特殊业务
|
||||||
|
|
||||||
|
**关键文件**:
|
||||||
|
1. 特殊业务 Service 方法
|
||||||
|
2. 特殊业务 Controller 接口
|
||||||
|
3. 特殊业务 VO 类
|
||||||
|
|
||||||
|
**功能清单**:
|
||||||
|
- ✓ 终止合同
|
||||||
|
- ✓ 续签合同(创建新合同,关联原合同)
|
||||||
|
- ✓ 转正式合同(试用→正式)
|
||||||
|
- ✓ 变更为三方合同(增加丙方客户)
|
||||||
|
- ✓ 新增车辆(合同执行中新增车辆)
|
||||||
|
|
||||||
|
**验证方式**:
|
||||||
|
- 测试每个特殊业务流程
|
||||||
|
- 验证数据关联正确性
|
||||||
|
- 验证状态流转正确性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键文件清单
|
||||||
|
|
||||||
|
### 数据库脚本
|
||||||
|
- `/Users/kkfluous/Projects/ai-coding/ln-oneos/oneos-backend/yudao-module-asset/sql/mysql/contract.sql`
|
||||||
|
|
||||||
|
### DO 类
|
||||||
|
- `ContractDO.java` - 合同主表
|
||||||
|
- `ContractVehicleDO.java` - 车辆订单
|
||||||
|
- `ContractVehicleServiceDO.java` - 服务项目
|
||||||
|
- `ContractAuthorizedDO.java` - 被授权人
|
||||||
|
- `ContractAttachmentDO.java` - 合同附件
|
||||||
|
- `ContractChangeHistoryDO.java` - 变更历史
|
||||||
|
|
||||||
|
### Mapper 接口
|
||||||
|
- `ContractMapper.java`
|
||||||
|
- `ContractVehicleMapper.java`
|
||||||
|
- `ContractVehicleServiceMapper.java`
|
||||||
|
- `ContractAuthorizedMapper.java`
|
||||||
|
- `ContractAttachmentMapper.java`
|
||||||
|
- `ContractChangeHistoryMapper.java`
|
||||||
|
|
||||||
|
### VO 类
|
||||||
|
- `ContractBaseVO.java` - 基础 VO
|
||||||
|
- `ContractSaveReqVO.java` - 创建/更新请求 VO
|
||||||
|
- `ContractRespVO.java` - 响应 VO
|
||||||
|
- `ContractPageReqVO.java` - 分页查询请求 VO
|
||||||
|
- `ContractDetailRespVO.java` - 详情响应 VO
|
||||||
|
- `ContractVehicleVO.java` - 车辆订单 VO
|
||||||
|
- `ContractVehicleServiceVO.java` - 服务项目 VO
|
||||||
|
- `ContractAuthorizedVO.java` - 被授权人 VO
|
||||||
|
|
||||||
|
### Service
|
||||||
|
- `ContractService.java` - Service 接口
|
||||||
|
- `ContractServiceImpl.java` - Service 实现
|
||||||
|
- `ContractBpmListener.java` - BPM 事件监听器
|
||||||
|
|
||||||
|
### Controller
|
||||||
|
- `ContractController.java` - REST API 控制器
|
||||||
|
|
||||||
|
### Convert
|
||||||
|
- `ContractConvert.java` - MapStruct 转换器
|
||||||
|
|
||||||
|
### 枚举类
|
||||||
|
- `ContractTypeEnum.java` - 合同类型枚举
|
||||||
|
- `ContractApprovalStatusEnum.java` - 审批状态枚举
|
||||||
|
- `ContractStatusEnum.java` - 合同状态枚举
|
||||||
|
|
||||||
|
### BPM 流程定义
|
||||||
|
- `rental_contract_approval.bpmn20.xml` - 租赁合同审批流程定义
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **数据一致性**: 合同、车辆订单、服务项目、被授权人等多表操作需要使用事务
|
||||||
|
2. **状态校验**: 每个操作前需要校验当前状态是否允许该操作
|
||||||
|
3. **权限控制**: 使用 `@PreAuthorize` 控制接口权限
|
||||||
|
4. **多租户**: 所有表都包含 `tenant_id` 字段,自动过滤
|
||||||
|
5. **软删除**: 使用 `deleted` 字段实现软删除
|
||||||
|
6. **审计字段**: 自动记录 `creator`、`create_time`、`updater`、`update_time`
|
||||||
|
7. **BPM 集成**: 流程实例 ID 需要保存到合同表,便于查询审批状态
|
||||||
|
8. **文件上传**: 使用 `FileApi` 上传附件,保存文件 ID 和 URL
|
||||||
|
9. **变更历史**: 每次重要操作都需要记录变更历史
|
||||||
|
10. **合同编码**: 自动生成,格式:HT-YYYY-NNN(HT=合同,YYYY=年份,NNN=序号)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 时间估算
|
||||||
|
|
||||||
|
- **阶段1**: 基础合同管理 - 2天
|
||||||
|
- 数据库表设计和创建 - 4小时
|
||||||
|
- DO/Mapper/VO/Convert - 4小时
|
||||||
|
- Service 实现 - 6小时
|
||||||
|
- Controller 实现 - 2小时
|
||||||
|
- 测试验证 - 2小时
|
||||||
|
|
||||||
|
- **阶段2**: BPM 审批集成 - 1天
|
||||||
|
- BPM 流程定义 - 2小时
|
||||||
|
- 事件监听器 - 2小时
|
||||||
|
- 审批相关接口 - 2小时
|
||||||
|
- 测试验证 - 2小时
|
||||||
|
|
||||||
|
- **阶段3**: 特殊业务流程 - 1天
|
||||||
|
- 续签/转正式/变更三方 - 4小时
|
||||||
|
- 新增车辆/终止合同 - 2小时
|
||||||
|
- 测试验证 - 2小时
|
||||||
|
|
||||||
|
**总计:约 4 天**
|
||||||
107
.claude_plans/quirky-gathering-flame.md
Normal file
107
.claude_plans/quirky-gathering-flame.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# 简化账单架构 — 删除3张bill表
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
当前能源模块有3层架构:原始记录 → energy_bill_detail(统一明细) → 3张bill表(按费用类型拆分的账单)。
|
||||||
|
实际业务中扣费发生在审核通过创建 bill_detail 时(立即扣),不需要月度账单汇总。
|
||||||
|
ETC 和电费的 bill 服务是未实现的 stub(`throw UnsupportedOperationException`),氢费 bill 服务虽然实现了但当前阶段不需要账单汇总、站方确认、提交财务等功能。
|
||||||
|
|
||||||
|
简化为:原始记录 → 审核 → energy_bill_detail(终态表,直接扣费)。billing 页面直接查 bill_detail 按 feeType 过滤。
|
||||||
|
|
||||||
|
## 改动范围
|
||||||
|
|
||||||
|
### 删除文件(~30个)
|
||||||
|
|
||||||
|
**Controller (3):**
|
||||||
|
- `modules/energy/controller/EnergyBillController.java`
|
||||||
|
- `modules/etc/controller/EtcBillController.java`
|
||||||
|
- `modules/electricity/controller/ElectricityBillController.java`
|
||||||
|
|
||||||
|
**Service (4-6):**
|
||||||
|
- `modules/energy/service/IEnergyBillService.java` + `impl/EnergyBillServiceImpl.java`
|
||||||
|
- `modules/etc/service/IEtcBillService.java` + `impl/EtcBillServiceImpl.java`(如存在)
|
||||||
|
- `modules/electricity/service/IElectricityBillService.java` + `impl/ElectricityBillServiceImpl.java`(如存在)
|
||||||
|
- `modules/energy/service/IBillAdjustmentService.java` + `impl/BillAdjustmentServiceImpl.java`(如存在)
|
||||||
|
|
||||||
|
**Mapper (3-4):**
|
||||||
|
- `modules/energy/mapper/EnergyHydrogenBillMapper.java`
|
||||||
|
- `modules/energy/mapper/EnergyBillAdjustmentMapper.java`(如存在)
|
||||||
|
- `modules/etc/mapper/EnergyEtcBillMapper.java`
|
||||||
|
- `modules/electricity/mapper/EnergyElectricityBillMapper.java`
|
||||||
|
|
||||||
|
**Entity PO (4):**
|
||||||
|
- `modules/energy/entity/bill/po/EnergyHydrogenBill.java`
|
||||||
|
- `modules/energy/entity/bill/po/EnergyBillAdjustment.java`(如存在)
|
||||||
|
- `modules/etc/entity/bill/po/EnergyEtcBill.java`
|
||||||
|
- `modules/electricity/entity/bill/po/EnergyElectricityBill.java`
|
||||||
|
|
||||||
|
**Entity VO/Query/Req(整个 bill 子包):**
|
||||||
|
- `modules/energy/entity/bill/vo/` — 所有 VO(EnergyBillVO, EnergyBillDetailVO, BillPreviewVO, BillStatisticsVO, BillAdjustmentVO)
|
||||||
|
- `modules/energy/entity/bill/query/BillQuery.java`
|
||||||
|
- `modules/energy/entity/bill/req/` — 所有 Req(BillGenerateReq, BillReviewReq, AddAdjustmentReq)
|
||||||
|
- `modules/etc/entity/bill/vo/EtcBillVO.java`
|
||||||
|
- `modules/etc/entity/bill/req/EtcBillGenerateReq.java`
|
||||||
|
- `modules/electricity/entity/bill/vo/ElectricityBillVO.java`
|
||||||
|
- `modules/electricity/entity/bill/req/ElectricityBillGenerateReq.java`
|
||||||
|
|
||||||
|
**State Machine(如存在):**
|
||||||
|
- `modules/energy/statemachine/BillStatusMachine.java`
|
||||||
|
|
||||||
|
**Finance 相关(仅删 bill 部分):**
|
||||||
|
- `modules/energy/service/finance/model/BillSubmitRequest.java`
|
||||||
|
- FinanceService 接口中的 `submitBill` 方法需移除
|
||||||
|
|
||||||
|
### 修改文件
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|---|---|
|
||||||
|
| `entity/billdetail/po/EnergyBillDetail.java` | 删除 `billId` 字段 |
|
||||||
|
| `entity/billdetail/vo/EnergyBillDetailVO.java` | 删除 `billId` 字段 |
|
||||||
|
| `service/impl/EnergyBillDetailServiceImpl.java` | toVO 中删除 `.billId()` |
|
||||||
|
| `entity/detail/po/EnergyHydrogenDetail.java` | 删除 `billId` 字段 |
|
||||||
|
| `entity/detail/vo/EnergyDetailVO.java` | 删除 `billId` 字段 |
|
||||||
|
| `entity/account/po/EnergyAccountTransaction.java` | 删除 `relatedBillId` 字段 |
|
||||||
|
| `entity/account/vo/TransactionVO.java` | 删除 `relatedBillId` 字段 |
|
||||||
|
| `service/impl/EnergyAccountTransactionServiceImpl.java` | toVO 中删除 `.relatedBillId()` |
|
||||||
|
| `service/finance/FinanceService.java` | 删除 `submitBill` 方法签名 |
|
||||||
|
| `service/finance/LocalFinanceServiceImpl.java` | 删除 `submitBill` 实现 |
|
||||||
|
|
||||||
|
### SQL 迁移
|
||||||
|
|
||||||
|
新建 `db/energy/V3__drop_bill_rollup_tables.sql`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
DROP TABLE IF EXISTS energy_bill_adjustment;
|
||||||
|
DROP TABLE IF EXISTS energy_hydrogen_bill;
|
||||||
|
DROP TABLE IF EXISTS energy_etc_bill;
|
||||||
|
DROP TABLE IF EXISTS energy_electricity_bill;
|
||||||
|
|
||||||
|
ALTER TABLE energy_bill_detail DROP COLUMN IF EXISTS bill_id;
|
||||||
|
ALTER TABLE energy_hydrogen_detail DROP COLUMN IF EXISTS bill_id;
|
||||||
|
ALTER TABLE etc_toll_record DROP COLUMN IF EXISTS bill_id;
|
||||||
|
ALTER TABLE energy_account_transaction DROP COLUMN IF EXISTS related_bill_id;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 执行顺序
|
||||||
|
|
||||||
|
1. **删除所有 bill 相关 Java 文件**
|
||||||
|
2. **修改 8 个文件**(删除 billId/relatedBillId 字段引用)
|
||||||
|
3. **编译验证**(确保无断裂引用)
|
||||||
|
4. **提交并推送**
|
||||||
|
5. **部署后执行 SQL 迁移**
|
||||||
|
|
||||||
|
## 不动的部分
|
||||||
|
|
||||||
|
- energy_bill_detail 表 + Controller + Service(核心,保留)
|
||||||
|
- 所有原始记录表和相关代码(station/etc/electricity 模块的 record 部分)
|
||||||
|
- 事件系统和监听器
|
||||||
|
- 审核流程(RawRecordReviewServiceImpl)
|
||||||
|
- 扣款服务(DeductionService)
|
||||||
|
- 前端(用户自行调整 API 路径)
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
1. 编译通过:`mvn compile` 无错误
|
||||||
|
2. 直接访问 `GET http://localhost:8702/energy/bill-detail/page?feeType=1` 返回数据
|
||||||
|
3. 原有的 `/energy/bill/page`、`/etc/bill/page`、`/electricity/bill/page` 返回 404(已删除)
|
||||||
|
4. 审核+扣费流程不受影响
|
||||||
192
.claude_plans/rosy-shimmying-rain.md
Normal file
192
.claude_plans/rosy-shimmying-rain.md
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# 加氢站对账单功能
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
加氢记录审核通过后,需要定期和加氢站对账结算。当前只有一个 `settlement_status` 字段(0未对账/1已出账/2已付款),但没有对账单实体。需要新建对账单模块,支持:生成对账单 → 查看/下载 → 确认付款 → 状态同步到氢费记录。
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
```
|
||||||
|
一张对账单 = 一个加氢站 + 一个时间段内的所有已审核订单
|
||||||
|
```
|
||||||
|
|
||||||
|
- **生成条件**:时间范围内该站所有订单必须已审核(reviewStatus=1)
|
||||||
|
- **付款含义**:我们向加氢站付款(成本结算)
|
||||||
|
- **状态流转**:生成(已出账) → 付款(已付款) / 删除(回退未对账)
|
||||||
|
- **关联订单**:对账单关联的订单 snapshot 快照,生成后订单 settlementStatus 同步为已出账
|
||||||
|
- **下载格式**:Excel + PDF
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据模型
|
||||||
|
|
||||||
|
### 新建表:`hydrogen_settlement_bill`(加氢站对账单)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | BIGINT PK | 主键 |
|
||||||
|
| bill_code | VARCHAR(32) UNIQUE | 对账单编号(SB+日期+序号) |
|
||||||
|
| station_id | BIGINT | 加氢站ID |
|
||||||
|
| station_name | VARCHAR(128) | 加氢站名称(冗余) |
|
||||||
|
| period_start | DATE | 对账起始日期 |
|
||||||
|
| period_end | DATE | 对账截止日期 |
|
||||||
|
| order_count | INT | 订单笔数 |
|
||||||
|
| total_gas_weight | DECIMAL(12,4) | 总加注量(kg) |
|
||||||
|
| total_amount | DECIMAL(12,2) | 总金额(元) — 成本金额 |
|
||||||
|
| status | TINYINT | 0-已出账 1-已付款 |
|
||||||
|
| paid_time | DATETIME | 付款时间 |
|
||||||
|
| paid_by | BIGINT | 付款确认人 |
|
||||||
|
| remark | VARCHAR(500) | 备注 |
|
||||||
|
| create_by/create_time/update_by/update_time/del_flag | | 审计字段 |
|
||||||
|
|
||||||
|
### 新建表:`hydrogen_settlement_bill_item`(对账单明细快照)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | BIGINT PK | 主键 |
|
||||||
|
| bill_id | BIGINT | 关联对账单ID |
|
||||||
|
| order_id | BIGINT | 关联氢费记录ID |
|
||||||
|
| order_number | VARCHAR(64) | 订单编号 |
|
||||||
|
| plate_number | VARCHAR(16) | 车牌号 |
|
||||||
|
| fill_end_time | DATETIME | 加注时间 |
|
||||||
|
| gas_weight | DECIMAL(10,4) | 加注重量(kg) |
|
||||||
|
| gas_price | DECIMAL(10,4) | 气价(元/kg) |
|
||||||
|
| total_amount | DECIMAL(10,2) | 金额(元) |
|
||||||
|
| create_time | DATETIME | 创建时间 |
|
||||||
|
|
||||||
|
> 注:明细快照不含客户/项目信息——加氢站对账只关心站方数据(车牌、时间、重量、气价、金额)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 设计
|
||||||
|
|
||||||
|
### 后端接口
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST | `/station/settlement-bill/generate` | 生成对账单(传 stationId, periodStart, periodEnd) |
|
||||||
|
| GET | `/station/settlement-bill/page` | 对账单分页列表 |
|
||||||
|
| GET | `/station/settlement-bill/{id}` | 对账单详情(含明细) |
|
||||||
|
| PUT | `/station/settlement-bill/pay/{id}` | 确认付款 |
|
||||||
|
| DELETE | `/station/settlement-bill/{id}` | 删除对账单(仅已出账可删) |
|
||||||
|
| GET | `/station/settlement-bill/export-excel/{id}` | 导出 Excel |
|
||||||
|
| GET | `/station/settlement-bill/export-pdf/{id}` | 导出 PDF |
|
||||||
|
|
||||||
|
### 生成对账单逻辑
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 校验:时间范围内该站所有订单 reviewStatus 必须全部为 1(已审核)
|
||||||
|
2. 校验:时间范围内该站不能有 settlementStatus != 0(未对账)的订单(避免重复出账)
|
||||||
|
3. 查询:该站 + 时间范围 + reviewStatus=1 + settlementStatus=0 的所有订单
|
||||||
|
4. 汇总:计算 orderCount, totalGasWeight, totalAmount
|
||||||
|
5. 创建:hydrogen_settlement_bill + 逐条 snapshot 到 bill_item
|
||||||
|
6. 更新:所有关联订单的 settlementStatus → 1(已出账)
|
||||||
|
7. 返回:对账单ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### 确认付款逻辑
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 校验:对账单 status 必须为 0(已出账)
|
||||||
|
2. 更新:对账单 status → 1(已付款),记录 paidTime, paidBy
|
||||||
|
3. 批量更新:关联订单的 settlementStatus → 2(已付款)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 删除对账单逻辑
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 校验:对账单 status 必须为 0(已出账,未付款才可删)
|
||||||
|
2. 批量回退:关联订单的 settlementStatus → 0(未对账)
|
||||||
|
3. 删除:对账单 + 明细(逻辑删除)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端设计
|
||||||
|
|
||||||
|
### 加氢记录页面 — 增加「生成对账单」按钮
|
||||||
|
|
||||||
|
工具栏新增按钮,点击弹出对话框:
|
||||||
|
- 选择加氢站(下拉)
|
||||||
|
- 选择对账时间范围(日期区间选择器)
|
||||||
|
- 点击「生成」→ 调用后端 → 成功后自动跳转对账单详情页
|
||||||
|
|
||||||
|
### 对账单页面(/energy/order/settlement-bill/:id)
|
||||||
|
|
||||||
|
不单独设菜单,通过加氢记录页面跳转。
|
||||||
|
|
||||||
|
**页面结构:**
|
||||||
|
```
|
||||||
|
┌─ 对账单详情 ─────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 对账单号:SB20260325001 │
|
||||||
|
│ 加氢站:嘉兴站 状态:[已出账] │
|
||||||
|
│ 对账周期:2026-03-01 ~ 2026-03-31 │
|
||||||
|
│ │
|
||||||
|
│ ┌────────┬────────┬────────┐ │
|
||||||
|
│ │ 笔数 │ 总加注量│ 总金额 │ │
|
||||||
|
│ │ 25 │ 150 kg │ ¥4,500 │ │
|
||||||
|
│ └────────┴────────┴────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [确认付款] [删除] [导出Excel] [导出PDF] │
|
||||||
|
│ │
|
||||||
|
│ ── 明细列表 ── │
|
||||||
|
│ 订单编号 | 车牌 | 加注时间 | 重量(kg) | 气价(元/kg) | 金额(元) │
|
||||||
|
│ ... │
|
||||||
|
└──────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 对账单列表页(可选,也可嵌在加氢记录页面作为 Tab)
|
||||||
|
|
||||||
|
暂不独立列表页,通过加氢记录页面「已出账/已付款」筛选 + 点击跳转查看。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件清单
|
||||||
|
|
||||||
|
### 后端 — ln-energy
|
||||||
|
|
||||||
|
| 操作 | 文件 |
|
||||||
|
|------|------|
|
||||||
|
| Create | `db/station/V6__create_settlement_bill.sql` |
|
||||||
|
| Create | `entity/settlement/po/HydrogenSettlementBill.java` |
|
||||||
|
| Create | `entity/settlement/po/HydrogenSettlementBillItem.java` |
|
||||||
|
| Create | `entity/settlement/vo/SettlementBillVO.java` |
|
||||||
|
| Create | `entity/settlement/vo/SettlementBillDetailVO.java` |
|
||||||
|
| Create | `entity/settlement/req/GenerateSettlementBillReq.java` |
|
||||||
|
| Create | `mapper/HydrogenSettlementBillMapper.java` |
|
||||||
|
| Create | `mapper/HydrogenSettlementBillItemMapper.java` |
|
||||||
|
| Create | `service/ISettlementBillService.java` |
|
||||||
|
| Create | `service/impl/SettlementBillServiceImpl.java` |
|
||||||
|
| Create | `controller/SettlementBillController.java` |
|
||||||
|
| Modify | `db/init/V1__init_ln_energy.sql` — 补建表语句 |
|
||||||
|
|
||||||
|
### 前端 — ln-one-os-web
|
||||||
|
|
||||||
|
| 操作 | 文件 |
|
||||||
|
|------|------|
|
||||||
|
| Create | `views/station/settlement/bill-detail.vue` — 对账单详情页 |
|
||||||
|
| Create | `views/station/settlement/generate-modal.vue` — 生成对账单对话框 |
|
||||||
|
| Create | `api/station/settlement/index.ts` — 对账单 API |
|
||||||
|
| Modify | `views/station/order/index.vue` — 工具栏加「生成对账单」按钮 |
|
||||||
|
| Modify | 后端动态菜单 — 隐藏菜单注册(hideInMenu) |
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
|
||||||
|
| 操作 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| Execute DDL | hydrogen_settlement_bill + hydrogen_settlement_bill_item |
|
||||||
|
| Menu | 注册隐藏菜单(对账单详情页路由) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
1. `mvn clean compile` 编译通过
|
||||||
|
2. DDL 执行到数据库
|
||||||
|
3. 测试:选择加氢站 + 时间范围 → 生成对账单 → 跳转详情
|
||||||
|
4. 测试:时间范围内有未审核订单 → 生成失败提示
|
||||||
|
5. 测试:确认付款 → 对账单状态+订单状态同步
|
||||||
|
6. 测试:删除对账单 → 订单状态回退
|
||||||
|
7. 测试:已付款的对账单不可删除
|
||||||
|
8. 测试:导出 Excel / PDF
|
||||||
234
.claude_plans/squishy-booping-lerdorf.md
Normal file
234
.claude_plans/squishy-booping-lerdorf.md
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
# 合同管理全功能实现计划
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
原型有7个页面(列表、新增、查看、新增车辆、续签、变更三方、转正式),当前前端只实现了列表+新增/编辑的基础框架。需要补全所有功能,包括后端缺失字段/接口。
|
||||||
|
|
||||||
|
## 架构决策
|
||||||
|
|
||||||
|
1. **单组件多模式**: form.vue 通过 `mode` 支持 7 种模式(create/edit/view/renew/convertThirdParty/convertFormal/addVehicle),不创建额外组件
|
||||||
|
2. **服务费项目**: 在车辆表格中使用可展开行(expandedRowRender)显示服务费明细
|
||||||
|
3. **附件上传**: 使用现有 `uploadFile` API (`api/infra/file/index.ts`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: 后端变更
|
||||||
|
|
||||||
|
### 1.1 SQL 迁移 — `asset_contract` 新增6字段
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE asset_contract
|
||||||
|
ADD COLUMN hydrogen_bearer VARCHAR(20) COMMENT '氢费承担方',
|
||||||
|
ADD COLUMN hydrogen_payment_method VARCHAR(20) COMMENT '氢气付款方式',
|
||||||
|
ADD COLUMN hydrogen_prepay DECIMAL(12,2) COMMENT '氢气预付款',
|
||||||
|
ADD COLUMN hydrogen_return_price DECIMAL(12,2) COMMENT '退还车氢气单价',
|
||||||
|
ADD COLUMN billing_method VARCHAR(50) COMMENT '账单计算方式',
|
||||||
|
ADD COLUMN main_vehicle_type VARCHAR(50) COMMENT '主车型';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 更新 ContractDO
|
||||||
|
|
||||||
|
文件: `dal/dataobject/contract/ContractDO.java`
|
||||||
|
添加: `hydrogenBearer`, `hydrogenPaymentMethod`, `hydrogenPrepay`(BigDecimal), `hydrogenReturnPrice`(BigDecimal), `billingMethod`, `mainVehicleType`
|
||||||
|
|
||||||
|
### 1.3 更新 ContractBaseVO
|
||||||
|
|
||||||
|
文件: `controller/admin/contract/vo/ContractBaseVO.java`
|
||||||
|
添加同样6个字段(自动流入 SaveReqVO/RespVO/DetailRespVO)
|
||||||
|
|
||||||
|
### 1.4 更新 ContractPageReqVO
|
||||||
|
|
||||||
|
文件: `controller/admin/contract/vo/ContractPageReqVO.java`
|
||||||
|
添加: `signingCompany`(String), `businessManagerId`(Long), `creator`(String)
|
||||||
|
|
||||||
|
### 1.5 更新 ContractMapper.selectPage
|
||||||
|
|
||||||
|
文件: `dal/mysql/contract/ContractMapper.java`
|
||||||
|
添加3个查询条件: `.eqIfPresent(signingCompany)`, `.eqIfPresent(businessManagerId)`, `.likeIfPresent(creator)`
|
||||||
|
|
||||||
|
### 1.6 ContractRespVO 增加 vehicleCount/deliveredCount
|
||||||
|
|
||||||
|
文件: `controller/admin/contract/vo/ContractRespVO.java`
|
||||||
|
添加: `Integer vehicleCount`, `Integer deliveredCount`
|
||||||
|
在 Controller 的 page 方法中查询填充
|
||||||
|
|
||||||
|
### 1.7 新增 ContractAttachmentVO
|
||||||
|
|
||||||
|
新文件: `controller/admin/contract/vo/ContractAttachmentVO.java`
|
||||||
|
字段: `id`, `attachmentType`, `fileName`, `fileUrl`, `fileSize`, `uploadTime`
|
||||||
|
|
||||||
|
### 1.8 更新 ContractDetailRespVO
|
||||||
|
|
||||||
|
添加 `List<ContractAttachmentVO> attachments` 字段
|
||||||
|
|
||||||
|
### 1.9 新增错误码
|
||||||
|
|
||||||
|
文件: `enums/ErrorCodeConstants.java` (从 1_008_005_010 开始)
|
||||||
|
- CONTRACT_STATUS_NOT_ALLOW_CONVERT
|
||||||
|
- CONTRACT_STATUS_NOT_ALLOW_ADD_VEHICLE
|
||||||
|
- CONTRACT_TYPE_NOT_TRIAL
|
||||||
|
|
||||||
|
### 1.10 新增4个 Controller 端点
|
||||||
|
|
||||||
|
文件: `controller/admin/contract/ContractController.java`
|
||||||
|
|
||||||
|
| 端点 | 方法 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| POST /convert-to-third-party | convertToThirdParty(id, ContractSaveReqVO) | 从原合同创建三方合同 |
|
||||||
|
| POST /convert-to-formal | convertToFormal(id, ContractSaveReqVO) | 试用合同转正式 |
|
||||||
|
| POST /add-vehicle | addVehicle(id, List<VehicleSaveVO>) | 往现有合同追加车辆 |
|
||||||
|
| POST /upload-seal | uploadSeal(id, fileUrl, fileName) | 上传盖章附件 |
|
||||||
|
|
||||||
|
### 1.11 ContractService 新增方法
|
||||||
|
|
||||||
|
文件: `service/contract/ContractService.java` + `ContractServiceImpl.java`
|
||||||
|
|
||||||
|
- `convertToThirdParty`: 验证合同进行中 → 创建新合同(thirdPartyEnabled=true, originalContractId) → 记录变更历史
|
||||||
|
- `convertToFormal`: 验证试用合同+进行中 → 创建正式合同(type=2) → 原合同标记终止 → 记录变更历史
|
||||||
|
- `addVehiclesToContract`: 验证合同进行中 → 追加车辆+服务费 → 记录变更历史
|
||||||
|
- `uploadSealedContract`: 创建 ContractAttachmentDO(type=2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: 前端 API
|
||||||
|
|
||||||
|
文件: `api/asset/contract.ts`
|
||||||
|
|
||||||
|
### 2.1 新增接口类型
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
ContractAuthorized { id?, name, phone, idCard }
|
||||||
|
ContractAttachment { id?, attachmentType, fileName, fileUrl, fileSize?, uploadTime? }
|
||||||
|
ContractVehicleService { id?, serviceName, serviceFee, effectiveDate? }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 更新 Contract 接口
|
||||||
|
|
||||||
|
添加: `hydrogenBearer`, `hydrogenPaymentMethod`, `hydrogenPrepay`, `hydrogenReturnPrice`, `billingMethod`, `mainVehicleType`, `authorizedPersons`, `attachments`
|
||||||
|
|
||||||
|
### 2.3 更新 ContractVehicle 接口
|
||||||
|
|
||||||
|
添加: `services?: ContractVehicleService[]`
|
||||||
|
|
||||||
|
### 2.4 新增 API 函数
|
||||||
|
|
||||||
|
`convertToThirdParty`, `convertToFormal`, `addVehicle`, `uploadSeal`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: 前端 data.ts
|
||||||
|
|
||||||
|
文件: `views/asset/contract/data.ts`
|
||||||
|
|
||||||
|
### 3.1 搜索表单增加字段
|
||||||
|
|
||||||
|
- `businessDeptId` — Select (业务部门)
|
||||||
|
- `businessManagerId` — Select (业务负责人)
|
||||||
|
- `creator` — Input (创建人)
|
||||||
|
|
||||||
|
### 3.2 新增服务费类型常量
|
||||||
|
|
||||||
|
`SERVICE_TYPE_OPTIONS` — 42项(代处理费用、罚款、违章处理违约金...)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: 前端 index.vue
|
||||||
|
|
||||||
|
文件: `views/asset/contract/index.vue`
|
||||||
|
|
||||||
|
### 4.1 新增操作处理函数
|
||||||
|
|
||||||
|
- `handleView(row)` → 打开 form modal, mode='view'
|
||||||
|
- `handleRenew(row)` → 打开 form modal, mode='renew'(替换占位)
|
||||||
|
- `handleConvertThirdParty(row)` → 打开 form modal, mode='convertThirdParty'
|
||||||
|
- `handleConvertFormal(row)` → 打开 form modal, mode='convertFormal'
|
||||||
|
- `handleAddVehicle(row)` → 打开 form modal, mode='addVehicle'
|
||||||
|
|
||||||
|
### 4.2 扩展 getRowActions
|
||||||
|
|
||||||
|
| 条件 | 操作 |
|
||||||
|
|------|------|
|
||||||
|
| 始终 | 查看 → handleView |
|
||||||
|
| draft/rejected/withdrawn | 编辑、提交审批、删除 |
|
||||||
|
| approving | 撤回审批 |
|
||||||
|
| contractStatus=2 (进行中) | 新增车辆、续签、终止 |
|
||||||
|
| contractStatus=2 且 !thirdPartyEnabled | 变更为三方 |
|
||||||
|
| contractStatus=2 且 contractType=1 (试用) | 转正式 |
|
||||||
|
| contractStatus=3 (已到期) | 续签 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: 前端 form.vue 重构
|
||||||
|
|
||||||
|
文件: `views/asset/contract/modules/form.vue`
|
||||||
|
|
||||||
|
### 5.1 模式管理
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type FormMode = 'create' | 'edit' | 'view' | 'renew' | 'convertThirdParty' | 'convertFormal' | 'addVehicle'
|
||||||
|
const mode = ref<FormMode>('create')
|
||||||
|
const isReadOnly = computed(() => mode.value === 'view')
|
||||||
|
const isContractReadOnly = computed(() => ['view', 'addVehicle'].includes(mode.value))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Modal 标题
|
||||||
|
|
||||||
|
根据 mode 显示: 新增合同 / 编辑合同 / 查看合同 / 续签合同 / 变更为三方合同 / 转正式合同 / 新增车辆
|
||||||
|
|
||||||
|
### 5.3 onOpenChange 分模式加载
|
||||||
|
|
||||||
|
- `create`: 清空所有表单
|
||||||
|
- `edit`: 获取详情 → 填充全部
|
||||||
|
- `view`: 获取详情 → 填充全部 → 禁用
|
||||||
|
- `renew`: 获取详情 → 清ID → 修改contractCode后缀 → 可编辑
|
||||||
|
- `convertThirdParty`: 获取详情 → 清ID → 设thirdPartyEnabled=true
|
||||||
|
- `convertFormal`: 获取详情 → 清ID → 设contractType=2 → 固定
|
||||||
|
- `addVehicle`: 获取详情 → 合同信息只读 → 清空车辆列表
|
||||||
|
|
||||||
|
### 5.4 onConfirm 分模式提交
|
||||||
|
|
||||||
|
各模式调用对应 API (create/update/renew/convertToThirdParty/convertToFormal/addVehicle)
|
||||||
|
|
||||||
|
### 5.5 表单禁用控制
|
||||||
|
|
||||||
|
- view模式: 所有表单 disabled, 隐藏确认按钮
|
||||||
|
- addVehicle模式: 客户/合同/三方表单 disabled, 仅车辆可编辑
|
||||||
|
- convertFormal模式: contractType 字段 disabled
|
||||||
|
|
||||||
|
### 5.6 车辆服务费项目
|
||||||
|
|
||||||
|
使用 Ant Design Table 的 `expandedRowRender` slot:
|
||||||
|
- 展开行显示子表格: 服务名称(Select), 服务费(InputNumber), 生效日期(DatePicker)
|
||||||
|
- 支持新增/删除行
|
||||||
|
- serviceCost 列自动计算 = sum(services[].serviceFee)
|
||||||
|
|
||||||
|
### 5.7 附件上传集成
|
||||||
|
|
||||||
|
- 上传时调用 `uploadFile` 获取 fileUrl
|
||||||
|
- 提交时将 attachments 数组(type=1 原件)随合同数据保存
|
||||||
|
- 查看模式展示附件列表,可下载
|
||||||
|
- 续签/转正式:原合同附件标记 isOriginal=true 不可删除
|
||||||
|
|
||||||
|
### 5.8 氢气/计费数据绑定
|
||||||
|
|
||||||
|
将 `hydrogenBearer`, `hydrogenPaymentMethod`, `hydrogenPrepay`, `hydrogenReturnPrice`, `billingMethod` 纳入保存数据;编辑时回显
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实施顺序
|
||||||
|
|
||||||
|
1. **后端**: SQL迁移 → DO/VO更新 → Mapper更新 → Service新增方法 → Controller新增端点 → 编译验证
|
||||||
|
2. **前端API**: 更新接口和函数
|
||||||
|
3. **前端data.ts**: 搜索表单 + 服务费常量
|
||||||
|
4. **前端form.vue**: 模式管理 → 禁用控制 → 分模式加载/提交 → 服务费展开行 → 附件上传 → 氢气/计费绑定
|
||||||
|
5. **前端index.vue**: 新操作函数 → getRowActions 扩展
|
||||||
|
6. **验证**: `mvn compile` + `pnpm run build:antd`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- 后端: `mvn compile` 无报错
|
||||||
|
- 前端: `pnpm run build:antd` 无报错
|
||||||
|
- SQL: 通过 PyMySQL 执行 ALTER TABLE
|
||||||
|
- 功能: 7种模式逐一打开/提交验证
|
||||||
701
.claude_plans/stateful-waddling-elephant.md
Normal file
701
.claude_plans/stateful-waddling-elephant.md
Normal file
@@ -0,0 +1,701 @@
|
|||||||
|
# Phase 2 ETC 模块完成计划(Controller + API Client + 前端)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Phase 1 基础设施层(FeeType 枚举、DeductionRequest、IDeductionService 重构、ContractMatchService、ReviewConfig 扩展)已完成并编译通过。
|
||||||
|
Phase 2 ETC 模块后端核心(Entity/Mapper/Service/Event/Listener)已完成并编译通过。
|
||||||
|
|
||||||
|
**本次任务:** 完成 Phase 2 剩余部分:
|
||||||
|
1. ETC 后端 Controller 层(3 个 Controller)
|
||||||
|
2. EtczjApiClient(etczj.com HTTP 客户端 + OCR 验证码)
|
||||||
|
3. 前端 ETC 配置页面 + ETC 账单查询页面
|
||||||
|
|
||||||
|
**用户决策:**
|
||||||
|
- 账户结构:共用一个能源账户(三种费用从同一余额扣款)
|
||||||
|
- 账单模式:各费用类型独立账单
|
||||||
|
- 业务流程:ETC/电费与氢费完全一致(合同匹配 → 审核 → 扣款)
|
||||||
|
- 数据源:先设计通用框架,具体 API/Excel 格式后续对接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 架构方案:独立明细表 + 共享账户层
|
||||||
|
|
||||||
|
```
|
||||||
|
modules/
|
||||||
|
station/ ← 已有:氢费数据源(不动)
|
||||||
|
etc/ ← 新增:ETC 数据源
|
||||||
|
electricity/ ← 新增:电费数据源
|
||||||
|
energy/ ← 已有:共享结算层(小幅扩展)
|
||||||
|
payment/ ← 已有:款项管理(小幅扩展)
|
||||||
|
```
|
||||||
|
|
||||||
|
**核心原则:** 三种费用的明细/账单各自独立(字段差异大),但扣款引擎、账户、流水、充值、款项管理完全共享。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: 基础设施层改造(energy 域)
|
||||||
|
|
||||||
|
### 1.1 新增 `FeeType` 枚举
|
||||||
|
|
||||||
|
**文件:** `modules/energy/enums/FeeType.java`(新建)
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum FeeType {
|
||||||
|
HYDROGEN(1, "氢费"),
|
||||||
|
ETC(2, "高速通行费"),
|
||||||
|
ELECTRICITY(3, "电费");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String label;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 重构 `IDeductionService` → 接受通用扣款请求
|
||||||
|
|
||||||
|
**改动文件:**
|
||||||
|
- `modules/energy/service/IDeductionService.java`
|
||||||
|
- `modules/energy/service/impl/DeductionServiceImpl.java`
|
||||||
|
|
||||||
|
**改动内容:**
|
||||||
|
|
||||||
|
新增通用 DTO `DeductionRequest`:
|
||||||
|
```java
|
||||||
|
@Data @Builder
|
||||||
|
public class DeductionRequest {
|
||||||
|
private FeeType feeType;
|
||||||
|
private Long detailId; // 明细ID(在各自的明细表中)
|
||||||
|
private Long customerId;
|
||||||
|
private String customerName;
|
||||||
|
private Long contractId;
|
||||||
|
private BigDecimal amount; // 扣款金额
|
||||||
|
private Integer paymentMode; // 1-预充值 2-月结 3-自行结算
|
||||||
|
private Integer deductionStatus; // 当前扣款状态(幂等守卫用)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
接口签名变更:
|
||||||
|
```java
|
||||||
|
// 原:DeductionResult deduct(EnergyHydrogenDetail detail);
|
||||||
|
// 新:
|
||||||
|
DeductionResult deduct(DeductionRequest request);
|
||||||
|
void reDeduct(DeductionRequest request, BigDecimal newAmount);
|
||||||
|
```
|
||||||
|
|
||||||
|
`DeductionServiceImpl` 改动点:
|
||||||
|
- `deduct()` 方法参数从 `EnergyHydrogenDetail` 改为 `DeductionRequest`,逻辑不变(读 customerId/amount/contractId/paymentMode)
|
||||||
|
- **移除** `detailMapper.updateById(detail)` —— 扣款服务不再直接更新明细表,改为返回 `DeductionResult`,由调用方负责更新各自的明细表扣款状态
|
||||||
|
- 流水 description 加入 `feeType.getLabel()`(如 "ETC扣款" / "电费扣款")
|
||||||
|
- `reDeduct()` 同理,不再直接操作 `detailMapper`
|
||||||
|
|
||||||
|
**向后兼容:** 在 `EnergyDetailImportListener`(氢费监听器)中构造 `DeductionRequest.fromHydrogenDetail(detail)`,调用新接口后手动更新氢费明细扣款状态。
|
||||||
|
|
||||||
|
### 1.3 `energy_account_transaction` 表加 `fee_type` 列
|
||||||
|
|
||||||
|
**DDL 迁移脚本:** `db/energy/V2__add_fee_type_to_transaction.sql`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE energy_account_transaction
|
||||||
|
ADD COLUMN fee_type TINYINT NOT NULL DEFAULT 1 COMMENT '费用类型:1-氢费 2-ETC 3-电费';
|
||||||
|
ALTER TABLE energy_account_transaction
|
||||||
|
ADD INDEX idx_eat_fee_type (fee_type);
|
||||||
|
```
|
||||||
|
|
||||||
|
`EnergyAccountTransaction.java` 新增 `feeType` 字段。
|
||||||
|
|
||||||
|
### 1.4 `customer_payment_item` 款项解构扩展
|
||||||
|
|
||||||
|
`fee_type` 枚举扩展(已有字段):
|
||||||
|
- 现有:1-租赁费 2-氢费充值 3-押金 4-违约金 5-其他
|
||||||
|
- 新增:6-ETC充值 7-电费充值
|
||||||
|
|
||||||
|
`PaymentFlowService` 的流转逻辑需根据新 fee_type 创建对应充值单。
|
||||||
|
|
||||||
|
### 1.5 通用合同匹配抽取
|
||||||
|
|
||||||
|
现有合同匹配逻辑在 `EnergyDetailImportListener` 中(车牌+时间→合同)。
|
||||||
|
抽取为共享服务:
|
||||||
|
|
||||||
|
**新建:** `modules/energy/service/IContractMatchService.java`
|
||||||
|
```java
|
||||||
|
public interface IContractMatchService {
|
||||||
|
ContractMatchResult matchContract(String plateNumber, Date eventTime);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回 `ContractMatchResult`(customerId, customerName, contractId, contractCode, costType, paymentMode)。
|
||||||
|
ETC/电费/氢费三个监听器共用此服务。
|
||||||
|
|
||||||
|
### 1.6 审核配置扩展
|
||||||
|
|
||||||
|
`energy_review_config` 表加 `fee_type` 列:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE energy_review_config
|
||||||
|
ADD COLUMN fee_type TINYINT NOT NULL DEFAULT 1 COMMENT '费用类型:1-氢费 2-ETC 3-电费';
|
||||||
|
DROP INDEX uk_erc_level_customer ON energy_review_config;
|
||||||
|
CREATE UNIQUE INDEX uk_erc_level_customer_fee ON energy_review_config (config_level, customer_id, fee_type);
|
||||||
|
```
|
||||||
|
|
||||||
|
每种费用类型可独立配置"是否需要审核后才扣款"。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: ETC 模块(`modules/etc/`)
|
||||||
|
|
||||||
|
### 2.0 ETC 数据源逆向分析(etczj.com - 浙江货车ETC)
|
||||||
|
|
||||||
|
**平台信息:** https://www.etczj.com (Nuxt.js + Element UI 前端,nginx/1.27.2 后端)
|
||||||
|
|
||||||
|
**认证流程:**
|
||||||
|
```
|
||||||
|
1. GET /createCaptcha?verifyCodeSize=null&tmp={random}&secret={guid} → 图形验证码图片
|
||||||
|
2. POST /member/toLogin?secret={guid}
|
||||||
|
Body: { acctId: "手机号", password: "Base64编码密码", checkCode: "验证码", msgCode: "", secret: guid }
|
||||||
|
Response: { state: "1", payload: { randomVal: "xxx", signState: "1" } }
|
||||||
|
3. 后续请求 Header: Randomval: {randomVal}(存 localStorage)
|
||||||
|
```
|
||||||
|
|
||||||
|
**核心业务 API:**
|
||||||
|
|
||||||
|
| API | 方法 | 说明 | 关键参数 |
|
||||||
|
|-----|------|------|----------|
|
||||||
|
| `/vehicleManage/queryVehicleConsumptionDetailPage` | POST | **通行记录分页** | transTimeBegin/End, postingTimeBegin/End, vehicleCode, licenseColor, vcEnStation, vcExStation, currPage, pageSize |
|
||||||
|
| `/vehicleManage/sumVehicleToll` | POST | **通行费汇总** | 同上(返回 totalPassAmount, totalServiceAmount) |
|
||||||
|
| `/vehicleManage/viewVehiclePage` | POST | **车辆列表** | vehicleCode, currPage, pageSize |
|
||||||
|
| `/largeFileExport/billDetails` | POST | **通行记录导出** | postingTimeBegin/End, transTimeBegin/End, vehicleCode |
|
||||||
|
| `/member/flowList` | POST | **账单流水** | - |
|
||||||
|
| `/member/viewMember` | POST | **用户信息** | - |
|
||||||
|
|
||||||
|
**通行记录响应字段映射 → `etc_toll_record` 表:**
|
||||||
|
|
||||||
|
| etczj 字段 | 含义 | → 表字段 |
|
||||||
|
|------------|------|----------|
|
||||||
|
| vehicleCode | 车牌号码 | plate_number |
|
||||||
|
| licenseColor | 车牌颜色(0-6) | license_color |
|
||||||
|
| cardType | 卡类型 | card_type |
|
||||||
|
| cardCode | 通行卡号 | etc_card_number |
|
||||||
|
| postingTimeStr | 记账日期 | posting_time |
|
||||||
|
| transDate + transTime | 通行日期+时间 | trans_time |
|
||||||
|
| enStation | 入口站 | entry_station_name |
|
||||||
|
| exStation | 出口站 | exit_station_name |
|
||||||
|
| dToll | 通行费用(元) | toll_amount |
|
||||||
|
| serviceFee | 服务费(元) | service_fee |
|
||||||
|
| appealStatus | 申诉状态 | appeal_status |
|
||||||
|
|
||||||
|
**同步策略设计要点:**
|
||||||
|
- 登录需图形验证码 → 需 OCR 或人工介入。建议:首次登录获取 session 后缓存 `randomVal`,session 过期时告警人工重新登录
|
||||||
|
- 增量同步:按 `postingTimeBegin/End`(记账日期)拉取,每次从 `lastSyncTime` 开始
|
||||||
|
- 去重键:`vehicleCode + transDate + transTime + enStation + exStation`(平台无唯一订单号)
|
||||||
|
- 分页:`currPage` + `pageSize`,默认 pageSize=5,建议调大到 100
|
||||||
|
- 日期限制:时间范围最多 31 天
|
||||||
|
|
||||||
|
### 2.1 数据库表
|
||||||
|
|
||||||
|
**`etc_toll_record`(ETC通行记录表):**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | BIGINT | 主键 |
|
||||||
|
| record_code | VARCHAR(32) | 记录编码(系统生成,唯一) |
|
||||||
|
| etc_card_number | VARCHAR(64) | 通行卡号(cardCode) |
|
||||||
|
| card_type | VARCHAR(32) | 卡类型(cardType) |
|
||||||
|
| plate_number | VARCHAR(16) | 车牌号 |
|
||||||
|
| license_color | TINYINT | 车牌颜色(0-6) |
|
||||||
|
| vehicle_id | BIGINT | 车辆ID(匹配后) |
|
||||||
|
| contract_id | BIGINT | 合同ID(匹配后) |
|
||||||
|
| customer_id | BIGINT | 客户ID(匹配后) |
|
||||||
|
| customer_name | VARCHAR(128) | 客户名称 |
|
||||||
|
| entry_station_name | VARCHAR(128) | 入口站 |
|
||||||
|
| exit_station_name | VARCHAR(128) | 出口站 |
|
||||||
|
| trans_time | DATETIME | 通行时间(transDate+transTime) |
|
||||||
|
| posting_time | DATETIME | 记账日期(postingTimeStr) |
|
||||||
|
| toll_amount | DECIMAL(10,2) | 通行费(元)(dToll) |
|
||||||
|
| service_fee | DECIMAL(10,2) | 服务费(元)(serviceFee) |
|
||||||
|
| total_amount | DECIMAL(10,2) | 合计金额(toll+service) |
|
||||||
|
| appeal_status | TINYINT | 申诉状态 |
|
||||||
|
| cost_type | TINYINT | 费用承担方 |
|
||||||
|
| payment_mode | TINYINT | 付款模式 |
|
||||||
|
| contract_matched | TINYINT | 合同匹配状态 |
|
||||||
|
| review_status | TINYINT | 审核状态 |
|
||||||
|
| deduction_status | TINYINT | 扣款状态 |
|
||||||
|
| bill_id | BIGINT | 关联账单ID |
|
||||||
|
| source_type | TINYINT | 来源:1-API同步 |
|
||||||
|
| source_dedup_key | VARCHAR(128) | 去重键(车牌+通行时间+入口+出口) |
|
||||||
|
| is_oneos_vehicle | TINYINT | 是否OneOS车辆 |
|
||||||
|
| + BaseEntity 审计字段 | | |
|
||||||
|
|
||||||
|
**`etc_sync_config`** 和 **`etc_sync_log`**:复用 station 的 sync_config/sync_log 结构,`provider_type` 固定为 `ETCZJ`。额外字段:`acct_id`(手机号)、`password`(加密存储)。
|
||||||
|
|
||||||
|
**`energy_etc_bill`(ETC账单表):**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | BIGINT | 主键 |
|
||||||
|
| bill_code | VARCHAR(32) | 账单编码 |
|
||||||
|
| customer_id | BIGINT | 客户ID |
|
||||||
|
| bill_period_start/end | DATE | 账单周期 |
|
||||||
|
| total_toll_count | INT | 通行笔数 |
|
||||||
|
| total_toll_amount | DECIMAL(12,2) | 通行总金额 |
|
||||||
|
| total_service_fee | DECIMAL(12,2) | 服务费总额 |
|
||||||
|
| receivable_amount | DECIMAL(12,2) | 应收金额 |
|
||||||
|
| actual_amount | DECIMAL(12,2) | 实收金额 |
|
||||||
|
| adjustment_amount | DECIMAL(12,2) | 调整额 |
|
||||||
|
| payment_status | TINYINT | 支付状态 |
|
||||||
|
| review_status | TINYINT | 审核状态 |
|
||||||
|
| + 审核/财务/审计字段 | | |
|
||||||
|
|
||||||
|
### 2.2 代码结构
|
||||||
|
|
||||||
|
```
|
||||||
|
modules/etc/
|
||||||
|
entity/
|
||||||
|
record/po/EtcTollRecord.java
|
||||||
|
record/vo/EtcTollRecordVO.java
|
||||||
|
record/query/EtcTollRecordQuery.java
|
||||||
|
record/req/UpdateEtcRecordReq.java
|
||||||
|
sync/po/EtcSyncConfig.java
|
||||||
|
sync/po/EtcSyncLog.java
|
||||||
|
bill/po/EnergyEtcBill.java
|
||||||
|
bill/vo/EtcBillVO.java
|
||||||
|
bill/req/EtcBillGenerateReq.java
|
||||||
|
mapper/
|
||||||
|
EtcTollRecordMapper.java
|
||||||
|
EtcSyncConfigMapper.java
|
||||||
|
EtcSyncLogMapper.java
|
||||||
|
EnergyEtcBillMapper.java
|
||||||
|
service/
|
||||||
|
IEtcTollRecordService.java
|
||||||
|
IEtcSyncConfigService.java
|
||||||
|
IEtcBillService.java
|
||||||
|
impl/EtcTollRecordServiceImpl.java
|
||||||
|
impl/EtcSyncConfigServiceImpl.java
|
||||||
|
impl/EtcBillServiceImpl.java
|
||||||
|
sync/EtcSyncStrategy.java ← 策略接口
|
||||||
|
sync/EtczjSyncStrategy.java ← etczj.com 具体实现(登录+分页拉取+字段映射)
|
||||||
|
sync/EtczjApiClient.java ← HTTP 客户端(登录/session管理/通行记录查询)
|
||||||
|
sync/EtczjApiResponse.java ← 响应 DTO
|
||||||
|
ingest/EtcIngestTemplate.java ← 模板方法(extract→validate→dedup→match→persist→event)
|
||||||
|
controller/
|
||||||
|
EtcTollRecordController.java
|
||||||
|
EtcSyncConfigController.java
|
||||||
|
EtcBillController.java
|
||||||
|
event/
|
||||||
|
EtcRecordImportedEvent.java
|
||||||
|
listener/
|
||||||
|
EtcDetailImportListener.java ← 监听 EtcRecordImportedEvent,执行合同匹配+扣款
|
||||||
|
validation/
|
||||||
|
EtcValidationChain.java
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
EtczjSyncStrategy 定时/手动同步:
|
||||||
|
1. EtczjApiClient.login(acctId, password) → 获取 randomVal session
|
||||||
|
2. 按日期范围分页调用 /vehicleManage/queryVehicleConsumptionDetailPage
|
||||||
|
- 从 lastSyncTime 开始,每次最多31天
|
||||||
|
- pageSize=100,循环翻页直到无更多数据
|
||||||
|
3. 字段映射:etczj 响应 → EtcTollRecordDTO
|
||||||
|
4. → EtcIngestTemplate.ingest()
|
||||||
|
→ extract → validate → dedup(source_dedup_key) → matchVehicle → persist(etc_toll_record)
|
||||||
|
→ publishEvent(EtcRecordImportedEvent)
|
||||||
|
5. → EtcDetailImportListener
|
||||||
|
→ 筛选 is_oneos_vehicle=1
|
||||||
|
→ contractMatchService.matchContract(plateNumber, transTime)
|
||||||
|
→ 审核配置检查(FeeType.ETC)
|
||||||
|
→ deductionService.deduct(DeductionRequest)
|
||||||
|
→ 更新 etc_toll_record.deduction_status
|
||||||
|
|
||||||
|
Session 管理:
|
||||||
|
- randomVal 缓存在 Redis,key: `etc:session:{configId}`,过期后自动重新登录
|
||||||
|
- 图形验证码处理:OCR 自动识别(Tesseract/百度OCR)
|
||||||
|
- GET /createCaptcha?secret={guid} → 图片
|
||||||
|
- OCR 识别 → 4位数字
|
||||||
|
- 识别失败重试(刷新验证码重试,最多3次)
|
||||||
|
- 3次均失败 → 告警通知人工介入
|
||||||
|
- 去重策略:vehicleCode + transTime + enStation + exStation 组合键
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 API 接口(约 20 个)
|
||||||
|
|
||||||
|
| 模块 | 接口数 |
|
||||||
|
|------|--------|
|
||||||
|
| ETC 通行记录 CRUD + 审核 + 导出 | 8 |
|
||||||
|
| ETC 同步配置 + 手动触发 + 日志 | 7 |
|
||||||
|
| ETC 账单生成 + 审核 + 调整项 | 8 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: 电费模块(`modules/electricity/`)
|
||||||
|
|
||||||
|
### 3.1 数据库表
|
||||||
|
|
||||||
|
**`electricity_charge_record`(充电记录表):**
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | BIGINT | 主键 |
|
||||||
|
| record_code | VARCHAR(32) | 记录编码 |
|
||||||
|
| charging_station_id | BIGINT | 充电站ID(关联已有 charging_station 表) |
|
||||||
|
| charging_station_name | VARCHAR(128) | 充电站名称 |
|
||||||
|
| plate_number | VARCHAR(16) | 车牌号 |
|
||||||
|
| vehicle_id / contract_id / customer_id | BIGINT | 匹配后填充 |
|
||||||
|
| charging_start_time | DATETIME | 充电开始 |
|
||||||
|
| charging_end_time | DATETIME | 充电结束 |
|
||||||
|
| charging_duration | INT | 时长(分钟) |
|
||||||
|
| kwh | DECIMAL(10,4) | 充电量(度) |
|
||||||
|
| unit_price | DECIMAL(10,4) | 单价(元/度) |
|
||||||
|
| charge_amount | DECIMAL(10,2) | 电费(元) |
|
||||||
|
| service_fee | DECIMAL(10,2) | 服务费(元) |
|
||||||
|
| total_amount | DECIMAL(10,2) | 合计金额 |
|
||||||
|
| cost_type / payment_mode / contract_matched / review_status / deduction_status / bill_id | | 与氢费/ETC 一致 |
|
||||||
|
| source_type | TINYINT | 来源:1-Excel导入 2-RPA导入 |
|
||||||
|
| source_row_key | VARCHAR(64) | 源Excel行唯一键(去重) |
|
||||||
|
| is_oneos_vehicle | TINYINT | 是否OneOS车辆 |
|
||||||
|
| + BaseEntity 审计字段 | | |
|
||||||
|
|
||||||
|
**`energy_electricity_bill`(电费账单表):** 结构类似 ETC 账单,特有字段:`total_kwh`, `total_charge_amount`, `total_service_fee`。
|
||||||
|
|
||||||
|
### 3.2 代码结构
|
||||||
|
|
||||||
|
与 ETC 模块镜像,核心差异:
|
||||||
|
- **无 API 同步**(暂时),数据入口为 Excel 导入(手动上传 / RPA 落盘后系统扫描)
|
||||||
|
- `ElectricityExcelIngestStrategy` 实现 `ElectricityIngestTemplate`
|
||||||
|
- 关联已有 `chargingstation/` 模块的充电站主数据
|
||||||
|
- 校验链包含:电量/金额一致性校验、充电站匹配校验
|
||||||
|
|
||||||
|
### 3.3 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
Excel 手动导入 / RPA 定期落盘
|
||||||
|
→ ElectricityIngestTemplate.ingest()
|
||||||
|
→ extract(解析Excel) → validate → dedup(source_row_key) → matchVehicle → persist
|
||||||
|
→ publishEvent(ElectricityRecordImportedEvent)
|
||||||
|
→ ElectricityDetailImportListener
|
||||||
|
→ 合同匹配 → 审核配置检查(FeeType.ELECTRICITY)→ 扣款 → 更新状态
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 API 接口(约 15 个)
|
||||||
|
|
||||||
|
| 模块 | 接口数 |
|
||||||
|
|------|--------|
|
||||||
|
| 充电记录 CRUD + 审核 + 导入/导出 | 8 |
|
||||||
|
| 电费账单生成 + 审核 + 调整项 | 7 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: 账户层统一视图
|
||||||
|
|
||||||
|
### 4.1 流水查询增加 fee_type 过滤
|
||||||
|
|
||||||
|
**改动文件:**
|
||||||
|
- `TransactionQuery.java` — 新增 `feeType` 可选参数
|
||||||
|
- `EnergyAccountController.java` — 流水分页接口支持按费用类型筛选
|
||||||
|
|
||||||
|
### 4.2 账户汇总接口
|
||||||
|
|
||||||
|
**新增接口:** `GET /energy/account/{id}/fee-summary`
|
||||||
|
|
||||||
|
返回各费用类型的扣款/充值汇总:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hydrogen": { "totalDeducted": 50000, "count": 320 },
|
||||||
|
"etc": { "totalDeducted": 12000, "count": 580 },
|
||||||
|
"electricity": { "totalDeducted": 8000, "count": 150 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: 前端菜单与页面
|
||||||
|
|
||||||
|
### 5.1 菜单结构
|
||||||
|
|
||||||
|
```
|
||||||
|
能源管理
|
||||||
|
├── 加氢明细(已有)
|
||||||
|
├── ETC 通行记录(新增)
|
||||||
|
├── 充电记录(新增)
|
||||||
|
├── 能源账户(已有,增加 fee_type 筛选)
|
||||||
|
├── 充值单管理(已有)
|
||||||
|
├── 氢费账单(已有)
|
||||||
|
├── ETC 账单(新增)
|
||||||
|
├── 电费账单(新增)
|
||||||
|
└── 审核配置(已有,增加 fee_type 维度)
|
||||||
|
|
||||||
|
加氢站管理(已有,不动)
|
||||||
|
|
||||||
|
ETC 管理(新增)
|
||||||
|
├── ETC 同步配置
|
||||||
|
└── ETC 同步日志
|
||||||
|
|
||||||
|
款项管理(已有)
|
||||||
|
└── fee_type 解构类型新增 ETC充值/电费充值
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实施顺序
|
||||||
|
|
||||||
|
| 步骤 | 内容 | 依赖 |
|
||||||
|
|------|------|------|
|
||||||
|
| **1** | Phase 1 基础设施(FeeType枚举、DeductionRequest、IDeductionService重构、DB迁移、ContractMatchService抽取、ReviewConfig扩展) | 无 |
|
||||||
|
| **2** | Phase 1 向后兼容(更新氢费监听器/服务适配新接口) | Step 1 |
|
||||||
|
| **3** | Phase 2 ETC 模块(实体→Mapper→Service→Controller→事件→监听器→测试) | Step 2 |
|
||||||
|
| **4** | Phase 3 电费模块(同上) | Step 2,可与 Step 3 并行 |
|
||||||
|
| **5** | Phase 4 账户层统一视图 | Step 3 & 4 |
|
||||||
|
| **6** | Phase 5 前端页面 | Step 3 & 4 |
|
||||||
|
| **7** | Payment 域扩展(fee_type 解构+流转) | Step 1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 关键改动文件清单
|
||||||
|
|
||||||
|
### 修改已有文件
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|------|------|
|
||||||
|
| `energy/service/IDeductionService.java` | 接口签名改为 `DeductionRequest` |
|
||||||
|
| `energy/service/impl/DeductionServiceImpl.java` | 实现适配,移除 detailMapper 直接操作 |
|
||||||
|
| `energy/entity/account/po/EnergyAccountTransaction.java` | 新增 `feeType` 字段 |
|
||||||
|
| `energy/listener/EnergyDetailImportListener.java` | 适配新 DeductionRequest,抽取合同匹配逻辑 |
|
||||||
|
| `energy/entity/review/po/EnergyReviewConfig.java` | 新增 `feeType` 字段 |
|
||||||
|
| `energy/service/impl/ReviewConfigServiceImpl.java` | 查询时加 feeType 条件 |
|
||||||
|
| `energy/controller/EnergyAccountController.java` | 流水查询加 feeType 过滤 |
|
||||||
|
| `energy/entity/account/query/TransactionQuery.java` | 新增 feeType 参数 |
|
||||||
|
| `payment/entity/po/CustomerPaymentItem.java` | fee_type 枚举扩展文档 |
|
||||||
|
| `payment/service/impl/PaymentFlowServiceImpl.java` | 支持 ETC/电费充值流转 |
|
||||||
|
|
||||||
|
### 新建文件(按 Phase)
|
||||||
|
- Phase 1: ~5 文件(FeeType, DeductionRequest, ContractMatchService 接口+实现, V2迁移脚本)
|
||||||
|
- Phase 2: ~20 文件(ETC 全套 entity/mapper/service/controller/event/listener)+ 3 张表 DDL
|
||||||
|
- Phase 3: ~18 文件(电费全套)+ 2 张表 DDL
|
||||||
|
- Phase 4: ~2 文件改动
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证方案
|
||||||
|
|
||||||
|
### 单元测试
|
||||||
|
- `DeductionServiceTest` —— 验证新接口支持三种 FeeType
|
||||||
|
- `EtcIngestTemplateTest` —— ETC 数据导入全流程
|
||||||
|
- `ElectricityIngestTemplateTest` —— 电费 Excel 导入全流程
|
||||||
|
- `ContractMatchServiceTest` —— 合同匹配逻辑
|
||||||
|
|
||||||
|
### 集成测试
|
||||||
|
- `EtcModuleIntegrationTest` —— ETC 同步 → 导入 → 合同匹配 → 审核 → 扣款 → 账单生成
|
||||||
|
- `ElectricityModuleIntegrationTest` —— Excel 导入 → 合同匹配 → 审核 → 扣款 → 账单生成
|
||||||
|
- `MultiFeeBillingIntegrationTest` —— 三种费用共享账户扣款 → 流水 fee_type 正确 → 各自独立出账单
|
||||||
|
|
||||||
|
### 回归验证
|
||||||
|
- 现有氢费全链路测试通过(`EnergyModuleIntegrationTest`)
|
||||||
|
- 现有款项管理测试通过(`PaymentModuleIntegrationTest`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 剩余实施计划(本次任务)
|
||||||
|
|
||||||
|
### 已完成
|
||||||
|
- [x] Phase 1 基础设施层(FeeType/DeductionRequest/IDeductionService/ContractMatchService/ReviewConfig)
|
||||||
|
- [x] Phase 2 DDL(`db/etc/V1__create_etc_tables.sql` - 4 张表)
|
||||||
|
- [x] Phase 2 Entity PO(EtcTollRecord/EtcSyncConfig/EtcSyncLog/EnergyEtcBill)
|
||||||
|
- [x] Phase 2 DTO(EtcTollRecordVO/EtcTollRecordQuery/EtcBillVO/EtcBillGenerateReq)
|
||||||
|
- [x] Phase 2 Mapper(4 个)
|
||||||
|
- [x] Phase 2 Service 接口 + 实现(IEtcTollRecordService/IEtcSyncConfigService/IEtcBillService)
|
||||||
|
- [x] Phase 2 Event(EtcRecordImportedEvent)
|
||||||
|
- [x] Phase 2 Listener(EtcDetailImportListener)
|
||||||
|
- [x] 编译通过
|
||||||
|
|
||||||
|
### Step 1: ETC 后端 Controller 层
|
||||||
|
|
||||||
|
**3 个 Controller,沿用 `EnergyBillController` 的精确模式:**
|
||||||
|
|
||||||
|
#### 1.1 `EtcTollRecordController.java`
|
||||||
|
**路径:** `modules/etc/controller/EtcTollRecordController.java`
|
||||||
|
**前缀:** `@RequestMapping("/etc/record")`
|
||||||
|
**Tag:** `@Tag(name = "ETC通行记录管理")`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 | 调用 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| GET | /page | 分页查询 | service.pageList(query) |
|
||||||
|
| GET | /detail/{id} | 详情 | service.getDetail(id) |
|
||||||
|
| PUT | /review | 单条审核 | service.review(id, approved, remark) |
|
||||||
|
| PUT | /batch-review | 批量审核 | service.batchReview(ids, approved) |
|
||||||
|
| PUT | /manual-match | 手动匹配合同 | service.manualMatch(detailId, contractId) |
|
||||||
|
| POST | /export | 导出 | TODO |
|
||||||
|
| GET | /statistics | 统计数据 | service.getStatistics() |
|
||||||
|
|
||||||
|
#### 1.2 `EtcSyncConfigController.java`
|
||||||
|
**路径:** `modules/etc/controller/EtcSyncConfigController.java`
|
||||||
|
**前缀:** `@RequestMapping("/etc/sync-config")`
|
||||||
|
**Tag:** `@Tag(name = "ETC同步配置")`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | /page | 分页查询 |
|
||||||
|
| GET | /detail/{id} | 详情 |
|
||||||
|
| POST | / | 新增 |
|
||||||
|
| PUT | / | 编辑 |
|
||||||
|
| DELETE | /{id} | 删除 |
|
||||||
|
| PUT | /toggle/{id} | 启用/停用 |
|
||||||
|
| POST | /trigger/{id} | 手动触发同步 |
|
||||||
|
| GET | /log/{configId} | 同步日志 |
|
||||||
|
|
||||||
|
#### 1.3 `EtcBillController.java`
|
||||||
|
**路径:** `modules/etc/controller/EtcBillController.java`
|
||||||
|
**前缀:** `@RequestMapping("/etc/bill")`
|
||||||
|
**Tag:** `@Tag(name = "ETC账单管理")`
|
||||||
|
|
||||||
|
| 方法 | 路径 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| GET | /page | 分页查询 |
|
||||||
|
| GET | /detail/{id} | 详情 |
|
||||||
|
| POST | /generate/preview | 生成预览 |
|
||||||
|
| POST | /generate | 确认生成 |
|
||||||
|
| PUT | /review | 审核 |
|
||||||
|
| PUT | /submit-finance/{id} | 提交财务 |
|
||||||
|
| DELETE | /{id} | 删除 |
|
||||||
|
| POST | /export | 导出 |
|
||||||
|
| GET | /statistics | 统计 |
|
||||||
|
| GET | /record/page/{billId} | 关联通行记录分页 |
|
||||||
|
|
||||||
|
**参考文件:**
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/energy/controller/EnergyBillController.java`
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/station/controller/SyncConfigController.java`
|
||||||
|
|
||||||
|
### Step 2: EtczjApiClient(etczj.com HTTP 客户端)
|
||||||
|
|
||||||
|
**新建文件:**
|
||||||
|
- `modules/etc/service/sync/EtczjApiClient.java` — HTTP 客户端
|
||||||
|
- `modules/etc/service/sync/EtczjApiResponse.java` — 响应 DTO
|
||||||
|
- `modules/etc/service/sync/EtczjTollRecordDTO.java` — 通行记录 DTO
|
||||||
|
- `modules/etc/service/sync/EtczjSyncStrategy.java` — 同步策略实现
|
||||||
|
|
||||||
|
**EtczjApiClient 核心方法:**
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class EtczjApiClient {
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
// 登录(获取 randomVal session)
|
||||||
|
public String login(String baseUrl, String acctId, String password) {
|
||||||
|
// 1. GET /createCaptcha?secret={guid} → 验证码图片
|
||||||
|
// 2. OCR 识别验证码(Tesseract)
|
||||||
|
// 3. POST /member/toLogin?secret={guid}
|
||||||
|
// Body: { acctId, password: Base64(password), checkCode, secret }
|
||||||
|
// 4. 返回 randomVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询通行记录
|
||||||
|
public EtczjApiResponse<List<EtczjTollRecordDTO>> queryTollRecords(
|
||||||
|
String baseUrl, String randomVal,
|
||||||
|
String transTimeBegin, String transTimeEnd,
|
||||||
|
int currPage, int pageSize) {
|
||||||
|
// POST /vehicleManage/queryVehicleConsumptionDetailPage
|
||||||
|
// Header: Randomval: {randomVal}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询通行费汇总
|
||||||
|
public EtczjApiResponse<Map<String, Object>> sumToll(
|
||||||
|
String baseUrl, String randomVal,
|
||||||
|
String transTimeBegin, String transTimeEnd) {
|
||||||
|
// POST /vehicleManage/sumVehicleToll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参考文件:**
|
||||||
|
- `ln-asset-management/src/main/java/com/ln/asset/modules/station/feign/HecriApiClient.java`(RestTemplate + ObjectMapper 模式)
|
||||||
|
|
||||||
|
### Step 3: 前端 ETC 页面
|
||||||
|
|
||||||
|
**技术栈:** Vben Admin (Vue 3 + TypeScript + Ant Design Vue + VxeTable)
|
||||||
|
|
||||||
|
**模式参考:**
|
||||||
|
- 路由:`playground/src/router/routes/modules/system.ts`
|
||||||
|
- API:`playground/src/api/system/role.ts`
|
||||||
|
- 列表页:`playground/src/views/system/role/list.vue`
|
||||||
|
- 列定义:`playground/src/views/system/role/data.ts`
|
||||||
|
|
||||||
|
#### 3.1 API 层
|
||||||
|
**新建文件:** `playground/src/api/etc/index.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export namespace EtcApi {
|
||||||
|
export interface EtcSyncConfig { id: string; providerType: string; acctId: string; syncEnabled: 0|1; syncCron: string; lastSyncTime: string; lastSyncStatus: 0|1; }
|
||||||
|
export interface EtcTollRecord { id: string; recordCode: string; plateNumber: string; entryStationName: string; exitStationName: string; transTime: string; tollAmount: number; serviceFee: number; totalAmount: number; reviewStatus: number; deductionStatus: number; }
|
||||||
|
export interface EtcBill { id: string; billCode: string; customerName: string; billPeriodStart: string; billPeriodEnd: string; totalTollCount: number; totalTollAmount: number; receivableAmount: number; actualAmount: number; reviewStatus: number; paymentStatus: number; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETC 同步配置 API
|
||||||
|
async function getEtcSyncConfigPage(params) { return requestClient.get('/etc/sync-config/page', { params }); }
|
||||||
|
async function addEtcSyncConfig(data) { return requestClient.post('/etc/sync-config', data); }
|
||||||
|
async function updateEtcSyncConfig(data) { return requestClient.put('/etc/sync-config', data); }
|
||||||
|
async function deleteEtcSyncConfig(id) { return requestClient.delete(`/etc/sync-config/${id}`); }
|
||||||
|
async function toggleEtcSync(id) { return requestClient.put(`/etc/sync-config/toggle/${id}`); }
|
||||||
|
async function triggerEtcSync(id) { return requestClient.post(`/etc/sync-config/trigger/${id}`); }
|
||||||
|
async function getEtcSyncLog(configId, params) { return requestClient.get(`/etc/sync-config/log/${configId}`, { params }); }
|
||||||
|
|
||||||
|
// ETC 账单 API
|
||||||
|
async function getEtcBillPage(params) { return requestClient.get('/etc/bill/page', { params }); }
|
||||||
|
async function getEtcBillDetail(id) { return requestClient.get(`/etc/bill/detail/${id}`); }
|
||||||
|
async function reviewEtcBill(data) { return requestClient.put('/etc/bill/review', data); }
|
||||||
|
// ... 其他
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 ETC 同步配置页面
|
||||||
|
**新建文件:**
|
||||||
|
- `playground/src/views/etc/sync-config/list.vue` — 配置列表(useVbenVxeGrid)
|
||||||
|
- `playground/src/views/etc/sync-config/data.ts` — 列定义 + 表单 schema
|
||||||
|
- `playground/src/views/etc/sync-config/modules/form.vue` — 新增/编辑表单(useVbenDrawer)
|
||||||
|
|
||||||
|
**页面功能:**
|
||||||
|
- 表格列:供应商类型、登录账号、API地址、同步频率、上次同步时间/状态、启用状态
|
||||||
|
- 操作:编辑、删除、启用/停用切换、手动触发同步
|
||||||
|
- 新增/编辑抽屉:账号、密码、API地址、同步 cron 表达式
|
||||||
|
|
||||||
|
#### 3.3 ETC 账单查询页面
|
||||||
|
**新建文件:**
|
||||||
|
- `playground/src/views/etc/bill/list.vue` — 账单列表
|
||||||
|
- `playground/src/views/etc/bill/data.ts` — 列定义 + 搜索表单
|
||||||
|
- `playground/src/views/etc/bill/modules/detail.vue` — 账单详情抽屉
|
||||||
|
|
||||||
|
**页面功能:**
|
||||||
|
- 搜索条件:客户名称、账单周期、审核状态、支付状态
|
||||||
|
- 表格列:账单编码、客户名称、账期、通行笔数、通行费总额、服务费总额、应收、实收、审核状态、支付状态
|
||||||
|
- 操作:查看详情、审核、提交财务、删除
|
||||||
|
- 详情抽屉:账单信息 + 关联通行记录分页列表
|
||||||
|
|
||||||
|
#### 3.4 路由配置
|
||||||
|
**新建文件:** `playground/src/router/routes/modules/etc.ts`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const routes: RouteRecordRaw[] = [{
|
||||||
|
meta: { icon: 'mdi:highway', order: 30, title: 'ETC管理' },
|
||||||
|
name: 'Etc',
|
||||||
|
path: '/etc',
|
||||||
|
children: [
|
||||||
|
{ path: '/etc/sync-config', name: 'EtcSyncConfig', meta: { icon: 'mdi:sync', title: 'ETC同步配置' }, component: () => import('#/views/etc/sync-config/list.vue') },
|
||||||
|
{ path: '/etc/bill', name: 'EtcBill', meta: { icon: 'mdi:file-document-outline', title: 'ETC账单' }, component: () => import('#/views/etc/bill/list.vue') },
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 实施顺序
|
||||||
|
|
||||||
|
| 步骤 | 内容 | 文件数 |
|
||||||
|
|------|------|--------|
|
||||||
|
| 1 | 3 个 ETC Controller | 3 |
|
||||||
|
| 2 | EtczjApiClient + DTO + SyncStrategy | 4 |
|
||||||
|
| 3 | 前端 API 层 | 1 |
|
||||||
|
| 4 | 前端 ETC 同步配置页面 | 3 |
|
||||||
|
| 5 | 前端 ETC 账单查询页面 | 3 |
|
||||||
|
| 6 | 前端路由配置 | 1 |
|
||||||
|
| **合计** | | **15 个文件** |
|
||||||
|
|
||||||
|
### 验证方案
|
||||||
|
- 后端:`mvn compile` 编译通过
|
||||||
|
- 前端:`cd ln-one-os-web/playground && pnpm dev` 启动无报错
|
||||||
|
- ETC 同步配置页面:可访问 /etc/sync-config,表格加载正常
|
||||||
|
- ETC 账单页面:可访问 /etc/bill,表格加载正常
|
||||||
|
- Swagger:`/swagger-ui.html` 可看到 ETC 相关 3 组 API
|
||||||
242
.claude_plans/streamed-enchanting-cascade.md
Normal file
242
.claude_plans/streamed-enchanting-cascade.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
# 数据迁移计划:lingniu_prod → 新系统
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
老系统 `lingniu_prod`(单库 255 张表)需要全量迁移到新系统的 3 个库。新系统已做架构重构:拆分为 `ln_asset_management`(资产管理)、`ln_energy`(能源计费)、`ry-cloud`(系统核心/RuoYi 框架)。新库中现有数据为测试数据,迁移前清空。
|
||||||
|
|
||||||
|
## 源与目标
|
||||||
|
|
||||||
|
| | 源 | 目标 |
|
||||||
|
|---|---|---|
|
||||||
|
| **Host** | rm-uf65w5v2r77n674x2ko.mysql.rds.aliyuncs.com | 47.100.22.206:3306 |
|
||||||
|
| **用户** | oneos_read (只读) | root |
|
||||||
|
| **数据库** | lingniu_prod (255 表) | ln_asset_management (114), ln_energy (19), ry-cloud (29) |
|
||||||
|
|
||||||
|
## ID 策略
|
||||||
|
|
||||||
|
- `ln_asset_management` / `ln_energy`:`IdType.AUTO`(MySQL 自增),插入时不指定 ID,获取 `LAST_INSERT_ID`
|
||||||
|
- `ry-cloud`:雪花算法 ID(已有数据如 sys_user ID = 2038797628680523778)
|
||||||
|
|
||||||
|
所有迁移记录保存到 `ln_migration.id_mapping` 表,维护 `(old_table, old_id) → (new_table, new_id)` 映射。
|
||||||
|
|
||||||
|
## 通用字段转换规则
|
||||||
|
|
||||||
|
| 老字段 | 新字段 | 转换 |
|
||||||
|
|---|---|---|
|
||||||
|
| `is_deleted` (tinyint 0/1) | `del_flag` (char '0'/'1') | `str(val)` |
|
||||||
|
| `creater_id` (bigint) | `create_by` (bigint) | 查 user id_mapping |
|
||||||
|
| `updater_id` (bigint) | `update_by` (bigint) | 查 user id_mapping |
|
||||||
|
| int 枚举 (如 `truck_type`) | varchar 字典码 | 查 dict_mapping |
|
||||||
|
| `datetime` | `date` | 取日期部分 |
|
||||||
|
| `double` | `decimal` | `Decimal(str(val))` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迁移分阶段执行
|
||||||
|
|
||||||
|
### Phase 0: 准备
|
||||||
|
|
||||||
|
1. 在目标库创建 `ln_migration` 数据库和 `id_mapping` 表
|
||||||
|
2. **清空**目标库 3 个库中所有业务表数据(保留 ry-cloud 框架种子数据如 sys_config, sys_oss_config, sys_tenant)
|
||||||
|
3. 读取老库 `tab_dic` 构建枚举映射字典 → `dict_mapping.py`
|
||||||
|
|
||||||
|
### Phase 1: 基础数据(无 FK 依赖)
|
||||||
|
|
||||||
|
| 老表 | 新表 (库) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_dic` | `sys_dict_type` + `sys_dict_data` (ry-cloud) | 681 | 按 dic_type 分组为 type+data |
|
||||||
|
| `tab_region` | `common_district` (asset) | 3,359 | 直接映射 |
|
||||||
|
| `tab_vehicle_model` | `vehicle_model` (asset) | 39 | 字段对齐 |
|
||||||
|
| `tab_insurance_company` | `insurance_company` (asset) | 20 | 直接映射 |
|
||||||
|
| `tab_hydrogen_site` | `hydrogen_station` (asset) | 101 | int→varchar 枚举 |
|
||||||
|
| `tab_parking` | `parking_lot_info` (asset) | 65 | 直接映射 |
|
||||||
|
| `tab_maintain_site` | `repair_station` (asset) | 64 | 直接映射 |
|
||||||
|
| `tab_rescue_site` | `rescue_team` (asset) | ~14 | 直接映射 |
|
||||||
|
| `tab_annual_review_service_station` | `inspection_station` (asset) | 14 | 直接映射 |
|
||||||
|
| `tab_truck_check_item` | `vehicle_check_item` (asset) | 294 | 直接映射 |
|
||||||
|
| `tab_contract_templates` | `contract_template` (asset) | 13 | 直接映射 |
|
||||||
|
| `tab_charge_station` | `charging_station` (asset) | ~数条 | 直接映射 |
|
||||||
|
|
||||||
|
### Phase 2: 系统数据(用户/组织/角色)
|
||||||
|
|
||||||
|
| 老表 | 新表 (ry-cloud) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_org` | `sys_dept` + `sys_organization` | 19 | 重建 ancestors 字段 |
|
||||||
|
| `tab_user` | `sys_user` | 405 | 密码需重置为 BCrypt 临时密码 |
|
||||||
|
| `tab_role` | `sys_role` | 69 | 雪花 ID |
|
||||||
|
| `tab_user_role` | `sys_user_role` | 201 | 查 user+role id_mapping |
|
||||||
|
| `tab_menu` | `sys_menu` | 464 | **评估**:新系统菜单已重新定义,可能跳过 |
|
||||||
|
| `tab_role_menu` | `sys_role_menu` | 6,236 | 依赖菜单决策 |
|
||||||
|
|
||||||
|
> **密码处理**:老系统使用自定义 salt+hash,新系统使用 BCrypt。迁移时统一设置临时密码(如 `Abc@123456` 的 BCrypt 值),首次登录强制修改。
|
||||||
|
|
||||||
|
### Phase 3: 核心业务实体
|
||||||
|
|
||||||
|
| 老表 | 新表 (asset) | 行数 | 复杂度 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_truck` | `vehicle_info` | 1,203 | 高:47→29 列,大量字段重组 |
|
||||||
|
| `tab_truck_status_info` | `vehicle_status` | 1,385 | 中:int→varchar 枚举 |
|
||||||
|
| `tab_driver` | `driver_info` | 212 | 低 |
|
||||||
|
| `tab_customer` | `customer_info` | 310 | 中 |
|
||||||
|
| `tab_truck_licence` | `vehicle_license` | 1,712 | 中:truck_id→vehicle_id FK |
|
||||||
|
| `tab_truck_insure` | `insurance_procurement` | 740 | 中:15→32 列扩展 |
|
||||||
|
| `tab_equipment_info` | `aftermarket_device` | 1,562 | 中 |
|
||||||
|
| `tab_violation_management` | `traffic_violation` | 1,285 | 低 |
|
||||||
|
| `tab_accident` + `tab_accident_cost_bearing` | `accident_info` + `accident_expense` | 293+823 | 中 |
|
||||||
|
| `tab_failure` | `vehicle_fault_manage` | 5,140 | 低 |
|
||||||
|
| `tab_vehicle_annual_inspection` | `vehicle_annual_inspection` | 353 | 低 |
|
||||||
|
| `tab_vehicle_preparation` | `prepare_car` | 1,736 | 低 |
|
||||||
|
| `tab_customer_invoice` | `invoice_info` | 219 | 低 |
|
||||||
|
| `tab_truck_device_info` | `aftermarket_device` 或 `vehicle_realtime_location` | 1,227 | 评估 |
|
||||||
|
| `tab_training_materials` | `training_material` (asset) | 3 | 低 |
|
||||||
|
|
||||||
|
**vehicle_info 字段映射(核心):**
|
||||||
|
```
|
||||||
|
tab_truck.plate_number → vehicle_info.plate_number
|
||||||
|
tab_truck.vin → vehicle_info.vin
|
||||||
|
tab_truck.truck_num → vehicle_info.vehicle_code
|
||||||
|
tab_truck.model (int) → vehicle_info.vehicle_model_id (FK, 查 vehicle_model id_mapping)
|
||||||
|
tab_truck.color → vehicle_info.body_color
|
||||||
|
tab_truck.buy_time → vehicle_info.purchase_date (datetime→date)
|
||||||
|
tab_truck.stock_area (int) → vehicle_info.packing_lot_id (FK, 查 parking_lot_info id_mapping)
|
||||||
|
tab_truck.mandatory_retirement_period → vehicle_info.mandatory_scrap_date
|
||||||
|
tab_truck.remarks → vehicle_info.remark
|
||||||
|
tab_truck.address → vehicle_info.province (提取省份)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: 合同域
|
||||||
|
|
||||||
|
| 老表 | 新表 (asset) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_contract` | `vehicle_lease_contract_info` | 681 | 38→67 列,大量新字段 NULL |
|
||||||
|
| `tab_contract_rent_order` | `vehicle_lease_order` | 679 | 关联合同 |
|
||||||
|
| `tab_contract_rent_truck` | `vehicle_lease_order_detail` | 3,865 | 关联 order + vehicle |
|
||||||
|
| `tab_contract_rent_truck_service_cost` | `vehicle_lease_order_service_item` | 1,645 | 关联 detail |
|
||||||
|
| `tab_contract_thirty_party` | `contract_authorized_person` | 682 | 直接映射 |
|
||||||
|
| `tab_contract_authorizer_information` | `contract_authorized_person` | 666 | 合并 |
|
||||||
|
| `tab_contract_costs` | `template_*` 系列表 | 15,059 | 按费用类型拆分 |
|
||||||
|
| `tab_contract_hydrogen_fees` | `template_hydrogen_fee` | 13 | 直接映射 |
|
||||||
|
|
||||||
|
### Phase 5: 交付/退车/换车
|
||||||
|
|
||||||
|
| 老表 | 新表 (asset) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_truck_rent_task` | `delivery_task_subject` | 3,104 | 任务容器 |
|
||||||
|
| `tab_truck_rent_take` | `delivery_order` + `delivery_vehicle` | 1,956 | 一拆多 |
|
||||||
|
| `tab_truck_rent_return` | `return_vehicle_task` | 1,079 | 重构 |
|
||||||
|
| `tab_truck_rent_return_cost` | `return_fees` | 73 | 直接映射 |
|
||||||
|
| `tab_truck_rent_return_dep_cost` | `return_settlement_*` 子表 | 4,634 | 按类型拆分 |
|
||||||
|
| `tab_truck_rent_replace` | `vehicle_replacement` | 247 | 直接映射 |
|
||||||
|
| `tab_standby_vehicle_main/detail` | `vehicle_abnormal_move` | 377+2,171 | 评估映射 |
|
||||||
|
|
||||||
|
### Phase 6: 账单/财务
|
||||||
|
|
||||||
|
| 老表 | 新表 (asset) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_rent_contract_bill` | `bills` | 25,625 | 合同账单 |
|
||||||
|
| `tab_rent_contract_bill_truck` | `vehicle_bills` + `vehicle_bill_service_items` | **270,283** | **大表,批量处理** |
|
||||||
|
| `tab_rent_contract_bill_other_cost` | 合并到 `vehicle_bills` | ~少量 | |
|
||||||
|
| `tab_finance_receivable` | `receivable_subject` + `receivable_vehicle` | 10,308 | 拆分 |
|
||||||
|
| `tab_finance_deposit_receive` | `customer_payment_receipt` | 2,078 | 映射 |
|
||||||
|
| `tab_finance_deposit_deduction` | `customer_payment_item` | 52 | 映射 |
|
||||||
|
|
||||||
|
### Phase 7: 能源域 → ln_energy
|
||||||
|
|
||||||
|
| 老表 | 新表 (ln_energy) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_energy_account` | `energy_account` | 201 | 结构重组 |
|
||||||
|
| `tab_energy_project_account` | `energy_account_project` | 307 | 直接映射 |
|
||||||
|
| `tab_energy_account_recharge` | `energy_recharge_order` | 942 | 字段扩展 |
|
||||||
|
| `tab_import_hydrogen_order` | `hydrogen_station_order` | 58,642 | 加氢原始订单 |
|
||||||
|
| `tab_energy_hydrogen_bill` | `energy_hydrogen_detail` | **58,554** | **大表** |
|
||||||
|
| `tab_import_ele_charge_order` | `electricity_charge_record` | 4,405 | 充电原始订单 |
|
||||||
|
| `tab_energy_electricity_bill` | `energy_bill_detail` | 4,355 | fee_type=electricity |
|
||||||
|
|
||||||
|
### Phase 8: 附件
|
||||||
|
|
||||||
|
| 老表 | 新表 (asset) | 行数 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_data_attachment` | `tab_data_attachment` | **247,447** | **最大表之一**,data_id 需 FK 重映射 |
|
||||||
|
| `tab_image_attachment` | 合并到 `tab_data_attachment` 或保留 | 51,516 | 评估 |
|
||||||
|
|
||||||
|
### Phase 9: 其他
|
||||||
|
|
||||||
|
| 老表 | 新表 | 行数 | 处理 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `tab_maintain_maintenance_project` | 评估 | 243,474 | 新系统无直接对应,可能跳过 |
|
||||||
|
| `tab_truck_rent_form_data` | 评估 | 456,061 | 表单数据,新系统结构不同 |
|
||||||
|
| `tab_preparation_form_data` | 评估 | 171,497 | 同上 |
|
||||||
|
| `tab_standby_vehicle_form_data` | 评估 | 143,034 | 同上 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 暂不迁移的表(无新系统对应)
|
||||||
|
|
||||||
|
- 审批流:`tab_approve_instance*`, `tab_approve_template_node`
|
||||||
|
- 工作流:`tab_flow_task*`, `tab_flow_template*`
|
||||||
|
- G7 车联网:`tab_g7s_org`, `tab_g7s_truck_mileage` (289K), `tab_g7s_in_out_event` (159K)
|
||||||
|
- 培训考试:`tab_train_*`, `tab_safety_training`
|
||||||
|
- 系统日志:`tab_api_access_log` (4.6M), `tab_user_log`, `tab_user_message`, `tab_short_message`
|
||||||
|
- 调度记录:`tab_schedule_execute_result` (347K), `tab_data_sync_task_record`
|
||||||
|
- 应用版本:`tab_app_version`, `tab_version_user_check`, `tab_release_version_log`
|
||||||
|
- 临时表:所有 `tab_aa_temp_*`, `*_copy*`, `temp_*`
|
||||||
|
- 视图:所有 `view_*`, `v_*`
|
||||||
|
- 汇总表:`truck_info`, `truck_equipment_info`, `truck_hydrogen_info`, `truck_mileage`, `truck_parking`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现方案:Python 脚本
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
/Users/kkfluous/Projects/lingniu/oneos-corp/migration/
|
||||||
|
config.py # 数据库连接配置
|
||||||
|
id_mapping.py # ID 映射表 CRUD
|
||||||
|
transform.py # 通用字段转换函数
|
||||||
|
dict_mapping.py # 老 int 枚举 → 新 varchar 编码映射
|
||||||
|
migrator.py # 基础迁移器(批量读写、mapping、日志)
|
||||||
|
|
||||||
|
phase0_prepare.py # 建 mapping 表、清空目标库
|
||||||
|
phase1_reference.py # 字典、区域、车型等基础数据
|
||||||
|
phase2_system.py # 组织、用户、角色
|
||||||
|
phase3_core.py # 车辆、司机、客户、证照、保险
|
||||||
|
phase4_contract.py # 合同、租赁订单
|
||||||
|
phase5_delivery.py # 交付、退车、换车
|
||||||
|
phase6_billing.py # 账单、财务
|
||||||
|
phase7_energy.py # 能源账户、加氢、充电
|
||||||
|
phase8_attachment.py # 附件
|
||||||
|
phase9_misc.py # 其他
|
||||||
|
|
||||||
|
verify.py # 行数校验、FK 完整性、抽样比对
|
||||||
|
run_all.py # 按顺序执行所有 phase
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键技术点
|
||||||
|
|
||||||
|
1. **批量处理**:大表(27 万+ 行)使用 `SSCursor` 服务端游标 + `executemany` 批量写入(每批 1000 行)
|
||||||
|
2. **ID 映射**:`ln_migration.id_mapping(source_table, source_id, target_db, target_table, target_id)`,FK 字段通过查映射表解析
|
||||||
|
3. **枚举映射**:从 `tab_dic` 读取所有字典项,预构建 `{(dic_type, int_value): new_dict_code}` 映射
|
||||||
|
4. **密码处理**:所有用户密码统一设为 BCrypt(`Abc@123456`),首次登录强制修改
|
||||||
|
5. **事务**:每个 Phase 按表粒度 commit,失败可单表重试
|
||||||
|
|
||||||
|
## 验证策略
|
||||||
|
|
||||||
|
1. **行数校验**:每张表迁移后对比源/目标行数
|
||||||
|
2. **抽样比对**:每张核心表随机取 10 条,比对关键业务字段
|
||||||
|
3. **FK 完整性**:检查所有外键列无孤儿记录
|
||||||
|
4. **业务逻辑**:能源账户余额一致、合同-车辆关联完整
|
||||||
|
5. **登录测试**:使用临时密码验证 sys_user 登录
|
||||||
|
|
||||||
|
## 回滚方案
|
||||||
|
|
||||||
|
1. 迁移前对目标 3 库做 `mysqldump` 备份
|
||||||
|
2. 回滚 = truncate 所有目标表 + 从备份恢复 + drop `ln_migration`
|
||||||
|
3. 单 Phase 回滚 = 根据 `id_mapping` 删除该 Phase 写入的记录
|
||||||
|
|
||||||
|
## 关键文件
|
||||||
|
|
||||||
|
- `ln-asset-management/.../BaseEntity.java` — IdType.AUTO, del_flag char(1)
|
||||||
|
- `ln-asset-management/.../VehicleInfo.java` — 新车辆实体 29 字段
|
||||||
|
- `ln-cloud/ruoyi-common/.../BaseEntity.java` — RuoYi 基础实体
|
||||||
|
- 老后端代码 `/Users/kkfluous/Projects/lingniu/ln_asset/lingniu_asset_server/lingniu-manager/src/main/java/org/lingniu/manager/model/` — 所有老实体类
|
||||||
51
.claude_plans/tingly-mixing-wave.md
Normal file
51
.claude_plans/tingly-mixing-wave.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 两项修复:结转不依赖考核记录 + 补发查对应月盈亏
|
||||||
|
|
||||||
|
## Context
|
||||||
|
两个bug需要修复:
|
||||||
|
1. 1月有结转但2月无考核记录 → 结转丢失(当前只遍历G[2]有记录的车)
|
||||||
|
2. 3月补发1月的奖金 → 应查1月盈亏,当前查的是3月盈亏
|
||||||
|
|
||||||
|
## 修改1:结转不依赖考核记录
|
||||||
|
|
||||||
|
### 规则
|
||||||
|
- 1月多跑够结转,2月即使没考核记录也发放结转奖金(完整月)
|
||||||
|
- 无考核记录时:考核里程=满月目标(如3000km),实际里程=0,视为消耗了一个月目标
|
||||||
|
- 无客户关联(因无考核记录)→ 不查盈亏,正常发放
|
||||||
|
|
||||||
|
### 修改 `calc_engine.py`
|
||||||
|
在 `calc_feb()` 中,遍历完 `G[2].items()` 后,额外遍历 `G[1]` 中有结转但不在 `G[2]` 中的key:
|
||||||
|
|
||||||
|
```
|
||||||
|
for k, g1 in G1.items():
|
||||||
|
if k not in G2 and g1['可结转'] >= 1:
|
||||||
|
# 创建虚拟的2月group:考核里程=满月目标,实际=0
|
||||||
|
# 发放结转奖金(完整月)
|
||||||
|
# 结转剩余月数 -= 1,传递给3月
|
||||||
|
```
|
||||||
|
|
||||||
|
`calc_mar()` 同理:遍历 `G[2]` 中有结转但不在 `G[3]` 中的key。
|
||||||
|
|
||||||
|
### 修改 `excel_writer.py`
|
||||||
|
- `build_payment_records`:无客户关联的结转,loss_status设为'否'(正常发放)
|
||||||
|
|
||||||
|
## 修改2:补发查对应月盈亏
|
||||||
|
|
||||||
|
### 规则
|
||||||
|
| 发放类型 | 查哪月盈亏 |
|
||||||
|
|---------|----------|
|
||||||
|
| 当月达标/累计补发/结转 | 当月 |
|
||||||
|
| 补发1月 | 1月 |
|
||||||
|
| 补发2月 | 2月 |
|
||||||
|
|
||||||
|
### 修改 `main.py`
|
||||||
|
- 将所有月份的 `loss_data` 都传入 `build_payment_records`(不只当月的)
|
||||||
|
|
||||||
|
### 修改 `excel_writer.py`
|
||||||
|
- `build_payment_records` 接收 `all_loss_data={1:..., 2:..., 3:...}`
|
||||||
|
- 根据发放类型确定查哪个月的盈亏表
|
||||||
|
- 奖金发放记录表新增列"盈亏查询月",标明查的是哪个月的盈亏
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
- 找一辆1月有结转但2月无记录的车,确认2月正常发放结转
|
||||||
|
- 找一辆3月补发1月的车,确认查的是1月盈亏而非3月
|
||||||
|
- 金额总数合理
|
||||||
90
.claude_plans/tranquil-sniffing-babbage.md
Normal file
90
.claude_plans/tranquil-sniffing-babbage.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# 用户权限校验实现计划
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
当前系统零认证,所有 API 完全开放。需要接入公司现有的 jumpToken 认证体系,并根据用户角色实现数据权限过滤。用户从资产管理平台跳转进入本系统,携带 jumpToken。
|
||||||
|
|
||||||
|
## 认证流程
|
||||||
|
|
||||||
|
```
|
||||||
|
用户带 jumpToken 访问 → 前端提取 jumpToken
|
||||||
|
→ 后端代理调用外部API换取 sessionToken
|
||||||
|
→ 后端调用外部API获取用户信息(roles, depCode)
|
||||||
|
→ 后端签发 JWT 返回前端
|
||||||
|
→ 前端所有请求带 JWT → 后端中间件验证
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三级权限模型
|
||||||
|
|
||||||
|
| 级别 | 角色条件 | 数据范围 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| **full** | roleName 含 "所有权限" / "数智中心" / "BI-Leader" | 全部数据 |
|
||||||
|
| **department** | roleName 含 "BI-Leader-Dep" | 自己部门的全部数据 |
|
||||||
|
| **personal** | 无以上角色 | 仅自己负责的车辆(bd=userId) |
|
||||||
|
|
||||||
|
部门匹配:用户 depCode → 查 tab_department 得 dep_name → 过滤车辆数据中的 department 字段
|
||||||
|
个人匹配:添加 managerId 字段(c.bd),按 userId 精确匹配(不用 userName 避免重名)
|
||||||
|
|
||||||
|
## 新增/修改文件
|
||||||
|
|
||||||
|
### 后端新增
|
||||||
|
- `src/server/auth/types.ts` — AuthUser、PermissionLevel、JwtPayload 类型
|
||||||
|
- `src/server/auth/login.ts` — `POST /api/auth/login`(接收外部token,调外部API获取用户信息,签发JWT)+ `GET /api/auth/exchange`(代理 jumpToken 换取 sessionToken,避免前端 CORS 问题)
|
||||||
|
- `src/server/auth/middleware.ts` — Hono 中间件,验证 JWT,跳过 /api/health 和 /api/auth/*
|
||||||
|
- `src/server/auth/permissions.ts` — `filterByPermission<T>(items, user)` 通用过滤函数
|
||||||
|
|
||||||
|
### 后端修改
|
||||||
|
- `src/server/index.ts` — 挂载 auth 路由和中间件
|
||||||
|
- `src/server/routes/mileage/vehicle-info.ts` — SQL 添加 `c.bd AS manager_id`
|
||||||
|
- `src/server/routes/mileage/types.ts` — CachedVehicle 添加 `managerId: number | null`
|
||||||
|
- `src/server/routes/mileage/cache.ts` — 传递 managerId
|
||||||
|
- `src/server/routes/mileage/monitoring.ts` — 请求时 filterByPermission + 重算筛选选项
|
||||||
|
- `src/server/routes/mileage/targets.ts` — 按权限过滤
|
||||||
|
- `src/server/routes/mileage/trend.ts` — 按权限限定车牌范围
|
||||||
|
- `src/server/routes/vehicles.ts` — 所有端点用 `getVehiclesForUser(c)` 替代 `getVehicles()`
|
||||||
|
- `src/server/types.ts` — Vehicle 类型添加 managerId
|
||||||
|
|
||||||
|
### 前端新增
|
||||||
|
- `src/auth/AuthProvider.tsx` — 认证上下文,管理 jumpToken 交换和 JWT 存储
|
||||||
|
- `src/auth/useAuth.ts` — 认证状态 hook
|
||||||
|
- `src/auth/api-client.ts` — 全局 fetchJson,自动附加 Authorization header
|
||||||
|
- `src/auth/UnauthorizedPage.tsx` — 未授权页面(图标 + 提示文字)
|
||||||
|
|
||||||
|
### 前端修改
|
||||||
|
- `src/App.tsx` — 包裹 AuthProvider,条件渲染 Shell / UnauthorizedPage
|
||||||
|
- `src/modules/mileage/api.ts` — fetchJson 改用 auth/api-client
|
||||||
|
- `src/modules/assets/api.ts` — fetchJson 改用 auth/api-client
|
||||||
|
|
||||||
|
### 环境配置
|
||||||
|
- `.env` 添加 `JWT_SECRET`、`EXTERNAL_API_BASE`
|
||||||
|
- `package.json` 添加 `jsonwebtoken`、`@types/jsonwebtoken`
|
||||||
|
|
||||||
|
## 关键设计决策
|
||||||
|
|
||||||
|
1. **缓存全局,请求时过滤** — 监控缓存保持全量数据,每次请求根据用户权限过滤,筛选选项也从过滤后数据重算
|
||||||
|
2. **jumpToken 交换走后端代理** — 避免前端 CORS 问题,外部 API 调用全部在服务端
|
||||||
|
3. **用 managerId(数字ID)匹配而非 userName** — 避免重名问题
|
||||||
|
4. **JWT 存 sessionStorage** — 刷新页面不丢失,关闭标签页自动清除
|
||||||
|
5. **filterByPermission 泛型函数** — 同时适配 Vehicle 和 CachedVehicle 类型
|
||||||
|
|
||||||
|
## 实施顺序
|
||||||
|
|
||||||
|
1. 安装依赖 (`jsonwebtoken`)
|
||||||
|
2. 后端 auth 模块(types → login → middleware → permissions)
|
||||||
|
3. 数据模型添加 managerId(vehicle-info → types → cache)
|
||||||
|
4. 挂载中间件到 server/index.ts
|
||||||
|
5. 集成权限过滤到各路由(vehicles.ts, monitoring, targets, trend)
|
||||||
|
6. 前端 auth 模块(AuthProvider, useAuth, api-client, UnauthorizedPage)
|
||||||
|
7. 前端 API 模块切换到 auth fetch
|
||||||
|
8. App.tsx 添加认证网关
|
||||||
|
9. 端到端测试三个权限级别
|
||||||
|
|
||||||
|
## 验证方式
|
||||||
|
|
||||||
|
1. 无 jumpToken 访问 → 显示"未授权访问"页面
|
||||||
|
2. 带有效 jumpToken 访问 → 正常进入,检查 JWT 签发
|
||||||
|
3. full 角色用户 → 看到全部 1004 辆车数据
|
||||||
|
4. department 角色用户 → 仅看到自己部门的车辆
|
||||||
|
5. personal 用户 → 仅看到自己负责的车辆
|
||||||
|
6. JWT 过期后请求 → 返回 401,前端显示未授权
|
||||||
|
7. 全屏监控筛选选项 → 仅显示用户权限范围内的部门/客户
|
||||||
124
.claude_plans/zippy-coalescing-starlight.md
Normal file
124
.claude_plans/zippy-coalescing-starlight.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# ETC & 电费审核 → 能源账单对齐氢费模式
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
氢费的完整流程已跑通:API 导入 → 设置 review_status=PENDING → 用户审核通过 → 调用 `RawRecordReviewServiceImpl.processReviewedRecord()` → 创建 `energy_bill_detail` → 触发扣款。
|
||||||
|
|
||||||
|
ETC 和电费目前的 review 方法只更新了审核状态,**没有创建 energy_bill_detail,没有走统一审核服务**。需要对齐。
|
||||||
|
|
||||||
|
## 改动范围
|
||||||
|
|
||||||
|
### 1. ETC: EtcTollRecordServiceImpl.review()
|
||||||
|
|
||||||
|
**文件:** `modules/etc/service/impl/EtcTollRecordServiceImpl.java`
|
||||||
|
|
||||||
|
当前 review() 只设 reviewStatus + remark。改为:
|
||||||
|
- 设 reviewStatus
|
||||||
|
- 如果 approved → 构建 `ReviewedRecordContext`,调用 `reviewService.processReviewedRecord(context)`
|
||||||
|
- 把返回的 `EnergyBillDetail.id` 写回 `EtcTollRecord.billDetailId`
|
||||||
|
- batchReview() 同理(逐条调用 review)
|
||||||
|
|
||||||
|
ReviewedRecordContext 构建:
|
||||||
|
```java
|
||||||
|
ReviewedRecordContext.builder()
|
||||||
|
.feeType(FeeType.ETC)
|
||||||
|
.rawRecordId(record.getId())
|
||||||
|
.rawTableType("etc_toll_record")
|
||||||
|
.sourceType(record.getSourceType())
|
||||||
|
.plateNumber(record.getPlateNumber())
|
||||||
|
.eventTime(record.getTransTime())
|
||||||
|
.rawUnitPrice(null) // ETC 无单价
|
||||||
|
.rawAmount(record.getTollFee())
|
||||||
|
.quantity(null) // ETC 无数量
|
||||||
|
.stationId(null)
|
||||||
|
.stationName(null)
|
||||||
|
.tenantId(...)
|
||||||
|
.build()
|
||||||
|
```
|
||||||
|
|
||||||
|
需要注入 `IRawRecordReviewService`。
|
||||||
|
|
||||||
|
### 2. 电费: ElectricityRecordServiceImpl.review()
|
||||||
|
|
||||||
|
**文件:** `modules/electricity/service/impl/ElectricityRecordServiceImpl.java`
|
||||||
|
|
||||||
|
当前 review() 设 reviewStatus + 直接调 executeDeduction(错误位置)。改为:
|
||||||
|
- 设 reviewStatus
|
||||||
|
- 如果 approved → 构建 `ReviewedRecordContext`,调用 `reviewService.processReviewedRecord(context)`
|
||||||
|
- 把返回的 `EnergyBillDetail.id` 写回 `ElectricityChargeRecord.billDetailId`
|
||||||
|
- **删除** `executeDeduction()`(扣款由 reviewService 统一处理)
|
||||||
|
- **删除** `triggerDeduction()` 方法
|
||||||
|
- **删除** `IDeductionService` 依赖
|
||||||
|
|
||||||
|
ReviewedRecordContext 构建:
|
||||||
|
```java
|
||||||
|
ReviewedRecordContext.builder()
|
||||||
|
.feeType(FeeType.ELECTRICITY)
|
||||||
|
.rawRecordId(record.getId())
|
||||||
|
.rawTableType("electricity_charge_record")
|
||||||
|
.sourceType(record.getSourceType())
|
||||||
|
.plateNumber(record.getPlateNumber())
|
||||||
|
.eventTime(record.getChargingEndTime())
|
||||||
|
.rawUnitPrice(null)
|
||||||
|
.rawAmount(record.getTotalAmount())
|
||||||
|
.quantity(record.getKwh())
|
||||||
|
.stationId(null)
|
||||||
|
.stationName(null)
|
||||||
|
.tenantId(...)
|
||||||
|
.build()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 电费 PO 精简 (同 ETC 模式)
|
||||||
|
|
||||||
|
**文件:** `modules/electricity/entity/record/po/ElectricityChargeRecord.java`
|
||||||
|
|
||||||
|
electricity_charge_record 也是原始账单表,应像 ETC 一样删除关联字段:
|
||||||
|
- 删除: contract_id, contract_code, customer_id, customer_name, cost_type, payment_mode, contract_matched, deduction_status, bill_id, is_oneos_vehicle
|
||||||
|
- 保留: bill_detail_id(关联统一账单)
|
||||||
|
- 对应删除 DB 列
|
||||||
|
|
||||||
|
### 4. 电费 Listener 简化
|
||||||
|
|
||||||
|
**文件:** `modules/electricity/listener/ElectricityDetailImportListener.java`
|
||||||
|
|
||||||
|
当前 listener 做了合同匹配并存到原始记录。因为原始记录不再存这些字段,简化为:
|
||||||
|
- 仅设 review_status = PENDING
|
||||||
|
- 移除合同匹配逻辑(交给 reviewService 统一处理)
|
||||||
|
|
||||||
|
### 5. ETC Listener 已OK
|
||||||
|
|
||||||
|
当前 EtcDetailImportListener 已经只设 review_status = PENDING,无需改动。
|
||||||
|
|
||||||
|
### 6. 电费 Service 接口清理
|
||||||
|
|
||||||
|
**文件:** `modules/electricity/service/IElectricityRecordService.java`
|
||||||
|
- 删除 `manualMatch()`, `triggerDeduction()` 方法签名
|
||||||
|
|
||||||
|
**文件:** `modules/electricity/controller/ElectricityRecordController.java`
|
||||||
|
- 删除 `/manual-match` 端点
|
||||||
|
|
||||||
|
### 7. 电费前端 VO/页面同步
|
||||||
|
|
||||||
|
同 ETC 的精简模式:VO 删除关联字段,前端 data.ts 列定义同步更新。
|
||||||
|
|
||||||
|
## 关键文件
|
||||||
|
|
||||||
|
| 文件 | 操作 |
|
||||||
|
|------|------|
|
||||||
|
| `etc/service/impl/EtcTollRecordServiceImpl.java` | 改 — review() 接入 reviewService |
|
||||||
|
| `electricity/service/impl/ElectricityRecordServiceImpl.java` | 改 — review() 接入 reviewService, 删除 deduction 逻辑 |
|
||||||
|
| `electricity/entity/record/po/ElectricityChargeRecord.java` | 改 — 删除关联字段 |
|
||||||
|
| `electricity/listener/ElectricityDetailImportListener.java` | 改 — 简化,去掉合同匹配 |
|
||||||
|
| `electricity/service/IElectricityRecordService.java` | 改 — 删除 manualMatch/triggerDeduction |
|
||||||
|
| `electricity/controller/ElectricityRecordController.java` | 改 — 删除 /manual-match |
|
||||||
|
| `electricity/entity/record/vo/ElectricityChargeRecordVO.java` | 改 — 删除关联字段 |
|
||||||
|
| `electricity/entity/record/query/ElectricityRecordQuery.java` | 改 — 删除关联查询条件 |
|
||||||
|
| 前端 `views/electricity/record/data.ts` | 改 — 对齐 VO |
|
||||||
|
| 前端 `api/electricity/index.ts` | 改 — 对齐类型 |
|
||||||
|
| 数据库 `electricity_charge_record` | ALTER — 删除列 |
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
1. ETC: 导入 → 列表展示 → 审核通过 → energy_bill_detail 有新记录 → bill_detail_id 回填
|
||||||
|
2. 电费: 导入 → 列表展示 → 审核通过 → energy_bill_detail 有新记录 → 扣款由 reviewService 处理
|
||||||
|
3. 三种费用的 energy_bill_detail 表中都有数据,feeType 分别为 1/2/3
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
.~*
|
||||||
|
*.csv
|
||||||
|
*.jpeg
|
||||||
|
*.jpg
|
||||||
|
*.png
|
||||||
|
.localized
|
||||||
@@ -98,6 +98,26 @@ def calc_feb(G1, G2):
|
|||||||
g2['可结转'] = fc + jr
|
g2['可结转'] = fc + jr
|
||||||
g2['2月已发'] = carry>0 or bonus2>0 or cbp2>0
|
g2['2月已发'] = carry>0 or bonus2>0 or cbp2>0
|
||||||
g2['1月已补发'] = bp1>0
|
g2['1月已补发'] = bp1>0
|
||||||
|
|
||||||
|
# 补充:1月有结转但2月无考核记录的车 → 创建虚拟2月group并发放结转
|
||||||
|
for k, g1 in G1.items():
|
||||||
|
if k not in G2 and g1['可结转'] >= 1:
|
||||||
|
bf = g1['奖励额']
|
||||||
|
carry = bf
|
||||||
|
feb_data['结转'].append({'车牌':k[0],'销售':g1['销售'],'部门':g1['部门'],'额':carry})
|
||||||
|
# 创建虚拟2月group(考核里程=满月目标,实际=0)
|
||||||
|
G2[k] = {
|
||||||
|
'recs':[], '应考核':g1['目标km'], '实际':0, '奖金':0, '天数':DAYS[2],
|
||||||
|
'有达标':False, '目标km':g1['目标km'], '奖励额':g1['奖励额'],
|
||||||
|
'部门':g1['部门'], '销售':g1['销售'], '车牌':k[0],
|
||||||
|
'cum_t':g1['应考核']+g1['目标km'], 'cum_a':g1['实际'],
|
||||||
|
'cum_q': g1['实际'] >= g1['应考核']+g1['目标km'],
|
||||||
|
'结转':carry, '补发1月':0, '补发1月对应':'',
|
||||||
|
'当月奖金':0, '累计补发2月':0, '结转占位':True,
|
||||||
|
'可结转': max(0, g1['可结转'] - 1),
|
||||||
|
'2月已发':True, '1月已补发':False,
|
||||||
|
'虚拟':True, # 标记为无考核记录
|
||||||
|
}
|
||||||
return feb_data
|
return feb_data
|
||||||
|
|
||||||
def calc_mar(G1, G2, G3, feb_data):
|
def calc_mar(G1, G2, G3, feb_data):
|
||||||
@@ -134,6 +154,25 @@ def calc_mar(G1, G2, G3, feb_data):
|
|||||||
g3['结转']=carry; g3['补发1月']=bj; g3['补发1月对应']=g1['销售'] if g1 and bj>0 else ''
|
g3['结转']=carry; g3['补发1月']=bj; g3['补发1月对应']=g1['销售'] if g1 and bj>0 else ''
|
||||||
g3['补发2月']=bf2; g3['补发2月对应']=g2['销售'] if g2 and bf2>0 else ''
|
g3['补发2月']=bf2; g3['补发2月对应']=g2['销售'] if g2 and bf2>0 else ''
|
||||||
g3['当月奖金']=bonus3; g3['累计补发3月']=cbp3; g3['结转占位']=carry>0
|
g3['当月奖金']=bonus3; g3['累计补发3月']=cbp3; g3['结转占位']=carry>0
|
||||||
|
|
||||||
|
# 补充:2月有结转但3月无考核记录的车
|
||||||
|
for k, g2 in G2.items():
|
||||||
|
if k not in G3 and g2.get('可结转', 0) >= 1:
|
||||||
|
bf = g2['奖励额']
|
||||||
|
carry = bf
|
||||||
|
mar_data['结转'].append({'车牌':k[0],'销售':g2['销售'],'部门':g2['部门'],'额':carry})
|
||||||
|
G3[k] = {
|
||||||
|
'recs':[], '应考核':g2['目标km'], '实际':0, '奖金':0, '天数':DAYS[3],
|
||||||
|
'有达标':False, '目标km':g2['目标km'], '奖励额':g2['奖励额'],
|
||||||
|
'部门':g2['部门'], '销售':g2['销售'], '车牌':k[0],
|
||||||
|
'cum_t':(G1.get(k,{}).get('应考核',0))+g2['应考核']+g2['目标km'],
|
||||||
|
'cum_a':(G1.get(k,{}).get('实际',0))+g2['实际'],
|
||||||
|
'cum_q':False,
|
||||||
|
'结转':carry, '补发1月':0, '补发1月对应':'',
|
||||||
|
'补发2月':0, '补发2月对应':'',
|
||||||
|
'当月奖金':0, '累计补发3月':0, '结转占位':True,
|
||||||
|
'虚拟':True,
|
||||||
|
}
|
||||||
return mar_data
|
return mar_data
|
||||||
|
|
||||||
def collect_vehicle_payments(G, feb_data, mar_data):
|
def collect_vehicle_payments(G, feb_data, mar_data):
|
||||||
|
|||||||
@@ -199,21 +199,38 @@ def write_summary_jan(wb, records, loss_data=None, plate_client=None):
|
|||||||
write_total(ws,rn,1,{'达标':jan_dl})
|
write_total(ws,rn,1,{'达标':jan_dl})
|
||||||
AW(ws)
|
AW(ws)
|
||||||
|
|
||||||
def build_payment_records(month, month_data, loss_data, plate_client):
|
def build_payment_records(month, month_data, all_loss_data, plate_client):
|
||||||
"""构建奖金发放记录列表,每条考核应发一行,叠加亏损筛选"""
|
"""构建奖金发放记录列表,每条考核应发一行,叠加亏损筛选。
|
||||||
|
all_loss_data: {1: loss_dict, 2: loss_dict, 3: loss_dict_or_None}
|
||||||
|
补发X月 → 查X月盈亏;其他 → 查当月盈亏;无客户 → 正常发放
|
||||||
|
"""
|
||||||
|
# 发放类型→查哪个月盈亏
|
||||||
|
def get_loss_month(cat, settle_month):
|
||||||
|
if '补发1月' in cat: return 1
|
||||||
|
if '补发2月' in cat: return 2
|
||||||
|
if '补发3月' in cat: return 3
|
||||||
|
return settle_month
|
||||||
|
|
||||||
records = []
|
records = []
|
||||||
for cat, dl in month_data.items():
|
for cat, dl in month_data.items():
|
||||||
|
loss_month = get_loss_month(cat, month)
|
||||||
|
loss_data = all_loss_data.get(loss_month)
|
||||||
|
|
||||||
for d in dl:
|
for d in dl:
|
||||||
client = (plate_client or {}).get(d['车牌'], '')
|
client = (plate_client or {}).get(d['车牌'], '')
|
||||||
if loss_data:
|
|
||||||
loss_status = loss_data.get(client, '未匹配') if client else '未匹配'
|
# 无客户关联(如虚拟结转记录)→ 正常发放
|
||||||
|
if not client:
|
||||||
|
loss_status = '否'
|
||||||
|
elif loss_data:
|
||||||
|
loss_status = loss_data.get(client, '未匹配')
|
||||||
else:
|
else:
|
||||||
loss_status = '否' # 无亏损表视为不亏损
|
loss_status = '未匹配' # 无亏损表视为未匹配,不发放
|
||||||
|
|
||||||
考核应发 = d['额']
|
考核应发 = d['额']
|
||||||
if loss_status == '是':
|
if loss_status == '是':
|
||||||
拦截 = 考核应发; 实发 = 0
|
拦截 = 考核应发; 实发 = 0
|
||||||
elif loss_status == '未匹配' and loss_data:
|
elif loss_status == '未匹配':
|
||||||
拦截 = 考核应发; 实发 = 0
|
拦截 = 考核应发; 实发 = 0
|
||||||
else:
|
else:
|
||||||
拦截 = 0; 实发 = 考核应发
|
拦截 = 0; 实发 = 考核应发
|
||||||
@@ -222,8 +239,9 @@ def build_payment_records(month, month_data, loss_data, plate_client):
|
|||||||
'车牌号': d['车牌'],
|
'车牌号': d['车牌'],
|
||||||
'业务员': d['销售'],
|
'业务员': d['销售'],
|
||||||
'部门': d['部门'],
|
'部门': d['部门'],
|
||||||
'客户名称': client,
|
'客户名称': client or '(无客户关联)',
|
||||||
'发放类型': cat,
|
'发放类型': cat,
|
||||||
|
'盈亏查询月': f'{loss_month}月',
|
||||||
'考核应发': 考核应发,
|
'考核应发': 考核应发,
|
||||||
'客户盈亏': loss_status,
|
'客户盈亏': loss_status,
|
||||||
'亏损拦截': 拦截,
|
'亏损拦截': 拦截,
|
||||||
@@ -235,7 +253,7 @@ def write_payment_record_sheet(wb, month, payment_records):
|
|||||||
"""写入奖金发放记录sheet"""
|
"""写入奖金发放记录sheet"""
|
||||||
ws = wb.create_sheet(f'{month}月奖金发放记录')
|
ws = wb.create_sheet(f'{month}月奖金发放记录')
|
||||||
|
|
||||||
headers = ['车牌号','业务员','部门','客户名称','发放类型',
|
headers = ['车牌号','业务员','部门','客户名称','发放类型','盈亏查询月',
|
||||||
'考核应发','客户盈亏','亏损拦截','实发金额']
|
'考核应发','客户盈亏','亏损拦截','实发金额']
|
||||||
WH(ws, headers)
|
WH(ws, headers)
|
||||||
|
|
||||||
@@ -246,25 +264,24 @@ def write_payment_record_sheet(wb, month, payment_records):
|
|||||||
rn = 2
|
rn = 2
|
||||||
for r in sorted(payment_records, key=lambda x: (x['业务员'], x['车牌号'])):
|
for r in sorted(payment_records, key=lambda x: (x['业务员'], x['车牌号'])):
|
||||||
WR(ws, rn, [r['车牌号'], r['业务员'], r['部门'], r['客户名称'], r['发放类型'],
|
WR(ws, rn, [r['车牌号'], r['业务员'], r['部门'], r['客户名称'], r['发放类型'],
|
||||||
|
r.get('盈亏查询月',''),
|
||||||
R(r['考核应发']), r['客户盈亏'], R(r['亏损拦截']), R(r['实发金额'])])
|
R(r['考核应发']), r['客户盈亏'], R(r['亏损拦截']), R(r['实发金额'])])
|
||||||
# 颜色
|
|
||||||
if r['客户盈亏'] == '是':
|
if r['客户盈亏'] == '是':
|
||||||
for ci in [7, 8, 9]: ws.cell(row=rn, column=ci).fill = red_fill
|
for ci in [8, 9, 10]: ws.cell(row=rn, column=ci).fill = red_fill
|
||||||
elif r['客户盈亏'] == '未匹配':
|
elif r['客户盈亏'] == '未匹配':
|
||||||
for ci in [7, 8, 9]: ws.cell(row=rn, column=ci).fill = yellow_fill
|
for ci in [8, 9, 10]: ws.cell(row=rn, column=ci).fill = yellow_fill
|
||||||
elif r['实发金额'] > 0:
|
elif r['实发金额'] > 0:
|
||||||
ws.cell(row=rn, column=9).fill = green_fill
|
ws.cell(row=rn, column=10).fill = green_fill
|
||||||
rn += 1
|
rn += 1
|
||||||
|
|
||||||
# 合计行
|
|
||||||
total_应发 = sum(r['考核应发'] for r in payment_records)
|
total_应发 = sum(r['考核应发'] for r in payment_records)
|
||||||
total_拦截 = sum(r['亏损拦截'] for r in payment_records)
|
total_拦截 = sum(r['亏损拦截'] for r in payment_records)
|
||||||
total_实发 = sum(r['实发金额'] for r in payment_records)
|
total_实发 = sum(r['实发金额'] for r in payment_records)
|
||||||
rn += 1
|
rn += 1
|
||||||
WR(ws, rn, ['', '', '', '', '合计', R(total_应发), '', R(total_拦截), R(total_实发)])
|
WR(ws, rn, ['', '', '', '', '合计', '', R(total_应发), '', R(total_拦截), R(total_实发)])
|
||||||
for ci in range(5, 10): ws.cell(row=rn, column=ci).font = Font(bold=True)
|
for ci in range(5, 11): ws.cell(row=rn, column=ci).font = Font(bold=True)
|
||||||
|
|
||||||
ws.auto_filter.ref = f"A1:I1"
|
ws.auto_filter.ref = f"A1:J1"
|
||||||
AW(ws)
|
AW(ws)
|
||||||
return payment_records
|
return payment_records
|
||||||
|
|
||||||
@@ -428,7 +445,10 @@ def write_salesperson_sheet(wb, person, dept, settle_month, D, G, month_data, ve
|
|||||||
if g_cur: break
|
if g_cur: break
|
||||||
if not g_cur: continue
|
if not g_cur: continue
|
||||||
|
|
||||||
|
if g_cur['recs']:
|
||||||
first = g_cur['recs'][0]
|
first = g_cur['recs'][0]
|
||||||
|
else:
|
||||||
|
first = {'合同编号':'','客户名称':'(无考核记录)','考核目标':g_cur.get('考核目标','')}
|
||||||
mkm = g_cur['目标km']
|
mkm = g_cur['目标km']
|
||||||
|
|
||||||
# 第1行:车辆信息 + 各月里程/目标
|
# 第1行:车辆信息 + 各月里程/目标
|
||||||
|
|||||||
4
main.py
4
main.py
@@ -85,8 +85,8 @@ for settle_month in [1, 2, 3]:
|
|||||||
# Sheet 5: 车辆考核追踪
|
# Sheet 5: 车辆考核追踪
|
||||||
write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data[settle_month], plate_client)
|
write_vehicle_tracking_sheet(wb, settle_month, G, master_vehicles, vehicle_payments, vehicle_info, loss_data[settle_month], plate_client)
|
||||||
|
|
||||||
# Sheet 6: 奖金发放记录(叠加亏损筛选的逐条明细)
|
# Sheet 6: 奖金发放记录(叠加亏损筛选,补发查对应月盈亏)
|
||||||
payment_records = build_payment_records(settle_month, month_data, loss_data[settle_month], plate_client)
|
payment_records = build_payment_records(settle_month, month_data, loss_data, plate_client)
|
||||||
write_payment_record_sheet(wb, settle_month, payment_records)
|
write_payment_record_sheet(wb, settle_month, payment_records)
|
||||||
|
|
||||||
# Sheet 7: 月汇总(从发放记录生成)
|
# Sheet 7: 月汇总(从发放记录生成)
|
||||||
|
|||||||
BIN
租赁任务考核_2026年1月.xlsx
Normal file
BIN
租赁任务考核_2026年1月.xlsx
Normal file
Binary file not shown.
BIN
租赁任务考核_2026年2月.xlsx
Normal file
BIN
租赁任务考核_2026年2月.xlsx
Normal file
Binary file not shown.
BIN
租赁任务考核_2026年3月.xlsx
Normal file
BIN
租赁任务考核_2026年3月.xlsx
Normal file
Binary file not shown.
BIN
车辆里程考核与奖金发放规则(V.1.2).docx
Normal file
BIN
车辆里程考核与奖金发放规则(V.1.2).docx
Normal file
Binary file not shown.
BIN
里程任务考核_1月核算.xlsx
Normal file
BIN
里程任务考核_1月核算.xlsx
Normal file
Binary file not shown.
BIN
里程任务考核_2月核算.xlsx
Normal file
BIN
里程任务考核_2月核算.xlsx
Normal file
Binary file not shown.
BIN
里程任务考核_3月核算.xlsx
Normal file
BIN
里程任务考核_3月核算.xlsx
Normal file
Binary file not shown.
BIN
里程任务考核_Q1汇总.xlsx
Normal file
BIN
里程任务考核_Q1汇总.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/数据来源/1月.xlsx
Normal file
BIN
里程考核绩效核算/数据来源/1月.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/数据来源/2月.xlsx
Normal file
BIN
里程考核绩效核算/数据来源/2月.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/数据来源/3月.xlsx
Normal file
BIN
里程考核绩效核算/数据来源/3月.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/数据来源/租赁任务考核_2026年1月.xlsx
Normal file
BIN
里程考核绩效核算/数据来源/租赁任务考核_2026年1月.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/数据来源/租赁任务考核_2026年2月.xlsx
Normal file
BIN
里程考核绩效核算/数据来源/租赁任务考核_2026年2月.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/数据来源/租赁任务考核_2026年3月.xlsx
Normal file
BIN
里程考核绩效核算/数据来源/租赁任务考核_2026年3月.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/核算结果/里程任务考核_1月核算.xlsx
Normal file
BIN
里程考核绩效核算/核算结果/里程任务考核_1月核算.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/核算结果/里程任务考核_2月核算.xlsx
Normal file
BIN
里程考核绩效核算/核算结果/里程任务考核_2月核算.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/核算结果/里程任务考核_3月核算.xlsx
Normal file
BIN
里程考核绩效核算/核算结果/里程任务考核_3月核算.xlsx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/考核依据/车辆里程考核与奖金发放规则(V.1.2).docx
Normal file
BIN
里程考核绩效核算/考核依据/车辆里程考核与奖金发放规则(V.1.2).docx
Normal file
Binary file not shown.
BIN
里程考核绩效核算/里程考核核算.zip
Normal file
BIN
里程考核绩效核算/里程考核核算.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user