2 天以前 ac28c343201a816fad993e58f551ed64a4abe08a
feat(approve): 添加车辆管理模块及审批功能

- 在ApprovalInstanceMapper.xml中添加form_config字段映射
- 添加VehicleBorrowRecordMapper和VehicleMapper依赖注入
- 实现车辆借出和延期审批的驳回处理逻辑
- 添加车辆借出审批完成后的状态处理方法handleVehicleBorrowApprovalFinished
- 实现车辆借用状态同步功能syncVehicleBorrowStatus
- 扩展RecordTypeEnum枚举添加车辆相关类型定义
- 重构TypeEnums将车辆审批拆分为借出和延期两种类型
- 添加车辆管理实体类Vehicle和车辆借出记录实体类VehicleBorrowRecord
- 实现车辆借出记录的数据传输对象VehicleBorrowRecordDto
- 创建车辆借出记录的数据访问层VehicleBorrowRecordMapper
- 实现车辆借出记录的业务逻辑服务VehicleBorrowRecordService
- 提供车辆借出记录的分页查询、增删改查等基础功能
- 实现车辆借出申请、归还、延期等核心业务流程
- 添加车辆借出和归还的附件存储功能
已添加19个文件
已修改4个文件
1707 ■■■■■ 文件已修改
doc/20260528_create_vehicle_borrow_record.sql 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260528_车辆管理借出归还延期前端联调文档.md 257 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/vehicle-api.md 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/vehicle.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/VehicleBorrowRecordDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/VehicleDto.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/VehicleBorrowRecordVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/VehicleVo.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/VehicleController.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/VehicleBorrowRecordMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/VehicleMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/Vehicle.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/VehicleBorrowRecord.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/VehicleBorrowRecordService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/VehicleService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/VehicleBorrowRecordServiceImpl.java 454 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/VehicleServiceImpl.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/TypeEnums.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalInstanceMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/VehicleBorrowRecordMapper.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/VehicleMapper.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260528_create_vehicle_borrow_record.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
CREATE TABLE `vehicle_borrow_record` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `borrow_no` VARCHAR(32) NOT NULL COMMENT '借出单号',
  `vehicle_id` BIGINT NOT NULL COMMENT '车辆ID',
  `vehicle_plate_number` VARCHAR(20) NOT NULL COMMENT '车牌号快照',
  `applicant_id` BIGINT NOT NULL COMMENT '申请人ID',
  `applicant_name` VARCHAR(64) NOT NULL COMMENT '申请人姓名',
  `applicant_dept_id` BIGINT DEFAULT NULL COMMENT '申请部门ID',
  `applicant_dept_name` VARCHAR(100) DEFAULT NULL COMMENT '申请部门名称',
  `borrow_reason` VARCHAR(500) DEFAULT NULL COMMENT '借出原因',
  `borrow_start_time` DATETIME NOT NULL COMMENT '借出开始时间',
  `planned_return_time` DATETIME NOT NULL COMMENT '计划归还时间',
  `actual_return_time` DATETIME DEFAULT NULL COMMENT '实际归还时间',
  `borrow_status` VARCHAR(20) NOT NULL DEFAULT 'DRAFT' COMMENT '借出状态 DRAFT IN_APPROVAL BORROWING RETURNED REJECTED',
  `approval_instance_id` BIGINT DEFAULT NULL COMMENT '借出审批实例ID',
  `approved_time` DATETIME DEFAULT NULL COMMENT '借出审批通过时间',
  `returned_time` DATETIME DEFAULT NULL COMMENT '归还时间',
  `extend_approval_instance_id` BIGINT DEFAULT NULL COMMENT '延期审批实例ID',
  `extend_status` VARCHAR(20) NOT NULL DEFAULT 'NONE' COMMENT '延期状态 NONE PENDING APPROVED REJECTED',
  `extend_target_return_time` DATETIME DEFAULT NULL COMMENT '延期目标归还时间',
  `extend_reason` VARCHAR(500) DEFAULT NULL COMMENT '延期原因',
  `extend_approved_time` DATETIME DEFAULT NULL COMMENT '延期审批通过时间',
  `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除 0未删除 1已删除',
  `create_user` BIGINT DEFAULT NULL COMMENT '创建人',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  `update_user` BIGINT DEFAULT NULL COMMENT '更新人',
  `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
  `dept_id` BIGINT DEFAULT NULL COMMENT '部门ID',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_vehicle_borrow_record_borrow_no` (`borrow_no`),
  KEY `idx_vehicle_borrow_record_vehicle_id` (`vehicle_id`),
  KEY `idx_vehicle_borrow_record_approval_instance_id` (`approval_instance_id`),
  KEY `idx_vehicle_borrow_record_extend_approval_instance_id` (`extend_approval_instance_id`),
  KEY `idx_vehicle_borrow_record_borrow_status` (`borrow_status`),
  KEY `idx_vehicle_borrow_record_extend_status` (`extend_status`),
  KEY `idx_vehicle_borrow_record_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆借出记录表';
doc/20260528_³µÁ¾¹ÜÀí½è³ö¹é»¹ÑÓÆÚǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,257 @@
# è½¦è¾†ç®¡ç†å€Ÿå‡ºå½’还延期前端联调文档
## 1. è¯´æ˜Ž
- è½¦è¾†ä¸»æ¡£ä»ç„¶èµ° `VehicleController` åŽŸæœ‰æŽ¥å£ã€‚
- å€Ÿå‡ºè®°å½•新增到同一个 `VehicleController` ä¸‹çš„ `/borrow/**` è·¯ç”±ã€‚
- å€Ÿå‡ºç”³è¯·ã€å»¶æœŸç”³è¯·éƒ½èµ°ååŒå®¡æ‰¹ï¼Œå®¡æ‰¹å®žä¾‹ç»Ÿä¸€ä½¿ç”¨ `/approvalInstance/approve`。
- åŽç«¯å·²æŒ‰ä¸šåŠ¡ç±»åž‹åŒºåˆ†ï¼š
  - `19`:车辆借出审批
  - `20`:车辆延期审批
## 2. è½¦è¾†ä¸»æ¡£æŽ¥å£
### 2.1 åˆ†é¡µæŸ¥è¯¢
- `GET /approve/vehicle/listPage`
查询参数:
| å‚数名 | ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- |
| current | Long | å½“前页 |
| size | Long | æ¯é¡µæ¡æ•° |
| plateNumber | String | è½¦ç‰Œå·æ¨¡ç³ŠæŸ¥è¯¢ |
| status | String | ä½¿ç”¨çŠ¶æ€ï¼Œ`IDLE` / `IN_USE` / `MAINTENANCE` / `SCRAPPED` |
### 2.2 æ–°å¢ž
- `POST /approve/vehicle/save`
```json
{
  "plateNumber": "粤A12345",
  "mileage": 12580.5,
  "status": "IDLE"
}
```
### 2.3 è¯¦æƒ…
- `GET /approve/vehicle/detail?id=1`
### 2.4 ä¿®æ”¹
- `POST /approve/vehicle/update`
### 2.5 åˆ é™¤
- `DELETE /approve/vehicle/delete`
```json
[1, 2, 3]
```
## 3. å€Ÿå‡ºè®°å½•接口
### 3.1 çŠ¶æ€å­—å…¸
#### å€Ÿå‡ºçŠ¶æ€ `borrowStatus`
| å€¼ | è¯´æ˜Ž |
| --- | --- |
| `DRAFT` | è‰ç¨¿ |
| `IN_APPROVAL` | å®¡æ‰¹ä¸­ |
| `BORROWING` | å€Ÿå‡ºä¸­ |
| `RETURNED` | å·²å½’还 |
| `REJECTED` | å·²é©³å›ž |
#### å»¶æœŸçŠ¶æ€ `extendStatus`
| å€¼ | è¯´æ˜Ž |
| --- | --- |
| `NONE` | æœªç”³è¯· |
| `PENDING` | å®¡æ‰¹ä¸­ |
| `APPROVED` | å·²é€šè¿‡ |
| `REJECTED` | å·²é©³å›ž |
### 3.2 åˆ†é¡µæŸ¥è¯¢
- `GET /approve/vehicle/borrow/listPage`
查询参数:
| å‚数名 | ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- |
| current | Long | å½“前页 |
| size | Long | æ¯é¡µæ¡æ•° |
| borrowNo | String | å€Ÿå‡ºå•号 |
| vehiclePlateNumber | String | è½¦ç‰Œå· |
| applicantName | String | ç”³è¯·äºº |
| borrowStatus | String | å€Ÿå‡ºçŠ¶æ€ |
| extendStatus | String | å»¶æœŸçŠ¶æ€ |
### 3.3 æ–°å¢žå€Ÿå‡ºç”³è¯·
- `POST /approve/vehicle/borrow/save`
请求体:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| vehicleId | Long | æ˜¯ | è½¦è¾†ID |
| borrowReason | String | å¦ | å€Ÿå‡ºåŽŸå›  |
| borrowStartTime | String | æ˜¯ | å€Ÿå‡ºå¼€å§‹æ—¶é—´ï¼Œ`yyyy-MM-dd HH:mm:ss` |
| plannedReturnTime | String | æ˜¯ | è®¡åˆ’归还时间,必须晚于开始时间 |
| borrowStatus | String | å¦ | `DRAFT` æˆ– `IN_APPROVAL`,默认 `DRAFT` |
| approvalTemplateId | Long | å¦ | ååŒå®¡æ‰¹æ¨¡æ¿ID,不传则按业务类型自动取最新启用模板 |
| borrowStorageBlobDTOs | Array | å¦ | å€Ÿå‡ºç”³è¯·é™„件列表 |
示例:
```json
{
  "vehicleId": 1,
  "borrowReason": "客户拜访",
  "borrowStartTime": "2026-05-28 09:00:00",
  "plannedReturnTime": "2026-05-28 18:00:00",
  "borrowStatus": "IN_APPROVAL"
}
```
返回规则:
- `DRAFT`:只保存草稿,不创建审批实例。
- `IN_APPROVAL`:保存后自动创建审批实例,返回后可直接进入审批列表。
- å®¡æ‰¹é€šè¿‡åŽï¼Œå€Ÿå‡ºçŠ¶æ€å˜ä¸º `BORROWING`,车辆状态变为 `IN_USE`。
- å®¡æ‰¹é©³å›žåŽï¼Œå€Ÿå‡ºçŠ¶æ€å˜ä¸º `REJECTED`。
### 3.4 è¯¦æƒ…
- `GET /approve/vehicle/borrow/detail?id=1`
### 3.5 ä¿®æ”¹å€Ÿå‡ºç”³è¯·
- `POST /approve/vehicle/borrow/update`
说明:
- ä»…允许 `DRAFT` å’Œ `REJECTED` çŠ¶æ€ä¿®æ”¹ã€‚
- å¦‚果修改后提交为 `IN_APPROVAL`,后端会重新发起协同审批。
### 3.6 åˆ é™¤å€Ÿå‡ºè®°å½•
- `DELETE /approve/vehicle/borrow/delete`
```json
[1, 2, 3]
```
说明:
- å®¡æ‰¹ä¸­å’Œå€Ÿå‡ºä¸­çš„记录不允许删除。
### 3.7 å½’还车辆
- `POST /approve/vehicle/borrow/return`
请求体:
```json
{
  "id": 1,
  "actualReturnTime": "2026-05-28 17:30:00"
}
```
说明:
- ä»…借出中的记录可归还。
- å¦‚果当前有延期审批中的申请,禁止直接归还。
- å½’还后借出状态变为 `RETURNED`,车辆状态回到 `IDLE`。
- å½’还接口支持附件字段 `returnStorageBlobDTOs`。
归还请求体示例:
```json
{
  "id": 1,
  "actualReturnTime": "2026-05-28 17:30:00",
  "returnStorageBlobDTOs": [
    {
      "id": 1001,
      "application": "file"
    }
  ]
}
```
### 3.8 å‘起延期申请
- `POST /approve/vehicle/borrow/delay`
请求体:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| id | Long | æ˜¯ | å€Ÿå‡ºè®°å½•ID |
| extendTargetReturnTime | String | æ˜¯ | å»¶æœŸåŽçš„归还时间,必须晚于当前计划归还时间 |
| extendReason | String | å¦ | å»¶æœŸåŽŸå›  |
| approvalTemplateId | Long | å¦ | å»¶æœŸå®¡æ‰¹æ¨¡æ¿ID,不传则按业务类型自动取最新启用模板 |
示例:
```json
{
  "id": 1,
  "extendTargetReturnTime": "2026-05-29 18:00:00",
  "extendReason": "客户临时加会,需要继续用车"
}
```
说明:
- ä»…借出中的记录可以发起延期。
- ä¸èƒ½é‡å¤å‘起未处理完的延期申请。
- å»¶æœŸå®¡æ‰¹é€šè¿‡åŽï¼Œä¼šæ›´æ–°å€Ÿå‡ºè®°å½•的计划归还时间,并保留延期审批记录。
### 3.9 é™„件字段约定
- å€Ÿå‡ºç”³è¯·é™„件字段名:`borrowStorageBlobDTOs`
- å½’还附件字段名:`returnStorageBlobDTOs`
- å­—段内容保持和现有文件上传组件回填数据一致,至少包含文件 `id`
## 4. ååŒå®¡æ‰¹è”è°ƒ
### 4.1 åˆ›å»ºå®¡æ‰¹å®žä¾‹åŽ
- åŽç«¯ä¼šè‡ªåЍ写入 `approval_instance`、`approval_instance_node`、`approval_task`、`approval_record`。
- å‰ç«¯å¯ä»¥è·³è½¬åˆ°å®¡æ‰¹åˆ—表或审批详情页,使用返回的 `approvalInstanceId` è¿›è¡Œè”动。
### 4.2 å®¡æ‰¹å¤„理
- å®¡æ‰¹æŽ¥å£ä»ç„¶ä½¿ç”¨ï¼š
`POST /approvalInstance/approve`
请求体示例:
```json
{
  "id": 10001,
  "approveAction": "APPROVED",
  "approveComment": "同意借出"
}
```
可选值:
- `APPROVED`
- `REJECTED`
### 4.3 é¡µé¢å»ºè®®è”动
- å€Ÿå‡ºç”³è¯·æäº¤åŽï¼Œå³ä¾§å®¡æ‰¹çŠ¶æ€æ˜¾ç¤º `审批中`。
- å®¡æ‰¹é€šè¿‡åŽï¼Œåˆ—表状态切换为 `借出中`。
- å»¶æœŸç”³è¯·æäº¤åŽï¼Œå±•示延期状态为 `审批中`。
- å»¶æœŸé€šè¿‡åŽï¼Œæ›´æ–°è®¡åˆ’归还时间。
sql/vehicle-api.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,221 @@
# è½¦è¾†ç®¡ç†æŽ¥å£æ–‡æ¡£
## åŸºç¡€ä¿¡æ¯
- **Base URL**: `http://localhost:端口号`
- **Content-Type**: `application/json`
- **认证方式**: éœ€è¦ç™»å½•(请求头携带 token)
## æ•°æ®å­—å…¸ - è½¦è¾†ä½¿ç”¨çŠ¶æ€
| æžšä¸¾å€¼ | å«ä¹‰ |
|--------|------|
| `IDLE` | ç©ºé—² |
| `IN_USE` | ä½¿ç”¨ä¸­ |
| `MAINTENANCE` | ç»´ä¿®ä¸­ |
| `SCRAPPED` | å·²æŠ¥åºŸ |
---
## 1. åˆ†é¡µæŸ¥è¯¢è½¦è¾†åˆ—表
### è¯·æ±‚信息
- **URL**: `/approve/vehicle/listPage`
- **Method**: `GET`
### è¯·æ±‚参数(Query String)
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| current | Long | æ˜¯ | å½“前页码,默认1 |
| size | Long | æ˜¯ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
| plateNumber | String | å¦ | è½¦ç‰Œå·ï¼ˆæ¨¡ç³ŠæŸ¥è¯¢ï¼‰ |
| status | String | å¦ | ä½¿ç”¨çŠ¶æ€ï¼ˆç²¾ç¡®åŒ¹é…ï¼‰ |
### è¯·æ±‚示例
```
GET /approve/vehicle/listPage?current=1&size=10&plateNumber=京&status=IN_USE
```
### å“åº”示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1,
        "plateNumber": "京A12345",
        "mileage": 52000.00,
        "status": "IN_USE",
        "deleted": 0,
        "createUser": 1,
        "createTime": "2026-05-28 10:00:00",
        "updateUser": 1,
        "updateTime": "2026-05-28 10:00:00",
        "deptId": 100
      }
    ],
    "total": 1,
    "size": 10,
    "current": 1,
    "pages": 1
  }
}
```
---
## 2. æ–°å¢žè½¦è¾†
### è¯·æ±‚信息
- **URL**: `/approve/vehicle/save`
- **Method**: `POST`
- **Content-Type**: `application/json`
### è¯·æ±‚体参数
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| plateNumber | String | æ˜¯ | è½¦ç‰Œå· |
| mileage | Number | å¦ | è½¦è¾†å…¬é‡Œæ•°ï¼Œé»˜è®¤0 |
| status | String | æ˜¯ | ä½¿ç”¨çŠ¶æ€ï¼šIDLE / IN_USE / MAINTENANCE / SCRAPPED |
### è¯·æ±‚示例
```json
{
  "plateNumber": "京A12345",
  "mileage": 0,
  "status": "IDLE"
}
```
### å“åº”示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": true
}
```
---
## 3. è½¦è¾†è¯¦æƒ…
### è¯·æ±‚信息
- **URL**: `/approve/vehicle/detail`
- **Method**: `GET`
### è¯·æ±‚参数(Query String)
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| id | Long | æ˜¯ | è½¦è¾†ID |
### è¯·æ±‚示例
```
GET /approve/vehicle/detail?id=1
```
### å“åº”示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 1,
    "plateNumber": "京A12345",
    "mileage": 52000.00,
    "status": "IN_USE",
    "deleted": 0,
    "createUser": 1,
    "createTime": "2026-05-28 10:00:00",
    "updateUser": 1,
    "updateTime": "2026-05-28 10:00:00",
    "deptId": 100
  }
}
```
---
## 4. ä¿®æ”¹è½¦è¾†
### è¯·æ±‚信息
- **URL**: `/approve/vehicle/update`
- **Method**: `POST`
- **Content-Type**: `application/json`
### è¯·æ±‚体参数
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| id | Long | æ˜¯ | è½¦è¾†ID |
| plateNumber | String | å¦ | è½¦ç‰Œå· |
| mileage | Number | å¦ | è½¦è¾†å…¬é‡Œæ•° |
| status | String | å¦ | ä½¿ç”¨çŠ¶æ€ |
### è¯·æ±‚示例
```json
{
  "id": 1,
  "plateNumber": "京A12345",
  "mileage": 52000.00,
  "status": "IN_USE"
}
```
### å“åº”示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": true
}
```
---
## 5. åˆ é™¤è½¦è¾†
### è¯·æ±‚信息
- **URL**: `/approve/vehicle/delete`
- **Method**: `DELETE`
- **Content-Type**: `application/json`
### è¯·æ±‚体参数
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| ids | Array<Long> | æ˜¯ | è½¦è¾†ID数组 |
### è¯·æ±‚示例
```json
[1, 2, 3]
```
### å“åº”示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": true
}
```
sql/vehicle.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
-- è½¦è¾†ç®¡ç†è¡¨
CREATE TABLE `vehicle` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '车辆ID',
  `plate_number` VARCHAR(20) NOT NULL COMMENT '车牌号',
  `mileage` DECIMAL(10,2) DEFAULT 0 COMMENT '车辆公里数',
  `status` VARCHAR(20) NOT NULL DEFAULT 'IDLE' COMMENT '使用状态: IDLE空闲 IN_USE使用中 MAINTENANCE维修中 SCRAPPED已报废',
  `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除: 0否 1是',
  `create_user` BIGINT COMMENT '创建人',
  `create_time` DATETIME COMMENT '创建时间',
  `update_user` BIGINT COMMENT '更新人',
  `update_time` DATETIME COMMENT '更新时间',
  `dept_id` BIGINT COMMENT '部门ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆管理表';
src/main/java/com/ruoyi/approve/bean/dto/VehicleBorrowRecordDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.VehicleBorrowRecord;
import com.ruoyi.basic.dto.StorageBlobDTO;
import lombok.Data;
import java.util.List;
@Data
public class VehicleBorrowRecordDto extends VehicleBorrowRecord {
    private Long approvalTemplateId;
    private String createTimeStart;
    private String createTimeEnd;
    private List<StorageBlobDTO> borrowStorageBlobDTOs;
    private List<StorageBlobDTO> returnStorageBlobDTOs;
    private String formConfig;
}
src/main/java/com/ruoyi/approve/bean/dto/VehicleDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.Vehicle;
import lombok.Data;
@Data
public class VehicleDto extends Vehicle {
    private String createTimeStart;
    private String createTimeEnd;
}
src/main/java/com/ruoyi/approve/bean/vo/VehicleBorrowRecordVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.VehicleBorrowRecord;
import com.ruoyi.basic.dto.StorageBlobVO;
import lombok.Data;
import java.util.List;
@Data
public class VehicleBorrowRecordVo extends VehicleBorrowRecord {
    private List<StorageBlobVO> borrowStorageBlobVOList;
    private List<StorageBlobVO> returnStorageBlobVOList;
}
src/main/java/com/ruoyi/approve/bean/vo/VehicleVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.Vehicle;
import lombok.Data;
@Data
public class VehicleVo extends Vehicle {
}
src/main/java/com/ruoyi/approve/controller/VehicleController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
package com.ruoyi.approve.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.VehicleBorrowRecordDto;
import com.ruoyi.approve.bean.dto.VehicleDto;
import com.ruoyi.approve.bean.vo.VehicleBorrowRecordVo;
import com.ruoyi.approve.bean.vo.VehicleVo;
import com.ruoyi.approve.service.VehicleService;
import com.ruoyi.approve.service.VehicleBorrowRecordService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * è½¦è¾†ç®¡ç† å‰ç«¯æŽ§åˆ¶å™¨
 */
@RestController
@RequestMapping("/approve/vehicle")
@Tag(name = "车辆管理", description = "车辆管理")
@AllArgsConstructor
public class VehicleController extends BaseController {
    private final VehicleService vehicleService;
    private final VehicleBorrowRecordService vehicleBorrowRecordService;
    @GetMapping("/listPage")
    @Operation(summary = "分页查询车辆列表")
    @Log(title = "车辆列表分页查询", businessType = BusinessType.OTHER)
    public R listPage(Page<VehicleVo> page, VehicleDto vehicle) {
        return R.ok(vehicleService.listPage(page, vehicle));
    }
    @PostMapping("/save")
    @Operation(summary = "新增车辆")
    @Log(title = "车辆新增", businessType = BusinessType.INSERT)
    public R save(@RequestBody VehicleDto vehicle) {
        return vehicleService.add(vehicle) ? R.ok() : R.fail();
    }
    @GetMapping("/detail")
    @Operation(summary = "车辆详情")
    public R detail(Long id) {
        return R.ok(vehicleService.detail(id));
    }
    @PutMapping("/update")
    @Operation(summary = "修改车辆")
    @Log(title = "车辆修改", businessType = BusinessType.UPDATE)
    public R update(@RequestBody VehicleDto vehicle) {
        return vehicleService.update(vehicle) ? R.ok() : R.fail();
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除车辆")
    @Log(title = "车辆删除", businessType = BusinessType.DELETE)
    public R delete(@RequestBody List<Long> ids) {
        return vehicleService.delete(ids) ? R.ok() : R.fail();
    }
    @GetMapping("/borrow/listPage")
    @Operation(summary = "分页查询借出记录列表")
    @Log(title = "借出记录分页查询", businessType = BusinessType.OTHER)
    public R borrowListPage(Page<VehicleBorrowRecordVo> page, VehicleBorrowRecordDto record) {
        return R.ok(vehicleBorrowRecordService.listPage(page, record));
    }
    @PostMapping("/borrow/save")
    @Operation(summary = "新增借出记录")
    @Log(title = "借出记录新增", businessType = BusinessType.INSERT)
    public R borrowSave(@RequestBody VehicleBorrowRecordDto record) {
        return vehicleBorrowRecordService.add(record) ? R.ok() : R.fail();
    }
    @GetMapping("/borrow/detail")
    @Operation(summary = "借出记录详情")
    public R borrowDetail(Long id) {
        return R.ok(vehicleBorrowRecordService.detail(id));
    }
    @PutMapping("/borrow/update")
    @Operation(summary = "修改借出记录")
    @Log(title = "借出记录修改", businessType = BusinessType.UPDATE)
    public R borrowUpdate(@RequestBody VehicleBorrowRecordDto record) {
        return vehicleBorrowRecordService.update(record) ? R.ok() : R.fail();
    }
    @DeleteMapping("/borrow/delete")
    @Operation(summary = "删除借出记录")
    @Log(title = "借出记录删除", businessType = BusinessType.DELETE)
    public R borrowDelete(@RequestBody List<Long> ids) {
        return vehicleBorrowRecordService.delete(ids) ? R.ok() : R.fail();
    }
    @PostMapping("/borrow/return")
    @Operation(summary = "归还车辆")
    @Log(title = "车辆归还", businessType = BusinessType.UPDATE)
    public R borrowReturn(@RequestBody VehicleBorrowRecordDto record) {
        return vehicleBorrowRecordService.returnVehicle(record) ? R.ok() : R.fail();
    }
    @PostMapping("/borrow/delay")
    @Operation(summary = "发起延期申请")
    @Log(title = "延期申请", businessType = BusinessType.INSERT)
    public R borrowDelay(@RequestBody VehicleBorrowRecordDto record) {
        return vehicleBorrowRecordService.delay(record) ? R.ok() : R.fail();
    }
}
src/main/java/com/ruoyi/approve/mapper/VehicleBorrowRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.VehicleBorrowRecordDto;
import com.ruoyi.approve.bean.vo.VehicleBorrowRecordVo;
import com.ruoyi.approve.pojo.VehicleBorrowRecord;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * è½¦è¾†å€Ÿå‡ºè®°å½• Mapper
 */
@Mapper
public interface VehicleBorrowRecordMapper extends BaseMapper<VehicleBorrowRecord> {
    IPage<VehicleBorrowRecordVo> listPage(Page<VehicleBorrowRecordVo> page, @Param("record") VehicleBorrowRecordDto record);
}
src/main/java/com/ruoyi/approve/mapper/VehicleMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.VehicleDto;
import com.ruoyi.approve.bean.vo.VehicleVo;
import com.ruoyi.approve.pojo.Vehicle;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * è½¦è¾†ç®¡ç† Mapper
 */
@Mapper
public interface VehicleMapper extends BaseMapper<Vehicle> {
    IPage<VehicleVo> listPage(Page<VehicleVo> page, @Param("vehicle") VehicleDto vehicle);
}
src/main/java/com/ruoyi/approve/pojo/Vehicle.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * è½¦è¾†ç®¡ç†è¡¨
 */
@Getter
@Setter
@ToString
@TableName("vehicle")
@ApiModel(value = "Vehicle对象", description = "车辆管理表")
public class Vehicle implements Serializable {
    private static final long serialVersionUID = 1L;
    @Schema(description = "车辆ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "车牌号")
    private String plateNumber;
    @Schema(description = "车辆公里数")
    private BigDecimal mileage;
    @Schema(description = "使用状态: IDLE空闲 IN_USE使用中")
    private String status;
    @Schema(description = "逻辑删除: 0未删除 1已删除")
    private Integer deleted;
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/VehicleBorrowRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,130 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * è½¦è¾†å€Ÿå‡ºè®°å½•表
 */
@Data
@TableName("vehicle_borrow_record")
@Schema(name = "VehicleBorrowRecord", description = "车辆借出记录表")
public class VehicleBorrowRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    @Schema(description = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "借出单号")
    private String borrowNo;
    @Schema(description = "车辆ID")
    private Long vehicleId;
    @Schema(description = "车牌号")
    private String vehiclePlateNumber;
    @Schema(description = "申请人ID")
    private Long applicantId;
    @Schema(description = "申请人姓名")
    private String applicantName;
    @Schema(description = "申请部门ID")
    private Long applicantDeptId;
    @Schema(description = "申请部门名称")
    private String applicantDeptName;
    @Schema(description = "借出原因")
    private String borrowReason;
    @Schema(description = "借出开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime borrowStartTime;
    @Schema(description = "计划归还时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime plannedReturnTime;
    @Schema(description = "实际归还时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime actualReturnTime;
    @Schema(description = "借出状态 DRAFT-草稿 IN_APPROVAL-审批中 BORROWING-借出中 RETURNED-已归还 REJECTED-已驳回")
    private String borrowStatus;
    @Schema(description = "借出审批实例ID")
    private Long approvalInstanceId;
    @Schema(description = "借出审批通过时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime approvedTime;
    @Schema(description = "归还时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime returnedTime;
    @Schema(description = "延期审批实例ID")
    private Long extendApprovalInstanceId;
    @Schema(description = "延期状态 NONE-未申请 PENDING-审批中 APPROVED-已通过 REJECTED-已驳回")
    private String extendStatus;
    @Schema(description = "延期目标归还时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime extendTargetReturnTime;
    @Schema(description = "延期原因")
    private String extendReason;
    @Schema(description = "延期审批通过时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime extendApprovedTime;
    @Schema(description = "逻辑删除: 0未删除 1已删除")
    private Integer deleted;
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @Schema(description = "部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/service/VehicleBorrowRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.approve.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.approve.bean.dto.VehicleBorrowRecordDto;
import com.ruoyi.approve.bean.vo.VehicleBorrowRecordVo;
import com.ruoyi.approve.pojo.VehicleBorrowRecord;
import com.ruoyi.basic.dto.StorageBlobDTO;
import java.util.List;
/**
 * è½¦è¾†å€Ÿå‡ºè®°å½• Service
 */
public interface VehicleBorrowRecordService extends IService<VehicleBorrowRecord> {
    IPage<VehicleBorrowRecordVo> listPage(Page<VehicleBorrowRecordVo> page, VehicleBorrowRecordDto record);
    Boolean add(VehicleBorrowRecordDto record);
    VehicleBorrowRecord detail(Long id);
    Boolean update(VehicleBorrowRecordDto record);
    Boolean delete(List<Long> ids);
    Boolean returnVehicle(VehicleBorrowRecordDto record);
    Boolean delay(VehicleBorrowRecordDto record);
    void saveBorrowAttachments(Long recordId, List<StorageBlobDTO> storageBlobDTOs);
    void saveReturnAttachments(Long recordId, List<StorageBlobDTO> storageBlobDTOs);
}
src/main/java/com/ruoyi/approve/service/VehicleService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.approve.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.approve.bean.dto.VehicleDto;
import com.ruoyi.approve.bean.vo.VehicleVo;
import com.ruoyi.approve.pojo.Vehicle;
import java.util.List;
/**
 * è½¦è¾†ç®¡ç† Service
 */
public interface VehicleService extends IService<Vehicle> {
    IPage<VehicleVo> listPage(Page<VehicleVo> page, VehicleDto vehicle);
    Boolean add(VehicleDto vehicle);
    Vehicle detail(Long id);
    Boolean update(VehicleDto vehicle);
    Boolean delete(List<Long> ids);
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
@@ -12,6 +12,8 @@
import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
import com.ruoyi.approve.mapper.FinReimbursementMapper;
import com.ruoyi.approve.mapper.VehicleBorrowRecordMapper;
import com.ruoyi.approve.mapper.VehicleMapper;
import com.ruoyi.approve.pojo.*;
import com.ruoyi.approve.service.*;
import com.ruoyi.approve.utils.ApproveProcessConfigNodeUtils;
@@ -86,6 +88,8 @@
    private final SalesQuotationMapper salesQuotationMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final QualityInspectHelper qualityInspectHelper;
    private final VehicleBorrowRecordMapper vehicleBorrowRecordMapper;
    private final VehicleMapper vehicleMapper;
    private final EnterpriseNewsScopeUserMapper enterpriseNewsScopeUserMapper;
    private final SysUserMapper sysUserMapper;
    private final SysUserDeptMapper sysUserDeptMapper;
@@ -328,6 +332,26 @@
                            .eq(FinReimbursement::getId, instance.getBusinessId())
                            .set(FinReimbursement::getBillStatus, "REJECTED")
            );
        } else if (TypeEnums.VEHICLE_BORROW_APPROVAL.getCode().equals(instance.getBusinessType())) {
            vehicleBorrowRecordMapper.update(
                    null,
                    new LambdaUpdateWrapper<VehicleBorrowRecord>()
                            .eq(VehicleBorrowRecord::getId, instance.getBusinessId())
                            .set(VehicleBorrowRecord::getBorrowStatus, "REJECTED")
                            .set(VehicleBorrowRecord::getApprovalInstanceId, instance.getId())
            );
            VehicleBorrowRecord borrowRecord = vehicleBorrowRecordMapper.selectById(instance.getBusinessId());
            if (borrowRecord != null) {
                syncVehicleBorrowStatus(borrowRecord.getVehicleId());
            }
        } else if (TypeEnums.VEHICLE_DELAY_APPROVAL.getCode().equals(instance.getBusinessType())) {
            vehicleBorrowRecordMapper.update(
                    null,
                    new LambdaUpdateWrapper<VehicleBorrowRecord>()
                            .eq(VehicleBorrowRecord::getId, instance.getBusinessId())
                            .set(VehicleBorrowRecord::getExtendStatus, "REJECTED")
                            .set(VehicleBorrowRecord::getExtendApprovalInstanceId, null)
            );
        }
        return R.ok("审批已驳回");
    }
@@ -445,6 +469,11 @@
        }
        if (TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode().equals(businessType)) {
            handleNewsApprovalFinished(instance, status);
            return;
        }
        if (TypeEnums.VEHICLE_BORROW_APPROVAL.getCode().equals(businessType)
                || TypeEnums.VEHICLE_DELAY_APPROVAL.getCode().equals(businessType)) {
            handleVehicleBorrowApprovalFinished(instance, status);
        }
    }
@@ -586,6 +615,104 @@
        shippingInfoMapper.updateById(shippingInfo);
    }
    private void handleVehicleBorrowApprovalFinished(ApprovalInstance instance, String status) {
        if (instance == null || instance.getBusinessId() == null) {
            return;
        }
        VehicleBorrowRecord record = vehicleBorrowRecordMapper.selectById(instance.getBusinessId());
        if (record == null || Integer.valueOf(1).equals(record.getDeleted())) {
            return;
        }
        if (TypeEnums.VEHICLE_BORROW_APPROVAL.getCode().equals(instance.getBusinessType())) {
            if ("APPROVED".equals(status)) {
                Vehicle vehicle = vehicleMapper.selectById(record.getVehicleId());
                if (vehicle == null) {
                    throw new ServiceException("车辆不存在");
                }
                VehicleBorrowRecord update = new VehicleBorrowRecord();
                update.setId(record.getId());
                update.setBorrowStatus("BORROWING");
                update.setApprovedTime(instance.getFinishTime());
                update.setApprovalInstanceId(instance.getId());
                vehicleBorrowRecordMapper.updateById(update);
                syncVehicleBorrowStatus(vehicle.getId());
                return;
            }
            if ("REJECTED".equals(status)) {
                VehicleBorrowRecord update = new VehicleBorrowRecord();
                update.setId(record.getId());
                update.setBorrowStatus("REJECTED");
                update.setApprovalInstanceId(instance.getId());
                vehicleBorrowRecordMapper.updateById(update);
                syncVehicleBorrowStatus(record.getVehicleId());
                return;
            }
            if ("PENDING".equals(status)) {
                VehicleBorrowRecord update = new VehicleBorrowRecord();
                update.setId(record.getId());
                update.setBorrowStatus("IN_APPROVAL");
                update.setApprovalInstanceId(instance.getId());
                vehicleBorrowRecordMapper.updateById(update);
            }
            return;
        }
        if (TypeEnums.VEHICLE_DELAY_APPROVAL.getCode().equals(instance.getBusinessType())) {
            if ("APPROVED".equals(status)) {
                vehicleBorrowRecordMapper.update(
                        null,
                        new LambdaUpdateWrapper<VehicleBorrowRecord>()
                                .eq(VehicleBorrowRecord::getId, record.getId())
                                .set(VehicleBorrowRecord::getBorrowStatus, "BORROWING")
                                .set(VehicleBorrowRecord::getExtendStatus, "APPROVED")
                                .set(VehicleBorrowRecord::getExtendApprovedTime, instance.getFinishTime())
                                .set(VehicleBorrowRecord::getPlannedReturnTime, record.getExtendTargetReturnTime())
                                .set(VehicleBorrowRecord::getExtendApprovalInstanceId, null)
                );
                return;
            }
            if ("REJECTED".equals(status)) {
                vehicleBorrowRecordMapper.update(
                        null,
                        new LambdaUpdateWrapper<VehicleBorrowRecord>()
                                .eq(VehicleBorrowRecord::getId, record.getId())
                                .set(VehicleBorrowRecord::getExtendStatus, "REJECTED")
                                .set(VehicleBorrowRecord::getExtendApprovalInstanceId, null)
                );
                return;
            }
            if ("PENDING".equals(status)) {
                VehicleBorrowRecord update = new VehicleBorrowRecord();
                update.setId(record.getId());
                update.setExtendStatus("PENDING");
                update.setExtendApprovalInstanceId(instance.getId());
                vehicleBorrowRecordMapper.updateById(update);
            }
        }
    }
    private void syncVehicleBorrowStatus(Long vehicleId) {
        if (vehicleId == null) {
            return;
        }
        long activeBorrowCount = vehicleBorrowRecordMapper.selectCount(
                new LambdaQueryWrapper<VehicleBorrowRecord>()
                        .eq(VehicleBorrowRecord::getVehicleId, vehicleId)
                        .eq(VehicleBorrowRecord::getDeleted, 0)
                        .in(VehicleBorrowRecord::getBorrowStatus, "IN_APPROVAL", "BORROWING")
        );
        Vehicle vehicle = vehicleMapper.selectById(vehicleId);
        if (vehicle == null) {
            throw new ServiceException("车辆不存在");
        }
        Vehicle vehicleUpdate = new Vehicle();
        vehicleUpdate.setId(vehicleId);
        vehicleUpdate.setStatus(activeBorrowCount > 0 ? "IN_USE" : "IDLE");
        vehicleMapper.updateById(vehicleUpdate);
    }
    private List<ApprovalTask> createNodeAndTasks(ApprovalInstance instance, ApprovalTemplateNode templateNode) {
        List<ApprovalTemplateNodeApprover> approvers = approvalTemplateNodeApproverMapper.selectList(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
src/main/java/com/ruoyi/approve/service/impl/VehicleBorrowRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,454 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.dto.VehicleBorrowRecordDto;
import com.ruoyi.approve.bean.vo.VehicleBorrowRecordVo;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.approve.mapper.VehicleBorrowRecordMapper;
import com.ruoyi.approve.mapper.VehicleMapper;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.ruoyi.approve.pojo.Vehicle;
import com.ruoyi.approve.pojo.VehicleBorrowRecord;
import com.ruoyi.approve.service.ApprovalInstanceService;
import com.ruoyi.approve.service.VehicleBorrowRecordService;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.TypeEnums;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
 * è½¦è¾†å€Ÿå‡ºè®°å½• Service å®žçް
 */
@Service
@RequiredArgsConstructor
public class VehicleBorrowRecordServiceImpl extends ServiceImpl<VehicleBorrowRecordMapper, VehicleBorrowRecord> implements VehicleBorrowRecordService {
    private static final String BORROW_STATUS_DRAFT = "DRAFT";
    private static final String BORROW_STATUS_IN_APPROVAL = "IN_APPROVAL";
    private static final String BORROW_STATUS_BORROWING = "BORROWING";
    private static final String BORROW_STATUS_RETURNED = "RETURNED";
    private static final String BORROW_STATUS_REJECTED = "REJECTED";
    private static final String VEHICLE_STATUS_IDLE = "IDLE";
    private static final String EXTEND_STATUS_NONE = "NONE";
    private static final String EXTEND_STATUS_PENDING = "PENDING";
    private static final String EXTEND_STATUS_APPROVED = "APPROVED";
    private static final String EXTEND_STATUS_REJECTED = "REJECTED";
    private final VehicleBorrowRecordMapper vehicleBorrowRecordMapper;
    private final VehicleMapper vehicleMapper;
    private final SysDeptMapper sysDeptMapper;
    private final ApprovalTemplateMapper approvalTemplateMapper;
    private final ApprovalInstanceService approvalInstanceService;
    private final FileUtil fileUtil;
    @Override
    public IPage<VehicleBorrowRecordVo> listPage(Page<VehicleBorrowRecordVo> page, VehicleBorrowRecordDto record) {
        return vehicleBorrowRecordMapper.listPage(page, record);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(VehicleBorrowRecordDto record) {
        validateBorrowRecord(record, false);
        Vehicle vehicle = getAvailableVehicle(record.getVehicleId());
        fillApplicantInfo(record);
        record.setBorrowNo(OrderUtils.countTodayByCreateTime(
                vehicleBorrowRecordMapper,
                "CLJC",
                "borrow_no",
                record.getCreateTime() != null ? record.getCreateTime() : LocalDateTime.now()
        ));
        record.setVehiclePlateNumber(vehicle.getPlateNumber());
        record.setBorrowStatus(normalizeBorrowStatus(record.getBorrowStatus()));
        record.setExtendStatus(EXTEND_STATUS_NONE);
        record.setDeleted(0);
        boolean saved = this.save(record);
        if (!saved || record.getId() == null) {
            throw new ServiceException("新增车辆借出记录失败");
        }
        saveBorrowAttachments(record.getId(), record.getBorrowStorageBlobDTOs());
        if (BORROW_STATUS_IN_APPROVAL.equals(record.getBorrowStatus())) {
            startApproval(record, TypeEnums.VEHICLE_BORROW_APPROVAL.getCode());
        }
        return true;
    }
    @Override
    public VehicleBorrowRecord detail(Long id) {
        if (id == null) {
            return null;
        }
        return this.getOne(
                new LambdaQueryWrapper<VehicleBorrowRecord>()
                        .eq(VehicleBorrowRecord::getId, id)
                        .eq(VehicleBorrowRecord::getDeleted, 0)
                        .last("LIMIT 1")
        );
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(VehicleBorrowRecordDto record) {
        validateBorrowRecord(record, true);
        if (record.getId() == null) {
            throw new ServiceException("借出记录ID不能为空");
        }
        VehicleBorrowRecord existing = detail(record.getId());
        if (existing == null) {
            throw new ServiceException("借出记录不存在");
        }
        if (!BORROW_STATUS_DRAFT.equals(existing.getBorrowStatus())
                && !BORROW_STATUS_REJECTED.equals(existing.getBorrowStatus())) {
            throw new ServiceException("当前状态的借出记录不允许修改");
        }
        Vehicle vehicle = getAvailableVehicle(record.getVehicleId());
        fillApplicantInfo(record);
        record.setVehiclePlateNumber(vehicle.getPlateNumber());
        record.setBorrowStatus(normalizeBorrowStatus(record.getBorrowStatus()));
        if (record.getExtendStatus() == null) {
            record.setExtendStatus(existing.getExtendStatus());
        }
        record.setApprovalInstanceId(existing.getApprovalInstanceId());
        record.setExtendApprovalInstanceId(existing.getExtendApprovalInstanceId());
        record.setDeleted(existing.getDeleted());
        boolean updated = this.updateById(record);
        if (!updated) {
            return false;
        }
        if (record.getBorrowStorageBlobDTOs() != null) {
            saveBorrowAttachments(record.getId(), record.getBorrowStorageBlobDTOs());
        }
        if (BORROW_STATUS_IN_APPROVAL.equals(record.getBorrowStatus())) {
            if (existing.getApprovalInstanceId() != null) {
                approvalInstanceService.delete(Collections.singletonList(existing.getApprovalInstanceId()));
            }
            startApproval(record, TypeEnums.VEHICLE_BORROW_APPROVAL.getCode());
        }
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        long activeCount = this.count(
                Wrappers.<VehicleBorrowRecord>lambdaQuery()
                        .in(VehicleBorrowRecord::getId, ids)
                        .eq(VehicleBorrowRecord::getDeleted, 0)
                        .in(VehicleBorrowRecord::getBorrowStatus, BORROW_STATUS_IN_APPROVAL, BORROW_STATUS_BORROWING)
        );
        if (activeCount > 0) {
            throw new ServiceException("审批中或借出中的记录不允许删除");
        }
        int rows = vehicleBorrowRecordMapper.update(
                null,
                Wrappers.<VehicleBorrowRecord>lambdaUpdate()
                        .in(VehicleBorrowRecord::getId, ids)
                        .eq(VehicleBorrowRecord::getDeleted, 0)
                        .set(VehicleBorrowRecord::getDeleted, 1)
        );
        return rows > 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean returnVehicle(VehicleBorrowRecordDto record) {
        if (record == null || record.getId() == null) {
            throw new ServiceException("借出记录ID不能为空");
        }
        VehicleBorrowRecord existing = detail(record.getId());
        if (existing == null) {
            throw new ServiceException("借出记录不存在");
        }
        if (!BORROW_STATUS_BORROWING.equals(existing.getBorrowStatus())) {
            throw new ServiceException("只有借出中的车辆才能归还");
        }
        if (EXTEND_STATUS_PENDING.equals(existing.getExtendStatus()) || existing.getExtendApprovalInstanceId() != null) {
            throw new ServiceException("延期审批中的记录不允许直接归还");
        }
        LocalDateTime now = record.getActualReturnTime() != null ? record.getActualReturnTime() : LocalDateTime.now();
        VehicleBorrowRecord update = new VehicleBorrowRecord();
        update.setId(existing.getId());
        update.setActualReturnTime(now);
        update.setReturnedTime(now);
        update.setBorrowStatus(BORROW_STATUS_RETURNED);
        update.setUpdateUser(SecurityUtils.getUserId());
        update.setUpdateTime(now);
        update.setExtendStatus(existing.getExtendStatus());
        int rows = vehicleBorrowRecordMapper.updateById(update);
        if (rows != 1) {
            throw new ServiceException("归还车辆失败");
        }
        saveReturnAttachments(existing.getId(), record.getReturnStorageBlobDTOs());
        Vehicle vehicle = new Vehicle();
        vehicle.setId(existing.getVehicleId());
        vehicle.setStatus("IDLE");
        vehicleMapper.updateById(vehicle);
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delay(VehicleBorrowRecordDto record) {
        if (record == null || record.getId() == null) {
            throw new ServiceException("借出记录ID不能为空");
        }
        if (record.getExtendTargetReturnTime() == null) {
            throw new ServiceException("延期后的归还时间不能为空");
        }
        VehicleBorrowRecord existing = detail(record.getId());
        if (existing == null) {
            throw new ServiceException("借出记录不存在");
        }
        if (!BORROW_STATUS_BORROWING.equals(existing.getBorrowStatus())) {
            throw new ServiceException("只有借出中的车辆才能申请延期");
        }
        if (EXTEND_STATUS_PENDING.equals(existing.getExtendStatus()) || existing.getExtendApprovalInstanceId() != null) {
            throw new ServiceException("已有延期审批中的申请,请勿重复提交");
        }
        if (existing.getPlannedReturnTime() != null
                && !record.getExtendTargetReturnTime().isAfter(existing.getPlannedReturnTime())) {
            throw new ServiceException("延期后的归还时间必须晚于当前计划归还时间");
        }
        VehicleBorrowRecord update = new VehicleBorrowRecord();
        update.setId(existing.getId());
        update.setExtendReason(record.getExtendReason());
        update.setExtendTargetReturnTime(record.getExtendTargetReturnTime());
        update.setExtendStatus(EXTEND_STATUS_PENDING);
        update.setUpdateUser(SecurityUtils.getUserId());
        update.setUpdateTime(LocalDateTime.now());
        int rows = vehicleBorrowRecordMapper.updateById(update);
        if (rows != 1) {
            throw new ServiceException("保存延期申请失败");
        }
        VehicleBorrowRecord refreshed = detail(existing.getId());
        startApproval(refreshed, TypeEnums.VEHICLE_DELAY_APPROVAL.getCode());
        return true;
    }
    private void validateBorrowRecord(VehicleBorrowRecord record, boolean ignoreId) {
        if (record == null) {
            throw new ServiceException("借出记录不能为空");
        }
        if (!ignoreId && record.getId() != null) {
            throw new ServiceException("新增借出记录不允许传入ID");
        }
        if (record.getVehicleId() == null) {
            throw new ServiceException("请选择车辆");
        }
        if (record.getBorrowStartTime() == null) {
            throw new ServiceException("借出开始时间不能为空");
        }
        if (record.getPlannedReturnTime() == null) {
            throw new ServiceException("计划归还时间不能为空");
        }
        if (!record.getPlannedReturnTime().isAfter(record.getBorrowStartTime())) {
            throw new ServiceException("计划归还时间必须晚于借出开始时间");
        }
        if (!StringUtils.hasText(record.getBorrowStatus())) {
            record.setBorrowStatus(BORROW_STATUS_DRAFT);
        }
        String normalized = normalizeBorrowStatus(record.getBorrowStatus());
        if (normalized == null) {
            throw new ServiceException("借出状态只支持 DRAFT æˆ– IN_APPROVAL");
        }
        record.setBorrowStatus(normalized);
        if (StringUtils.hasText(record.getExtendStatus())) {
            String extendStatus = record.getExtendStatus().trim().toUpperCase(Locale.ROOT);
            if (!EXTEND_STATUS_NONE.equals(extendStatus)
                    && !EXTEND_STATUS_PENDING.equals(extendStatus)
                    && !EXTEND_STATUS_APPROVED.equals(extendStatus)
                    && !EXTEND_STATUS_REJECTED.equals(extendStatus)) {
                throw new ServiceException("延期状态不合法");
            }
        }
    }
    private Vehicle getAvailableVehicle(Long vehicleId) {
        Vehicle vehicle = vehicleMapper.selectOne(
                new LambdaQueryWrapper<Vehicle>()
                        .eq(Vehicle::getId, vehicleId)
                        .eq(Vehicle::getDeleted, 0)
                        .last("LIMIT 1")
        );
        if (vehicle == null) {
            throw new ServiceException("车辆不存在");
        }
        long activeBorrowCount = this.count(
                Wrappers.<VehicleBorrowRecord>lambdaQuery()
                        .eq(VehicleBorrowRecord::getVehicleId, vehicleId)
                        .eq(VehicleBorrowRecord::getDeleted, 0)
                        .in(VehicleBorrowRecord::getBorrowStatus, BORROW_STATUS_IN_APPROVAL, BORROW_STATUS_BORROWING)
        );
        if (activeBorrowCount > 0) {
            throw new ServiceException("当前车辆已有借出记录,不能重复借出");
        }
        if (!VEHICLE_STATUS_IDLE.equals(vehicle.getStatus())) {
            throw new ServiceException("当前车辆不在可借出状态");
        }
        return vehicle;
    }
    private void fillApplicantInfo(VehicleBorrowRecord record) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser == null || loginUser.getUser() == null) {
            throw new ServiceException("请先登录");
        }
        SysUser user = loginUser.getUser();
        record.setApplicantId(user.getUserId());
        record.setApplicantName(user.getNickName());
        record.setApplicantDeptId(user.getDeptId());
        if (user.getDeptId() != null) {
            SysDept dept = sysDeptMapper.selectById(user.getDeptId());
            record.setApplicantDeptName(dept != null ? dept.getDeptName() : null);
        }
    }
    private void startApproval(VehicleBorrowRecord record, Long businessType) {
        Long templateId = null;
        if (record instanceof VehicleBorrowRecordDto) {
            templateId = ((VehicleBorrowRecordDto) record).getApprovalTemplateId();
        }
        ApprovalTemplate approvalTemplate = resolveApprovalTemplate(templateId, businessType);
        ApprovalInstanceDto approvalInstanceDto = new ApprovalInstanceDto();
        approvalInstanceDto.setTemplateId(approvalTemplate.getId());
        approvalInstanceDto.setTemplateName(approvalTemplate.getTemplateName());
        approvalInstanceDto.setBusinessId(record.getId());
        approvalInstanceDto.setBusinessType(businessType);
        approvalInstanceDto.setTitle(buildApprovalTitle(record, businessType));
        approvalInstanceDto.setApplicantId(record.getApplicantId());
        approvalInstanceDto.setApplicantName(record.getApplicantName());
        approvalInstanceDto.setApplyTime(LocalDateTime.now());
        approvalInstanceDto.setStatus("PENDING");
        approvalInstanceDto.setCurrentLevel(1);
        boolean saved = approvalInstanceService.add(approvalInstanceDto);
        if (!saved || approvalInstanceDto.getId() == null) {
            throw new ServiceException("发起车辆审批失败");
        }
        VehicleBorrowRecord update = new VehicleBorrowRecord();
        update.setId(record.getId());
        if (TypeEnums.VEHICLE_BORROW_APPROVAL.getCode().equals(businessType)) {
            update.setApprovalInstanceId(approvalInstanceDto.getId());
            update.setBorrowStatus(BORROW_STATUS_IN_APPROVAL);
        } else if (TypeEnums.VEHICLE_DELAY_APPROVAL.getCode().equals(businessType)) {
            update.setExtendApprovalInstanceId(approvalInstanceDto.getId());
            update.setExtendStatus(EXTEND_STATUS_PENDING);
        }
        int rows = vehicleBorrowRecordMapper.updateById(update);
        if (rows != 1) {
            throw new ServiceException("回填审批实例失败");
        }
    }
    private ApprovalTemplate resolveApprovalTemplate(Long templateId, Long businessType) {
        if (templateId != null) {
            ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectById(templateId);
            if (approvalTemplate == null || !Integer.valueOf(0).equals(approvalTemplate.getDeleted())) {
                throw new ServiceException("审批模板不存在");
            }
            if (approvalTemplate.getEnabled() == null || approvalTemplate.getEnabled() == 0) {
                throw new ServiceException("审批模板未启用");
            }
            if (approvalTemplate.getBusinessType() != null && !approvalTemplate.getBusinessType().equals(businessType)) {
                throw new ServiceException("审批模板类型与当前业务不匹配");
            }
            return approvalTemplate;
        }
        ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne(
                Wrappers.<ApprovalTemplate>lambdaQuery()
                        .eq(ApprovalTemplate::getBusinessType, businessType)
                        .eq(ApprovalTemplate::getEnabled, (byte) 1)
                        .eq(ApprovalTemplate::getDeleted, 0)
                        .orderByDesc(ApprovalTemplate::getId)
                        .last("LIMIT 1")
        );
        if (approvalTemplate == null) {
            throw new ServiceException("请先配置车辆审批模板");
        }
        return approvalTemplate;
    }
    @Override
    public void saveBorrowAttachments(Long recordId, List<StorageBlobDTO> storageBlobDTOs) {
        if (recordId == null || recordId <= 0) {
            return;
        }
        if (storageBlobDTOs == null) {
            return;
        }
        fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(
                ApplicationTypeEnum.FILE.getType(),
                RecordTypeEnum.VEHICLE_BORROW_RECORD,
                recordId,
                storageBlobDTOs
        );
    }
    @Override
    public void saveReturnAttachments(Long recordId, List<StorageBlobDTO> storageBlobDTOs) {
        if (recordId == null || recordId <= 0) {
            return;
        }
        if (storageBlobDTOs == null) {
            return;
        }
        fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(
                ApplicationTypeEnum.FILE.getType(),
                RecordTypeEnum.VEHICLE_RETURN_RECORD,
                recordId,
                storageBlobDTOs
        );
    }
    private String buildApprovalTitle(VehicleBorrowRecord record, Long businessType) {
        if (TypeEnums.VEHICLE_BORROW_APPROVAL.getCode().equals(businessType)) {
            return "车辆借出申请:" + record.getBorrowNo();
        }
        if (TypeEnums.VEHICLE_DELAY_APPROVAL.getCode().equals(businessType)) {
            return "车辆延期申请:" + record.getBorrowNo();
        }
        return "车辆审批:" + record.getBorrowNo();
    }
    private String normalizeBorrowStatus(String borrowStatus) {
        if (!StringUtils.hasText(borrowStatus)) {
            return BORROW_STATUS_DRAFT;
        }
        String normalized = borrowStatus.trim().toUpperCase(Locale.ROOT);
        if (BORROW_STATUS_DRAFT.equals(normalized)
                || BORROW_STATUS_IN_APPROVAL.equals(normalized)
                || BORROW_STATUS_BORROWING.equals(normalized)
                || BORROW_STATUS_RETURNED.equals(normalized)
                || BORROW_STATUS_REJECTED.equals(normalized)) {
            return normalized;
        }
        return null;
    }
}
src/main/java/com/ruoyi/approve/service/impl/VehicleServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.VehicleDto;
import com.ruoyi.approve.bean.vo.VehicleVo;
import com.ruoyi.approve.mapper.VehicleMapper;
import com.ruoyi.approve.pojo.Vehicle;
import com.ruoyi.approve.service.VehicleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 * è½¦è¾†ç®¡ç† Service å®žçް
 */
@Service
@RequiredArgsConstructor
public class VehicleServiceImpl extends ServiceImpl<VehicleMapper, Vehicle> implements VehicleService {
    private final VehicleMapper vehicleMapper;
    @Override
    public IPage<VehicleVo> listPage(Page<VehicleVo> page, VehicleDto vehicle) {
        return vehicleMapper.listPage(page, vehicle);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(VehicleDto vehicle) {
        return this.save(vehicle);
    }
    @Override
    public Vehicle detail(Long id) {
        if (id == null) {
            return null;
        }
        return this.getOne(
                new LambdaQueryWrapper<Vehicle>()
                        .eq(Vehicle::getId, id)
                        .eq(Vehicle::getDeleted, 0)
                        .last("LIMIT 1")
        );
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(VehicleDto vehicle) {
        if (vehicle == null || vehicle.getId() == null) {
            return false;
        }
        return this.updateById(vehicle);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        return this.update(
                Wrappers.<Vehicle>lambdaUpdate()
                        .in(Vehicle::getId, ids)
                        .eq(Vehicle::getDeleted, 0)
                        .set(Vehicle::getDeleted, 1)
        );
    }
}
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -201,7 +201,11 @@
    ENTERPRISE_NEWS("enterprise_news"),
    APPROVAL_INSTANCE("approval_instance"),
    ACCOUNT_INVOICE_APPLICATION("account_invoice_application"),
    ACCOUNT_PURCHASE_INVOICE("account_purchase_invoice");
    ACCOUNT_PURCHASE_INVOICE("account_purchase_invoice"),
    // Vehicle
    VEHICLE("vehicle"),
    VEHICLE_BORROW_RECORD("vehicle_borrow_record"),
    VEHICLE_RETURN_RECORD("vehicle_return_record");
    private final String type;
    RecordTypeEnum(String type) { this.type = type; }
src/main/java/com/ruoyi/common/enums/TypeEnums.java
@@ -7,8 +7,8 @@
@AllArgsConstructor
public enum TypeEnums implements BaseEnum<Long> {
    PUBLIC_OUT(1L, "公出管理"),
    LEAVE(2L, "请假管理"),
//    PUBLIC_OUT(1L, "公出管理"),
//    LEAVE(2L, "请假管理"),
    BUSINESS_TRIP(3L, "出差管理"),
    REIMBURSEMENT(4L, "报销管理"),
    PURCHASE_APPROVAL(5L, "采购审批"),
@@ -25,7 +25,8 @@
    TRAVEL_REIMBURSEMENT_APPROVAL(16L, "出差报销审批"),
    EXPENSE_APPROVAL(17L, "费用审批"),
    ENTERPRISE_NEWS_APPROVAL(18L, "企业新闻审批"),
    VEHICLE_APPROVAL(19L, "车辆审批");
    VEHICLE_BORROW_APPROVAL(19L, "车辆借出审批"),
    VEHICLE_DELAY_APPROVAL(20L, "车辆延期审批");
src/main/resources/mapper/approve/ApprovalInstanceMapper.xml
@@ -22,6 +22,7 @@
        <result column="update_user" property="updateUser" />
        <result column="update_time" property="updateTime" />
        <result column="deleted" property="deleted" />
        <result column="form_config" property="formConfig" />
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.approve.bean.vo.ApprovalInstanceVo">
        select ai.*,su.nick_name as create_user_name from
src/main/resources/mapper/approve/VehicleBorrowRecordMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.approve.mapper.VehicleBorrowRecordMapper">
    <select id="listPage" resultType="com.ruoyi.approve.bean.vo.VehicleBorrowRecordVo">
        SELECT *
        FROM vehicle_borrow_record
        <where>
            deleted = 0
            <if test="record.borrowNo != null and record.borrowNo != ''">
                AND borrow_no LIKE CONCAT('%', #{record.borrowNo}, '%')
            </if>
            <if test="record.vehiclePlateNumber != null and record.vehiclePlateNumber != ''">
                AND vehicle_plate_number LIKE CONCAT('%', #{record.vehiclePlateNumber}, '%')
            </if>
            <if test="record.applicantName != null and record.applicantName != ''">
                AND applicant_name LIKE CONCAT('%', #{record.applicantName}, '%')
            </if>
            <if test="record.borrowStatus != null and record.borrowStatus != ''">
                AND borrow_status = #{record.borrowStatus}
            </if>
            <if test="record.extendStatus != null and record.extendStatus != ''">
                AND extend_status = #{record.extendStatus}
            </if>
            <if test="record.vehicleId != null">
                AND vehicle_id = #{record.vehicleId}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>
</mapper>
src/main/resources/mapper/approve/VehicleMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.approve.mapper.VehicleMapper">
    <select id="listPage" resultType="com.ruoyi.approve.bean.vo.VehicleVo">
        SELECT * FROM vehicle
        <where>
            deleted = 0
            <if test="vehicle.plateNumber != null and vehicle.plateNumber != ''">
                AND plate_number LIKE CONCAT('%', #{vehicle.plateNumber}, '%')
            </if>
            <if test="vehicle.status != null and vehicle.status != ''">
                AND status = #{vehicle.status}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>
</mapper>