From cf330c6a6c2ae7ec86d20de465d5af27a4684cf6 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 03 十一月 2026 19:16:58 +0800
Subject: [PATCH] 巡检任务频次增加“每年”支持

---
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java |   80 +++++++++++++++++++++++++++++++++++++++
 1 files changed, 79 insertions(+), 1 deletions(-)

diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
index 779dc5f..032ef14 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -6,6 +6,7 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.device.pojo.DeviceLedger;
 import com.ruoyi.inspectiontask.dto.TimingTaskDto;
 import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
 import com.ruoyi.inspectiontask.pojo.TimingTask;
@@ -35,6 +36,7 @@
     private final TimingTaskMapper timingTaskMapper;
     private final TimingTaskScheduler timingTaskScheduler;
     private final SysUserMapper sysUserMapper;
+    private final com.ruoyi.device.mapper.DeviceLedgerMapper deviceLedgerMapper;
     private static final int ENABLED = 1;
     private static final int DISABLED = 0;
 
@@ -53,6 +55,7 @@
         if (timingTask.getIsEnabled() != null) {
             queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
         }
+        queryWrapper.orderByDesc(TimingTask::getCreateTime);
         IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
 
         // 2. 濡傛灉娌℃湁鏁版嵁锛岀洿鎺ヨ繑鍥炵┖鍒嗛〉
@@ -130,6 +133,7 @@
                 throw new IllegalArgumentException("瀹氭椂浠诲姟涓嶅瓨鍦�");
             }
         }
+        validateDeviceExists(timingTaskDto, oldTimingTask);
         TimingTask timingTask = new TimingTask();
         BeanUtils.copyProperties(timingTaskDto, timingTask);
         timingTask.setIsEnabled(resolveEnabledValue(timingTask.getIsEnabled(), oldTimingTask));
@@ -180,6 +184,24 @@
         }
     }
 
+    private void validateDeviceExists(TimingTaskDto timingTaskDto, TimingTask oldTimingTask) {
+        Integer taskId = timingTaskDto.getTaskId();
+        if (taskId == null && oldTimingTask != null) {
+            taskId = oldTimingTask.getTaskId();
+        }
+        if (taskId == null || taskId <= 0) {
+            return;
+        }
+        DeviceLedger deviceLedger = deviceLedgerMapper.selectById(taskId.longValue());
+        if (deviceLedger == null) {
+            throw new IllegalArgumentException("鎵�閫夎澶囦笉瀛樺湪锛岃閲嶆柊閫夋嫨");
+        }
+        boolean deviceChanged = oldTimingTask == null || !Objects.equals(oldTimingTask.getTaskId(), taskId);
+        if (deviceChanged && Integer.valueOf(0).equals(deviceLedger.getIsEnabled())) {
+            throw new IllegalArgumentException("鎵�閫夎澶囧凡鍋滅敤锛屼笉鑳藉畨鎺掑贰妫�");
+        }
+    }
+
     public LocalDateTime calculateFirstExecutionTime(TimingTask task) {
         // 鏍规嵁棰戠巼绫诲瀷鍜岃鎯呰绠楅娆℃墽琛屾椂闂�
         String frequencyType = task.getFrequencyType();
@@ -193,12 +215,16 @@
             // 濡傛灉鏄瘡鏈堟墽琛岋紝璁$畻涓嬩釜鏈堢殑鍏蜂綋鏃ユ湡
             return calculateMonthlyFirstExecution(task.getFrequencyDetail());
         } else if ("QUARTERLY".equals(frequencyType)) {
-            // 鑷畾涔夐鐜囷紝濡傛瘡灏忔椂銆佹瘡30鍒嗛挓绛�
             return calculateCustomFirstExecution(task.getFrequencyDetail());
+        } else if ("YEARLY".equals(frequencyType)) {
+            return calculateAnnualFirstExecution(task.getFrequencyDetail());
         } else {
             throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + task.getFrequencyType());
         }
     }
+
+    /** 姣忓勾宸℃榛樿鎵ц鏃堕棿锛�07:00锛堥〉闈笂涓嶇簿纭埌鏃跺垎锛� */
+    private static final LocalTime DEFAULT_EXECUTION_TIME = LocalTime.of(7, 0);
 
     private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
         // frequencyDetail鍙兘鏄叿浣撴椂闂达紝濡� "14:30"
@@ -313,6 +339,35 @@
         return targetDateTime;
     }
 
+    private LocalDateTime calculateAnnualFirstExecution(String frequencyDetail) {
+        String[] parts = frequencyDetail.split(",");
+        // 姣忓勾棰戠巼涓嶉渶瑕佺簿纭埌鏃跺垎锛氶粯璁ゆ牸寮忎负 "鏈�,鏃�"锛屽吋瀹规棫鐨� "鏈�,鏃�,HH:mm"
+        if (parts.length != 2 && parts.length != 3) {
+            throw new IllegalArgumentException("鍙傛暟鏍煎紡閿欒锛屽簲涓�'12,31'鏍煎紡");
+        }
+
+        int month;
+        int day;
+        try {
+            month = Integer.parseInt(parts[0].trim());
+            day = Integer.parseInt(parts[1].trim());
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("鏈堜唤鎴栨棩鏈熸牸寮忛敊璇�", e);
+        }
+        if (month < 1 || month > 12 || day < 1 || day > 31) {
+            throw new IllegalArgumentException("鏈堜唤蹇呴』鍦�1-12涔嬮棿锛屾棩鏈熷繀椤诲湪1-31涔嬮棿");
+        }
+
+        LocalTime time = parts.length == 3
+                ? LocalTime.parse(parts[2].trim(), DateTimeFormatter.ofPattern("HH:mm"))
+                : DEFAULT_EXECUTION_TIME;
+        LocalDateTime now = LocalDateTime.now();
+        int year = now.getYear();
+        LocalDateTime target = LocalDateTime.of(LocalDate.of(year, month, Math.min(day, YearMonth.of(year, month).lengthOfMonth())), time);
+        return target.isAfter(now) ? target : LocalDateTime.of(LocalDate.of(year + 1, month,
+                Math.min(day, YearMonth.of(year + 1, month).lengthOfMonth())), time);
+    }
+
     private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
         return null;
     }
@@ -348,6 +403,8 @@
                                                      LocalDateTime currentTime) {
         try {
             switch (frequencyType) {
+                case "YEARLY":
+                    return calculateYearlyNextTime(frequencyDetail, currentTime);
                 case "DAILY":
                     return calculateDailyNextTime(frequencyDetail, currentTime);
                 case "WEEKLY":
@@ -365,6 +422,27 @@
     }
 
     /**
+     * 璁$畻姣忓勾浠诲姟鐨勪笅娆℃墽琛屾椂闂�
+     * frequencyDetail 鏍煎紡涓� "鏈�,鏃�"锛屽吋瀹� "鏈�,鏃�,HH:mm"锛堥粯璁� 07:00锛�
+     */
+    private LocalDateTime calculateYearlyNextTime(String detail, LocalDateTime current) {
+        String[] parts = detail.split(",");
+        int month = Integer.parseInt(parts[0].trim());
+        int day = Integer.parseInt(parts[1].trim());
+        LocalTime time = parts.length >= 3 ? LocalTime.parse(parts[2].trim()) : DEFAULT_EXECUTION_TIME;
+
+        int year = current.getYear();
+        LocalDateTime target = LocalDateTime.of(
+                year, month, Math.min(day, YearMonth.of(year, month).lengthOfMonth()), time.getHour(), time.getMinute());
+        if (!current.isBefore(target)) {
+            year++;
+            target = LocalDateTime.of(
+                    year, month, Math.min(day, YearMonth.of(year, month).lengthOfMonth()), time.getHour(), time.getMinute());
+        }
+        return target;
+    }
+
+    /**
      * 璁$畻姣忔棩浠诲姟鐨勪笅娆℃墽琛屾椂闂�
      */
     private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {

--
Gitblit v1.9.3