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();
|
}
|
}
|