cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java
@@ -17,12 +17,14 @@
import com.ruoyi.common.utils.WxCpUtils;
import com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto;
import com.ruoyi.device.dto.DeviceMaintenancePlanDto;
import com.ruoyi.device.mapper.DeviceMaintenanceAttachmentMapper;
import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
import com.ruoyi.device.mapper.DeviceMaintenancePlanDetailsMapper;
import com.ruoyi.device.mapper.DeviceMaintenancePlanMapper;
import com.ruoyi.device.mapper.DeviceMapper;
import com.ruoyi.device.pojo.Device;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.pojo.DeviceMaintenanceAttachment;
import com.ruoyi.device.pojo.DeviceMaintenancePlan;
import com.ruoyi.device.pojo.DeviceMaintenancePlanDetails;
import com.ruoyi.device.service.DeviceMaintenancePlanDetailsService;
@@ -34,12 +36,16 @@
import com.ruoyi.system.service.InformationNotificationService;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
@@ -48,7 +54,9 @@
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -79,6 +87,14 @@
    private DeviceMapper deviceMapper;
    @Resource
    private DeviceMaintenanceMapper deviceMaintenanceMapper;
    @Resource
    private DeviceMaintenanceAttachmentMapper deviceMaintenanceAttachmentMapper;
    @Value("${file.path}")
    private String filePath;
    @Value("${wordUrl}")
    private String wordUrl;
    /**
     * 分页查询设备保养计划
@@ -141,13 +157,22 @@
        }
        this.saveOrUpdate(deviceMaintenancePlanDto);
        // 删除原本的详情
        // 删除原本的详情;明细重建后仍保留原明细id的附件不清理,id丢失的才清理
        List<DeviceMaintenancePlanDetails> oldDetails = deviceMaintenancePlanDetailsService.list(
                Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
                        .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, deviceMaintenancePlanDto.getMaintenancePlanId()));
        deviceMaintenancePlanDetailsService.remove(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery().eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, deviceMaintenancePlanDto.getMaintenancePlanId()));
        List<Integer> lostDetailIds = oldDetails.stream().map(DeviceMaintenancePlanDetails::getMaintenancePlanDetailId)
                .collect(Collectors.toList());
        // 详情赋值并保存
        List<DeviceMaintenancePlanDetailsDto> deviceMaintenancePlanDetails = deviceMaintenancePlanDto.getDeviceMaintenancePlanDetails();
        if (CollectionUtils.isNotEmpty(deviceMaintenancePlanDetails)) { // 详情不为空
            List<Integer> keptDetailIds = deviceMaintenancePlanDetails.stream()
                    .map(DeviceMaintenancePlanDetails::getMaintenancePlanDetailId)
                    .filter(id -> id != null)
                    .collect(Collectors.toList());
            lostDetailIds.removeAll(keptDetailIds);
            List<DeviceMaintenancePlanDetails> collect = deviceMaintenancePlanDetails.stream().map(deviceMaintenancePlanDetail -> { // 遍历详情
                deviceMaintenancePlanDetail.setDeviceId(deviceMaintenancePlanDto.getDeviceId()); // 设备ID
                deviceMaintenancePlanDetail.setMaintenancePlanId(deviceMaintenancePlanDto.getMaintenancePlanId()); // 保养计划ID
                DeviceMaintenancePlanDetails planDetails = new DeviceMaintenancePlanDetails();
                BeanUtils.copyProperties(deviceMaintenancePlanDetail, planDetails);
@@ -155,6 +180,7 @@
            }).collect(Collectors.toList());
            deviceMaintenancePlanDetailsService.saveBatch(collect);
        }
        removeAttachmentsByDetailIds(lostDetailIds);
        return Result.success();
    }
@@ -165,8 +191,12 @@
     */
    @Override
    public Result deleteMaintenancePlan(DeviceMaintenancePlanDto deviceMaintenancePlanDto) {
        List<DeviceMaintenancePlanDetails> details = deviceMaintenancePlanDetailsService.list(
                Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
                        .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, deviceMaintenancePlanDto.getMaintenancePlanId()));
        this.removeById(deviceMaintenancePlanDto);
        deviceMaintenancePlanDetailsService.remove(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery().eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, deviceMaintenancePlanDto.getMaintenancePlanId()));
        removeAttachmentsByDetailIds(details.stream().map(DeviceMaintenancePlanDetails::getMaintenancePlanDetailId).collect(Collectors.toList()));
        return Result.success();
    }
@@ -181,27 +211,27 @@
        // 查询设备保养计划
        DeviceMaintenancePlanDto deviceMaintenancePlan = baseMapper.selectMaintenancePlanById(maintenancePlanId);
        // 查询设备保养计划详情
        List<DeviceMaintenancePlanDetailsDto> deviceMaintenancePlanDetailsDtoList = deviceMaintenancePlanDetailsMapper.deviceInspectionRecordDetailsList(maintenancePlanId);
        // 查询设备保养计划详情,按“设备 + 保养月份”兜底去重
        List<DeviceMaintenancePlanDetailsDto> deviceMaintenancePlanDetailsDtoList = dedupeMaintenanceDetails(
                deviceMaintenancePlanDetailsMapper.deviceInspectionRecordDetailsList(maintenancePlanId));
        // 设置序号
        deviceMaintenancePlanDetailsDtoList.forEach(deviceInspectionRecordDetails -> {
            deviceInspectionRecordDetails.setIndex(deviceMaintenancePlanDetailsDtoList.indexOf(deviceInspectionRecordDetails) + 1);
        });
        for (int i = 0; i < deviceMaintenancePlanDetailsDtoList.size(); i++) {
            deviceMaintenancePlanDetailsDtoList.get(i).setIndex(i + 1);
        }
        // 获取路径
        InputStream inputStream = this.getClass().getResourceAsStream("/static/word/maintenance-plan.docx");
        Configure configure = Configure.builder()
                .bind("deviceMaintenancePlanDetailsDtoList", new HackLoopTableRenderPolicy())
                .build();
        XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
                new HashMap<String, Object>() {{
                    put("deviceMaintenancePlan", deviceMaintenancePlan);
                    put("deviceMaintenancePlanDetailsDtoList", deviceMaintenancePlanDetailsDtoList);
        Map<String, Object> renderData = new HashMap<>();
        renderData.put("deviceMaintenancePlan", deviceMaintenancePlan);
        renderData.put("deviceMaintenancePlanDetailsDtoList", deviceMaintenancePlanDetailsDtoList);
                    // 编制人签名地址
                    put("compilerUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenancePlan.getCompilerId()));
        renderData.put("compilerUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenancePlan.getCompilerId()));
                    // 审核人签名地址
                    put("auditUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenancePlan.getAuditId()));
                }});
        renderData.put("auditUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceMaintenancePlan.getAuditId()));
        XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(renderData);
        try {
            response.setContentType("application/msword");
@@ -233,7 +263,8 @@
        // 查询详情
        DeviceMaintenancePlanDto deviceMaintenancePlanDto = new DeviceMaintenancePlanDto();
        BeanUtils.copyProperties(deviceMaintenancePlan, deviceMaintenancePlanDto);
        deviceMaintenancePlanDto.setDeviceMaintenancePlanDetails(deviceMaintenancePlanDetailsMapper.deviceInspectionRecordDetailsList(maintenancePlanId));
        deviceMaintenancePlanDto.setDeviceMaintenancePlanDetails(dedupeMaintenanceDetails(
                deviceMaintenancePlanDetailsMapper.deviceInspectionRecordDetailsList(maintenancePlanId)));
        return Result.success(deviceMaintenancePlanDto);
    }
@@ -659,9 +690,94 @@
            List<Integer> recordIds = records.stream().map(DeviceMaintenance::getId).collect(Collectors.toList());
            deviceMaintenanceMapper.deleteBatchIds(recordIds);
        }
        removeAttachmentsByDetailIds(List.of(maintenancePlanDetailId));
        return deviceMaintenancePlanDetailsMapper.deleteById(maintenancePlanDetailId) > 0;
    }
    @Override
    public List<DeviceMaintenanceAttachment> selectAttachments(Integer maintenancePlanDetailId) {
        if (maintenancePlanDetailId == null) {
            return new ArrayList<>();
        }
        return deviceMaintenanceAttachmentMapper.selectList(Wrappers.<DeviceMaintenanceAttachment>lambdaQuery()
                .eq(DeviceMaintenanceAttachment::getMaintenancePlanDetailId, maintenancePlanDetailId)
                .orderByAsc(DeviceMaintenanceAttachment::getSort, DeviceMaintenanceAttachment::getId));
    }
    @Override
    public DeviceMaintenanceAttachment uploadAttachment(Integer maintenancePlanDetailId, MultipartFile file) {
        if (maintenancePlanDetailId == null) {
            throw new ErrorException("缺少保养计划明细id");
        }
        if (deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId) == null) {
            throw new ErrorException("保养计划明细不存在");
        }
        if (file == null || file.isEmpty()) {
            throw new ErrorException("附件不能为空");
        }
        // 系统全局上传上限为200MB,这里提前给出友好提示
        if (file.getSize() > 200L * 1024 * 1024) {
            throw new ErrorException("附件不能超过200MB");
        }
        String originalName = file.getOriginalFilename();
        if (originalName == null || originalName.trim().isEmpty()) {
            originalName = "未命名文件";
        }
        // 个别浏览器可能带上路径前缀,只保留文件名本身
        originalName = originalName.replaceAll(".*[/\\\\]", "");
        try {
            // 图片放图片目录(/img 静态路径),其余任意格式放 word 目录(/word 静态路径)
            boolean image = originalName.matches("(?i).+\\.(jpg|jpeg|png|gif)$");
            File directory = new File(image ? filePath : wordUrl);
            if (!directory.exists()) {
                directory.mkdirs();
            }
            String fileUrl = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS")) + "-" + originalName;
            file.transferTo(new File(directory, fileUrl));
            DeviceMaintenanceAttachment attachment = new DeviceMaintenanceAttachment();
            attachment.setMaintenancePlanDetailId(maintenancePlanDetailId);
            attachment.setFileName(originalName);
            attachment.setFileUrl(fileUrl);
            attachment.setSort(selectAttachments(maintenancePlanDetailId).size());
            deviceMaintenanceAttachmentMapper.insert(attachment);
            return attachment;
        } catch (IOException e) {
            throw new ErrorException("附件上传失败");
        }
    }
    @Override
    public boolean deleteAttachment(Long id) {
        return deviceMaintenanceAttachmentMapper.deleteById(id) > 0;
    }
    /**
     * 删除计划明细对应的附件记录
     */
    private void removeAttachmentsByDetailIds(List<Integer> maintenancePlanDetailIds) {
        if (CollectionUtils.isEmpty(maintenancePlanDetailIds)) {
            return;
        }
        deviceMaintenanceAttachmentMapper.delete(Wrappers.<DeviceMaintenanceAttachment>lambdaQuery()
                .in(DeviceMaintenanceAttachment::getMaintenancePlanDetailId, maintenancePlanDetailIds));
    }
    /**
     * 同一计划、同一设备只保留一条;SQL 已按“已有保养记录优先、审核通过优先、最新优先”排序,这里取首条
     */
    private List<DeviceMaintenancePlanDetailsDto> dedupeMaintenanceDetails(List<DeviceMaintenancePlanDetailsDto> details) {
        if (CollectionUtils.isEmpty(details)) {
            return details;
        }
        Map<String, DeviceMaintenancePlanDetailsDto> unique = new LinkedHashMap<>();
        for (DeviceMaintenancePlanDetailsDto detail : details) {
            String key = detail.getDeviceId() != null ? String.valueOf(detail.getDeviceId())
                    : "name:" + detail.getDeviceName();
            unique.putIfAbsent(key, detail);
        }
        return new ArrayList<>(unique.values());
    }
    /**
     * 两个保养日期之间相差的月数
     */