From 6ab1565a1a86623af925719935d605e875d98af4 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期五, 23 十月 2026 18:08:56 +0800
Subject: [PATCH] 设备核查计划:设备档案变更即时登记计划明细,提前核查复用待核查明细并回写档案,已完成记录不可覆盖;新增附件字段、明细固定排序与删除保护;人员查询新增包含admin的类型;检验保存/提交按页面模板校验,复核驳回按检验室删除复核人

---
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java |  303 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 301 insertions(+), 2 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 4a4d45b..a8b3ace 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
@@ -22,10 +22,14 @@
 import com.ruoyi.device.excel.upload.DeviceExaminePlanUpload;
 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.DeviceExaminePlan;
 import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
+import com.ruoyi.device.pojo.DeviceExamineRecord;
+import com.ruoyi.device.pojo.DeviceExamineRecordContrast;
 import com.ruoyi.device.service.DeviceExaminePlanDetailsService;
 import com.ruoyi.device.service.DeviceExaminePlanService;
 import com.ruoyi.framework.exception.ErrorException;
@@ -45,7 +49,11 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 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.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -74,6 +82,10 @@
     private DeviceMapper deviceMapper;
     @Resource
     private UserMapper userMapper;
+    @Resource
+    private DeviceExamineRecordMapper deviceExamineRecordMapper;
+    @Resource
+    private DeviceExamineRecordContrastMapper deviceExamineRecordContrastMapper;
 
     /**
      * 鏂板璁惧鏍告煡璁″垝
@@ -289,9 +301,9 @@
                     put("deviceExaminePlan", deviceExaminePlanDto);
                     put("deviceExaminePlanDetailsDtoList", deviceExaminePlanDetailsDtoList);
                     //鑾峰彇鏍告煡浜虹殑绛惧悕鍦板潃
-                    put("writeUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExaminePlanDto.getWriteUserId()));
+                    put("writeUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getWriteUserId()));
                     //鑾峰彇瀹℃煡浜虹殑绛惧悕鍦板潃
-                    put("reviewUserUrl", UserUtils.getFinalUserSignatureUrl(deviceExaminePlanDto.getRatifyUserId()));
+                    put("reviewUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceExaminePlanDto.getRatifyUserId()));
                 }});
 
         try {
@@ -346,4 +358,291 @@
 
         return deviceExaminePlanDto;
     }
+
+    /**
+     * 鎸夊勾浠芥煡璇㈣澶囨牳鏌ヨ鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     */
+    @Override
+    public DeviceExaminePlanDto getOrCreatePlanByYear(String planYear) {
+        if (StringUtils.isBlank(planYear)) {
+            planYear = String.valueOf(LocalDate.now().getYear());
+        }
+        DeviceExaminePlan plan = getOrCreatePlanEntity(planYear);
+        ensureExamineDetails(plan);
+        DeviceExaminePlanDto planDto = new DeviceExaminePlanDto();
+        BeanUtils.copyProperties(plan, planDto);
+        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        planDto.setWriteTimeStr(plan.getWriteTime() == null ? null : plan.getWriteTime().format(dateFormatter));
+        planDto.setRatifyTimeStr(plan.getRatifyTime() == null ? null : plan.getRatifyTime().format(dateFormatter));
+        if (plan.getWriteUserId() != null) {
+            User writeUser = userMapper.selectById(plan.getWriteUserId());
+            if (writeUser != null) {
+                planDto.setWriteName(writeUser.getName());
+            }
+        }
+        if (plan.getRatifyUserId() != null) {
+            User ratifyUser = userMapper.selectById(plan.getRatifyUserId());
+            if (ratifyUser != null) {
+                planDto.setRatifyName(ratifyUser.getName());
+            }
+        }
+        return planDto;
+    }
+
+    /**
+     * 璁惧妗f鍙樻洿鍚庯紝绔嬪嵆鎶婅澶囩櫥璁板埌涓嬫鏍告煡鏃ユ湡鎵�鍦ㄥ勾浠界殑璁惧鏍告煡璁″垝鏄庣粏
+     */
+    @Override
+    public void syncDeviceExamineDetail(Device device) {
+        LocalDate due = calcExamineDueDate(device);
+        if (due == null) {
+            return;
+        }
+        DeviceExaminePlan plan = getOrCreatePlanEntity(String.valueOf(due.getYear()));
+        fillExamineDetailIfAbsent(plan, device);
+    }
+
+    /**
+     * 澶嶆牳閫氳繃鍚庯紝鎶婅澶囨。妗堢殑鏈�杩�/涓嬫鏍告煡鏃ユ湡婊氬姩鍒版湰杞�
+     */
+    @Override
+    public void updateDeviceExamineDate(Integer planDetailsId) {
+        DeviceExaminePlanDetails detail = deviceExaminePlanDetailsMapper.selectById(planDetailsId);
+        if (detail == null || detail.getDeviceId() == null) {
+            return;
+        }
+        Device device = deviceMapper.selectById(detail.getDeviceId());
+        if (device == null) {
+            return;
+        }
+        // 鎻愬墠鏍告煡鏃惰鍒掓湀浠藉彲鑳借繕娌″埌锛屽彇瀹為檯瀹屾垚褰撳ぉ锛涘惁鍒欐寜鏈疆璁″垝鏍告煡鏈堜唤
+        LocalDate checkDate = parseCheckDate(detail.getCheckTime());
+        if (checkDate.isAfter(LocalDate.now())) {
+            checkDate = LocalDate.now();
+        }
+        LocalDateTime checkDateTime = checkDate.atStartOfDay();
+        // 鍙厑璁稿悜鍓嶆粴鍔紝閬垮厤閲嶅澶嶆牳鏃ц褰曟椂鎶婃棩鏈熸敼鍥炲幓
+        if (device.getLastExamineDate() != null && !checkDateTime.isAfter(device.getLastExamineDate())) {
+            return;
+        }
+        device.setLastExamineDate(checkDateTime);
+        if (StringUtils.isNotBlank(device.getExamineCycle())) {
+            try {
+                device.setNextExamineDate(checkDateTime.plusMonths(Long.parseLong(device.getExamineCycle().trim())));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        deviceMapper.updateById(device);
+        // 鏈疆鏍告煡瀹屾垚鍚庣珛鍗崇櫥璁颁笅涓�杞紝鍘嗗彶鏄庣粏鍜岃褰曚繚鎸佷笉鍔�
+        syncDeviceExamineDetail(device);
+    }
+
+    /**
+     * 鏌ヨ骞翠唤瀵瑰簲鐨勮鍒掞紝涓嶅瓨鍦ㄥ垯鍒涘缓
+     */
+    private DeviceExaminePlan getOrCreatePlanEntity(String planYear) {
+        DeviceExaminePlan plan = baseMapper.selectOne(Wrappers.<DeviceExaminePlan>lambdaQuery()
+                .eq(DeviceExaminePlan::getPlanYear, planYear)
+                .orderByDesc(DeviceExaminePlan::getPlanId)
+                .last("limit 1"));
+        if (plan == null) {
+            plan = new DeviceExaminePlan();
+            plan.setPlanName(planYear + "骞村害璁惧鏍告煡璁″垝");
+            plan.setPlanYear(planYear);
+            plan.setWriteUserId(SecurityUtils.getUserId().intValue());
+            plan.setWriteTime(LocalDateTime.now());
+            baseMapper.insert(plan);
+        }
+        return plan;
+    }
+
+    /**
+     * 鎵弿鎵�鏈夎澶囷紝鎶婃湰骞村害鍙婂線骞撮�炬湡鏈牳鏌ョ殑璁惧琛ヨ繘璁″垝
+     */
+    private void ensureExamineDetails(DeviceExaminePlan plan) {
+        List<Device> devices = deviceMapper.selectList(null);
+        for (Device device : devices) {
+            fillExamineDetailIfAbsent(plan, device);
+        }
+    }
+
+    /**
+     * 璁惧鏈疆搴旀牳鏌ユ棩鏈燂紙娌″~涓嬫鏃ユ湡鏃剁敤鏈�杩戞棩鏈�+鏍告煡鍛ㄦ湡鍏滃簳锛�
+     */
+    private LocalDate calcExamineDueDate(Device device) {
+        LocalDateTime base = device.getNextExamineDate();
+        if (base == null && device.getLastExamineDate() != null && StringUtils.isNotBlank(device.getExamineCycle())) {
+            try {
+                base = device.getLastExamineDate().plusMonths(Long.parseLong(device.getExamineCycle().trim()));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        return base == null ? null : base.toLocalDate();
+    }
+
+    /**
+     * 璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃惰嚜鍔ㄨˉ涓�鏉★紱鏍告煡鏃堕棿鍙栧簲鏍告煡鏈堜唤
+     */
+    private void fillExamineDetailIfAbsent(DeviceExaminePlan plan, Device device) {
+        LocalDate due = calcExamineDueDate(device);
+        if (due == null) {
+            return;
+        }
+        if (plan.getPlanYear() != null && String.valueOf(due.getYear()).compareTo(plan.getPlanYear()) > 0) {
+            return;
+        }
+        String checkTime = due.format(DateTimeFormatter.ofPattern("yyyy-MM"));
+        Long exist = deviceExaminePlanDetailsMapper.selectCount(Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+                .eq(DeviceExaminePlanDetails::getPlanId, plan.getPlanId())
+                .eq(DeviceExaminePlanDetails::getDeviceId, device.getId())
+                .likeRight(DeviceExaminePlanDetails::getCheckTime, checkTime));
+        if (exist != null && exist > 0) {
+            return;
+        }
+        DeviceExaminePlanDetails detail = new DeviceExaminePlanDetails();
+        detail.setPlanId(plan.getPlanId());
+        detail.setDeviceId(device.getId());
+        String deviceName = device.getDeviceName() == null ? "" : device.getDeviceName();
+        if (StringUtils.isNotBlank(device.getSpecificationModel())) {
+            deviceName = deviceName + " " + device.getSpecificationModel();
+        }
+        detail.setDeviceName(deviceName);
+        detail.setDeviceNumber(device.getManagementNumber());
+        detail.setCheckTime(checkTime);
+        deviceExaminePlanDetailsMapper.insert(detail);
+    }
+
+    /**
+     * 璁″垝鏄庣粏鐨勬牳鏌ユ椂闂村彲鑳芥槸 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) {
+        if (deviceId == null) {
+            throw new ErrorException("璇烽�夋嫨鏍告煡璁惧");
+        }
+        Device device = deviceMapper.selectById(deviceId);
+        if (device == null) {
+            throw new ErrorException("鏍告煡璁惧涓嶅瓨鍦�");
+        }
+        String checkMonth = normalizeCheckMonth(checkTime);
+        DeviceExaminePlan plan = getOrCreatePlanEntity(checkMonth.substring(0, 4));
+        List<DeviceExaminePlanDetails> details = deviceExaminePlanDetailsMapper.selectList(
+                Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
+                        .eq(DeviceExaminePlanDetails::getDeviceId, deviceId)
+                        .orderByAsc(DeviceExaminePlanDetails::getCheckTime));
+        DeviceExaminePlanDetails nearest = null;
+        long nearestDistance = Long.MAX_VALUE;
+        for (DeviceExaminePlanDetails detail : details) {
+            // 宸叉湁鏍告煡璁板綍鐨勬槑缁嗕唬琛ㄥ凡瀹屾垚鐨勪竴杞紝鍚庣画鏍告煡姘歌繙涓嶅鐢ㄣ�佷笉瑕嗙洊
+            Long recordCount = deviceExamineRecordMapper.selectCount(Wrappers.<DeviceExamineRecord>lambdaQuery()
+                    .eq(DeviceExamineRecord::getPlanDetailsId, detail.getPlanDetailsId()));
+            if (recordCount != null && recordCount > 0) {
+                continue;
+            }
+            if (detail.getPlanId() != null && detail.getPlanId().equals(plan.getPlanId())) {
+                return detail;
+            }
+            long distance = monthDistance(detail.getCheckTime(), checkMonth);
+            if (distance < nearestDistance) {
+                nearest = detail;
+                nearestDistance = distance;
+            }
+        }
+        // 鎻愬墠鏍告煡鏃朵紭鍏堝鐢ㄧ郴缁熺櫥璁般�佸皻鏈牳鏌ョ殑杩欎竴杞紙鍙兘鍦ㄥ埆鐨勫勾浠借鍒掗噷锛�
+        if (nearest != null) {
+            return nearest;
+        }
+        DeviceExaminePlanDetails detail = new DeviceExaminePlanDetails();
+        detail.setPlanId(plan.getPlanId());
+        detail.setDeviceId(device.getId());
+        String deviceName = device.getDeviceName() == null ? "" : device.getDeviceName();
+        if (StringUtils.isNotBlank(device.getSpecificationModel())) {
+            deviceName = deviceName + " " + device.getSpecificationModel();
+        }
+        detail.setDeviceName(deviceName);
+        detail.setDeviceNumber(device.getManagementNumber());
+        detail.setCheckTime(checkMonth);
+        deviceExaminePlanDetailsMapper.insert(detail);
+        return detail;
+    }
+
+    /**
+     * 瑙勮寖鏍告煡鏈堜唤涓� yyyy-MM锛岄潪娉曟垨涓虹┖鏃跺彇褰撳墠鏈堜唤
+     */
+    private String normalizeCheckMonth(String checkTime) {
+        if (StringUtils.isNotBlank(checkTime)) {
+            String normalized = checkTime.trim().replace('.', '-').replace('/', '-');
+            if (normalized.length() >= 7) {
+                try {
+                    return YearMonth.parse(normalized.substring(0, 7)).toString();
+                } catch (Exception ignored) {
+                }
+            }
+        }
+        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM"));
+    }
+
+    /**
+     * 涓や釜鏈堜唤涔嬮棿鐩稿樊鐨勬湀鏁�
+     */
+    private long monthDistance(String fromCheckTime, String targetMonth) {
+        try {
+            YearMonth from = YearMonth.parse(normalizeCheckMonth(fromCheckTime));
+            YearMonth target = YearMonth.parse(targetMonth);
+            return Math.abs(ChronoUnit.MONTHS.between(from, target));
+        } catch (Exception e) {
+            return Long.MAX_VALUE;
+        }
+    }
+
+    /**
+     * 淇敼璁″垝鏄庣粏锛涙牳鏌ユ湀浠借法骞存椂锛屾妸鏄庣粏褰掑埌鏍告煡鏈堜唤鎵�灞炲勾浠界殑璁″垝涓�
+     */
+    @Override
+    public boolean updateExaminePlanDetail(DeviceExaminePlanDetails detail) {
+        if (detail == null || detail.getPlanDetailsId() == null) {
+            throw new ErrorException("缂哄皯璁惧鏍告煡璁″垝鏄庣粏id");
+        }
+        if (StringUtils.isNotBlank(detail.getCheckTime())) {
+            String checkMonth = normalizeCheckMonth(detail.getCheckTime());
+            detail.setCheckTime(checkMonth);
+            DeviceExaminePlan plan = getOrCreatePlanEntity(checkMonth.substring(0, 4));
+            detail.setPlanId(plan.getPlanId());
+        }
+        return deviceExaminePlanDetailsMapper.updateById(detail) > 0;
+    }
+
+    /**
+     * 鍒犻櫎璁″垝鏄庣粏锛涘凡鏈夋牳鏌�/姣斿璁板綍鐨勪笉鍏佽鍒犻櫎锛屼繚璇佸巻鍙茬暀妗�
+     */
+    @Override
+    public boolean removeExaminePlanDetail(Integer planDetailsId) {
+        if (planDetailsId == null) {
+            throw new ErrorException("缂哄皯璁惧鏍告煡璁″垝鏄庣粏id");
+        }
+        Long recordCount = deviceExamineRecordMapper.selectCount(Wrappers.<DeviceExamineRecord>lambdaQuery()
+                .eq(DeviceExamineRecord::getPlanDetailsId, planDetailsId));
+        Long contrastCount = deviceExamineRecordContrastMapper.selectCount(Wrappers.<DeviceExamineRecordContrast>lambdaQuery()
+                .eq(DeviceExamineRecordContrast::getPlanDetailsId, planDetailsId));
+        if ((recordCount != null && recordCount > 0) || (contrastCount != null && contrastCount > 0)) {
+            throw new ErrorException("璇ヨ鍒掑凡瀛樺湪鏍告煡璁板綍锛屼负淇濈暀鍘嗗彶璁板綍涓嶅厑璁稿垹闄�");
+        }
+        return deviceExaminePlanDetailsService.removeById(planDetailsId);
+    }
 }

--
Gitblit v1.9.3