From 3d397976d08f0359b8cfeb4e4f22403ae5d0c04f Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期六, 24 十月 2026 20:44:14 +0800
Subject: [PATCH] feat(device): 设备核查计划附件与设备使用记录联动改造

---
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java |  252 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 223 insertions(+), 29 deletions(-)

diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
index a8b3ace..da709e6 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java
@@ -20,12 +20,14 @@
 import com.ruoyi.device.dto.DeviceExaminePlanDetailsDto;
 import com.ruoyi.device.dto.DeviceExaminePlanDto;
 import com.ruoyi.device.excel.upload.DeviceExaminePlanUpload;
+import com.ruoyi.device.mapper.DeviceExamineAttachmentMapper;
 import com.ruoyi.device.mapper.DeviceExaminePlanDetailsMapper;
 import com.ruoyi.device.mapper.DeviceExaminePlanMapper;
 import com.ruoyi.device.mapper.DeviceExamineRecordContrastMapper;
 import com.ruoyi.device.mapper.DeviceExamineRecordMapper;
 import com.ruoyi.device.mapper.DeviceMapper;
 import com.ruoyi.device.pojo.Device;
+import com.ruoyi.device.pojo.DeviceExamineAttachment;
 import com.ruoyi.device.pojo.DeviceExaminePlan;
 import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
 import com.ruoyi.device.pojo.DeviceExamineRecord;
@@ -38,6 +40,7 @@
 import com.ruoyi.system.mapper.UserMapper;
 import com.ruoyi.system.service.InformationNotificationService;
 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;
@@ -45,6 +48,7 @@
 
 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;
@@ -86,6 +90,14 @@
     private DeviceExamineRecordMapper deviceExamineRecordMapper;
     @Resource
     private DeviceExamineRecordContrastMapper deviceExamineRecordContrastMapper;
+    @Resource
+    private DeviceExamineAttachmentMapper deviceExamineAttachmentMapper;
+
+    @Value("${file.path}")
+    private String filePath;
+
+    @Value("${wordUrl}")
+    private String wordUrl;
 
     /**
      * 鏂板璁惧鏍告煡璁″垝
@@ -415,12 +427,8 @@
         if (device == null) {
             return;
         }
-        // 鎻愬墠鏍告煡鏃惰鍒掓湀浠藉彲鑳借繕娌″埌锛屽彇瀹為檯瀹屾垚褰撳ぉ锛涘惁鍒欐寜鏈疆璁″垝鏍告煡鏈堜唤
-        LocalDate checkDate = parseCheckDate(detail.getCheckTime());
-        if (checkDate.isAfter(LocalDate.now())) {
-            checkDate = LocalDate.now();
-        }
-        LocalDateTime checkDateTime = checkDate.atStartOfDay();
+        // 瀹℃牳閫氳繃褰撳ぉ鍗宠涓哄疄闄呮牳鏌ュ畬鎴愭椂闂�
+        LocalDateTime checkDateTime = LocalDate.now().atStartOfDay();
         // 鍙厑璁稿悜鍓嶆粴鍔紝閬垮厤閲嶅澶嶆牳鏃ц褰曟椂鎶婃棩鏈熸敼鍥炲幓
         if (device.getLastExamineDate() != null && !checkDateTime.isAfter(device.getLastExamineDate())) {
             return;
@@ -513,26 +521,22 @@
     }
 
     /**
-     * 璁″垝鏄庣粏鐨勬牳鏌ユ椂闂村彲鑳芥槸 yyyy-MM 鎴� yyyy-MM-dd锛岀粺涓�鍙栨湀浠介鏃�
-     */
-    private LocalDate parseCheckDate(String checkTime) {
-        if (StringUtils.isNotBlank(checkTime)) {
-            String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
-            if (normalized.length() >= 7) {
-                try {
-                    return YearMonth.parse(normalized.substring(0, 7)).atDay(1);
-                } catch (Exception ignored) {
-                }
-            }
-        }
-        return LocalDate.now();
-    }
-
-    /**
      * 涓烘寚瀹氳澶囨壘涓�鏉″彲褰曞叆鏍告煡璁板綍鐨勮鍒掓槑缁嗭紱娌℃湁鍙敤鏄庣粏鏃跺湪褰撳墠骞村害璁″垝鏂板鏈疆鏄庣粏
      */
     @Override
     public DeviceExaminePlanDetails resolveExaminePlanDetail(Integer deviceId, String checkTime) {
+        return findExaminePlanDetail(deviceId, checkTime, true);
+    }
+
+    /**
+     * 鏌ユ壘鎸囧畾璁惧鍙鐢ㄧ殑璁″垝鏄庣粏锛涘彧鏌ヨ锛屼笉鏂板璁″垝鎴栨槑缁�
+     */
+    @Override
+    public DeviceExaminePlanDetails findExaminePlanDetail(Integer deviceId, String checkTime) {
+        return findExaminePlanDetail(deviceId, checkTime, false);
+    }
+
+    private DeviceExaminePlanDetails findExaminePlanDetail(Integer deviceId, String checkTime, boolean createIfAbsent) {
         if (deviceId == null) {
             throw new ErrorException("璇烽�夋嫨鏍告煡璁惧");
         }
@@ -541,7 +545,16 @@
             throw new ErrorException("鏍告煡璁惧涓嶅瓨鍦�");
         }
         String checkMonth = normalizeCheckMonth(checkTime);
-        DeviceExaminePlan plan = getOrCreatePlanEntity(checkMonth.substring(0, 4));
+        String planYear = checkMonth.substring(0, 4);
+        DeviceExaminePlan plan;
+        if (createIfAbsent) {
+            plan = getOrCreatePlanEntity(planYear);
+        } else {
+            plan = baseMapper.selectOne(Wrappers.<DeviceExaminePlan>lambdaQuery()
+                    .eq(DeviceExaminePlan::getPlanYear, planYear)
+                    .orderByDesc(DeviceExaminePlan::getPlanId)
+                    .last("limit 1"));
+        }
         List<DeviceExaminePlanDetails> details = deviceExaminePlanDetailsMapper.selectList(
                 Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
                         .eq(DeviceExaminePlanDetails::getDeviceId, deviceId)
@@ -555,7 +568,7 @@
             if (recordCount != null && recordCount > 0) {
                 continue;
             }
-            if (detail.getPlanId() != null && detail.getPlanId().equals(plan.getPlanId())) {
+            if (plan != null && detail.getPlanId() != null && detail.getPlanId().equals(plan.getPlanId())) {
                 return detail;
             }
             long distance = monthDistance(detail.getCheckTime(), checkMonth);
@@ -568,6 +581,9 @@
         if (nearest != null) {
             return nearest;
         }
+        if (!createIfAbsent) {
+            return null;
+        }
         DeviceExaminePlanDetails detail = new DeviceExaminePlanDetails();
         detail.setPlanId(plan.getPlanId());
         detail.setDeviceId(device.getId());
@@ -577,7 +593,8 @@
         }
         detail.setDeviceName(deviceName);
         detail.setDeviceNumber(device.getManagementNumber());
-        detail.setCheckTime(checkMonth);
+        String detailCheckTime = normalizeCheckDate(checkTime);
+        detail.setCheckTime(StringUtils.isBlank(detailCheckTime) ? checkMonth : detailCheckTime);
         deviceExaminePlanDetailsMapper.insert(detail);
         return detail;
     }
@@ -596,6 +613,23 @@
             }
         }
         return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
+    }
+
+    /**
+     * 瑙勮寖鏍告煡鏃ユ湡锛氬甫鍏蜂綋鏃ユ湡鐨勪繚鐣欏埌鏃ワ紝鍙~鏈堜唤鐨勮鑼冧负 yyyy-MM
+     */
+    private String normalizeCheckDate(String checkTime) {
+        if (StringUtils.isBlank(checkTime)) {
+            return null;
+        }
+        String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+        if (normalized.length() >= 10) {
+            try {
+                return LocalDate.parse(normalized.substring(0, 10)).toString();
+            } catch (Exception ignored) {
+            }
+        }
+        return normalizeCheckMonth(normalized);
     }
 
     /**
@@ -620,9 +654,9 @@
             throw new ErrorException("缂哄皯璁惧鏍告煡璁″垝鏄庣粏id");
         }
         if (StringUtils.isNotBlank(detail.getCheckTime())) {
-            String checkMonth = normalizeCheckMonth(detail.getCheckTime());
-            detail.setCheckTime(checkMonth);
-            DeviceExaminePlan plan = getOrCreatePlanEntity(checkMonth.substring(0, 4));
+            String checkTime = normalizeCheckDate(detail.getCheckTime());
+            detail.setCheckTime(checkTime);
+            DeviceExaminePlan plan = getOrCreatePlanEntity(checkTime.substring(0, 4));
             detail.setPlanId(plan.getPlanId());
         }
         return deviceExaminePlanDetailsMapper.updateById(detail) > 0;
@@ -636,6 +670,10 @@
         if (planDetailsId == null) {
             throw new ErrorException("缂哄皯璁惧鏍告煡璁″垝鏄庣粏id");
         }
+        DeviceExaminePlanDetails detail = deviceExaminePlanDetailsMapper.selectById(planDetailsId);
+        if (detail == null) {
+            return false;
+        }
         Long recordCount = deviceExamineRecordMapper.selectCount(Wrappers.<DeviceExamineRecord>lambdaQuery()
                 .eq(DeviceExamineRecord::getPlanDetailsId, planDetailsId));
         Long contrastCount = deviceExamineRecordContrastMapper.selectCount(Wrappers.<DeviceExamineRecordContrast>lambdaQuery()
@@ -643,6 +681,162 @@
         if ((recordCount != null && recordCount > 0) || (contrastCount != null && contrastCount > 0)) {
             throw new ErrorException("璇ヨ鍒掑凡瀛樺湪鏍告煡璁板綍锛屼负淇濈暀鍘嗗彶璁板綍涓嶅厑璁稿垹闄�");
         }
-        return deviceExaminePlanDetailsService.removeById(planDetailsId);
+        boolean removed = deviceExaminePlanDetailsService.removeById(planDetailsId);
+        if (removed && detail.getDeviceId() != null) {
+            rollbackDeviceExamineDate(detail);
+        }
+        return removed;
+    }
+
+    /**
+     * 鍒犻櫎鏈鏍哥殑璁″垝鍚庯紝鎶婅澶囨。妗堟仮澶嶅埌鏈�杩戜竴鏉″鏍搁�氳繃鐨勮褰曪紱
+     * 娌℃湁宸插畬鎴愯褰曟椂锛屾墠娓呯┖妗f鐨勬牳鏌ユ棩鏈熷拰鍛ㄦ湡
+     */
+    private void rollbackDeviceExamineDate(DeviceExaminePlanDetails removedDetail) {
+        Device device = deviceMapper.selectById(removedDetail.getDeviceId());
+        if (device == null) {
+            return;
+        }
+        Long remainCount = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+                .eq(DeviceExaminePlanDetails::getDeviceId, removedDetail.getDeviceId()));
+        boolean isCurrentRound = sameExamineMonth(removedDetail.getCheckTime(), device.getNextExamineDate());
+        // 鍒犻櫎鐨勪笉鏄。妗堝綋鍓嶇櫥璁扮殑閭d竴杞紝涓旇澶囪繕鏈夊叾浠栬鍒掓椂锛屼笉鍔ㄦ。妗�
+        if (!isCurrentRound && remainCount != null && remainCount > 0) {
+            return;
+        }
+        LocalDate lastDone = null;
+        List<DeviceExaminePlanDetails> details = deviceExaminePlanDetailsMapper.selectList(
+                Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+                        .eq(DeviceExaminePlanDetails::getDeviceId, removedDetail.getDeviceId()));
+        for (DeviceExaminePlanDetails item : details) {
+            if (!hasPassedExamineRecord(item.getPlanDetailsId())) {
+                continue;
+            }
+            LocalDate date = parseCheckMonth(item.getCheckTime());
+            if (date != null && (lastDone == null || date.isAfter(lastDone))) {
+                lastDone = date;
+            }
+        }
+        if (lastDone == null) {
+            deviceMapper.update(null, Wrappers.<Device>lambdaUpdate()
+                    .eq(Device::getId, removedDetail.getDeviceId())
+                    .set(Device::getLastExamineDate, null)
+                    .set(Device::getNextExamineDate, null)
+                    .set(Device::getExamineCycle, null));
+            return;
+        }
+        LocalDateTime last = lastDone.atStartOfDay();
+        LocalDateTime next = null;
+        if (StringUtils.isNotBlank(device.getExamineCycle())) {
+            try {
+                next = last.plusMonths(Long.parseLong(device.getExamineCycle().trim()));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        deviceMapper.update(null, Wrappers.<Device>lambdaUpdate()
+                .eq(Device::getId, removedDetail.getDeviceId())
+                .set(Device::getLastExamineDate, last)
+                .set(Device::getNextExamineDate, next));
+    }
+
+    /**
+     * 鏄庣粏涓嬫槸鍚﹀瓨鍦ㄥ鏍搁�氳繃鐨勬牳鏌ヨ褰曟垨姣斿璁板綍
+     */
+    private boolean hasPassedExamineRecord(Integer planDetailsId) {
+        Long recordCount = deviceExamineRecordMapper.selectCount(Wrappers.<DeviceExamineRecord>lambdaQuery()
+                .eq(DeviceExamineRecord::getPlanDetailsId, planDetailsId)
+                .eq(DeviceExamineRecord::getReviewStatus, 1));
+        if (recordCount != null && recordCount > 0) {
+            return true;
+        }
+        Long contrastCount = deviceExamineRecordContrastMapper.selectCount(Wrappers.<DeviceExamineRecordContrast>lambdaQuery()
+                .eq(DeviceExamineRecordContrast::getPlanDetailsId, planDetailsId)
+                .eq(DeviceExamineRecordContrast::getReviewStatus, 1));
+        return contrastCount != null && contrastCount > 0;
+    }
+
+    /**
+     * 璁″垝鏄庣粏鐨勬牳鏌ユ湀浠芥槸鍚﹀氨鏄。妗堜笅娆℃牳鏌ユ棩鏈熸墍鍦ㄦ湀浠�
+     */
+    private boolean sameExamineMonth(String checkTime, LocalDateTime nextExamineDate) {
+        if (StringUtils.isBlank(checkTime) || nextExamineDate == null) {
+            return false;
+        }
+        String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+        return normalized.length() >= 7
+                && normalized.substring(0, 7).equals(nextExamineDate.format(DateTimeFormatter.ofPattern("yyyy-MM")));
+    }
+
+    /**
+     * 瑙f瀽鏄庣粏鏍告煡鏈堜唤涓哄綋鏈堥鏃ワ紝鏍煎紡涓嶅悎娉曟椂杩斿洖 null
+     */
+    private LocalDate parseCheckMonth(String checkTime) {
+        if (StringUtils.isNotBlank(checkTime)) {
+            String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+            if (normalized.length() >= 7) {
+                try {
+                    return YearMonth.parse(normalized.substring(0, 7)).atDay(1);
+                } catch (Exception ignored) {
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<DeviceExamineAttachment> selectAttachments(Integer planDetailsId) {
+        if (planDetailsId == null) {
+            return new ArrayList<>();
+        }
+        return deviceExamineAttachmentMapper.selectList(Wrappers.<DeviceExamineAttachment>lambdaQuery()
+                .eq(DeviceExamineAttachment::getPlanDetailsId, planDetailsId)
+                .orderByAsc(DeviceExamineAttachment::getSort, DeviceExamineAttachment::getId));
+    }
+
+    @Override
+    public DeviceExamineAttachment uploadAttachment(Integer planDetailsId, MultipartFile file) {
+        if (planDetailsId == null) {
+            throw new ErrorException("缂哄皯鏍告煡璁″垝鏄庣粏id");
+        }
+        if (deviceExaminePlanDetailsMapper.selectById(planDetailsId) == 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));
+            DeviceExamineAttachment attachment = new DeviceExamineAttachment();
+            attachment.setPlanDetailsId(planDetailsId);
+            attachment.setFileName(originalName);
+            attachment.setFileUrl(fileUrl);
+            attachment.setSort(selectAttachments(planDetailsId).size());
+            deviceExamineAttachmentMapper.insert(attachment);
+            return attachment;
+        } catch (IOException e) {
+            throw new ErrorException("闄勪欢涓婁紶澶辫触");
+        }
+    }
+
+    @Override
+    public boolean deleteAttachment(Long id) {
+        return deviceExamineAttachmentMapper.deleteById(id) > 0;
     }
 }

--
Gitblit v1.9.3