✨ 新增异常关联维修记录:维修记录加 exception_id,异常详情内联记录并新增录入接口

This commit is contained in:
2026-10-29 08:58:17 +08:00
parent c677caf817
commit 6880de54ff
12 changed files with 319 additions and 35 deletions
@@ -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("已保存维修记录");
}
}
@@ -14,6 +14,7 @@
<result column="is_deleted" property="isDeleted"/>
<result column="vehicle_type" property="vehicleType"/>
<result column="vehicle_no" property="vehicleNo"/>
<result column="exception_id" property="exceptionId"/>
<result column="maintainer" property="maintainer"/>
<result column="maintenance_time" property="maintenanceTime"/>
<result column="location" property="location"/>
@@ -27,55 +28,63 @@
<result column="mileage_unit" property="mileageUnit"/>
<result column="attachments" property="attachments"/>
<result column="remark" property="remark"/>
<result column="waybill_no" property="waybillNo"/>
<result column="exception_type" property="exceptionType"/>
<result column="exception_reason" property="exceptionReason"/>
</resultMap>
<select id="selectMaintenanceRecordPage" resultMap="maintenanceRecordResultMap">
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 &lt; 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 &lt; 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
<if test="maintenanceRecord.createDept != null">
AND create_dept = #{maintenanceRecord.createDept}
AND r.create_dept = #{maintenanceRecord.createDept}
</if>
<if test="maintenanceRecord.vehicleType != null and maintenanceRecord.vehicleType != ''">
AND vehicle_type = #{maintenanceRecord.vehicleType}
AND r.vehicle_type = #{maintenanceRecord.vehicleType}
</if>
<if test="maintenanceRecord.vehicleNo != null and maintenanceRecord.vehicleNo != ''">
<bind name="vehicleNoLike" value="'%' + maintenanceRecord.vehicleNo + '%'"/>
AND vehicle_no LIKE #{vehicleNoLike}
AND r.vehicle_no LIKE #{vehicleNoLike}
</if>
<if test="maintenanceRecord.createTimeStart != null">
AND create_time &gt;= #{maintenanceRecord.createTimeStart}
AND r.create_time &gt;= #{maintenanceRecord.createTimeStart}
</if>
<if test="maintenanceRecord.createTimeEnd != null">
AND create_time &lt;= #{maintenanceRecord.createTimeEnd}
AND r.create_time &lt;= #{maintenanceRecord.createTimeEnd}
</if>
ORDER BY create_time DESC
ORDER BY r.create_time DESC
</select>
</mapper>
@@ -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 BaseService<ExceptionDisposal
*/
void adjustRoute(ExceptionDisposalRouteAdjustRequest request);
/**
* 录入关联维修记录:写一条维修记录并追加一条跟进记录,不结案。
* 一条异常至多一条维修记录;已撤销的异常不允许录入。
*/
void submitMaintenanceRecord(ExceptionMaintenanceRecordRequest request);
}
@@ -41,22 +41,28 @@ 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.entity.ExceptionDisposalFollowRecord;
import org.springblade.transport.pojo.entity.LoadingManage;
import org.springblade.transport.pojo.entity.MaintenanceRecord;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.ExceptionDisposalFollowRecordVO;
import org.springblade.transport.pojo.vo.ExceptionDisposalVO;
import org.springblade.transport.pojo.vo.MaintenanceRecordVO;
import org.springblade.transport.service.IDriverWaybillService;
import org.springblade.transport.service.IExceptionDisposalService;
import org.springblade.transport.service.IMaintenanceRecordService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.MpUserDataScopeSupport;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.support.WaybillProcessSupport;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
@@ -92,6 +98,12 @@ public class ExceptionDisposalServiceImpl
/** 拒绝原因长度上限,与上报说明、异常原因同量级,且远小于跟进记录列宽 */
private static final int REJECT_REASON_MAX_LENGTH = 200;
/** 异常子流程录入的维修记录固定为车辆:入口只对「车辆故障维修」类异常开放 */
private static final String MAINTENANCE_VEHICLE_TYPE = "车辆";
/** 维修记录跟进说明前缀;金额必填,维修单位按实际填写拼在后面 */
private static final String MAINTENANCE_FOLLOW_PREFIX = "已录入维修记录:费用";
/** 路线与任务信息的列宽上限,与 PC 改路线同一口径 */
private static final int ROUTE_JSON_MAX_LENGTH = 8000;
private static final int TASK_INFO_JSON_MAX_LENGTH = 8000;
@@ -119,17 +131,20 @@ public class ExceptionDisposalServiceImpl
private final IDriverWaybillService driverWaybillService;
private final MpUserDataScopeSupport mpUserDataScopeSupport;
private final LoadingManageMapper loadingManageMapper;
private final IMaintenanceRecordService maintenanceRecordService;
public ExceptionDisposalServiceImpl(ExceptionDisposalFollowRecordMapper followRecordMapper,
IWaybillService waybillService,
IDriverWaybillService driverWaybillService,
MpUserDataScopeSupport mpUserDataScopeSupport,
LoadingManageMapper loadingManageMapper) {
LoadingManageMapper loadingManageMapper,
IMaintenanceRecordService maintenanceRecordService) {
this.followRecordMapper = followRecordMapper;
this.waybillService = waybillService;
this.driverWaybillService = driverWaybillService;
this.mpUserDataScopeSupport = mpUserDataScopeSupport;
this.loadingManageMapper = loadingManageMapper;
this.maintenanceRecordService = maintenanceRecordService;
}
@Override
@@ -146,6 +161,7 @@ public class ExceptionDisposalServiceImpl
ExceptionDisposalVO vo = convertVO(entity);
fillRouteNodes(vo, fillRouteAndCargo(vo, entity.getWaybillId()));
vo.setFollowRecords(followRecords(id));
vo.setMaintenanceRecord(maintenanceRecord(id));
return vo;
}
@@ -527,6 +543,76 @@ public class ExceptionDisposalServiceImpl
updateById(disposal);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void submitMaintenanceRecord(ExceptionMaintenanceRecordRequest request) {
if (request == null || request.getId() == null) {
throw new ServiceException("请选择需要录入维修记录的异常");
}
ExceptionDisposal disposal = getExisting(request.getId());
if (STATUS_REVOKED.equals(disposal.getDisposalStatus())) {
throw new ServiceException("已撤销的异常不允许录入维修记录");
}
if (Func.isBlank(disposal.getVehicleNo())) {
throw new ServiceException("异常未记录车牌号,无法录入维修记录");
}
// 唯一索引占位后无法再插入,先按 exception_id 拦住(含已逻辑删除的记录)
long recorded = maintenanceRecordService.count(Wrappers.<MaintenanceRecord>lambdaQuery()
.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.<MaintenanceRecord>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())) {
@@ -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<MaintenanceRec
return saveOrUpdate(maintenanceRecord);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteLogic(List<Long> ids) {
if (Func.isNotEmpty(ids)) {
// 唯一索引不因逻辑删除释放:不清空 exception_id,异常侧会永远显示「已录入」却查不到记录
update(Wrappers.<MaintenanceRecord>lambdaUpdate()
.set(MaintenanceRecord::getExceptionId, null)
.in(MaintenanceRecord::getId, ids));
}
return super.deleteLogic(ids);
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<MaintenanceRecordExcel> importMaintenanceRecord(List<MaintenanceRecordExcel> data) {