From 2f07589b54d56085af77b50d580890cd4130b444 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期一, 19 十月 2026 15:19:53 +0800
Subject: [PATCH] 设备模块后端业务规则、计划接口与日期联动

---
 cnas-device/src/main/java/com/ruoyi/device/service/DeviceCalibrationPlanService.java                |   17 
 cnas-device/src/main/java/com/ruoyi/device/dto/DeviceMaintenancePlanDetailsDto.java                 |    3 
 cnas-device/src/main/java/com/ruoyi/device/util/DeviceFlowPermissionUtils.java                      |   30 +
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenancePlanServiceImpl.java       |  161 +++++++
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceCalibrationPlanController.java          |   13 
 cnas-device/src/main/java/com/ruoyi/device/service/DeviceStateService.java                          |    5 
 cnas-device/src/main/resources/mapper/DeviceMaintenancePlanDetailsMapper.xml                        |   25 +
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java  |   40 +
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExaminePlanServiceImpl.java           |  169 ++++++++
 cnas-device/src/main/java/com/ruoyi/device/service/DeviceExaminePlanService.java                    |   20 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java                      |  172 -------
 cnas-device/src/main/java/com/ruoyi/device/service/DeviceMaintenancePlanService.java                |   27 +
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBorrowServiceImpl.java                |   16 
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceStateController.java                    |    2 
 cnas-device/src/main/resources/mapper/DeviceExaminePlanDetailsMapper.xml                            |    9 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java               |   12 
 cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceCalibrationPlanDetailMapper.java            |    3 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java                 |   62 ++
 cnas-device/src/main/java/com/ruoyi/device/dto/DeviceExaminePlanDetailsDto.java                     |    3 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java              |   42 +
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java              |   11 
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenancePlanController.java          |   42 ++
 ruoyi-common/src/main/java/com/ruoyi/common/numgen/LambdaUtils.java                                 |   11 
 cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceMaintenancePlanDetailsMapper.java           |   10 
 cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCalibrationPlanDetailDto.java                  |    2 
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMetricRecordController.java             |   49 ++
 cnas-device/src/main/resources/mapper/DeviceCalibrationPlanDetailMapper.xml                         |   13 
 cnas-require/src/main/resources/mapper/FeStandardSubstanceMapper.xml                                |   26 
 cnas-device/src/main/java/com/ruoyi/device/controller/DeviceExaminePlanController.java              |   10 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java |    7 
 cnas-device/src/main/resources/mapper/DeviceMapper.xml                                              |    2 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java         |   42 +
 ruoyi-system/src/main/resources/mapper/system/UserMapper.xml                                        |   23 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java         |    7 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java        |   42 +
 cnas-require/src/main/java/com/ruoyi/require/pojo/FeStandardSubstance.java                          |    4 
 cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java       |  127 +++++
 37 files changed, 1,055 insertions(+), 204 deletions(-)

diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceCalibrationPlanController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceCalibrationPlanController.java
index 438d008..0f8043a 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceCalibrationPlanController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceCalibrationPlanController.java
@@ -3,6 +3,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.core.domain.Result;
+import com.ruoyi.device.dto.DeviceCalibrationPlanDetailDto;
 import com.ruoyi.device.dto.DeviceCalibrationPlanDto;
 import com.ruoyi.device.pojo.DeviceCalibrationPlan;
 import com.ruoyi.device.pojo.DeviceCalibrationPlanDetail;
@@ -118,12 +119,22 @@
     }
 
     /**
+     * 鎸夊勾浠芥煡璇㈣澶囨牎鍑嗚鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     * @return
+     */
+    @Operation(summary = "鎸夊勾浠芥煡璇㈣澶囨牎鍑嗚鍒掞紙鑷姩鍒涘缓骞惰ˉ鍏ㄥ埌鐐规槑缁嗭級")
+    @GetMapping("/getPlanByYear")
+    public Result<DeviceCalibrationPlanDto> getPlanByYear(String planYear) {
+        return Result.success(deviceCalibrationPlanService.getOrCreatePlanByYear(planYear));
+    }
+
+    /**
      * 璁惧鏍″噯璁″垝璇︽儏鍒楄〃
      * @return
      */
     @Operation(summary = "璁惧鏍″噯璁″垝璇︽儏鍒楄〃")
     @GetMapping("/pageDeviceCalibrationPlanDetail")
-    public Result<IPage<DeviceCalibrationPlanDetail>> pageDeviceCalibrationPlanDetail(Page page, DeviceCalibrationPlanDetail deviceCalibrationPlanDetails) {
+    public Result<IPage<DeviceCalibrationPlanDetailDto>> pageDeviceCalibrationPlanDetail(Page page, DeviceCalibrationPlanDetail deviceCalibrationPlanDetails) {
         return Result.success(deviceCalibrationPlanService.pageDeviceCalibrationPlanDetail(page, deviceCalibrationPlanDetails));
     }
 
diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceExaminePlanController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceExaminePlanController.java
index deeeb1c..b2aa097 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceExaminePlanController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceExaminePlanController.java
@@ -129,6 +129,16 @@
     }
 
     /**
+     * 鎸夊勾浠芥煡璇㈣澶囨牳鏌ヨ鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     * @return
+     */
+    @Operation(summary = "鎸夊勾浠芥煡璇㈣澶囨牳鏌ヨ鍒掞紙鑷姩鍒涘缓骞惰ˉ鍏ㄥ埌鐐规槑缁嗭級")
+    @GetMapping("/getPlanByYear")
+    public Result<DeviceExaminePlanDto> getPlanByYear(String planYear) {
+        return Result.success(deviceExaminePlanService.getOrCreatePlanByYear(planYear));
+    }
+
+    /**
      * 璁惧鏍告煡璁″垝璇︽儏鍒楄〃
      * @return
      */
diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
index 7220c40..58dd316 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
@@ -5,6 +5,7 @@
 import com.ruoyi.common.core.domain.entity.User;
 import com.ruoyi.device.dto.DeviceMaintenanceDto;
 import com.ruoyi.device.pojo.DeviceMaintenance;
+import com.ruoyi.device.service.DeviceMaintenancePlanService;
 import com.ruoyi.device.service.DeviceMaintenanceService;
 import com.ruoyi.system.mapper.UserMapper;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -27,6 +28,9 @@
 
     @Autowired
     private UserMapper userMapper;
+
+    @Autowired
+    private DeviceMaintenancePlanService deviceMaintenancePlanService;
 
 
     /**
@@ -55,7 +59,12 @@
         User user = userMapper.selectById(deviceMaintenance.getMaintenanceUserId());
         deviceMaintenance.setMaintenanceUserName(user.getName());
 
-        return Result.success(deviceMaintenanceService.saveOrUpdate(deviceMaintenance));
+        boolean saved = deviceMaintenanceService.saveOrUpdate(deviceMaintenance);
+        // 淇濆吇瀹屾垚鍚庯紝鎶婅澶囨。妗堢殑鏈�杩�/涓嬫淇濆吇鏃ユ湡婊氬姩鍒版湰杞�
+        if (saved && deviceMaintenance.getMaintenanceDate() != null) {
+            deviceMaintenancePlanService.updateDeviceMaintenanceDate(deviceMaintenance.getDeviceId(), deviceMaintenance.getMaintenanceDate());
+        }
+        return Result.success(saved);
     }
 
     /**
diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenancePlanController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenancePlanController.java
index 81be805..3de42ec 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenancePlanController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMaintenancePlanController.java
@@ -3,8 +3,11 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.core.domain.Result;
+import com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto;
 import com.ruoyi.device.dto.DeviceMaintenancePlanDto;
 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 io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
@@ -29,6 +32,9 @@
 public class DeviceMaintenancePlanController {
     @Resource
     private DeviceMaintenancePlanService deviceMaintenancePlanService;
+
+    @Resource
+    private DeviceMaintenancePlanDetailsService deviceMaintenancePlanDetailsService;
 
     /**
      * 鍒嗛〉鏌ヨ璁惧淇濆吇璁″垝
@@ -80,6 +86,42 @@
     }
 
     /**
+     * 鎸夊勾浠芥煡璇㈣澶囦繚鍏昏鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     */
+    @Operation(summary = "鎸夊勾浠芥煡璇㈣澶囦繚鍏昏鍒掞紙鑷姩鍒涘缓骞惰ˉ鍏ㄥ埌鐐规槑缁嗭級")
+    @GetMapping("/getPlanByYear")
+    public Result<DeviceMaintenancePlanDto> getPlanByYear(String planYear) {
+        return Result.success(deviceMaintenancePlanService.getOrCreatePlanByYear(planYear));
+    }
+
+    /**
+     * 璁惧淇濆吇璁″垝璇︽儏鍒楄〃锛堝惈鍒扮偣鏍囩孩锛�
+     */
+    @Operation(summary = "璁惧淇濆吇璁″垝璇︽儏鍒楄〃")
+    @GetMapping("/pageMaintenancePlanDetail")
+    public Result<IPage<DeviceMaintenancePlanDetailsDto>> pageMaintenancePlanDetail(Page page, Integer planId, String deviceName, String deviceNumber) {
+        return Result.success(deviceMaintenancePlanService.pageMaintenancePlanDetail(page, planId, deviceName, deviceNumber));
+    }
+
+    /**
+     * 淇敼璁惧淇濆吇璁″垝璇︽儏
+     */
+    @Operation(summary = "淇敼璁惧淇濆吇璁″垝璇︽儏")
+    @PostMapping("/updateMaintenancePlanDetail")
+    public Result updateMaintenancePlanDetail(@RequestBody DeviceMaintenancePlanDetails deviceMaintenancePlanDetails) {
+        return Result.success(deviceMaintenancePlanDetailsService.updateById(deviceMaintenancePlanDetails));
+    }
+
+    /**
+     * 鍒犻櫎璁惧淇濆吇璁″垝璇︽儏
+     */
+    @Operation(summary = "鍒犻櫎璁惧淇濆吇璁″垝璇︽儏")
+    @DeleteMapping("/delMaintenancePlanDetail")
+    public Result delMaintenancePlanDetail(Integer maintenancePlanDetailId) {
+        return Result.success(deviceMaintenancePlanDetailsService.removeById(maintenancePlanDetailId));
+    }
+
+    /**
      * 瀵煎嚭璁惧淇濆吇璁″垝
      */
     @Operation(summary = "瀵煎嚭璁惧淇濆吇璁″垝")
diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMetricRecordController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMetricRecordController.java
index 2f52724..6984929 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMetricRecordController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceMetricRecordController.java
@@ -2,12 +2,15 @@
 
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.common.core.domain.Result;
 import com.ruoyi.common.numgen.NumberGenerator;
 import com.ruoyi.common.utils.FileSaveUtil;
 import com.ruoyi.device.dto.DeviceMetricRecordDto;
+import com.ruoyi.device.mapper.DeviceMapper;
+import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceMetricRecord;
 import com.ruoyi.device.pojo.DeviceMetricsCopy;
 import com.ruoyi.device.service.DeviceMetricRecordService;
@@ -18,6 +21,8 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.Date;
 import java.util.Locale;
 
@@ -43,6 +48,9 @@
     @Autowired
     private NumberGenerator<DeviceMetricRecord> numberGenerator;
 
+    @Autowired
+    private DeviceMapper deviceMapper;
+
     @Operation(summary = "璁惧鏍″噯鍒嗛〉鏌ヨ")
     @GetMapping("/deviceMetricRecordPage")
     public Result deviceMetricRecordPage(@RequestParam("deviceId") Integer deviceId, Page page, @RequestParam("type") String type) {
@@ -62,12 +70,51 @@
         }
         deviceMetricRecordService.saveOrUpdate(deviceMetricRecordDto);
         if (CollectionUtils.isNotEmpty(deviceMetricRecordDto.getDeviceMetricsCopyList())) {
-            deviceMetricRecordDto.getDeviceMetricsCopyList().forEach(i -> i.setDeviceMetricsId(deviceMetricRecordDto.getId()));
+            // 鏍″噯鏉$洰鏉ヨ嚜鏍″噯椤圭洰缁存姢琛紝涓婚敭涓嶈兘甯﹁繘璁板綍鏄庣粏琛紝鍚﹀垯浼氳鐩栧巻鍙茶褰曠殑鏉$洰
+            deviceMetricRecordDto.getDeviceMetricsCopyList().forEach(i -> {
+                i.setId(null);
+                i.setDeviceMetricsId(deviceMetricRecordDto.getId());
+            });
             deviceMetricsCopyService.saveOrUpdateBatch(deviceMetricRecordDto.getDeviceMetricsCopyList());
         }
+        // 鏍″噯瀹屾垚鍚庯紝鎶婅澶囨。妗堢殑鏈�杩�/涓嬫鏍″噯鏃ユ湡婊氬姩鍒版柊鍛ㄦ湡
+        if ("calibrate".equals(deviceMetricRecordDto.getType()) && deviceMetricRecordDto.getDeviceId() != null
+                && deviceMetricRecordDto.getCalibrationDate() != null) {
+            updateDeviceCalibrationDate(deviceMetricRecordDto);
+        }
         return Result.success();
     }
 
+    /**
+     * 婊氬姩璁惧妗f鐨勬渶杩�/涓嬫鏍″噯鏃ユ湡锛氫粎褰撴湰娆℃牎鍑嗘棩鏈熸瘮妗f涓殑鏈�杩戞牎鍑嗘棩鏈熸洿鏂版椂鐢熸晥
+     */
+    private void updateDeviceCalibrationDate(DeviceMetricRecordDto dto) {
+        Device device = deviceMapper.selectById(dto.getDeviceId());
+        if (device == null) {
+            return;
+        }
+        LocalDateTime calibrationDate = LocalDateTime.ofInstant(dto.getCalibrationDate().toInstant(), ZoneId.systemDefault());
+        if (device.getLastCalibrationDate() != null && !calibrationDate.isAfter(device.getLastCalibrationDate())) {
+            return;
+        }
+        device.setLastCalibrationDate(calibrationDate);
+        LocalDateTime next = null;
+        if (StringUtils.isNotBlank(device.getCalibrationDate())) {
+            try {
+                next = calibrationDate.plusMonths(Long.parseLong(device.getCalibrationDate().trim()));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        if (next == null && dto.getNextCalibrationDate() != null) {
+            next = LocalDateTime.ofInstant(dto.getNextCalibrationDate().toInstant(), ZoneId.systemDefault());
+        }
+        if (next != null) {
+            device.setNextCalibrationDate(next);
+            device.setActivationDate(next);
+        }
+        deviceMapper.updateById(device);
+    }
+
     @Operation(summary = "璁惧鏍″噯 鍒犻櫎")
     @DeleteMapping("/deleteDeviceMetricRecord")
     public Result deleteDeviceMetricRecord(@RequestParam("id") Integer id) {
diff --git a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceStateController.java b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceStateController.java
index c888fc4..4a62ad9 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceStateController.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/controller/DeviceStateController.java
@@ -49,7 +49,7 @@
             String processNumber = numberGenerator.generateNumberWithPrefix(3, "DG-TC-23FM " + month + "-" + year + month, DeviceState::getProcessNumber);
             deviceState.setProcessNumber(processNumber);
         }
-        deviceStateService.saveOrUpdate(deviceState);
+        deviceStateService.saveDeviceState(deviceState);
         return Result.success();
     }
 
diff --git a/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCalibrationPlanDetailDto.java b/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCalibrationPlanDetailDto.java
index 73ef496..33fb731 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCalibrationPlanDetailDto.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCalibrationPlanDetailDto.java
@@ -18,4 +18,6 @@
     private String lastDateStr;
     @Schema(description = "鏈勾璁″垝鏍″噯鏃堕棿Str")
     private String planDateStr;
+    @Schema(description = "鏄惁鍒扮偣闇�鏍″噯鏍囩孩锛�1鏄級")
+    private Integer redFlag;
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceExaminePlanDetailsDto.java b/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceExaminePlanDetailsDto.java
index 08a320c..16e0ac2 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceExaminePlanDetailsDto.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceExaminePlanDetailsDto.java
@@ -20,4 +20,7 @@
     @Schema(description = "瀵规瘮鐘舵��, 0: 鏈紑濮�, 1:寰呮壒鍑�, 2:閫氳繃, 3:涓嶉�氳繃")
     private Integer recordContrastStatus;
 
+    @Schema(description = "鍒扮偣鏍囩孩, 1:闇�鏍告煡")
+    private Integer redFlag;
+
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceMaintenancePlanDetailsDto.java b/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceMaintenancePlanDetailsDto.java
index 00185c1..b2a3a97 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceMaintenancePlanDetailsDto.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/dto/DeviceMaintenancePlanDetailsDto.java
@@ -29,4 +29,7 @@
 
     @Schema(description = "褰掑睘瀹為獙瀹�")
     private String storagePoint;
+
+    @Schema(description = "鍒扮偣鏍囩孩, 1:闇�淇濆吇")
+    private Integer redFlag;
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceCalibrationPlanDetailMapper.java b/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceCalibrationPlanDetailMapper.java
index 3f45575..f59a0f8 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceCalibrationPlanDetailMapper.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceCalibrationPlanDetailMapper.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.device.dto.DeviceCalibrationPlanDetailDto;
 import com.ruoyi.device.pojo.DeviceCalibrationPlanDetail;
 import org.apache.ibatis.annotations.Param;
 
@@ -23,5 +24,5 @@
      * @param ew
      * @return
      */
-    IPage<DeviceCalibrationPlanDetail> pageDeviceCalibrationPlanDetail(Page page, @Param("ew") QueryWrapper<DeviceCalibrationPlanDetail> ew);
+    IPage<DeviceCalibrationPlanDetailDto> pageDeviceCalibrationPlanDetail(Page page, @Param("ew") QueryWrapper<DeviceCalibrationPlanDetail> ew);
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceMaintenancePlanDetailsMapper.java b/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceMaintenancePlanDetailsMapper.java
index 86760eb..5b526e4 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceMaintenancePlanDetailsMapper.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/mapper/DeviceMaintenancePlanDetailsMapper.java
@@ -1,6 +1,8 @@
 package com.ruoyi.device.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto;
 import com.ruoyi.device.pojo.DeviceMaintenancePlanDetails;
 import org.apache.ibatis.annotations.Param;
@@ -22,4 +24,12 @@
      * @param maintenancePlanId 璁惧淇濆吇璁″垝id
      */
     List<DeviceMaintenancePlanDetailsDto> deviceInspectionRecordDetailsList(@Param("maintenancePlanId") Integer maintenancePlanId);
+
+    /**
+     * 鍒嗛〉鏌ヨ璁惧淇濆吇璁″垝璇︽儏锛堝惈璁惧淇℃伅涓庡埌鐐规爣绾級
+     */
+    IPage<DeviceMaintenancePlanDetailsDto> pageMaintenancePlanDetail(Page page,
+                                                                     @Param("planId") Integer planId,
+                                                                     @Param("deviceName") String deviceName,
+                                                                     @Param("deviceNumber") String deviceNumber);
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceCalibrationPlanService.java b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceCalibrationPlanService.java
index 0a85152..d989cbe 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceCalibrationPlanService.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceCalibrationPlanService.java
@@ -3,7 +3,9 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.device.dto.DeviceCalibrationPlanDetailDto;
 import com.ruoyi.device.dto.DeviceCalibrationPlanDto;
+import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceCalibrationPlan;
 import com.ruoyi.device.pojo.DeviceCalibrationPlanDetail;
 import org.springframework.web.multipart.MultipartFile;
@@ -62,7 +64,20 @@
      * @param DeviceCalibrationPlanDetails
      * @return
      */
-    IPage<DeviceCalibrationPlanDetail> pageDeviceCalibrationPlanDetail(Page page, DeviceCalibrationPlanDetail deviceCalibrationPlanDetails);
+    IPage<DeviceCalibrationPlanDetailDto> pageDeviceCalibrationPlanDetail(Page page, DeviceCalibrationPlanDetail deviceCalibrationPlanDetails);
+
+    /**
+     * 鎸夊勾浠芥煡璇㈣澶囨牎鍑嗚鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     * @param planYear 骞翠唤
+     * @return
+     */
+    DeviceCalibrationPlanDto getOrCreatePlanByYear(String planYear);
+
+    /**
+     * 璁惧妗f鍙樻洿鍚庯紝鎶婅澶囧悓姝ュ埌瀵瑰簲骞翠唤鐨勮澶囨牎鍑嗚鍒掓槑缁嗭紙浠呭綋宸茶繘鍏ユ彁閱掓湡/宸茶繃鏈熸椂鐢熸垚锛�
+     * @param device 璁惧
+     */
+    void syncDeviceCalibrationDetail(Device device);
 
 
     /**
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceExaminePlanService.java b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceExaminePlanService.java
index a468993..69c5aa3 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceExaminePlanService.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceExaminePlanService.java
@@ -5,6 +5,7 @@
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.device.dto.DeviceExaminePlanDetailsDto;
 import com.ruoyi.device.dto.DeviceExaminePlanDto;
+import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceExaminePlan;
 import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
 import org.springframework.web.multipart.MultipartFile;
@@ -86,4 +87,23 @@
      * @return
      */
     DeviceExaminePlanDto getDeviceExaminePlan(Integer planId);
+
+    /**
+     * 鎸夊勾浠芥煡璇㈣澶囨牳鏌ヨ鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     * @param planYear 骞翠唤
+     * @return 璁″垝涓昏〃淇℃伅
+     */
+    DeviceExaminePlanDto getOrCreatePlanByYear(String planYear);
+
+    /**
+     * 璁惧妗f鍙樻洿鍚庯紝鎶婅澶囧悓姝ュ埌瀵瑰簲骞翠唤鐨勮澶囨牳鏌ヨ鍒掓槑缁嗭紙浠呭綋宸茶繘鍏ユ彁閱掓湡/宸茶繃鏈熸椂鐢熸垚锛�
+     * @param device 璁惧
+     */
+    void syncDeviceExamineDetail(Device device);
+
+    /**
+     * 鏍告煡璁板綍澶嶆牳閫氳繃鍚庯紝婊氬姩璁惧妗f鐨勬渶杩�/涓嬫鏍告煡鏃ユ湡
+     * @param planDetailsId 璁″垝鏄庣粏id
+     */
+    void updateDeviceExamineDate(Integer planDetailsId);
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceMaintenancePlanService.java b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceMaintenancePlanService.java
index cfa3ab3..6856892 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceMaintenancePlanService.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceMaintenancePlanService.java
@@ -4,10 +4,13 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.common.core.domain.Result;
+import com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto;
 import com.ruoyi.device.dto.DeviceMaintenancePlanDto;
+import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceMaintenancePlan;
 
 import jakarta.servlet.http.HttpServletResponse;
+import java.time.LocalDate;
 
 /**
  * <p>
@@ -76,4 +79,28 @@
      */
     Result reviewMaintenancePlanStatus(DeviceMaintenancePlanDto deviceMaintenancePlanDto);
 
+    /**
+     * 鎸夊勾浠芥煡璇㈣澶囦繚鍏昏鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     * @param planYear 璁″垝骞翠唤
+     * @return 璁惧淇濆吇璁″垝
+     */
+    DeviceMaintenancePlanDto getOrCreatePlanByYear(String planYear);
+
+    /**
+     * 璁惧妗f鍙樻洿鍚庯紝鎶婂凡鍒扮偣鐨勮澶囧悓姝ュ埌瀵瑰簲骞翠唤鐨勮澶囦繚鍏昏鍒掓槑缁�
+     * @param device 璁惧
+     */
+    void syncDeviceMaintenanceDetail(Device device);
+
+    /**
+     * 璁惧淇濆吇璁板綍淇濆瓨鍚庯紝婊氬姩璁惧妗f鐨勬渶杩�/涓嬫淇濆吇鏃ユ湡
+     * @param deviceId 璁惧id
+     * @param maintenanceDate 鏈淇濆吇鏃ユ湡
+     */
+    void updateDeviceMaintenanceDate(Integer deviceId, LocalDate maintenanceDate);
+
+    /**
+     * 鍒嗛〉鏌ヨ璁惧淇濆吇璁″垝璇︽儏锛堝惈鍒扮偣鏍囩孩锛�
+     */
+    IPage<DeviceMaintenancePlanDetailsDto> pageMaintenancePlanDetail(Page page, Integer planId, String deviceName, String deviceNumber);
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceStateService.java b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceStateService.java
index f18cf47..50de5c4 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/DeviceStateService.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/DeviceStateService.java
@@ -18,6 +18,11 @@
  */
 public interface DeviceStateService extends IService<DeviceState> {
 
+    /**
+     * 淇濆瓨鍋滅敤/鍚敤娴佺▼锛屾祦绋嬬粨鏉燂紙鍏抽棴锛夋椂鍚屾璁惧妗f鐘舵��
+     */
+    boolean saveDeviceState(DeviceState deviceState);
+
     IPage<DeviceStateDto> getDeviceStatePage(Integer deviceId, Page page, String processNumber);
 
     /**
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java
index f2b6809..e833754 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceAccidentReportServiceImpl.java
@@ -6,6 +6,7 @@
 import com.deepoove.poi.XWPFTemplate;
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.device.dto.DeviceAccidentReportDto;
@@ -14,6 +15,7 @@
 import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceAccidentReport;
 import com.ruoyi.device.service.DeviceAccidentReportService;
+import com.ruoyi.device.util.DeviceFlowPermissionUtils;
 import com.ruoyi.inspect.util.UserUtils;
 import com.ruoyi.system.mapper.UserMapper;
 import org.springframework.beans.BeanUtils;
@@ -65,6 +67,7 @@
     public boolean addDeviceAccidentReport(DeviceAccidentReport deviceAccidentReport) {
         DeviceAccidentReport accidentReport = new DeviceAccidentReport();
         User user = userMapper.selectById(SecurityUtils.getUserId().intValue());
+        checkFlowPermission(deviceAccidentReport, user);
         switch (deviceAccidentReport.getFlowType()) {
             case 0:
                 // 鎶ュ憡
@@ -136,6 +139,43 @@
         return true;
     }
 
+    private void checkFlowPermission(DeviceAccidentReport deviceAccidentReport, User user) {
+        Integer flowType = deviceAccidentReport.getFlowType();
+        Integer accidentReportId = deviceAccidentReport.getAccidentReportId();
+        if (flowType != null && flowType > 0) {
+            DeviceAccidentReport existing = baseMapper.selectById(accidentReportId);
+            if (existing == null) {
+                throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+            }
+            DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+            Integer expectedUserId;
+            switch (flowType) {
+                case 1:
+                    expectedUserId = existing.getAssessorUserId();
+                    break;
+                case 2:
+                    expectedUserId = existing.getDepartmentHeadUserId();
+                    break;
+                case 3:
+                    expectedUserId = existing.getTechnicalDirectorUserId();
+                    break;
+                case 4:
+                    expectedUserId = existing.getDirectorHeadUserId();
+                    break;
+                default:
+                    expectedUserId = null;
+                    break;
+            }
+            DeviceFlowPermissionUtils.checkResponsible(expectedUserId, user.getId());
+        } else if (accidentReportId != null) {
+            DeviceAccidentReport existing = baseMapper.selectById(accidentReportId);
+            if (existing != null) {
+                DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+                DeviceFlowPermissionUtils.checkResponsible(existing.getReportUserId(), user.getId());
+            }
+        }
+    }
+
     /**
      * 瀵煎嚭璁惧浜嬫晠鎶ュ憡
      * @param accidentReportId 璁惧浜嬫晠鎶ュ憡id
@@ -167,7 +207,7 @@
                     // 鎶�鏈礋璐d汉绛惧悕
                     put("technicalDirectorUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getTechnicalDirectorUserId()));
                     // 涓讳换绛惧悕
-                    put("directorHeadUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getDepartmentHeadUserId()));
+                    put("directorHeadUserUrl", UserUtils.getFinalUserSignatureOrTransparent(deviceAccidentReport.getDirectorHeadUserId()));
                 }});
 
         try {
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBorrowServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBorrowServiceImpl.java
index a402359..05cdbd0 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBorrowServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBorrowServiceImpl.java
@@ -6,6 +6,7 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.numgen.NumberGenerator;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.common.utils.SecurityUtils;
@@ -75,6 +76,21 @@
             deviceLogMapper.insert(deviceLog);
         } else {
             DeviceBorrow borrow = deviceBorrowMapper.selectById(deviceBorrow.getId());
+            if (borrow == null) {
+                throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+            }
+            if ("鍏抽棴".equals(borrow.getNowState())) {
+                throw new ServiceException("璇ユ祦绋嬪凡缁撴潫锛屼笉鑳藉啀鎿嶄綔");
+            }
+            String expectedUser = null;
+            if ("褰掕繕".equals(borrow.getNowState())) {
+                expectedUser = borrow.getNowUser();
+            } else if ("鍊熷嚭".equals(borrow.getNowState())) {
+                expectedUser = borrow.getSubmitOperationUser();
+            }
+            if (expectedUser != null && !expectedUser.isBlank() && !expectedUser.equals(user.getName())) {
+                throw new ServiceException("鎮ㄤ笉鏄綋鍓嶇幆鑺傝矗浠讳汉锛屾棤鏉冩搷浣�");
+            }
             deviceBorrowMapper.updateById(deviceBorrow);
             //閫氳繃鎴栬�呴┏鍥炲鍔犳祦绋嬭窡韪�
             if (deviceBorrow.getNowState().equals("鍏抽棴")) {
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java
index c41f0dc..0de29d7 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceBreakdownMaintenanceServiceImpl.java
@@ -6,6 +6,7 @@
 import com.deepoove.poi.XWPFTemplate;
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.device.dto.DeviceBreakdownMaintenance;
@@ -13,6 +14,7 @@
 import com.ruoyi.device.mapper.DeviceMapper;
 import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.service.DeviceBreakdownMaintenanceService;
+import com.ruoyi.device.util.DeviceFlowPermissionUtils;
 import com.ruoyi.inspect.util.UserUtils;
 import com.ruoyi.system.mapper.UserMapper;
 import org.springframework.beans.BeanUtils;
@@ -63,6 +65,7 @@
         DeviceBreakdownMaintenance breakdownMaintenance = new DeviceBreakdownMaintenance();
         //褰撳墠鐧诲綍鐢ㄦ埛
         User user = userMapper.selectById(SecurityUtils.getUserId().intValue());
+        checkFlowPermission(deviceBreakdownMaintenance, user);
 
         switch (deviceBreakdownMaintenance.getFlowType()) {
             case 0:
@@ -72,11 +75,6 @@
                 breakdownMaintenance.setApplicantUserId(user.getId());
                 breakdownMaintenance.setApplicantUser(user.getName());
                 breakdownMaintenance.setRepairDate(deviceBreakdownMaintenance.getRepairDate());
-
-                // 澶勭悊浜轰俊鎭�
-                User departmentHeadUser = userMapper.selectById(deviceBreakdownMaintenance.getDepartmentHeadUserId());
-                breakdownMaintenance.setApplicantUserId(departmentHeadUser.getId());
-                breakdownMaintenance.setApplicantUser(departmentHeadUser.getName());
 
                 baseMapper.insert(breakdownMaintenance);
                 break;
@@ -103,6 +101,38 @@
         return true;
     }
 
+    private void checkFlowPermission(DeviceBreakdownMaintenance deviceBreakdownMaintenance, User user) {
+        Integer flowType = deviceBreakdownMaintenance.getFlowType();
+        Integer maintenanceId = deviceBreakdownMaintenance.getMaintenanceId();
+        if (flowType != null && flowType > 0) {
+            DeviceBreakdownMaintenance existing = baseMapper.selectById(maintenanceId);
+            if (existing == null) {
+                throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+            }
+            DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+            Integer expectedUserId;
+            switch (flowType) {
+                case 1:
+                    expectedUserId = existing.getDepartmentHeadUserId();
+                    break;
+                case 2:
+                    // 缁翠慨璁板綍鐜妭鏈崟鐙寚瀹氳矗浠讳汉锛岀敱鐢宠浜哄~鍐欑淮淇粨鏋�
+                    expectedUserId = existing.getApplicantUserId();
+                    break;
+                default:
+                    expectedUserId = null;
+                    break;
+            }
+            DeviceFlowPermissionUtils.checkResponsible(expectedUserId, user.getId());
+        } else if (maintenanceId != null) {
+            DeviceBreakdownMaintenance existing = baseMapper.selectById(maintenanceId);
+            if (existing != null) {
+                DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+                DeviceFlowPermissionUtils.checkResponsible(existing.getApplicantUserId(), user.getId());
+            }
+        }
+    }
+
     /**
      * 瀵煎嚭璁惧鏁呴殰缁翠慨
      * @param maintenanceId 璁惧鏁呴殰缁翠慨id
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java
index 9bf98af..e92fdec 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceCalibrationPlanServiceImpl.java
@@ -22,6 +22,8 @@
 import com.ruoyi.device.excel.upload.DeviceCalibrationPlanDetailUpload;
 import com.ruoyi.device.mapper.DeviceCalibrationPlanDetailMapper;
 import com.ruoyi.device.mapper.DeviceCalibrationPlanMapper;
+import com.ruoyi.device.mapper.DeviceMapper;
+import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceCalibrationPlan;
 import com.ruoyi.device.pojo.DeviceCalibrationPlanDetail;
 import com.ruoyi.device.service.DeviceCalibrationPlanDetailService;
@@ -71,6 +73,8 @@
     private InformationNotificationService informationNotificationService;
     @Resource
     private UserMapper userMapper;
+    @Resource
+    private DeviceMapper deviceMapper;
 
 
     /**
@@ -238,14 +242,133 @@
      * @return
      */
     @Override
-    public IPage<DeviceCalibrationPlanDetail> pageDeviceCalibrationPlanDetail(Page page, DeviceCalibrationPlanDetail deviceCalibrationPlanDetails) {
+    public IPage<DeviceCalibrationPlanDetailDto> pageDeviceCalibrationPlanDetail(Page page, DeviceCalibrationPlanDetail deviceCalibrationPlanDetails) {
         if (deviceCalibrationPlanDetails.getPlanId() == null) {
-            return new Page();
+            return new Page<>();
         }
         return deviceCalibrationPlanDetailMapper.pageDeviceCalibrationPlanDetail(page, QueryWrappers.queryWrappers(deviceCalibrationPlanDetails));
     }
 
     /**
+     * 鎸夊勾浠芥煡璇㈣澶囨牎鍑嗚鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     * @param planYear 骞翠唤
+     * @return
+     */
+    @Override
+    public DeviceCalibrationPlanDto getOrCreatePlanByYear(String planYear) {
+        if (StringUtils.isBlank(planYear)) {
+            planYear = String.valueOf(LocalDate.now().getYear());
+        }
+        DeviceCalibrationPlan plan = getOrCreatePlanEntity(planYear);
+        ensureCalibrationDetails(plan);
+        DeviceCalibrationPlanDto planDto = new DeviceCalibrationPlanDto();
+        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鍙樻洿鍚庯紝鎶婅澶囧悓姝ュ埌瀵瑰簲骞翠唤鐨勮澶囨牎鍑嗚鍒掓槑缁嗭紙浠呭綋宸茶繘鍏ユ彁閱掓湡/宸茶繃鏈熸椂鐢熸垚锛�
+     * @param device 璁惧
+     */
+    @Override
+    public void syncDeviceCalibrationDetail(Device device) {
+        LocalDate remind = calcCalibrationRemindDate(device);
+        if (remind == null || remind.isAfter(LocalDate.now())) {
+            return;
+        }
+        DeviceCalibrationPlan plan = getOrCreatePlanEntity(String.valueOf(remind.getYear()));
+        fillCalibrationDetailIfAbsent(plan, device);
+    }
+
+    /**
+     * 鏌ヨ骞翠唤瀵瑰簲鐨勮鍒掞紝涓嶅瓨鍦ㄥ垯鍒涘缓
+     */
+    private DeviceCalibrationPlan getOrCreatePlanEntity(String planYear) {
+        DeviceCalibrationPlan plan = baseMapper.selectOne(Wrappers.<DeviceCalibrationPlan>lambdaQuery()
+                .eq(DeviceCalibrationPlan::getPlanYear, planYear)
+                .orderByDesc(DeviceCalibrationPlan::getPlanId)
+                .last("limit 1"));
+        if (plan == null) {
+            plan = new DeviceCalibrationPlan();
+            plan.setPlanName(planYear + "骞村害璁惧鏍″噯璁″垝");
+            plan.setPlanYear(planYear);
+            plan.setWriteUserId(SecurityUtils.getUserId().intValue());
+            plan.setWriteTime(LocalDateTime.now());
+            baseMapper.insert(plan);
+        }
+        return plan;
+    }
+
+    /**
+     * 鎵弿鎵�鏈夎澶囷紝鎶婂凡鍒扮偣锛堜笅娆℃牎鍑嗘棩鏈熷墠1涓湀璧凤紝鍚凡杩囨湡锛夌殑璁惧琛ヨ繘鏈勾搴﹁鍒�
+     */
+    private void ensureCalibrationDetails(DeviceCalibrationPlan plan) {
+        List<Device> devices = deviceMapper.selectList(null);
+        for (Device device : devices) {
+            fillCalibrationDetailIfAbsent(plan, device);
+        }
+    }
+
+    /**
+     * 璁惧鐨勬彁閱掕捣鐐规棩鏈燂細涓嬫鏍″噯鏃ユ湡鍓�1涓湀锛堝彇涓嶅埌涓嬫鏃ユ湡鏃剁敤鏍″噯鏈夋晥鏈熷墠1涓湀鍏滃簳锛�
+     */
+    private LocalDate calcCalibrationRemindDate(Device device) {
+        LocalDateTime base = device.getNextCalibrationDate() != null ? device.getNextCalibrationDate() : device.getActivationDate();
+        return base == null ? null : base.toLocalDate().minusMonths(1);
+    }
+
+    /**
+     * 宸插埌鐐逛笖璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃讹紝鑷姩琛ヤ竴鏉�
+     */
+    private void fillCalibrationDetailIfAbsent(DeviceCalibrationPlan plan, Device device) {
+        LocalDate remind = calcCalibrationRemindDate(device);
+        // 鏈埌鐐逛笉鐢熸垚锛涙湭鏉ュ勾浠界殑鎻愰啋涓嶈ˉ杩涘綋鍓嶈鍒�
+        if (remind == null || remind.isAfter(LocalDate.now())) {
+            return;
+        }
+        if (plan.getPlanYear() != null && String.valueOf(remind.getYear()).compareTo(plan.getPlanYear()) > 0) {
+            return;
+        }
+        Long exist = deviceCalibrationPlanDetailMapper.selectCount(Wrappers.<DeviceCalibrationPlanDetail>lambdaQuery()
+                .eq(DeviceCalibrationPlanDetail::getPlanId, plan.getPlanId())
+                .eq(DeviceCalibrationPlanDetail::getDeviceId, device.getId())
+                .eq(DeviceCalibrationPlanDetail::getPlanDate, remind));
+        if (exist != null && exist > 0) {
+            return;
+        }
+        DeviceCalibrationPlanDetail detail = new DeviceCalibrationPlanDetail();
+        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.setDeviceAmount("1");
+        detail.setDeviceNumber(device.getManagementNumber());
+        detail.setVerificationUnit(device.getCalibrationServices());
+        detail.setVerificationCycles(device.getCalibrationDate());
+        detail.setLastDate(device.getLastCalibrationDate() == null ? null : device.getLastCalibrationDate().toLocalDate());
+        detail.setPlanDate(remind);
+        deviceCalibrationPlanDetailMapper.insert(detail);
+    }
+
+    /**
      * 瀵煎嚭璁惧鏍″噯璁″垝
      * @param deviceCalibrationPlanId
      * @param response
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 30df898..7ef252c 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
@@ -45,7 +45,10 @@
 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.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -346,4 +349,170 @@
 
         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 remind = calcExamineRemindDate(device);
+        if (remind == null || remind.isAfter(LocalDate.now())) {
+            return;
+        }
+        DeviceExaminePlan plan = getOrCreatePlanEntity(String.valueOf(remind.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);
+    }
+
+    /**
+     * 鏌ヨ骞翠唤瀵瑰簲鐨勮鍒掞紝涓嶅瓨鍦ㄥ垯鍒涘缓
+     */
+    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;
+    }
+
+    /**
+     * 鎵弿鎵�鏈夎澶囷紝鎶婂凡鍒扮偣锛堜笅娆℃牳鏌ユ棩鏈熷墠1涓湀璧凤紝鍚凡杩囨湡锛夌殑璁惧琛ヨ繘鏈勾搴﹁鍒�
+     */
+    private void ensureExamineDetails(DeviceExaminePlan plan) {
+        List<Device> devices = deviceMapper.selectList(null);
+        for (Device device : devices) {
+            fillExamineDetailIfAbsent(plan, device);
+        }
+    }
+
+    /**
+     * 璁惧鎻愰啋璧风偣锛氫笅娆℃牳鏌ユ棩鏈熷墠1涓湀锛堟病濉笅娆℃棩鏈熸椂鐢ㄦ渶杩戞棩鏈�+鏍告煡鍛ㄦ湡鍏滃簳锛�
+     */
+    private LocalDate calcExamineRemindDate(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().minusMonths(1);
+    }
+
+    /**
+     * 宸插埌鐐逛笖璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃惰嚜鍔ㄨˉ涓�鏉�
+     */
+    private void fillExamineDetailIfAbsent(DeviceExaminePlan plan, Device device) {
+        LocalDate remind = calcExamineRemindDate(device);
+        if (remind == null || remind.isAfter(LocalDate.now())) {
+            return;
+        }
+        if (plan.getPlanYear() != null && String.valueOf(remind.getYear()).compareTo(plan.getPlanYear()) > 0) {
+            return;
+        }
+        String checkTime = remind.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();
+    }
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java
index 10706a8..491c1b1 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordContrastServiceImpl.java
@@ -15,6 +15,7 @@
 import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
 import com.ruoyi.device.pojo.DeviceExamineRecordContrast;
 import com.ruoyi.device.pojo.DeviceExamineRecordContrastDetails;
+import com.ruoyi.device.service.DeviceExaminePlanService;
 import com.ruoyi.device.service.DeviceExamineRecordContrastDetailsService;
 import com.ruoyi.device.service.DeviceExamineRecordContrastService;
 import com.ruoyi.framework.exception.ErrorException;
@@ -49,6 +50,8 @@
 
     @Resource
     private DeviceExamineRecordContrastDetailsService deviceExamineRecordContrastDetailsService;
+    @Resource
+    private DeviceExaminePlanService deviceExaminePlanService;
     @Resource
     private DeviceExaminePlanDetailsMapper deviceExaminePlanDetailsMapper;
     @Resource
@@ -135,6 +138,10 @@
         }
 
         this.update(wrapper);
+        // 瀹℃牳閫氳繃鍚庯紝璁惧妗f鐨勬渶杩�/涓嬫鏍告煡鏃ユ湡杩涘叆涓嬩竴杞�
+        if (dto.getReviewStatus() != null && dto.getReviewStatus() == 1) {
+            deviceExaminePlanService.updateDeviceExamineDate(dto.getPlanDetailsId());
+        }
         return true;
     }
 
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
index d5fea12..9e882f2 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExamineRecordServiceImpl.java
@@ -20,6 +20,7 @@
 import com.ruoyi.device.pojo.DeviceExaminePlanDetails;
 import com.ruoyi.device.pojo.DeviceExamineRecord;
 import com.ruoyi.device.pojo.DeviceExamineRecordDetail;
+import com.ruoyi.device.service.DeviceExaminePlanService;
 import com.ruoyi.device.service.DeviceExamineRecordDetailService;
 import com.ruoyi.device.service.DeviceExamineRecordService;
 import com.ruoyi.framework.exception.ErrorException;
@@ -53,6 +54,8 @@
 
     @Resource
     private DeviceExamineRecordDetailService deviceExamineRecordDetailService;
+    @Resource
+    private DeviceExaminePlanService deviceExaminePlanService;
     @Resource
     private DeviceExaminePlanDetailsMapper deviceExaminePlanDetailsMapper;
     @Resource
@@ -186,6 +189,10 @@
         }
 
         this.update(wrapper);
+        // 澶嶆牳閫氳繃鍚庯紝璁惧妗f鐨勬渶杩�/涓嬫鏍告煡鏃ユ湡杩涘叆涓嬩竴杞�
+        if (dto.getReviewStatus() != null && dto.getReviewStatus() == 1) {
+            deviceExaminePlanService.updateDeviceExamineDate(dto.getPlanDetailsId());
+        }
         return true;
     }
 
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java
index 394f24e..1cd1784 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceExternalApplyServiceImpl.java
@@ -6,11 +6,13 @@
 import com.deepoove.poi.XWPFTemplate;
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.device.mapper.DeviceExternalApplyMapper;
 import com.ruoyi.device.pojo.DeviceExternalApply;
 import com.ruoyi.device.service.DeviceExternalApplyService;
+import com.ruoyi.device.util.DeviceFlowPermissionUtils;
 import com.ruoyi.inspect.util.UserUtils;
 import com.ruoyi.system.mapper.UserMapper;
 import org.springframework.beans.BeanUtils;
@@ -59,6 +61,7 @@
         DeviceExternalApply apply = new DeviceExternalApply();
         // 褰撳墠鐧诲綍鐢ㄦ埛淇℃伅鍜岄儴闂�
         User user = userMapper.selectById(SecurityUtils.getUserId().intValue());
+        checkFlowPermission(deviceExternalApply, user);
         switch (deviceExternalApply.getFlowType()) {
             case 0:
                 BeanUtils.copyProperties(deviceExternalApply, apply);
@@ -67,11 +70,6 @@
                 apply.setApplicantUserId(user.getId());
                 apply.setApplicantUser(user.getName());
                 apply.setApplicantDate(LocalDate.now());
-
-                // 澶勭悊浜轰俊鎭�
-                User departmentHeadUser = userMapper.selectById(deviceExternalApply.getDepartmentHeadUserId());
-                apply.setApplicantUserId(departmentHeadUser.getId());
-                apply.setApplicantUser(departmentHeadUser.getName());
 
                 baseMapper.insert(apply);
                 break;
@@ -115,6 +113,40 @@
         return true;
     }
 
+    private void checkFlowPermission(DeviceExternalApply deviceExternalApply, User user) {
+        Integer flowType = deviceExternalApply.getFlowType();
+        Integer externalApplyId = deviceExternalApply.getExternalApplyId();
+        if (flowType != null && flowType > 0) {
+            DeviceExternalApply existing = baseMapper.selectById(externalApplyId);
+            if (existing == null) {
+                throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+            }
+            DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+            Integer expectedUserId;
+            switch (flowType) {
+                case 1:
+                    expectedUserId = existing.getDepartmentHeadUserId();
+                    break;
+                case 2:
+                    expectedUserId = existing.getMeteringRoomUserId();
+                    break;
+                case 3:
+                    expectedUserId = existing.getApproverUserId();
+                    break;
+                default:
+                    expectedUserId = null;
+                    break;
+            }
+            DeviceFlowPermissionUtils.checkResponsible(expectedUserId, user.getId());
+        } else if (externalApplyId != null) {
+            DeviceExternalApply existing = baseMapper.selectById(externalApplyId);
+            if (existing != null) {
+                DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+                DeviceFlowPermissionUtils.checkResponsible(existing.getApplicantUserId(), user.getId());
+            }
+        }
+    }
+
     /**
      * 瀵煎嚭鍒╃敤澶栭儴璁惧鐢宠
      *
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java
index fd39bf7..bb75c8e 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceImpowerServiceImpl.java
@@ -10,6 +10,7 @@
 import com.ruoyi.common.core.domain.Result;
 import com.ruoyi.common.core.domain.entity.InformationNotification;
 import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.WxCpUtils;
@@ -233,6 +234,17 @@
      */
     @Override
     public Result reviewImpowerStatus(DeviceImpowerDto deviceImpowerDto) {
+        DeviceImpower existing = baseMapper.selectById(deviceImpowerDto.getImpowerId());
+        if (existing == null) {
+            throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+        }
+        if (existing.getStatus() != null && existing.getStatus() == 1) {
+            throw new ServiceException("璇ュ崟鎹凡瀹℃牳瀹屾垚锛屼笉鑳藉啀鎿嶄綔");
+        }
+        if (existing.getAuditId() != null
+                && !existing.getAuditId().equals(SecurityUtils.getUserId().intValue())) {
+            throw new ServiceException("鎮ㄤ笉鏄綋鍓嶅鏍镐汉锛屾棤鏉冩搷浣�");
+        }
         LambdaUpdateWrapper<DeviceImpower> wrapper = Wrappers.<DeviceImpower>lambdaUpdate()
                 .eq(DeviceImpower::getImpowerId, deviceImpowerDto.getImpowerId())
                 .set(DeviceImpower::getAuditDate, LocalDateTime.now())
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 2a067e0..656bb10 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
@@ -2,6 +2,7 @@
 
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -18,6 +19,8 @@
 import com.ruoyi.device.dto.DeviceMaintenancePlanDto;
 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.DeviceMaintenancePlan;
 import com.ruoyi.device.pojo.DeviceMaintenancePlanDetails;
 import com.ruoyi.device.service.DeviceMaintenancePlanDetailsService;
@@ -30,13 +33,16 @@
 import org.springframework.beans.BeanUtils;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URLEncoder;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -50,6 +56,7 @@
  * @since 2024-12-16 06:10:52
  */
 @Service
+@Transactional(rollbackFor = Exception.class)
 public class DeviceMaintenancePlanServiceImpl extends ServiceImpl<DeviceMaintenancePlanMapper, DeviceMaintenancePlan> implements DeviceMaintenancePlanService {
 
     @Resource
@@ -62,6 +69,8 @@
     private InformationNotificationService informationNotificationService;
     @Resource
     private UserMapper userMapper;
+    @Resource
+    private DeviceMapper deviceMapper;
 
     /**
      * 鍒嗛〉鏌ヨ璁惧淇濆吇璁″垝
@@ -288,5 +297,157 @@
         return Result.success();
     }
 
+    /**
+     * 鎸夊勾浠芥煡璇㈣澶囦繚鍏昏鍒掞紙涓嶅瓨鍦ㄥ垯鑷姩鍒涘缓锛夛紝骞惰嚜鍔ㄨˉ鍏ㄥ凡鍒扮偣鐨勮澶囨槑缁�
+     */
+    @Override
+    public DeviceMaintenancePlanDto getOrCreatePlanByYear(String planYear) {
+        if (StringUtils.isBlank(planYear)) {
+            planYear = String.valueOf(LocalDate.now().getYear());
+        }
+        DeviceMaintenancePlan plan = getOrCreatePlanEntity(planYear);
+        ensureMaintenanceDetails(plan);
+        DeviceMaintenancePlanDto planDto = new DeviceMaintenancePlanDto();
+        BeanUtils.copyProperties(plan, planDto);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        planDto.setDatePreparationStr(plan.getDatePreparation() == null ? null : plan.getDatePreparation().format(formatter));
+        planDto.setAuditDateStr(plan.getAuditDate() == null ? null : plan.getAuditDate().format(formatter));
+        if (StringUtils.isBlank(planDto.getCompiler()) && plan.getCompilerId() != null) {
+            User compiler = userMapper.selectById(plan.getCompilerId());
+            if (compiler != null) {
+                planDto.setCompiler(compiler.getName());
+            }
+        }
+        if (StringUtils.isBlank(planDto.getAudit()) && plan.getAuditId() != null) {
+            User audit = userMapper.selectById(plan.getAuditId());
+            if (audit != null) {
+                planDto.setAudit(audit.getName());
+            }
+        }
+        return planDto;
+    }
+
+    /**
+     * 璁惧妗f鍙樻洿鍚庯紝鎶婂凡鍒扮偣鐨勮澶囧悓姝ュ埌瀵瑰簲骞翠唤鐨勮澶囦繚鍏昏鍒掓槑缁�
+     */
+    @Override
+    public void syncDeviceMaintenanceDetail(Device device) {
+        LocalDate remind = calcMaintenanceRemindDate(device);
+        if (remind == null || remind.isAfter(LocalDate.now())) {
+            return;
+        }
+        DeviceMaintenancePlan plan = getOrCreatePlanEntity(String.valueOf(remind.getYear()));
+        fillMaintenanceDetailIfAbsent(plan, device);
+    }
+
+    /**
+     * 淇濆吇璁板綍淇濆瓨鍚庯紝婊氬姩璁惧妗f鐨勬渶杩�/涓嬫淇濆吇鏃ユ湡
+     */
+    @Override
+    public void updateDeviceMaintenanceDate(Integer deviceId, LocalDate maintenanceDate) {
+        if (deviceId == null || maintenanceDate == null) {
+            return;
+        }
+        Device device = deviceMapper.selectById(deviceId);
+        if (device == null) {
+            return;
+        }
+        LocalDateTime recordDate = maintenanceDate.atStartOfDay();
+        // 鍙厑璁稿悜鍓嶆粴鍔紝閬垮厤琛ュ綍鏃ц褰曟椂鎶婃棩鏈熸敼鍥炲幓
+        if (device.getLastMaintenanceDate() != null && !recordDate.isAfter(device.getLastMaintenanceDate())) {
+            return;
+        }
+        device.setLastMaintenanceDate(recordDate);
+        if (StringUtils.isNotBlank(device.getMaintenanceCycle())) {
+            try {
+                device.setNextMaintenanceDate(recordDate.plusMonths(Long.parseLong(device.getMaintenanceCycle().trim())));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        deviceMapper.updateById(device);
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨ璁惧淇濆吇璁″垝璇︽儏锛堝惈鍒扮偣鏍囩孩锛�
+     */
+    @Override
+    public IPage<DeviceMaintenancePlanDetailsDto> pageMaintenancePlanDetail(Page page, Integer planId, String deviceName, String deviceNumber) {
+        if (planId == null) {
+            return new Page<>();
+        }
+        return deviceMaintenancePlanDetailsMapper.pageMaintenancePlanDetail(page, planId, deviceName, deviceNumber);
+    }
+
+    /**
+     * 鏌ヨ骞翠唤瀵瑰簲鐨勮鍒掞紝涓嶅瓨鍦ㄥ垯鍒涘缓
+     */
+    private DeviceMaintenancePlan getOrCreatePlanEntity(String planYear) {
+        DeviceMaintenancePlan plan = baseMapper.selectOne(Wrappers.<DeviceMaintenancePlan>lambdaQuery()
+                .eq(DeviceMaintenancePlan::getPlanYear, planYear)
+                .orderByDesc(DeviceMaintenancePlan::getMaintenancePlanId)
+                .last("limit 1"));
+        if (plan == null) {
+            plan = new DeviceMaintenancePlan();
+            plan.setPlanName(planYear + "骞村害璁惧淇濆吇璁″垝");
+            plan.setPlanYear(planYear);
+            Integer userId = SecurityUtils.getUserId().intValue();
+            User user = userMapper.selectById(userId);
+            plan.setCompilerId(userId);
+            plan.setCompiler(user == null ? null : user.getName());
+            plan.setDatePreparation(LocalDateTime.now());
+            baseMapper.insert(plan);
+        }
+        return plan;
+    }
+
+    /**
+     * 鎵弿鎵�鏈夎澶囷紝鎶婂凡鍒扮偣锛堜笅娆′繚鍏绘棩鏈熷墠1涓湀璧凤紝鍚凡杩囨湡锛夌殑璁惧琛ヨ繘鏈勾搴﹁鍒�
+     */
+    private void ensureMaintenanceDetails(DeviceMaintenancePlan plan) {
+        List<Device> devices = deviceMapper.selectList(null);
+        for (Device device : devices) {
+            fillMaintenanceDetailIfAbsent(plan, device);
+        }
+    }
+
+    /**
+     * 璁惧鎻愰啋璧风偣锛氫笅娆′繚鍏绘棩鏈熷墠1涓湀锛堟病濉笅娆℃棩鏈熸椂鐢ㄦ渶杩戞棩鏈�+淇濆吇鍛ㄦ湡鍏滃簳锛�
+     */
+    private LocalDate calcMaintenanceRemindDate(Device device) {
+        LocalDateTime base = device.getNextMaintenanceDate();
+        if (base == null && device.getLastMaintenanceDate() != null && StringUtils.isNotBlank(device.getMaintenanceCycle())) {
+            try {
+                base = device.getLastMaintenanceDate().plusMonths(Long.parseLong(device.getMaintenanceCycle().trim()));
+            } catch (NumberFormatException ignored) {
+            }
+        }
+        return base == null ? null : base.toLocalDate().minusMonths(1);
+    }
+
+    /**
+     * 宸插埌鐐逛笖璁″垝涓皻鏃犺璁惧鏈疆鏄庣粏鏃惰嚜鍔ㄨˉ涓�鏉�
+     */
+    private void fillMaintenanceDetailIfAbsent(DeviceMaintenancePlan plan, Device device) {
+        LocalDate remind = calcMaintenanceRemindDate(device);
+        if (remind == null || remind.isAfter(LocalDate.now())) {
+            return;
+        }
+        if (plan.getPlanYear() != null && String.valueOf(remind.getYear()).compareTo(plan.getPlanYear()) > 0) {
+            return;
+        }
+        Long exist = deviceMaintenancePlanDetailsMapper.selectCount(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
+                .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, plan.getMaintenancePlanId())
+                .eq(DeviceMaintenancePlanDetails::getDeviceId, device.getId())
+                .eq(DeviceMaintenancePlanDetails::getMaintenanceDate, remind));
+        if (exist != null && exist > 0) {
+            return;
+        }
+        DeviceMaintenancePlanDetails detail = new DeviceMaintenancePlanDetails();
+        detail.setMaintenancePlanId(plan.getMaintenancePlanId());
+        detail.setDeviceId(device.getId());
+        detail.setMaintenanceIntervals(device.getMaintenanceCycle());
+        detail.setMaintenanceDate(remind);
+        deviceMaintenancePlanDetailsMapper.insert(detail);
+    }
 
 }
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java
index 1e36655..c3f492c 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceScrappedServiceImpl.java
@@ -7,6 +7,7 @@
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.common.core.domain.Result;
 import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.QueryWrappers;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.device.dto.DeviceScrappedDto;
@@ -15,6 +16,7 @@
 import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceScrapped;
 import com.ruoyi.device.service.DeviceScrappedService;
+import com.ruoyi.device.util.DeviceFlowPermissionUtils;
 import com.ruoyi.inspect.util.UserUtils;
 import com.ruoyi.system.mapper.UserMapper;
 import org.springframework.beans.BeanUtils;
@@ -66,6 +68,7 @@
         DeviceScrapped scrapped = new DeviceScrapped();
         // 褰撳墠鐧诲綍鐢ㄦ埛淇℃伅鍜岄儴闂�
         User user = userMapper.selectById(SecurityUtils.getUserId().intValue());
+        checkFlowPermission(deviceScrapped, user);
         switch (deviceScrapped.getFlowType()) {
             case 0:
                 BeanUtils.copyProperties(deviceScrapped, scrapped);
@@ -74,11 +77,6 @@
                 scrapped.setApplicantUserId(user.getId());
                 scrapped.setApplicantUser(user.getName());
                 scrapped.setApplicantDate(LocalDate.now());
-
-                // 澶勭悊浜轰俊鎭�
-                User departmentHeadUser = userMapper.selectById(deviceScrapped.getDepartmentHeadUserId());
-                scrapped.setApplicantUserId(departmentHeadUser.getId());
-                scrapped.setApplicantUser(departmentHeadUser.getName());
 
                 baseMapper.insert(scrapped);
                 break;
@@ -121,6 +119,40 @@
         return true;
     }
 
+    private void checkFlowPermission(DeviceScrapped deviceScrapped, User user) {
+        Integer flowType = deviceScrapped.getFlowType();
+        Integer scrappedId = deviceScrapped.getScrappedId();
+        if (flowType != null && flowType > 0) {
+            DeviceScrapped existing = baseMapper.selectById(scrappedId);
+            if (existing == null) {
+                throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+            }
+            DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+            Integer expectedUserId;
+            switch (flowType) {
+                case 1:
+                    expectedUserId = existing.getDepartmentHeadUserId();
+                    break;
+                case 2:
+                    expectedUserId = existing.getMeteringRoomUserId();
+                    break;
+                case 3:
+                    expectedUserId = existing.getApproverUserId();
+                    break;
+                default:
+                    expectedUserId = null;
+                    break;
+            }
+            DeviceFlowPermissionUtils.checkResponsible(expectedUserId, user.getId());
+        } else if (scrappedId != null) {
+            DeviceScrapped existing = baseMapper.selectById(scrappedId);
+            if (existing != null) {
+                DeviceFlowPermissionUtils.checkFinished(existing.getIsFinish());
+                DeviceFlowPermissionUtils.checkResponsible(existing.getApplicantUserId(), user.getId());
+            }
+        }
+    }
+
     /**
      * 瀵煎嚭璁惧鎶ュ簾鐢宠
      *
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
index 5a007c1..a0cc9d1 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
@@ -25,15 +25,14 @@
 import com.ruoyi.device.mapper.CollectBridgeMapper;
 import com.ruoyi.device.mapper.DeviceCalibrationPlanDetailMapper;
 import com.ruoyi.device.mapper.DeviceCalibrationPlanMapper;
-import com.ruoyi.device.mapper.DeviceExaminePlanDetailsMapper;
-import com.ruoyi.device.mapper.DeviceExaminePlanMapper;
 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.mapper.DeviceMetricRecordMapper;
 import com.ruoyi.device.pojo.*;
 import com.ruoyi.device.service.DataConfigService;
+import com.ruoyi.device.service.DeviceCalibrationPlanService;
+import com.ruoyi.device.service.DeviceExaminePlanService;
+import com.ruoyi.device.service.DeviceMaintenancePlanService;
 import com.ruoyi.device.service.DeviceService;
 import com.ruoyi.device.service.DeviceDocumentsService;
 import com.ruoyi.device.utils.DataAcquisition;
@@ -89,17 +88,11 @@
 
     private CollectBridgeMapper collectBridgeMapper;
 
-    private DeviceCalibrationPlanMapper deviceCalibrationPlanMapper;
+    private DeviceCalibrationPlanService deviceCalibrationPlanService;
 
-    private DeviceCalibrationPlanDetailMapper deviceCalibrationPlanDetailMapper;
+    private DeviceExaminePlanService deviceExaminePlanService;
 
-    private DeviceExaminePlanMapper deviceExaminePlanMapper;
-
-    private DeviceExaminePlanDetailsMapper deviceExaminePlanDetailsMapper;
-
-    private DeviceMaintenancePlanMapper deviceMaintenancePlanMapper;
-
-    private DeviceMaintenancePlanDetailsMapper deviceMaintenancePlanDetailsMapper;
+    private DeviceMaintenancePlanService deviceMaintenancePlanService;
 
     @Override
     public IPage<DeviceDto> selectDeviceParameter(Page page, DeviceDto itemParameter, Boolean laboratoryNameIsNull) {
@@ -121,153 +114,22 @@
     @Override
     public int upDeviceParameter(Device itemParameter) {
         int rows = deviceMapper.updateById(itemParameter);
-        // 妗f淇濉啓浜嗘牎鍑嗘湁鏁堟湡鍜屾牎鍑嗗懆鏈熷悗锛岃嚜鍔ㄥ悓姝ュ埌璁惧鏍″噯璁″垝
+        // 妗f淇濉啓浜嗘牎鍑嗕俊鎭悗锛屽埌鐐圭殑鑷姩鍚屾鍒拌澶囨牎鍑嗚鍒�
         Device device = deviceMapper.selectById(itemParameter.getId());
-        if (device != null && device.getActivationDate() != null && StringUtils.isNotBlank(device.getCalibrationDate())) {
-            syncCalibrationPlanDetail(device);
+        if (device != null && (device.getNextCalibrationDate() != null || device.getActivationDate() != null)) {
+            deviceCalibrationPlanService.syncDeviceCalibrationDetail(device);
         }
-        // 妗f淇濉啓浜嗘牳鏌ヤ俊鎭悗锛岃嚜鍔ㄥ悓姝ュ埌璁惧鏍告煡璁″垝
-        if (device != null && device.getNextExamineDate() != null && StringUtils.isNotBlank(device.getExamineCycle())) {
-            syncExaminePlanDetail(device);
+        // 妗f淇濉啓浜嗘牳鏌ヤ俊鎭悗锛屽埌鐐圭殑鑷姩鍚屾鍒拌澶囨牳鏌ヨ鍒�
+        if (device != null && (device.getNextExamineDate() != null
+                || (device.getLastExamineDate() != null && StringUtils.isNotBlank(device.getExamineCycle())))) {
+            deviceExaminePlanService.syncDeviceExamineDetail(device);
         }
-        // 妗f淇濉啓浜嗕繚鍏讳俊鎭悗锛岃嚜鍔ㄥ悓姝ュ埌璁惧淇濆吇璁″垝
-        if (device != null && device.getNextMaintenanceDate() != null && StringUtils.isNotBlank(device.getMaintenanceCycle())) {
-            syncMaintenancePlanDetail(device);
+        // 妗f淇濉啓浜嗕繚鍏讳俊鎭悗锛屽埌鐐圭殑鑷姩鍚屾鍒拌澶囦繚鍏昏鍒�
+        if (device != null && (device.getNextMaintenanceDate() != null
+                || (device.getLastMaintenanceDate() != null && StringUtils.isNotBlank(device.getMaintenanceCycle())))) {
+            deviceMaintenancePlanService.syncDeviceMaintenanceDetail(device);
         }
         return rows;
-    }
-
-    /**
-     * 鎶婅澶囧悓姝ュ埌浠婂勾鐨勮澶囨牎鍑嗚鍒掓槑缁嗭細鏈勾璁″垝鏍″噯鏃堕棿 = 鏍″噯鏈夋晥鏈熷墠涓�涓湀
-     */
-    private void syncCalibrationPlanDetail(Device device) {
-        String planYear = String.valueOf(LocalDate.now().getYear());
-        DeviceCalibrationPlan plan = deviceCalibrationPlanMapper.selectOne(Wrappers.<DeviceCalibrationPlan>lambdaQuery()
-                .eq(DeviceCalibrationPlan::getPlanYear, planYear)
-                .and(w -> w.isNull(DeviceCalibrationPlan::getRatifyStatus).or().ne(DeviceCalibrationPlan::getRatifyStatus, 1))
-                .orderByDesc(DeviceCalibrationPlan::getPlanId)
-                .last("limit 1"));
-        if (plan == null) {
-            plan = new DeviceCalibrationPlan();
-            plan.setPlanName(planYear + "骞村害璁惧鏍″噯璁″垝");
-            plan.setPlanYear(planYear);
-            plan.setWriteUserId(SecurityUtils.getUserId().intValue());
-            plan.setWriteTime(LocalDateTime.now());
-            deviceCalibrationPlanMapper.insert(plan);
-        }
-
-        DeviceCalibrationPlanDetail detail = deviceCalibrationPlanDetailMapper.selectOne(Wrappers.<DeviceCalibrationPlanDetail>lambdaQuery()
-                .eq(DeviceCalibrationPlanDetail::getPlanId, plan.getPlanId())
-                .eq(DeviceCalibrationPlanDetail::getDeviceId, device.getId())
-                .last("limit 1"));
-        boolean isInsert = detail == null;
-        if (isInsert) {
-            detail = new DeviceCalibrationPlanDetail();
-            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.setDeviceAmount("1");
-        detail.setDeviceNumber(device.getManagementNumber());
-        detail.setVerificationUnit(device.getCalibrationServices());
-        detail.setVerificationCycles(device.getCalibrationDate());
-        detail.setLastDate(device.getLastCalibrationDate() == null ? null : device.getLastCalibrationDate().toLocalDate());
-        detail.setPlanDate(device.getActivationDate().toLocalDate().minusMonths(1));
-        if (isInsert) {
-            deviceCalibrationPlanDetailMapper.insert(detail);
-        } else {
-            deviceCalibrationPlanDetailMapper.updateById(detail);
-        }
-    }
-
-    /**
-     * 鎶婅澶囧悓姝ュ埌浠婂勾鐨勮澶囨牳鏌ヨ鍒掓槑缁嗭細鏍告煡鏃堕棿 = 涓嬫鏍告煡鏃ユ湡鍓嶄竴涓湀
-     */
-    private void syncExaminePlanDetail(Device device) {
-        String planYear = String.valueOf(LocalDate.now().getYear());
-        DeviceExaminePlan plan = deviceExaminePlanMapper.selectOne(Wrappers.<DeviceExaminePlan>lambdaQuery()
-                .eq(DeviceExaminePlan::getPlanYear, planYear)
-                .and(w -> w.isNull(DeviceExaminePlan::getRatifyStatus).or().ne(DeviceExaminePlan::getRatifyStatus, 1))
-                .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());
-            deviceExaminePlanMapper.insert(plan);
-        }
-
-        DeviceExaminePlanDetails detail = deviceExaminePlanDetailsMapper.selectOne(Wrappers.<DeviceExaminePlanDetails>lambdaQuery()
-                .eq(DeviceExaminePlanDetails::getPlanId, plan.getPlanId())
-                .eq(DeviceExaminePlanDetails::getDeviceId, device.getId())
-                .last("limit 1"));
-        boolean isInsert = detail == null;
-        if (isInsert) {
-            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(device.getNextExamineDate().toLocalDate().minusMonths(1).toString());
-        if (isInsert) {
-            deviceExaminePlanDetailsMapper.insert(detail);
-        } else {
-            deviceExaminePlanDetailsMapper.updateById(detail);
-        }
-    }
-
-    /**
-     * 鎶婅澶囧悓姝ュ埌浠婂勾鐨勮澶囦繚鍏昏鍒掓槑缁嗭細璁″垝淇濆吇鏃堕棿 = 涓嬫淇濆吇鏃ユ湡鍓嶄竴涓湀
-     */
-    private void syncMaintenancePlanDetail(Device device) {
-        String planYear = String.valueOf(LocalDate.now().getYear());
-        DeviceMaintenancePlan plan = deviceMaintenancePlanMapper.selectOne(Wrappers.<DeviceMaintenancePlan>lambdaQuery()
-                .eq(DeviceMaintenancePlan::getPlanYear, planYear)
-                .and(w -> w.isNull(DeviceMaintenancePlan::getStatus).or().ne(DeviceMaintenancePlan::getStatus, 1))
-                .orderByDesc(DeviceMaintenancePlan::getMaintenancePlanId)
-                .last("limit 1"));
-        if (plan == null) {
-            plan = new DeviceMaintenancePlan();
-            plan.setPlanName(planYear + "骞村害璁惧淇濆吇璁″垝");
-            plan.setPlanYear(planYear);
-            Integer userId = SecurityUtils.getUserId().intValue();
-            User user = userMapper.selectById(userId);
-            plan.setCompilerId(userId);
-            if (user != null) {
-                plan.setCompiler(user.getName());
-            }
-            plan.setDatePreparation(LocalDateTime.now());
-            deviceMaintenancePlanMapper.insert(plan);
-        }
-
-        DeviceMaintenancePlanDetails detail = deviceMaintenancePlanDetailsMapper.selectOne(Wrappers.<DeviceMaintenancePlanDetails>lambdaQuery()
-                .eq(DeviceMaintenancePlanDetails::getMaintenancePlanId, plan.getMaintenancePlanId())
-                .eq(DeviceMaintenancePlanDetails::getDeviceId, device.getId())
-                .last("limit 1"));
-        boolean isInsert = detail == null;
-        if (isInsert) {
-            detail = new DeviceMaintenancePlanDetails();
-            detail.setMaintenancePlanId(plan.getMaintenancePlanId());
-            detail.setDeviceId(device.getId());
-        }
-        detail.setMaintenanceIntervals(device.getMaintenanceCycle());
-        detail.setMaintenanceDate(device.getNextMaintenanceDate().toLocalDate().minusMonths(1));
-        if (isInsert) {
-            deviceMaintenancePlanDetailsMapper.insert(detail);
-        } else {
-            deviceMaintenancePlanDetailsMapper.updateById(detail);
-        }
     }
 
     @Override
diff --git a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java
index e2acac2..06ce59a 100644
--- a/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java
+++ b/cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceStateServiceImpl.java
@@ -9,12 +9,19 @@
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.basic.mapper.LaboratoryMapper;
 import com.ruoyi.basic.pojo.Laboratory;
+import com.ruoyi.common.core.domain.entity.User;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.device.dto.DeviceStateDto;
+import com.ruoyi.device.mapper.DeviceMapper;
 import com.ruoyi.device.mapper.DeviceStateMapper;
+import com.ruoyi.device.pojo.Device;
 import com.ruoyi.device.pojo.DeviceState;
 import com.ruoyi.device.service.DeviceStateService;
 import com.ruoyi.inspect.util.UserUtils;
+import com.ruoyi.system.mapper.UserMapper;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
@@ -37,6 +44,59 @@
 
     @Resource
     private LaboratoryMapper laboratoryMapper;
+    @Resource
+    private UserMapper userMapper;
+    @Resource
+    private DeviceMapper deviceMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveDeviceState(DeviceState deviceState) {
+        User user = userMapper.selectById(SecurityUtils.getUserId().intValue());
+        if (deviceState.getStateId() != null) {
+            DeviceState existing = baseMapper.selectById(deviceState.getStateId());
+            if (existing == null) {
+                throw new ServiceException("鍗曟嵁涓嶅瓨鍦�");
+            }
+            if ("鍏抽棴".equals(existing.getCurrentState())) {
+                throw new ServiceException("璇ユ祦绋嬪凡缁撴潫锛屼笉鑳藉啀鎿嶄綔");
+            }
+            if (StringUtils.isNotBlank(existing.getCurrentResponsible())
+                    && !existing.getCurrentResponsible().equals(user.getName())) {
+                throw new ServiceException("鎮ㄤ笉鏄綋鍓嶇幆鑺傝矗浠讳汉锛屾棤鏉冩搷浣�");
+            }
+        }
+        boolean saved = saveOrUpdate(deviceState);
+        if (saved && "鍏抽棴".equals(deviceState.getCurrentState())) {
+            updateDeviceStatus(deviceState);
+        }
+        return saved;
+    }
+
+    /**
+     * 娴佺▼鏈�缁堟壒鍑嗗悗锛屾妸鍋滅敤/鍚敤缁撴灉鍚屾鍒拌澶囨。妗堢姸鎬�
+     */
+    private void updateDeviceStatus(DeviceState deviceState) {
+        if (deviceState.getDeviceId() == null) {
+            return;
+        }
+        Device device = deviceMapper.selectById(deviceState.getDeviceId());
+        if (device == null) {
+            return;
+        }
+        Integer status = null;
+        if ("0scrap".equals(deviceState.getDeviceStatus())) {
+            status = 3;
+        } else if ("1startUsing".equals(deviceState.getDeviceStatus())) {
+            status = 0;
+        } else if ("2stopUsing".equals(deviceState.getDeviceStatus())) {
+            status = 2;
+        }
+        if (status != null) {
+            device.setDeviceStatus(status);
+            deviceMapper.updateById(device);
+        }
+    }
 
     @Override
     public IPage<DeviceStateDto> getDeviceStatePage(Integer deviceId, Page page, String processNumber) {
@@ -86,7 +146,7 @@
         try {
             response.setContentType("application/msword");
             String fileName = URLEncoder.encode(
-                    "璁惧缁存姢淇濆吇璁板綍", "UTF-8");
+                    "璁惧鍋滅敤鍚敤璁板綍", "UTF-8");
             response.setHeader("Content-disposition",
                     "attachment;filename=" + fileName + ".docx");
             OutputStream os = response.getOutputStream();
diff --git a/cnas-device/src/main/java/com/ruoyi/device/util/DeviceFlowPermissionUtils.java b/cnas-device/src/main/java/com/ruoyi/device/util/DeviceFlowPermissionUtils.java
new file mode 100644
index 0000000..9bab4fb
--- /dev/null
+++ b/cnas-device/src/main/java/com/ruoyi/device/util/DeviceFlowPermissionUtils.java
@@ -0,0 +1,30 @@
+package com.ruoyi.device.util;
+
+import com.ruoyi.common.exception.ServiceException;
+
+/**
+ * 璁惧娴佺▼鏉冮檺鏍¢獙
+ */
+public final class DeviceFlowPermissionUtils {
+
+    private DeviceFlowPermissionUtils() {
+    }
+
+    /**
+     * 鏍¢獙褰撳墠鐢ㄦ埛鏄惁涓烘寚瀹氱幆鑺傝矗浠讳汉锛屽巻鍙插崟鎹湭鎸囧畾璐d换浜烘椂鏀捐
+     */
+    public static void checkResponsible(Integer expectedUserId, Integer currentUserId) {
+        if (expectedUserId != null && !expectedUserId.equals(currentUserId)) {
+            throw new ServiceException("鎮ㄤ笉鏄綋鍓嶇幆鑺傝矗浠讳汉锛屾棤鏉冩搷浣�");
+        }
+    }
+
+    /**
+     * 娴佺▼缁撴潫鍚庣姝㈠啀娆℃搷浣�
+     */
+    public static void checkFinished(Integer isFinish) {
+        if (isFinish != null && isFinish == 1) {
+            throw new ServiceException("璇ュ崟鎹凡瀹℃壒瀹屾垚锛屼笉鑳藉啀鎿嶄綔");
+        }
+    }
+}
diff --git a/cnas-device/src/main/resources/mapper/DeviceCalibrationPlanDetailMapper.xml b/cnas-device/src/main/resources/mapper/DeviceCalibrationPlanDetailMapper.xml
index d6bce8c..9dc7a5a 100644
--- a/cnas-device/src/main/resources/mapper/DeviceCalibrationPlanDetailMapper.xml
+++ b/cnas-device/src/main/resources/mapper/DeviceCalibrationPlanDetailMapper.xml
@@ -3,11 +3,16 @@
 <mapper namespace="com.ruoyi.device.mapper.DeviceCalibrationPlanDetailMapper">
 
     <!-- 璁惧鏍″噯璁″垝璇︽儏鍒楄〃 -->
-    <select id="pageDeviceCalibrationPlanDetail" resultType="com.ruoyi.device.pojo.DeviceCalibrationPlanDetail">
+    <select id="pageDeviceCalibrationPlanDetail" resultType="com.ruoyi.device.dto.DeviceCalibrationPlanDetailDto">
         select * from (
-        select *
-        from device_calibration_plan_detail
-        order by plan_date asc
+        select t.*,
+        case when d.next_calibration_date is not null
+        and d.next_calibration_date &lt;= date_add(curdate(), interval 1 month)
+        and (d.last_calibration_date is null or d.last_calibration_date &lt; t.plan_date)
+        then 1 else 0 end as red_flag
+        from device_calibration_plan_detail t
+        left join device d on d.id = t.device_id
+        order by t.plan_date asc
         ) a
         <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
             ${ew.customSqlSegment}
diff --git a/cnas-device/src/main/resources/mapper/DeviceExaminePlanDetailsMapper.xml b/cnas-device/src/main/resources/mapper/DeviceExaminePlanDetailsMapper.xml
index 9eb714c..56597c1 100644
--- a/cnas-device/src/main/resources/mapper/DeviceExaminePlanDetailsMapper.xml
+++ b/cnas-device/src/main/resources/mapper/DeviceExaminePlanDetailsMapper.xml
@@ -33,11 +33,16 @@
         WHEN dd.review_status = 0 THEN 3
         WHEN dd.review_status = 1 THEN 2
         WHEN dd.record_contrast_id IS NOT NULL THEN 1
-        ELSE 0 END record_contrast_status
+        ELSE 0 END record_contrast_status,
+        CASE WHEN dev.next_examine_date IS NOT NULL
+            AND dev.next_examine_date &lt;= date_add(curdate(), interval 1 month)
+            AND (dev.last_examine_date IS NULL OR dev.last_examine_date &lt; str_to_date(concat(left(d.check_time, 7), '-01'), '%Y-%m-%d'))
+            THEN 1 ELSE 0 END red_flag
         from device_examine_plan_details d
         left join user u on u.id = d.check_charger_user_id
         left join device_examine_record dc on d.plan_details_id = dc.plan_details_id
-        left join device_examine_record_contrast dd on dd.plan_details_id = dc.plan_details_id
+        left join device_examine_record_contrast dd on dd.plan_details_id = d.plan_details_id
+        left join device dev on dev.id = d.device_id
         order by check_time asc
         ) a
         <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
diff --git a/cnas-device/src/main/resources/mapper/DeviceMaintenancePlanDetailsMapper.xml b/cnas-device/src/main/resources/mapper/DeviceMaintenancePlanDetailsMapper.xml
index aeb49ce..219a418 100644
--- a/cnas-device/src/main/resources/mapper/DeviceMaintenancePlanDetailsMapper.xml
+++ b/cnas-device/src/main/resources/mapper/DeviceMaintenancePlanDetailsMapper.xml
@@ -17,4 +17,29 @@
         where dmpd.maintenance_plan_id = #{maintenancePlanId}
     </select>
 
+    <!-- 璁惧淇濆吇璁″垝璇︽儏鍒楄〃锛堝惈鍒扮偣鏍囩孩锛� -->
+    <select id="pageMaintenancePlanDetail" resultType="com.ruoyi.device.dto.DeviceMaintenancePlanDetailsDto">
+        select
+            dmpd.*,
+            d.device_name,
+            d.management_number device_number,
+            d.specification_model,
+            d.management_number,
+            d.storage_point,
+            case when d.next_maintenance_date is not null
+                and d.next_maintenance_date &lt;= date_add(curdate(), interval 1 month)
+                and (d.last_maintenance_date is null or d.last_maintenance_date &lt; dmpd.maintenance_date)
+                then 1 else 0 end as red_flag
+        from device_maintenance_plan_details dmpd
+        left join device d on dmpd.device_id = d.id
+        where dmpd.maintenance_plan_id = #{planId}
+        <if test="deviceName != null and deviceName != ''">
+            and d.device_name like concat('%', #{deviceName}, '%')
+        </if>
+        <if test="deviceNumber != null and deviceNumber != ''">
+            and d.management_number like concat('%', #{deviceNumber}, '%')
+        </if>
+        order by dmpd.maintenance_date asc
+    </select>
+
 </mapper>
diff --git a/cnas-device/src/main/resources/mapper/DeviceMapper.xml b/cnas-device/src/main/resources/mapper/DeviceMapper.xml
index 8009a5a..b84746b 100644
--- a/cnas-device/src/main/resources/mapper/DeviceMapper.xml
+++ b/cnas-device/src/main/resources/mapper/DeviceMapper.xml
@@ -97,7 +97,7 @@
         cmr.calibration_date as last_calibration_date_two,
         cmr.next_calibration_date as next_calibration_date_two,
         GROUP_CONCAT(distinct inspection_item SEPARATOR ';') AS insProductItem,
-        u.name ,
+        u.name AS equipment_manager_user,
         sd.dept_name AS laboratory_name
         FROM
         device d
diff --git a/cnas-require/src/main/java/com/ruoyi/require/pojo/FeStandardSubstance.java b/cnas-require/src/main/java/com/ruoyi/require/pojo/FeStandardSubstance.java
index 0c6bc72..27225dd 100644
--- a/cnas-require/src/main/java/com/ruoyi/require/pojo/FeStandardSubstance.java
+++ b/cnas-require/src/main/java/com/ruoyi/require/pojo/FeStandardSubstance.java
@@ -52,6 +52,10 @@
     @Schema(description = "鏁伴噺")
     private Long quantity;
 
+    @Schema(description = "鍓╀綑鏁伴噺")
+    @TableField(exist = false)
+    private Long remainingQuantity;
+
     @Schema(description = "鍗曚綅")
     private String unit;
 
diff --git a/cnas-require/src/main/resources/mapper/FeStandardSubstanceMapper.xml b/cnas-require/src/main/resources/mapper/FeStandardSubstanceMapper.xml
index 5a9d0bd..bcb37fc 100644
--- a/cnas-require/src/main/resources/mapper/FeStandardSubstanceMapper.xml
+++ b/cnas-require/src/main/resources/mapper/FeStandardSubstanceMapper.xml
@@ -24,33 +24,41 @@
         <result column="update_time" property="updateTime" />
     </resultMap>
     <select id="getPage" resultType="com.ruoyi.require.pojo.FeStandardSubstance">
-        select * from cnas_fe_standard_substance
+        select s.*,
+               GREATEST(COALESCE(s.quantity, 0) - COALESCE((
+                   SELECT SUM(CASE WHEN r.borrow_quantity IS NULL THEN COALESCE(s.quantity, 0)
+                                   ELSE r.borrow_quantity END)
+                   FROM cnas_fe_standard_substance_record r
+                   WHERE r.substance_id = s.id AND COALESCE(r.status, '0') &lt;&gt; '1'
+               ), 0), 0) AS remaining_quantity
+        from cnas_fe_standard_substance s
         <where>
             <if test="ew.name != null and ew.name != ''">
-                name like CONCAT('%', #{ew.name}, '%')
+                s.name like CONCAT('%', #{ew.name}, '%')
             </if>
             <if test="ew.factoryManufacturer != null and ew.factoryManufacturer != ''">
-                and factory_manufacturer like CONCAT('%', #{ew.factoryManufacturer}, '%')
+                and s.factory_manufacturer like CONCAT('%', #{ew.factoryManufacturer}, '%')
             </if>
             <if test="ew.factoryNum != null and ew.factoryNum != ''">
-                and factory_num like CONCAT('%', #{factoryNum}, '%')
+                and s.factory_num like CONCAT('%', #{ew.factoryNum}, '%')
             </if>
             <if test="ew.manageNum != null and ew.manageNum != ''">
-                and manage_num like CONCAT('%', #{ew.manageNum}, '%')
+                and s.manage_num like CONCAT('%', #{ew.manageNum}, '%')
             </if>
             <if test="ew.model != null and ew.model != ''">
-                and model like CONCAT('%', #{ew.model}, '%')
+                and s.model like CONCAT('%', #{ew.model}, '%')
             </if>
             <if test="ew.effectiveDate != null and ew.effectiveDate != ''">
-                and effective_date =  #{ew.effectiveDate}
+                and s.effective_date =  #{ew.effectiveDate}
             </if>
             <if test="ew.fileNum != null and ew.fileNum != ''">
-                and file_num like CONCAT('%', #{ew.fileNum}, '%')
+                and s.file_num like CONCAT('%', #{ew.fileNum}, '%')
             </if>
             <if test="ew.position != null and ew.position != ''">
-                and position like CONCAT('%', #{ew.position}, '%')
+                and s.position like CONCAT('%', #{ew.position}, '%')
             </if>
         </where>
+        ORDER BY s.acquisition_date DESC, s.id DESC
     </select>
 
 </mapper>
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/numgen/LambdaUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/numgen/LambdaUtils.java
index f8288ce..d342f03 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/numgen/LambdaUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/numgen/LambdaUtils.java
@@ -58,12 +58,17 @@
      */
     public static <T> SerializedLambda resolve(SFunction<T, ?> func) {
         Class<?> clazz = func.getClass();
-        String canonicalName = clazz.getCanonicalName();
-        return Optional.ofNullable(FUNC_CACHE.get(canonicalName))
+        // lambda/鏂规硶寮曠敤鐢熸垚鐨勬槸闅愯棌绫伙紝getCanonicalName() 杩斿洖 null锛孋oncurrentHashMap 涓嶆帴鍙� null key
+        String cacheKey = clazz.getCanonicalName();
+        if (cacheKey == null) {
+            cacheKey = clazz.getName();
+        }
+        final String key = cacheKey;
+        return Optional.ofNullable(FUNC_CACHE.get(key))
             .map(WeakReference::get)
             .orElseGet(() -> {
                 SerializedLambda lambda = SerializedLambda.resolve(func);
-                FUNC_CACHE.put(canonicalName, new WeakReference<>(lambda));
+                FUNC_CACHE.put(key, new WeakReference<>(lambda));
                 return lambda;
             });
     }
diff --git a/ruoyi-system/src/main/resources/mapper/system/UserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/UserMapper.xml
index 7db5013..7075623 100644
--- a/ruoyi-system/src/main/resources/mapper/system/UserMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/UserMapper.xml
@@ -114,11 +114,24 @@
                name_en,
                status
         from user u
-        where depart_lims_id is not null
-          and depart_lims_id != ''
-          and depart_lims_id = (select u2.depart_lims_id
-                                from user u2
-                                where u2.id = #{userId})
+        where (
+            (depart_lims_id is not null
+             and depart_lims_id != ''
+             and depart_lims_id = (select u2.depart_lims_id
+                                   from user u2
+                                   where u2.id = #{userId}))
+            or
+            (
+                ((select u3.depart_lims_id
+                  from user u3
+                  where u3.id = #{userId}) is null
+                 or (select u3.depart_lims_id
+                     from user u3
+                     where u3.id = #{userId}) = '')
+                and u.status = '0'
+                and u.del_flag = '0'
+            )
+        )
     </select>
 
 </mapper>

--
Gitblit v1.9.3