From c21cb3d8cebd9cdef4df2efa372c5b9257c713e4 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期三, 28 十月 2026 17:57:40 +0800
Subject: [PATCH] feat(device,supply,satisfaction): 设备计划后端改造、耗材存放位置与满意度照片支持

---
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java |  252 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 237 insertions(+), 15 deletions(-)

diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java
index 656bb10..1b680fb 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java
@@ -17,14 +17,17 @@
 import com.ruoyi.common.utils.WxCpUtils;
 import com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto;
 import com.ruoyi.device.dto.DeviceMaintenancePlanDto;
+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.DeviceMaintenancePlan;
 import com.ruoyi.device.pojo.DeviceMaintenancePlanDetails;
 import com.ruoyi.device.service.DeviceMaintenancePlanDetailsService;
 import com.ruoyi.device.service.DeviceMaintenancePlanService;
+import com.ruoyi.framework.exception.ErrorException;
 import com.ruoyi.inspect.util.HackLoopTableRenderPolicy;
 import com.ruoyi.inspect.util.UserUtils;
 import com.ruoyi.system.mapper.UserMapper;
@@ -42,9 +45,12 @@
 import java.net.URLEncoder;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.YearMonth;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -71,6 +77,8 @@
     private UserMapper userMapper;
     @Resource
     private DeviceMapper deviceMapper;
+    @Resource
+    private DeviceMaintenanceMapper deviceMaintenanceMapper;
 
     /**
      * 鍒嗛〉鏌ヨ璁惧淇濆吇璁″垝
@@ -328,20 +336,38 @@
     }
 
     /**
-     * 璁惧妗f鍙樻洿鍚庯紝鎶婂凡鍒扮偣鐨勮澶囧悓姝ュ埌瀵瑰簲骞翠唤鐨勮澶囦繚鍏昏鍒掓槑缁�
+     * 淇濆吇璁″垝骞翠唤鍒楄〃锛氬垹闄ゆ病鏈変换浣曟槑缁嗙殑绌哄勾浠借鍒掑悗锛岃繑鍥炴湁鏄庣粏鐨勫勾浠斤紙鍊掑簭锛�
+     */
+    @Override
+    public List<String> getMaintenancePlanYears() {
+        // 鍘嗗彶鏁版嵁鑷剤锛氬厛璁╁凡瀹℃牳鐨勬槑缁嗘寜瀹℃牳瀹屾垚骞翠唤褰掍綅锛屽啀娓呯悊绌哄勾浠借鍒�
+        reclassifyReviewedDetails();
+        List<DeviceMaintenancePlan> plans = baseMapper.selectList(null);
+        for (DeviceMaintenancePlan plan : plans) {
+            Long detailCount = deviceMaintenancePlanDetailsMapper.selectCount(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
+                    .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, plan.getMaintenancePlanId()));
+            if (detailCount == null || detailCount == 0) {
+                removeById(plan.getMaintenancePlanId());
+            }
+        }
+        return baseMapper.selectPlanYears();
+    }
+
+    /**
+     * 璁惧妗f鍙樻洿鍚庯紝绔嬪嵆鎶婅澶囩櫥璁板埌涓嬫淇濆吇鏃ユ湡鎵�鍦ㄥ勾浠界殑璁惧淇濆吇璁″垝鏄庣粏
      */
     @Override
     public void syncDeviceMaintenanceDetail(Device device) {
-        LocalDate remind = calcMaintenanceRemindDate(device);
-        if (remind == null || remind.isAfter(LocalDate.now())) {
+        LocalDate due = calcMaintenanceDueDate(device);
+        if (due == null) {
             return;
         }
-        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(remind.getYear()));
+        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(due.getYear()));
         fillMaintenanceDetailIfAbsent(plan, device);
     }
 
     /**
-     * 淇濆吇璁板綍淇濆瓨鍚庯紝婊氬姩璁惧妗f鐨勬渶杩�/涓嬫淇濆吇鏃ユ湡
+     * 瀹℃牳閫氳繃鍚庯紝鎸夊鏍搁�氳繃褰撳ぉ婊氬姩璁惧妗f鐨勬渶杩�/涓嬫淇濆吇鏃ユ湡
      */
     @Override
     public void updateDeviceMaintenanceDate(Integer deviceId, LocalDate maintenanceDate) {
@@ -365,6 +391,75 @@
             }
         }
         deviceMapper.updateById(device);
+        // 鏈疆淇濆吇瀹屾垚鍚庣珛鍗崇櫥璁颁笅涓�杞紝鍘嗗彶鏄庣粏淇濇寔涓嶅姩
+        syncDeviceMaintenanceDetail(device);
+    }
+
+    /**
+     * 瀹℃牳閫氳繃鍚庯紝鎶婅鍒掓槑缁嗗綊鍒板鏍稿畬鎴愬勾浠界殑璁″垝涓嬶紱鎻愬墠瀹屾垚鐨勬槑缁嗕笉鍐嶇暀鍦ㄥ師璁″垝骞翠唤
+     */
+    @Override
+    public void reclassifyMaintenanceDetailByReviewDate(Integer maintenancePlanDetailId, LocalDate reviewDate) {
+        if (maintenancePlanDetailId == null || reviewDate == null) {
+            return;
+        }
+        DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId);
+        if (detail == null || detail.getMaintenancePlanId() == null) {
+            return;
+        }
+        DeviceMaintenancePlan sourcePlan = baseMapper.selectById(detail.getMaintenancePlanId());
+        if (sourcePlan == null) {
+            return;
+        }
+        String targetYear = String.valueOf(reviewDate.getYear());
+        if (StringUtils.isNotBlank(sourcePlan.getPlanYear()) && targetYear.equals(sourcePlan.getPlanYear().trim())) {
+            return;
+        }
+        moveMaintenanceDetailToYear(detail, reviewDate);
+    }
+
+    /**
+     * 鎶婅鍒掓槑缁嗘尓鍒板鏍稿畬鎴愬勾浠界殑璁″垝涓嬶紝璁″垝鏃ユ湡鎸夊疄闄呭畬鎴愭棩鏈熻褰�
+     */
+    private void moveMaintenanceDetailToYear(DeviceMaintenancePlanDetails detail, LocalDate reviewDate) {
+        DeviceMaintenancePlan targetPlan = getOrCreatePlanEntity(String.valueOf(reviewDate.getYear()));
+        DeviceMaintenancePlanDetails update = new DeviceMaintenancePlanDetails();
+        update.setMaintenancePlanDetailId(detail.getMaintenancePlanDetailId());
+        update.setMaintenancePlanId(targetPlan.getMaintenancePlanId());
+        // 璁″垝鏃ユ湡鎸夊疄闄呭畬鎴愭棩鏈熻褰曪紝鍒楄〃鎸夊畬鎴愬勾浠芥帓搴忥紝涓嶅啀鎸夊師璁″垝鏃ユ湡鏍囩孩
+        update.setMaintenanceDate(reviewDate);
+        deviceMaintenancePlanDetailsMapper.updateById(update);
+    }
+
+    /**
+     * 鍘嗗彶鏁版嵁鑷剤锛氬凡瀹℃牳閫氳繃浣嗚鍒掑勾浠戒笌瀹℃牳瀹屾垚骞翠唤涓嶄竴鑷寸殑鏄庣粏锛屾寜瀹℃牳瀹屾垚骞翠唤褰掍綅
+     */
+    private void reclassifyReviewedDetails() {
+        List<DeviceMaintenance> records = deviceMaintenanceMapper.selectList(Wrappers.<DeviceMaintenance>lambdaQuery()
+                .eq(DeviceMaintenance::getReviewStatus, 1)
+                .isNotNull(DeviceMaintenance::getMaintenancePlanDetailId)
+                .isNotNull(DeviceMaintenance::getReviewTime));
+        if (CollectionUtils.isEmpty(records)) {
+            return;
+        }
+        List<Integer> detailIds = records.stream().map(DeviceMaintenance::getMaintenancePlanDetailId)
+                .distinct().collect(Collectors.toList());
+        Map<Integer, DeviceMaintenancePlanDetails> detailMap = deviceMaintenancePlanDetailsMapper.selectBatchIds(detailIds)
+                .stream().collect(Collectors.toMap(DeviceMaintenancePlanDetails::getMaintenancePlanDetailId, item -> item, (a, b) -> a));
+        Map<Integer, String> planYears = baseMapper.selectList(null).stream()
+                .collect(Collectors.toMap(DeviceMaintenancePlan::getMaintenancePlanId,
+                        item -> item.getPlanYear() == null ? "" : item.getPlanYear().trim(), (a, b) -> a));
+        for (DeviceMaintenance record : records) {
+            DeviceMaintenancePlanDetails detail = detailMap.get(record.getMaintenancePlanDetailId());
+            if (detail == null || detail.getMaintenancePlanId() == null) {
+                continue;
+            }
+            String reviewYear = String.valueOf(record.getReviewTime().getYear());
+            if (reviewYear.equals(planYears.get(detail.getMaintenancePlanId()))) {
+                continue;
+            }
+            moveMaintenanceDetailToYear(detail, record.getReviewTime().toLocalDate());
+        }
     }
 
     /**
@@ -401,7 +496,7 @@
     }
 
     /**
-     * 鎵弿鎵�鏈夎澶囷紝鎶婂凡鍒扮偣锛堜笅娆′繚鍏绘棩鏈熷墠1涓湀璧凤紝鍚凡杩囨湡锛夌殑璁惧琛ヨ繘鏈勾搴﹁鍒�
+     * 鎵弿鎵�鏈夎澶囷紝鎶婂簲淇濆吇鏃ユ湡钀藉湪璇ヨ鍒掑勾浠界殑璁惧琛ヨ繘璁″垝锛堝綋鍓嶅勾鍙婁互寰�骞村害鍖呭惈閫炬湡鏈繚鍏荤殑璁惧锛�
      */
     private void ensureMaintenanceDetails(DeviceMaintenancePlan plan) {
         List<Device> devices = deviceMapper.selectList(null);
@@ -411,9 +506,9 @@
     }
 
     /**
-     * 璁惧鎻愰啋璧风偣锛氫笅娆′繚鍏绘棩鏈熷墠1涓湀锛堟病濉笅娆℃棩鏈熸椂鐢ㄦ渶杩戞棩鏈�+淇濆吇鍛ㄦ湡鍏滃簳锛�
+     * 璁惧鏈疆搴斾繚鍏绘棩鏈燂紙娌″~涓嬫鏃ユ湡鏃剁敤鏈�杩戞棩鏈�+淇濆吇鍛ㄦ湡鍏滃簳锛�
      */
-    private LocalDate calcMaintenanceRemindDate(Device device) {
+    private LocalDate calcMaintenanceDueDate(Device device) {
         LocalDateTime base = device.getNextMaintenanceDate();
         if (base == null && device.getLastMaintenanceDate() != null && StringUtils.isNotBlank(device.getMaintenanceCycle())) {
             try {
@@ -421,24 +516,42 @@
             } catch (NumberFormatException ignored) {
             }
         }
-        return base == null ? null : base.toLocalDate().minusMonths(1);
+        return base == null ? null : base.toLocalDate();
     }
 
     /**
-     * 宸插埌鐐逛笖璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃惰嚜鍔ㄨˉ涓�鏉�
+     * 璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃惰嚜鍔ㄨˉ涓�鏉★紱淇濆吇鏃堕棿鍙栧簲淇濆吇鏃ユ湡
      */
     private void fillMaintenanceDetailIfAbsent(DeviceMaintenancePlan plan, Device device) {
-        LocalDate remind = calcMaintenanceRemindDate(device);
-        if (remind == null || remind.isAfter(LocalDate.now())) {
+        LocalDate due = calcMaintenanceDueDate(device);
+        if (due == null) {
             return;
         }
-        if (plan.getPlanYear() != null && String.valueOf(remind.getYear()).compareTo(plan.getPlanYear()) > 0) {
+        if (StringUtils.isBlank(plan.getPlanYear())) {
             return;
         }
+        int planYearValue;
+        try {
+            planYearValue = Integer.parseInt(plan.getPlanYear().trim());
+        } catch (NumberFormatException e) {
+            return;
+        }
+        int dueYear = due.getYear();
+        // 鏈潵骞翠唤鐨勮鍒掑彧鐧昏搴斾繚鍏绘棩鏈熷氨鍦ㄨ骞翠唤鐨勮澶囷紝閬垮厤鎶婂線骞寸殑鏃ф棩鏈熻ˉ杩涙湭鏉ョ殑璁″垝
+        if (planYearValue > LocalDate.now().getYear()) {
+            if (dueYear != planYearValue) {
+                return;
+            }
+        } else if (dueYear > planYearValue) {
+            return;
+        }
+        // 鍚屼竴璁惧鍚屼竴搴斾繚鍏绘湀浠藉彧淇濈暀涓�鏉★紝閬垮厤鎵嬫敼杩囪鍐呮棩鏈熷悗鍒锋柊閲嶅鐢熸垚
+        LocalDate monthStart = due.withDayOfMonth(1);
         Long exist = deviceMaintenancePlanDetailsMapper.selectCount(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
                 .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, plan.getMaintenancePlanId())
                 .eq(DeviceMaintenancePlanDetails::getDeviceId, device.getId())
-                .eq(DeviceMaintenancePlanDetails::getMaintenanceDate, remind));
+                .ge(DeviceMaintenancePlanDetails::getMaintenanceDate, monthStart)
+                .lt(DeviceMaintenancePlanDetails::getMaintenanceDate, monthStart.plusMonths(1)));
         if (exist != null && exist > 0) {
             return;
         }
@@ -446,8 +559,117 @@
         detail.setMaintenancePlanId(plan.getMaintenancePlanId());
         detail.setDeviceId(device.getId());
         detail.setMaintenanceIntervals(device.getMaintenanceCycle());
-        detail.setMaintenanceDate(remind);
+        detail.setMaintenanceDate(due);
         deviceMaintenancePlanDetailsMapper.insert(detail);
     }
 
+    /**
+     * 涓轰繚鍏昏褰曟壘涓�鏉″彲鍏宠仈鐨勮鍒掓槑缁嗭紱娌℃湁鍙敤鏄庣粏鏃跺湪褰撳墠骞村害璁″垝鏂板鏈疆鏄庣粏
+     */
+    @Override
+    public DeviceMaintenancePlanDetails resolveMaintenancePlanDetail(Integer deviceId, Integer maintenancePlanDetailId, LocalDate maintenanceDate) {
+        if (maintenancePlanDetailId != null) {
+            DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId);
+            if (detail == null) {
+                throw new ErrorException("淇濆吇璁″垝鏄庣粏涓嶅瓨鍦�");
+            }
+            return detail;
+        }
+        if (deviceId == null) {
+            throw new ErrorException("缂哄皯璁惧id");
+        }
+        Device device = deviceMapper.selectById(deviceId);
+        if (device == null) {
+            throw new ErrorException("淇濆吇璁惧涓嶅瓨鍦�");
+        }
+        LocalDate planDate = maintenanceDate == null ? LocalDate.now() : maintenanceDate;
+        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(planDate.getYear()));
+        List<DeviceMaintenancePlanDetails> details = deviceMaintenancePlanDetailsMapper.selectList(
+                Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
+                        .eq(DeviceMaintenancePlanDetails::getDeviceId, deviceId)
+                        .orderByAsc(DeviceMaintenancePlanDetails::getMaintenanceDate));
+        DeviceMaintenancePlanDetails nearest = null;
+        long nearestDistance = Long.MAX_VALUE;
+        for (DeviceMaintenancePlanDetails detail : details) {
+            // 宸叉湁淇濆吇璁板綍鐨勬槑缁嗕唬琛ㄥ凡瀹屾垚鐨勪竴杞紝鍚庣画璁板綍姘歌繙涓嶅鐢ㄣ�佷笉瑕嗙洊
+            Long recordCount = deviceMaintenanceMapper.selectCount(Wrappers.<DeviceMaintenance>lambdaQuery()
+                    .eq(DeviceMaintenance::getMaintenancePlanDetailId, detail.getMaintenancePlanDetailId()));
+            if (recordCount != null && recordCount > 0) {
+                continue;
+            }
+            if (detail.getMaintenancePlanId() != null && detail.getMaintenancePlanId().equals(plan.getMaintenancePlanId())) {
+                return detail;
+            }
+            long distance = monthDistance(detail.getMaintenanceDate(), planDate);
+            if (distance < nearestDistance) {
+                nearest = detail;
+                nearestDistance = distance;
+            }
+        }
+        if (nearest != null) {
+            return nearest;
+        }
+        DeviceMaintenancePlanDetails detail = new DeviceMaintenancePlanDetails();
+        detail.setMaintenancePlanId(plan.getMaintenancePlanId());
+        detail.setDeviceId(device.getId());
+        detail.setMaintenanceIntervals(device.getMaintenanceCycle());
+        detail.setMaintenanceDate(planDate);
+        deviceMaintenancePlanDetailsMapper.insert(detail);
+        return detail;
+    }
+
+    /**
+     * 淇敼璁惧淇濆吇璁″垝璇︽儏锛堝凡瀹℃牳鐨勬槑缁嗕笉鍏佽淇敼锛�
+     */
+    @Override
+    public boolean updateMaintenancePlanDetail(DeviceMaintenancePlanDetails detail) {
+        if (detail == null || detail.getMaintenancePlanDetailId() == null) {
+            throw new ErrorException("缂哄皯璁惧淇濆吇璁″垝鏄庣粏id");
+        }
+        DeviceMaintenance record = deviceMaintenanceMapper.selectOne(Wrappers.<DeviceMaintenance>lambdaQuery()
+                .eq(DeviceMaintenance::getMaintenancePlanDetailId, detail.getMaintenancePlanDetailId())
+                .last("limit 1"));
+        if (record != null && record.getReviewStatus() != null) {
+            throw new ErrorException("璇ヨ鍒掑凡瀹℃牳锛屼笉鍏佽淇敼");
+        }
+        return deviceMaintenancePlanDetailsMapper.updateById(detail) > 0;
+    }
+
+    /**
+     * 鍒犻櫎璁惧淇濆吇璁″垝璇︽儏锛涘凡鍑哄鏍哥粨鏋滅殑璁板綍闅忓巻鍙茬暀妗o紝鏈鏍哥殑璁板綍闅忔槑缁嗕竴璧峰垹闄�
+     */
+    @Override
+    public boolean removeMaintenancePlanDetail(Integer maintenancePlanDetailId) {
+        if (maintenancePlanDetailId == null) {
+            throw new ErrorException("缂哄皯璁惧淇濆吇璁″垝鏄庣粏id");
+        }
+        DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectById(maintenancePlanDetailId);
+        if (detail == null) {
+            return false;
+        }
+        List<DeviceMaintenance> records = deviceMaintenanceMapper.selectList(Wrappers.<DeviceMaintenance>lambdaQuery()
+                .eq(DeviceMaintenance::getMaintenancePlanDetailId, maintenancePlanDetailId));
+        // 瀹℃牳涓嶉�氳繃鐨勮褰曞厑璁歌繛鍚屾槑缁嗕竴璧峰垹闄わ紱瀹℃牳閫氳繃鐨勮褰曚负鍘嗗彶鐣欐。锛屼笉鍏佽鍒犻櫎
+        boolean hasPassedRecord = records.stream().anyMatch(item -> item.getReviewStatus() != null
+                && item.getReviewStatus() == 1);
+        if (hasPassedRecord) {
+            throw new ErrorException("璇ヨ鍒掑凡瀛樺湪瀹℃牳閫氳繃鐨勪繚鍏昏褰曪紝涓轰繚鐣欏巻鍙茶褰曚笉鍏佽鍒犻櫎");
+        }
+        if (!records.isEmpty()) {
+            List<Integer> recordIds = records.stream().map(DeviceMaintenance::getId).collect(Collectors.toList());
+            deviceMaintenanceMapper.deleteBatchIds(recordIds);
+        }
+        return deviceMaintenancePlanDetailsMapper.deleteById(maintenancePlanDetailId) > 0;
+    }
+
+    /**
+     * 涓や釜淇濆吇鏃ユ湡涔嬮棿鐩稿樊鐨勬湀鏁�
+     */
+    private long monthDistance(LocalDate from, LocalDate target) {
+        if (from == null || target == null) {
+            return Long.MAX_VALUE;
+        }
+        return Math.abs(ChronoUnit.MONTHS.between(YearMonth.from(from), YearMonth.from(target)));
+    }
+
 }

--
Gitblit v1.9.3