diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ExceptionMaintenanceRecordRequest.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ExceptionMaintenanceRecordRequest.java
new file mode 100644
index 00000000..bae3f139
--- /dev/null
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/ExceptionMaintenanceRecordRequest.java
@@ -0,0 +1,90 @@
+/**
+ * BladeX Commercial License Agreement
+ * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
+ *
+ * Use of this software is governed by the Commercial License Agreement
+ * obtained after purchasing a license from BladeX.
+ *
+ * 1. This software is for development use only under a valid license
+ * from BladeX.
+ *
+ * 2. Redistribution of this software's source code to any third party
+ * without a commercial license is strictly prohibited.
+ *
+ * 3. Licensees may copyright their own code but cannot use segments
+ * from this software for such purposes. Copyright of this software
+ * remains with BladeX.
+ *
+ * Using this software signifies agreement to this License, and the software
+ * must not be used for illegal purposes.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
+ * not liable for any claims arising from secondary or illegal development.
+ *
+ * Author: Chill Zhuang (bladejava@qq.com)
+ */
+package org.springblade.transport.pojo.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 异常处置 → 录入维修记录请求
+ *
+ * 车牌号与车船类型不接收前端传值:车牌取自异常处置,车船类型固定「车辆」,
+ * 避免维修记录挂到与异常无关的车上。
+ *
+ * @author Chill
+ */
+@Data
+@Schema(description = "异常处置录入维修记录请求")
+public class ExceptionMaintenanceRecordRequest implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "异常处置ID")
+ private Long id;
+
+ @Schema(description = "维修时间")
+ private LocalDateTime maintenanceTime;
+
+ @Schema(description = "维修位置")
+ private String location;
+
+ @Schema(description = "维修人")
+ private String maintainer;
+
+ @Schema(description = "维修单位")
+ private String company;
+
+ @Schema(description = "联系方式")
+ private String contact;
+
+ @Schema(description = "更换零件")
+ private String replacedPart;
+
+ @Schema(description = "费用")
+ private BigDecimal cost;
+
+ @Schema(description = "里程数(公里)")
+ private BigDecimal mileage;
+
+ @Schema(description = "出厂时间")
+ private LocalDateTime factoryTime;
+
+ @Schema(description = "详细地址")
+ private String address;
+
+ @Schema(description = "备注说明")
+ private String remark;
+
+ @Schema(description = "附件JSON,[{\"name\":\"...\",\"url\":\"...\"}]")
+ private String attachments;
+
+}
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java
index 34d88f10..2d0a6219 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/MaintenanceRecord.java
@@ -61,6 +61,12 @@ public class MaintenanceRecord extends TenantEntity {
*/
@Schema(description = "车牌号/船号")
private String vehicleNo;
+ /**
+ * 关联异常处置ID(小程序录入时写入)
+ */
+ @Schema(description = "关联异常处置ID(小程序录入时写入)")
+ @TableField(updateStrategy = FieldStrategy.NEVER)
+ private Long exceptionId;
/**
* 维修人
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java
index d120aa54..b53e854b 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ExceptionDisposalVO.java
@@ -106,6 +106,10 @@ public class ExceptionDisposalVO extends ExceptionDisposal {
@Schema(description = "可调整的路线站点,少于两个站时为空")
private List routeNodes;
+ @TableField(exist = false)
+ @Schema(description = "关联的维修记录(车辆故障维修异常录入,未录入时为空对象,判定须看 id)")
+ private MaintenanceRecordVO maintenanceRecord;
+
/**
* 一个可调整的站点:站序声明、地址、该站的装卸标记与项目名
*/
diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java
index bfacf9f3..27d8923c 100644
--- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java
+++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/MaintenanceRecordVO.java
@@ -76,4 +76,25 @@ public class MaintenanceRecordVO extends MaintenanceRecord {
@Schema(description = "更新人姓名")
private String updateUserName;
+ /**
+ * 关联运单号(由 exception_id 关联异常处置带出)
+ */
+ @TableField(exist = false)
+ @Schema(description = "关联运单号")
+ private String waybillNo;
+
+ /**
+ * 关联异常类型(一级)
+ */
+ @TableField(exist = false)
+ @Schema(description = "关联异常类型")
+ private String exceptionType;
+
+ /**
+ * 关联异常原因(二级)
+ */
+ @TableField(exist = false)
+ @Schema(description = "关联异常原因")
+ private String exceptionReason;
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java
index 2b42643a..d9151be7 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java
@@ -36,6 +36,7 @@ import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
import org.springblade.transport.pojo.dto.ExceptionDisposalRejectRequest;
import org.springblade.transport.pojo.dto.ExceptionDisposalRouteAdjustRequest;
import org.springblade.transport.pojo.dto.ExceptionDisposalUpdateRequest;
+import org.springblade.transport.pojo.dto.ExceptionMaintenanceRecordRequest;
import org.springblade.transport.pojo.entity.ExceptionDisposal;
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
import org.springblade.transport.service.IExceptionDisposalService;
@@ -140,4 +141,12 @@ public class ExceptionDisposalController extends BladeController {
return R.success("已调整路线");
}
+ @PostMapping("/maintenance-record")
+ @ApiOperationSupport(order = 11)
+ @Operation(summary = "录入维修记录", description = "车辆故障维修类异常的子流程:写一条关联维修记录并追加跟进记录,不结案;一条异常至多一条;仅需登录态")
+ public R submitMaintenanceRecord(@RequestBody ExceptionMaintenanceRecordRequest request) {
+ exceptionDisposalService.submitMaintenanceRecord(request);
+ return R.success("已保存维修记录");
+ }
+
}
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml
index 527af228..01a85e04 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/MaintenanceRecordMapper.xml
@@ -14,6 +14,7 @@
+
@@ -27,55 +28,63 @@
+
+
+
SELECT
- id,
- tenant_id,
- create_user,
- create_dept,
- create_time,
- update_user,
- update_time,
- status,
- is_deleted,
- vehicle_type,
- vehicle_no,
- maintainer,
- maintenance_time,
- location,
- replaced_part,
- cost,
- company,
- contact,
- address,
- factory_time,
- CASE WHEN mileage = -1 THEN NULL WHEN mileage < 0 THEN 0 ELSE mileage END AS mileage,
- mileage_unit,
- attachments,
- remark
+ r.id,
+ r.tenant_id,
+ r.create_user,
+ r.create_dept,
+ r.create_time,
+ r.update_user,
+ r.update_time,
+ r.status,
+ r.is_deleted,
+ r.vehicle_type,
+ r.vehicle_no,
+ r.exception_id,
+ r.maintainer,
+ r.maintenance_time,
+ r.location,
+ r.replaced_part,
+ r.cost,
+ r.company,
+ r.contact,
+ r.address,
+ r.factory_time,
+ CASE WHEN r.mileage = -1 THEN NULL WHEN r.mileage < 0 THEN 0 ELSE r.mileage END AS mileage,
+ r.mileage_unit,
+ r.attachments,
+ r.remark,
+ d.waybill_no,
+ d.exception_type,
+ d.exception_reason
FROM
- blade_vehicle_maintenance_record
+ blade_vehicle_maintenance_record r
+ LEFT JOIN blade_exception_disposal d ON d.id = r.exception_id AND d.is_deleted = 0
WHERE
- is_deleted = 0
+ r.is_deleted = 0
- AND create_dept = #{maintenanceRecord.createDept}
+ AND r.create_dept = #{maintenanceRecord.createDept}
- AND vehicle_type = #{maintenanceRecord.vehicleType}
+ AND r.vehicle_type = #{maintenanceRecord.vehicleType}
- AND vehicle_no LIKE #{vehicleNoLike}
+ AND r.vehicle_no LIKE #{vehicleNoLike}
- AND create_time >= #{maintenanceRecord.createTimeStart}
+ AND r.create_time >= #{maintenanceRecord.createTimeStart}
- AND create_time <= #{maintenanceRecord.createTimeEnd}
+ AND r.create_time <= #{maintenanceRecord.createTimeEnd}
- ORDER BY create_time DESC
+ ORDER BY r.create_time DESC
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IExceptionDisposalService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IExceptionDisposalService.java
index de58cb2a..2677ef4b 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IExceptionDisposalService.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IExceptionDisposalService.java
@@ -28,6 +28,7 @@ import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest;
import org.springblade.transport.pojo.dto.ExceptionDisposalRejectRequest;
import org.springblade.transport.pojo.dto.ExceptionDisposalRouteAdjustRequest;
import org.springblade.transport.pojo.dto.ExceptionDisposalUpdateRequest;
+import org.springblade.transport.pojo.dto.ExceptionMaintenanceRecordRequest;
import org.springblade.transport.pojo.entity.ExceptionDisposal;
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
@@ -75,4 +76,10 @@ public interface IExceptionDisposalService extends BaseServicelambdaQuery()
+ .eq(MaintenanceRecord::getExceptionId, disposal.getId()));
+ if (recorded > 0) {
+ throw new ServiceException("该异常已录入维修记录");
+ }
+
+ MaintenanceRecord record = new MaintenanceRecord();
+ record.setExceptionId(disposal.getId());
+ record.setVehicleType(MAINTENANCE_VEHICLE_TYPE);
+ record.setVehicleNo(disposal.getVehicleNo());
+ record.setMaintenanceTime(request.getMaintenanceTime());
+ record.setLocation(request.getLocation());
+ record.setMaintainer(request.getMaintainer());
+ record.setCompany(request.getCompany());
+ record.setContact(request.getContact());
+ record.setReplacedPart(request.getReplacedPart());
+ record.setCost(request.getCost());
+ record.setMileage(request.getMileage());
+ record.setFactoryTime(request.getFactoryTime());
+ record.setAddress(request.getAddress());
+ record.setRemark(request.getRemark());
+ record.setAttachments(request.getAttachments());
+ // 复用维修记录自身的校验与落库(车船类型、车牌、费用、出厂时间等口径与 PC 一致)
+ try {
+ maintenanceRecordService.submit(record);
+ } catch (DuplicateKeyException exception) {
+ // 并发重复提交:前一条尚在事务中、计数看不见,唯一索引是最终防线
+ throw new ServiceException("该异常已录入维修记录");
+ }
+
+ String followContent = maintenanceFollowContent(record.getCost(), record.getCompany());
+ insertFollowRecord(disposal.getId(), followContent);
+ disposal.setLatestFollowContent(followContent);
+ // 录入维修记录不结案:已了结的异常只补记录与最近跟进,不回退状态
+ if (!WaybillProcessSupport.SETTLED_DISPOSAL_STATUSES.contains(disposal.getDisposalStatus())) {
+ disposal.setDisposalStatus(STATUS_PROCESSING);
+ }
+ updateById(disposal);
+ }
+
+ /** 跟进说明:金额必填,维修单位为空时只写金额 */
+ private String maintenanceFollowContent(BigDecimal cost, String company) {
+ String amount = Func.isEmpty(cost) ? "" : cost.setScale(2, RoundingMode.HALF_UP).toPlainString();
+ String content = MAINTENANCE_FOLLOW_PREFIX + amount + "元";
+ return Func.isBlank(company) ? content : content + ",维修单位" + company.trim();
+ }
+
+ /** 异常关联的维修记录:一条异常至多一条,逻辑删除的视为未录入 */
+ private MaintenanceRecordVO maintenanceRecord(Long disposalId) {
+ MaintenanceRecord record = maintenanceRecordService.getOne(Wrappers.lambdaQuery()
+ .eq(MaintenanceRecord::getExceptionId, disposalId)
+ .eq(MaintenanceRecord::getIsDeleted, 0)
+ .last("LIMIT 1"));
+ return record == null ? null : BeanUtil.copyProperties(record, MaintenanceRecordVO.class);
+ }
+
/** 已了结(已完成 / 已撤销)的异常不允许再次处置 */
private void assertNotSettled(ExceptionDisposal disposal) {
if (WaybillProcessSupport.SETTLED_DISPOSAL_STATUSES.contains(disposal.getDisposalStatus())) {
diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MaintenanceRecordServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MaintenanceRecordServiceImpl.java
index b49f2563..da691c39 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MaintenanceRecordServiceImpl.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MaintenanceRecordServiceImpl.java
@@ -27,6 +27,7 @@ package org.springblade.transport.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.utils.BeanUtil;
@@ -81,6 +82,18 @@ public class MaintenanceRecordServiceImpl extends BaseServiceImpl ids) {
+ if (Func.isNotEmpty(ids)) {
+ // 唯一索引不因逻辑删除释放:不清空 exception_id,异常侧会永远显示「已录入」却查不到记录
+ update(Wrappers.lambdaUpdate()
+ .set(MaintenanceRecord::getExceptionId, null)
+ .in(MaintenanceRecord::getId, ids));
+ }
+ return super.deleteLogic(ids);
+ }
+
@Override
@Transactional(rollbackFor = Exception.class)
public List importMaintenanceRecord(List data) {
diff --git a/doc/sql/transport/blade_vehicle_maintenance_record.sql b/doc/sql/transport/blade_vehicle_maintenance_record.sql
index db77ce8d..63148ebd 100644
--- a/doc/sql/transport/blade_vehicle_maintenance_record.sql
+++ b/doc/sql/transport/blade_vehicle_maintenance_record.sql
@@ -7,6 +7,7 @@ CREATE TABLE `blade_vehicle_maintenance_record` (
`tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户ID',
`vehicle_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '车船类型',
`vehicle_no` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '车牌号/船号',
+ `exception_id` bigint NULL DEFAULT NULL COMMENT '关联异常处置ID(小程序录入时写入)',
`maintainer` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '维修人',
`maintenance_time` datetime NOT NULL COMMENT '维修时间',
`location` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '维修位置',
@@ -29,5 +30,6 @@ CREATE TABLE `blade_vehicle_maintenance_record` (
`is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_vehicle_maintenance_record_no`(`vehicle_no`) USING BTREE,
- INDEX `idx_vehicle_maintenance_record_time`(`maintenance_time`) USING BTREE
+ INDEX `idx_vehicle_maintenance_record_time`(`maintenance_time`) USING BTREE,
+ UNIQUE INDEX `uk_vehicle_maintenance_record_exception`(`exception_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '车辆维修记录';
diff --git a/doc/sql/transport/blade_vehicle_maintenance_record_exception_20261029.sql b/doc/sql/transport/blade_vehicle_maintenance_record_exception_20261029.sql
new file mode 100644
index 00000000..7e40a217
--- /dev/null
+++ b/doc/sql/transport/blade_vehicle_maintenance_record_exception_20261029.sql
@@ -0,0 +1,35 @@
+-- 维修记录关联异常处置
+-- 日期:2026-10-29
+-- 小程序调度端在「车辆故障维修」类异常下录入维修记录,记录通过 exception_id 关联该条异常。
+-- 一条异常至多一条维修记录,故建唯一索引;唯一索引允许多个 NULL,PC 手工录入/导入的记录不受影响。
+
+DELIMITER $$
+
+DROP PROCEDURE IF EXISTS `upgrade_blade_vehicle_maintenance_record_exception_20261029`$$
+CREATE PROCEDURE `upgrade_blade_vehicle_maintenance_record_exception_20261029`()
+BEGIN
+ DECLARE db_name varchar(128);
+ SET db_name = DATABASE();
+
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_schema = db_name
+ AND table_name = 'blade_vehicle_maintenance_record'
+ AND column_name = 'exception_id') THEN
+ ALTER TABLE `blade_vehicle_maintenance_record`
+ ADD COLUMN `exception_id` bigint NULL DEFAULT NULL COMMENT '关联异常处置ID(小程序录入时写入)'
+ AFTER `vehicle_no`;
+ END IF;
+
+ IF NOT EXISTS (SELECT 1 FROM information_schema.statistics
+ WHERE table_schema = db_name
+ AND table_name = 'blade_vehicle_maintenance_record'
+ AND index_name = 'uk_vehicle_maintenance_record_exception') THEN
+ ALTER TABLE `blade_vehicle_maintenance_record`
+ ADD UNIQUE INDEX `uk_vehicle_maintenance_record_exception`(`exception_id`) USING BTREE;
+ END IF;
+END$$
+
+CALL `upgrade_blade_vehicle_maintenance_record_exception_20261029`()$$
+DROP PROCEDURE `upgrade_blade_vehicle_maintenance_record_exception_20261029`$$
+
+DELIMITER ;
diff --git a/doc/sql/transport/transport.sql b/doc/sql/transport/transport.sql
index b8152384..cc1fab90 100644
--- a/doc/sql/transport/transport.sql
+++ b/doc/sql/transport/transport.sql
@@ -5620,6 +5620,7 @@ CREATE TABLE `blade_vehicle_maintenance_record` (
`tenant_id` varchar(12) DEFAULT '000000' COMMENT '租户ID',
`vehicle_type` varchar(20) NOT NULL COMMENT '车船类型',
`vehicle_no` varchar(30) NOT NULL COMMENT '车牌号/船号',
+ `exception_id` bigint(20) DEFAULT NULL COMMENT '关联异常处置ID(小程序录入时写入)',
`maintainer` varchar(20) DEFAULT NULL COMMENT '维修人',
`maintenance_time` datetime NOT NULL COMMENT '维修时间',
`location` varchar(100) DEFAULT NULL COMMENT '维修位置',
@@ -5642,7 +5643,8 @@ CREATE TABLE `blade_vehicle_maintenance_record` (
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_vehicle_maintenance_record_no` (`vehicle_no`) USING BTREE,
- KEY `idx_vehicle_maintenance_record_time` (`maintenance_time`) USING BTREE
+ KEY `idx_vehicle_maintenance_record_time` (`maintenance_time`) USING BTREE,
+ UNIQUE KEY `uk_vehicle_maintenance_record_exception` (`exception_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆维修记录';
-- ----------------------------