From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master

---
 jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/util/LimsSxtJihuaUtil.java |  273 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 273 insertions(+), 0 deletions(-)

diff --git a/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/util/LimsSxtJihuaUtil.java b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/util/LimsSxtJihuaUtil.java
new file mode 100644
index 0000000..4e55d52
--- /dev/null
+++ b/jnpf-lims/jnpf-lims-biz/src/main/java/jnpf/util/LimsSxtJihuaUtil.java
@@ -0,0 +1,273 @@
+package jnpf.util;
+
+import jnpf.limsEntity.LimsSxtQuyangJihuaEntity;
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * 鍙栨牱璁″垝鍛ㄦ湡鍒ゆ柇宸ュ叿绫�
+ *
+ */
+@Slf4j
+public class LimsSxtJihuaUtil {
+    /**
+     * 鍒ゆ柇璁″垝鍦ㄦ寚瀹氭棩鏈熸槸鍚﹀簲璇ユ墽琛�
+     *
+     * @param plan 璁″垝瀹炰綋
+     * @param date 鐩爣鏃ユ湡
+     * @return true-闇�瑕佹墽琛岋紝false-涓嶉渶瑕佹墽琛�
+     */
+    public static boolean shouldExecuteOnDate(LimsSxtQuyangJihuaEntity plan, LocalDate date) {
+        if (plan == null || date == null) {
+            log.debug("璁″垝鎴栨棩鏈熶负绌�");
+            return false;
+        }
+
+        // 1. 妫�鏌ュ垹闄ゆ爣蹇�
+        if (isDeleted(plan)) {
+            log.debug("璁″垝宸插垹闄わ紝fId={}", plan.getId());
+            return false;
+        }
+
+        // 2. 妫�鏌ユ祦绋嬬姸鎬侊紙2=宸查�氳繃鐢熸晥锛屾牴鎹笟鍔¤皟鏁达級
+        if (!isApproved(plan)) {
+            log.debug("璁″垝娴佺▼鐘舵�佹湭鐢熸晥锛宖Id={}, state={}", plan.getId(), plan.getFFlowState());
+            return false;
+        }
+
+        // 3. 妫�鏌ユ槸鍚﹀湪璁″垝鏃堕棿鑼冨洿鍐�
+        if (!isInDateRange(plan, date)) {
+            log.debug("璁″垝涓嶅湪鏃堕棿鑼冨洿鍐咃紝fId={}, date={}", plan.getId(), date);
+            return false;
+        }
+
+        // 4. 鏍规嵁鍛ㄦ湡绫诲瀷鍒ゆ柇
+        String cycleType = plan.getJihuaZhouqiLeixing();
+        if (cycleType == null || cycleType.isEmpty()) {
+            log.debug("璁″垝鍛ㄦ湡绫诲瀷涓虹┖锛宖Id={}", plan.getId());
+            return false;
+        }
+
+        int interval = parseInterval(plan.getJihuaZhouqiJiange());
+        if (interval < 0) {
+            log.debug("璁″垝鍛ㄦ湡闂撮殧鏃犳晥锛宖Id={}, interval={}", plan.getId(), interval);
+            return false;
+        }
+
+        LocalDate startDate = plan.getKaishiShijian().toInstant()
+                .atZone(java.time.ZoneId.systemDefault()).toLocalDate();
+
+        return matchCycle(cycleType, startDate, date, interval);
+    }
+
+    // ==================== 鍓嶇疆妫�鏌ユ柟娉� ====================
+
+    /**
+     * 妫�鏌ユ槸鍚﹀凡鍒犻櫎锛歠_delete_mark = 1 琛ㄧず宸插垹闄�
+     */
+    private static boolean isDeleted(LimsSxtQuyangJihuaEntity plan) {
+        Integer deleteMark = plan.getFDeleteMark();
+        return deleteMark != null && deleteMark == 1;
+    }
+
+    /**
+     * 妫�鏌ユ祦绋嬬姸鎬佹槸鍚﹀凡閫氳繃锛�2=宸查�氳繃锛�
+     */
+    private static boolean isApproved(LimsSxtQuyangJihuaEntity plan) {
+        Integer flowState = plan.getFFlowState();
+        return flowState != null && flowState == 2;
+    }
+
+    /**
+     * 妫�鏌ユ槸鍚﹀湪璁″垝鏃堕棿鑼冨洿鍐咃紙鍖呭惈璧锋鏃ユ湡锛�
+     */
+    private static boolean isInDateRange(LimsSxtQuyangJihuaEntity plan, LocalDate date) {
+        LocalDate startDate = plan.getKaishiShijian().toInstant()
+                .atZone(java.time.ZoneId.systemDefault()).toLocalDate();
+        LocalDate endDate = plan.getJieshuShijian().toInstant()
+                .atZone(java.time.ZoneId.systemDefault()).toLocalDate();
+        return !date.isBefore(startDate) && !date.isAfter(endDate);
+    }
+
+    /**
+     * 瑙f瀽鍛ㄦ湡闂撮殧
+     */
+    private static int parseInterval(String intervalStr) {
+        if (intervalStr == null || intervalStr.trim().isEmpty()) {
+            return -1;
+        }
+        try {
+            return Integer.parseInt(intervalStr.trim());
+        } catch (NumberFormatException e) {
+            log.error("瑙f瀽鍛ㄦ湡闂撮殧澶辫触: {}", intervalStr, e);
+            return -1;
+        }
+    }
+
+    // ==================== 鍛ㄦ湡鍖归厤鏂规硶 ====================
+
+    /**
+     * 鏍规嵁鍛ㄦ湡绫诲瀷鍖归厤
+     */
+    private static boolean matchCycle(String cycleType, LocalDate startDate, LocalDate targetDate, int interval) {
+        long daysDiff = ChronoUnit.DAYS.between(startDate, targetDate);
+
+        switch (cycleType.toLowerCase()) {
+            case "daily":
+                return matchDaily(daysDiff, interval);
+
+            case "weekly":
+                return matchWeekly(startDate, targetDate, daysDiff, interval);
+
+            case "monthly":
+                return matchMonthly(startDate, targetDate, interval);
+
+            case "quarterly":
+                return matchQuarterly(startDate, targetDate, interval);
+
+            case "yearly":
+                return matchYearly(startDate, targetDate, interval);
+
+            default:
+                log.warn("鏈煡鐨勫懆鏈熺被鍨�: {}", cycleType);
+                return false;
+        }
+    }
+
+    // ==================== 鍚勫懆鏈熷叿浣撳尮閰嶉�昏緫 ====================
+
+    /**
+     * 姣忔棩鍛ㄦ湡鍖归厤
+     * 閫昏緫锛氳窛寮�濮嬫棩鏈熺殑澶╂暟宸� % (闂撮殧 + 1) = 0
+     * 绀轰緥锛氬紑濮�7/14锛岄棿闅�1 鈫� 7/14, 7/16, 7/18...
+     */
+    private static boolean matchDaily(long daysDiff, int interval) {
+        return daysDiff % executionStep(interval) == 0;
+    }
+
+    /**
+     * 姣忓懆鍛ㄦ湡鍖归厤
+     * 閫昏緫锛氭槦鏈熷嚑鐩稿悓 + 鍛ㄦ暟宸� % (闂撮殧 + 1) = 0
+     * 绀轰緥锛氬紑濮�7/14(鍛ㄤ簩)锛岄棿闅�1 鈫� 闅斿懆鍛ㄤ簩鎵ц
+     */
+    private static boolean matchWeekly(LocalDate startDate, LocalDate targetDate, long daysDiff, int interval) {
+        // 鏄熸湡鍑犵浉鍚岋紙LocalDate.DayOfWeek: 鍛ㄤ竴=1, 鍛ㄦ棩=7锛�
+        if (targetDate.getDayOfWeek() != startDate.getDayOfWeek()) {
+            return false;
+        }
+        long weekDiff = daysDiff / 7;
+        return weekDiff % executionStep(interval) == 0;
+    }
+
+    /**
+     * 姣忔湀鍛ㄦ湡鍖归厤锛堝惈鏈堟湯杈圭晫澶勭悊锛�
+     * 閫昏緫锛氭棩鏈熷尮閰� + 鏈堜唤宸� % (闂撮殧 + 1) = 0
+     * 绀轰緥锛氬紑濮�1/31锛岄棿闅�1 鈫� 1/31, 3/31, 5/31...
+     */
+    private static boolean matchMonthly(LocalDate startDate, LocalDate targetDate, int interval) {
+        // 鏃ユ湡鍖归厤锛堝惈鏈堟湯杈圭晫锛�
+        if (!isSameDayOfMonthWithBoundary(startDate, targetDate)) {
+            return false;
+        }
+        long monthDiff = ChronoUnit.MONTHS.between(
+                startDate.withDayOfMonth(1),
+                targetDate.withDayOfMonth(1)
+        );
+        return monthDiff % executionStep(interval) == 0;
+    }
+
+    /**
+     * 姣忓鍛ㄦ湡鍖归厤锛堝惈鏈堟湯杈圭晫澶勭悊锛�
+     * 閫昏緫锛氬悓鏈堝悓鏃� + 瀛e害宸� % (闂撮殧 + 1) = 0
+     * 绀轰緥锛氬紑濮�1/31锛岄棿闅�1 鈫� 1/31, 7/31...
+     */
+    private static boolean matchQuarterly(LocalDate startDate, LocalDate targetDate, int interval) {
+        // 鏃ユ湡鍖归厤锛堝惈鏈堟湯杈圭晫锛�
+        if (!isSameDayOfMonthWithBoundary(startDate, targetDate)) {
+            return false;
+        }
+        long monthDiff = ChronoUnit.MONTHS.between(
+                startDate.withDayOfMonth(1),
+                targetDate.withDayOfMonth(1)
+        );
+        if (monthDiff % 3 != 0) {
+            return false;
+        }
+        long quarterDiff = monthDiff / 3;
+        return quarterDiff % executionStep(interval) == 0;
+    }
+
+    /**
+     * 姣忓勾鍛ㄦ湡鍖归厤锛堝惈2鏈�29鏃ュ鐞嗭級
+     * 閫昏緫锛氭湀鏃ョ浉鍚� + 骞翠唤宸� % (闂撮殧 + 1) = 0
+     * 绀轰緥锛氬紑濮�2024-02-29锛岄棿闅�1 鈫� 2024-02-29, 2026-02-28...
+     */
+    private static boolean matchYearly(LocalDate startDate, LocalDate targetDate, int interval) {
+        // 鏈堟棩鍖归厤锛堝惈2鏈�29鏃ュ鐞嗭級
+        if (!isSameMonthAndDayWithLeapYear(startDate, targetDate)) {
+            return false;
+        }
+        long yearDiff = targetDate.getYear() - startDate.getYear();
+        return yearDiff % executionStep(interval) == 0;
+    }
+
+    private static long executionStep(int interval) {
+        return (long) interval + 1;
+    }
+
+    // ==================== 杈圭晫澶勭悊鏂规硶 ====================
+
+    /**
+     * 鍒ゆ柇鏃ユ湡鏄惁鍖归厤锛堝惈鏈堟湯杈圭晫澶勭悊锛�
+     * <p>
+     * 瑙勫垯锛�
+     * - 濡傛灉寮�濮嬫棩鏈熸槸褰撴湀鏈�鍚庝竴澶� 鈫� 鐩爣鏃ユ湡涔熷繀椤绘槸褰撴湀鏈�鍚庝竴澶╋紙鑷姩閫傞厤涓嶅悓鏈堜唤鐨勫ぉ鏁板樊寮傦級
+     * - 鍚﹀垯 鈫� 鏃ユ湡蹇呴』鐩稿悓
+     * <p>
+     * 绀轰緥锛�
+     * - 寮�濮� 1/31 鈫� 鍖归厤 1/31, 2/28, 3/31, 4/30, 5/31...
+     * - 寮�濮� 2/28 鈫� 鍖归厤 1/28, 2/28, 3/28, 4/28...
+     * - 寮�濮� 2/29 鈫� 鍖归厤 2/29锛堥棸骞达級鎴� 2/28锛堥潪闂板勾锛�
+     */
+    private static boolean isSameDayOfMonthWithBoundary(LocalDate startDate, LocalDate targetDate) {
+        // 濡傛灉寮�濮嬫棩鏈熸槸褰撴湀鏈�鍚庝竴澶�
+        if (startDate.getDayOfMonth() == startDate.lengthOfMonth()) {
+            // 鐩爣鏃ユ湡涔熷繀椤绘槸褰撴湀鏈�鍚庝竴澶�
+            return targetDate.getDayOfMonth() == targetDate.lengthOfMonth();
+        }
+        // 鍚﹀垯鏃ユ湡蹇呴』鐩稿悓
+        return startDate.getDayOfMonth() == targetDate.getDayOfMonth();
+    }
+
+    /**
+     * 鍒ゆ柇鏈堟棩鏄惁鍖归厤锛堝惈2鏈�29鏃ュ鐞嗭級
+     * <p>
+     * 瑙勫垯锛�
+     * - 寮�濮嬫棩鏈熸槸 2/29 鈫� 鐩爣鏃ユ湡蹇呴』鏄� 2/29锛堥棸骞达級鎴� 2/28锛堥潪闂板勾锛�
+     * - 鍚﹀垯 鈫� 鏈堟棩瀹屽叏鐩稿悓
+     * <p>
+     * 绀轰緥锛�
+     * - 寮�濮� 2024-02-29 鈫� 鍖归厤 2024-02-29, 2025-02-28, 2026-02-28, 2028-02-29
+     * - 寮�濮� 2024-03-15 鈫� 鍖归厤 2025-03-15, 2026-03-15...
+     */
+    private static boolean isSameMonthAndDayWithLeapYear(LocalDate startDate, LocalDate targetDate) {
+        // 濡傛灉寮�濮嬫棩鏈熸槸2鏈�29鏃�
+        if (startDate.getMonthValue() == 2 && startDate.getDayOfMonth() == 29) {
+            // 鐩爣鏃ユ湡蹇呴』鏄�2鏈�
+            if (targetDate.getMonthValue() != 2) {
+                return false;
+            }
+            // 闂板勾蹇呴』鏄�29鏃ワ紝闈為棸骞村彲浠ユ槸28鏃�
+            if (targetDate.isLeapYear()) {
+                return targetDate.getDayOfMonth() == 29;
+            } else {
+                return targetDate.getDayOfMonth() == 28;
+            }
+        }
+        // 鏅�氭棩鏈燂細鏈堟棩瀹屽叏鐩稿悓
+        return startDate.getMonthValue() == targetDate.getMonthValue()
+                && startDate.getDayOfMonth() == targetDate.getDayOfMonth();
+    }
+}

--
Gitblit v1.8.0