刘光辉
14 小时以前 34981c30a78e8bbd7791131059a9210f9928b62c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
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("计划流程状态未生效,fId={}, 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("计划周期类型为空,fId={}", plan.getId());
            return false;
        }
 
        int interval = parseInterval(plan.getJihuaZhouqiJiange());
        if (interval < 0) {
            log.debug("计划周期间隔无效,fId={}, interval={}", plan.getId(), interval);
            return false;
        }
 
        LocalDate startDate = plan.getKaishiShijian().toInstant()
                .atZone(java.time.ZoneId.systemDefault()).toLocalDate();
 
        return matchCycle(cycleType, startDate, date, interval);
    }
 
    // ==================== 前置检查方法 ====================
 
    /**
     * 检查是否已删除:f_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);
    }
 
    /**
     * 解析周期间隔
     */
    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("解析周期间隔失败: {}", 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;
    }
 
    /**
     * 每季周期匹配(含月末边界处理)
     * 逻辑:同月同日 + 季度差 % (间隔 + 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();
    }
}