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 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 listPage(Page 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() .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 ids) { if (ids == null || ids.isEmpty()) { return false; } long activeCount = this.count( Wrappers.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.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() .eq(Vehicle::getId, vehicleId) .eq(Vehicle::getDeleted, 0) .last("LIMIT 1") ); if (vehicle == null) { throw new ServiceException("车辆不存在"); } long activeBorrowCount = this.count( Wrappers.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.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 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 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; } }