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 @@ + + + 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='车辆维修记录'; -- ----------------------------