|
|
@@ -0,0 +1,582 @@
|
|
|
+package com.awspaas.user.apps.donenow_ctt;
|
|
|
+
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.Month;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.time.temporal.TemporalAdjusters;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 周期计算工具类
|
|
|
+ * 支持:月/季/半年/年周期、自然月/实际月模式、周期列表生成与费率计算
|
|
|
+ */
|
|
|
+public class PeriodCalculationUtil {
|
|
|
+ // 周期类型常量(与原IVTConstant对齐,可根据实际常量类调整)
|
|
|
+ public static final String PERIOD_TYPE_ONCE = "ONCE"; // 一次性收费
|
|
|
+ public static final String PERIOD_TYPE_MONTH = "MONTH"; // 按月支付
|
|
|
+ public static final String PERIOD_TYPE_QUARTER = "QUARTER"; // 按季支付
|
|
|
+ public static final String PERIOD_TYPE_HALFYEAR = "HALFYEAR"; // 按半年支付
|
|
|
+ public static final String PERIOD_TYPE_YEAR = "YEAR"; // 按年支付
|
|
|
+
|
|
|
+ // 日期格式化器(复用避免重复创建)
|
|
|
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+ private static final DateTimeFormatter MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 私有构造:防止工具类实例化
|
|
|
+ */
|
|
|
+ private PeriodCalculationUtil() {
|
|
|
+ throw new AssertionError("工具类不允许实例化");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 核心方法:生成周期列表(含费率)
|
|
|
+ *
|
|
|
+ * @param ruleCate 周期类型(参考类内PERIOD_TYPE_*常量)
|
|
|
+ * @param contractStartDate 合同开始日期
|
|
|
+ * @param periodBeginDate 服务开始日期(周期计算起始点)
|
|
|
+ * @param periodEndDate 服务结束日期(周期计算终点)
|
|
|
+ * @param isNaturalMonth 是否自然月模式(true=自然月,false=实际月)
|
|
|
+ * @return 周期列表(含起止日期、费率)
|
|
|
+ * @throws IllegalArgumentException 当参数非法时抛出(如日期null、周期类型无效)
|
|
|
+ */
|
|
|
+ public static List<Period> getPeriodList(
|
|
|
+ String ruleCate,
|
|
|
+ LocalDate contractStartDate,
|
|
|
+ LocalDate periodBeginDate,
|
|
|
+ LocalDate periodEndDate,
|
|
|
+ boolean isNaturalMonth
|
|
|
+ ) {
|
|
|
+ // 1. 参数校验:避免空指针和非法参数
|
|
|
+ validateParams(ruleCate, contractStartDate, periodBeginDate, periodEndDate);
|
|
|
+
|
|
|
+ List<Period> periodList = new ArrayList<>();
|
|
|
+ // 2. 一次性收费:直接生成单个周期
|
|
|
+ if (PERIOD_TYPE_ONCE.equals(ruleCate)) {
|
|
|
+ Period oncePeriod = new Period();
|
|
|
+ oncePeriod.setPeriodBeginDate(periodBeginDate);
|
|
|
+ oncePeriod.setPeriodEndDate(periodEndDate);
|
|
|
+ oncePeriod.setRate(BigDecimal.ONE);
|
|
|
+ periodList.add(oncePeriod);
|
|
|
+ return periodList;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 非一次性收费:按周期类型计算(月/季/半年/年)
|
|
|
+ LocalDate startDate = null;
|
|
|
+ LocalDate currLastDay = null; // 当前周期结束日
|
|
|
+ int totalPeriodDays = 0; // 完整周期总天数(用于计算费率)
|
|
|
+
|
|
|
+ // 3.1 计算首个周期的结束日和完整周期天数
|
|
|
+ switch (ruleCate) {
|
|
|
+ case PERIOD_TYPE_MONTH:
|
|
|
+ if (isNaturalMonth) {
|
|
|
+ currLastDay = getNaturalMonthLastDay(periodBeginDate);
|
|
|
+ totalPeriodDays = periodBeginDate.lengthOfMonth();
|
|
|
+ } else {
|
|
|
+ currLastDay = getActualMonthFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
|
|
|
+ totalPeriodDays = getActualMonthTotalDays(contractStartDate, periodBeginDate);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PERIOD_TYPE_QUARTER:
|
|
|
+ if (isNaturalMonth) {
|
|
|
+ currLastDay = getNaturalQuarterLastDay(periodBeginDate);
|
|
|
+ totalPeriodDays = getDaysInCurrentQuarter(periodBeginDate);
|
|
|
+ } else {
|
|
|
+ currLastDay = getActualQuarterFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
|
|
|
+ totalPeriodDays = getActualQuarterTotalDays(contractStartDate, periodBeginDate);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PERIOD_TYPE_HALFYEAR:
|
|
|
+ if (isNaturalMonth) {
|
|
|
+ currLastDay = getNaturalHalfYearLastDay(periodBeginDate);
|
|
|
+ totalPeriodDays = getDaysInCurrentHalfYear(periodBeginDate);
|
|
|
+ } else {
|
|
|
+ currLastDay = getActualHalfYearFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
|
|
|
+ totalPeriodDays = getActualHalfYearTotalDays(contractStartDate, periodBeginDate);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PERIOD_TYPE_YEAR:
|
|
|
+ if (isNaturalMonth) {
|
|
|
+ currLastDay = getNaturalYearLastDay(periodBeginDate);
|
|
|
+ totalPeriodDays = periodBeginDate.lengthOfYear();
|
|
|
+ } else {
|
|
|
+ currLastDay = getActualYearFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
|
|
|
+ totalPeriodDays = getActualYearTotalDays(contractStartDate, periodBeginDate);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ throw new IllegalArgumentException("不支持的周期类型:" + ruleCate);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3.2 处理首个周期
|
|
|
+ if (currLastDay != null) {
|
|
|
+ Period firstPeriod = buildPeriod(periodBeginDate, currLastDay, totalPeriodDays);
|
|
|
+ periodList.add(firstPeriod);
|
|
|
+ // 更新下一个周期的起始日(当前周期结束日+1)
|
|
|
+ startDate = currLastDay.plusDays(1);
|
|
|
+ } else {
|
|
|
+ startDate = periodBeginDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3.3 循环生成后续周期(直到超过服务结束日)
|
|
|
+ if (startDate != null && !startDate.isAfter(periodEndDate)) {
|
|
|
+ while (!startDate.isAfter(periodEndDate)) {
|
|
|
+ LocalDate nextPeriodStart = getNextPeriodStart(startDate, ruleCate);
|
|
|
+ LocalDate periodEnd = nextPeriodStart.minusDays(1); // 当前周期结束日
|
|
|
+
|
|
|
+ // 若周期结束日超过服务结束日,截断并计算实际费率
|
|
|
+ BigDecimal rate = BigDecimal.ONE;
|
|
|
+ if (periodEnd.isAfter(periodEndDate)) {
|
|
|
+ int actualDays = getPeriodDays(startDate, periodEndDate);
|
|
|
+ int fullPeriodDays = getPeriodDays(startDate, periodEnd);
|
|
|
+ rate = divideToBigDecimal(actualDays, fullPeriodDays);
|
|
|
+ periodEnd = periodEndDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ Period cyclePeriod = new Period();
|
|
|
+ cyclePeriod.setPeriodBeginDate(startDate);
|
|
|
+ cyclePeriod.setPeriodEndDate(periodEnd);
|
|
|
+ cyclePeriod.setRate(rate);
|
|
|
+ periodList.add(cyclePeriod);
|
|
|
+
|
|
|
+ // 更新下一个周期起始日
|
|
|
+ startDate = periodEnd.plusDays(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return periodList;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ------------------------------ 私有工具方法:参数校验 ------------------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 参数校验:防止空指针和非法日期范围
|
|
|
+ */
|
|
|
+ private static void validateParams(
|
|
|
+ String ruleCate,
|
|
|
+ LocalDate contractStartDate,
|
|
|
+ LocalDate periodBeginDate,
|
|
|
+ LocalDate periodEndDate
|
|
|
+ ) {
|
|
|
+ if (StringUtils.isBlank(ruleCate)) {
|
|
|
+ throw new IllegalArgumentException("周期类型(ruleCate)不能为空");
|
|
|
+ }
|
|
|
+ if (contractStartDate == null) {
|
|
|
+ throw new IllegalArgumentException("合同开始日期(contractStartDate)不能为空");
|
|
|
+ }
|
|
|
+ if (periodBeginDate == null) {
|
|
|
+ throw new IllegalArgumentException("服务开始日期(periodBeginDate)不能为空");
|
|
|
+ }
|
|
|
+ if (periodEndDate == null) {
|
|
|
+ throw new IllegalArgumentException("服务结束日期(periodEndDate)不能为空");
|
|
|
+ }
|
|
|
+ if (periodBeginDate.isAfter(periodEndDate)) {
|
|
|
+ throw new IllegalArgumentException("服务开始日期不能晚于服务结束日期");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ------------------------------ 私有工具方法:自然月模式计算 ------------------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自然月:获取当前月的最后一天(处理非1号开始的情况)
|
|
|
+ */
|
|
|
+ private static LocalDate getNaturalMonthLastDay(LocalDate periodBeginDate) {
|
|
|
+ return periodBeginDate.getDayOfMonth() != 1
|
|
|
+ ? periodBeginDate.with(TemporalAdjusters.lastDayOfMonth())
|
|
|
+ : periodBeginDate.plusMonths(1).minusDays(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自然季度:获取当前季度的最后一天(处理非季度初开始的情况)
|
|
|
+ */
|
|
|
+ private static LocalDate getNaturalQuarterLastDay(LocalDate periodBeginDate) {
|
|
|
+ Month endMonth = getQuarterEndMonth(periodBeginDate.getMonth());
|
|
|
+ return LocalDate.of(periodBeginDate.getYear(), endMonth, endMonth.length(periodBeginDate.isLeapYear()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自然半年:获取当前半年的最后一天(处理非半年初开始的情况)
|
|
|
+ */
|
|
|
+ private static LocalDate getNaturalHalfYearLastDay(LocalDate periodBeginDate) {
|
|
|
+ Month endMonth = getHalfYearEndMonth(periodBeginDate.getMonth());
|
|
|
+ return LocalDate.of(periodBeginDate.getYear(), endMonth, endMonth.length(periodBeginDate.isLeapYear()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自然年:获取当前年的最后一天(处理非年初开始的情况)
|
|
|
+ */
|
|
|
+ private static LocalDate getNaturalYearLastDay(LocalDate periodBeginDate) {
|
|
|
+ return (periodBeginDate.getDayOfMonth() != 1 || periodBeginDate.getMonthValue() != 1)
|
|
|
+ ? LocalDate.of(periodBeginDate.getYear(), 12, 31)
|
|
|
+ : periodBeginDate.plusYears(1).minusDays(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ------------------------------ 私有工具方法:实际月模式计算 ------------------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际月:首个周期结束日(根据合同开始日期与服务开始日期的关系)
|
|
|
+ */
|
|
|
+ private static LocalDate getActualMonthFirstEndDay(
|
|
|
+ LocalDate contractStartDate,
|
|
|
+ LocalDate periodBeginDate,
|
|
|
+ LocalDate periodEndDate
|
|
|
+ ) {
|
|
|
+ LocalDate endDay;
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.minusDays(1);
|
|
|
+ } else if (contractStartDate.isBefore(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.plusMonths(1).minusDays(1);
|
|
|
+ } else {
|
|
|
+ // 合同开始日 = 服务开始日:首个周期为1个月后
|
|
|
+ endDay = contractStartDate.plusMonths(1).minusDays(1);
|
|
|
+ }
|
|
|
+ // 若结束日超过服务结束日,截断
|
|
|
+ return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际月:完整周期总天数(用于计算费率)
|
|
|
+ */
|
|
|
+ private static int getActualMonthTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ return getPeriodDays(contractStartDate.minusMonths(1), contractStartDate.minusDays(1));
|
|
|
+ } else {
|
|
|
+ return getPeriodDays(contractStartDate, contractStartDate.plusMonths(1).minusDays(1));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际季度:首个周期结束日
|
|
|
+ */
|
|
|
+ private static LocalDate getActualQuarterFirstEndDay(
|
|
|
+ LocalDate contractStartDate,
|
|
|
+ LocalDate periodBeginDate,
|
|
|
+ LocalDate periodEndDate
|
|
|
+ ) {
|
|
|
+ LocalDate endDay;
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.minusDays(1);
|
|
|
+ } else if (contractStartDate.isBefore(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.plusMonths(3).minusDays(1);
|
|
|
+ } else {
|
|
|
+ endDay = contractStartDate.plusMonths(3).minusDays(1);
|
|
|
+ }
|
|
|
+ return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际季度:完整周期总天数
|
|
|
+ */
|
|
|
+ private static int getActualQuarterTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ return getPeriodDays(contractStartDate.minusMonths(3), contractStartDate.minusDays(1));
|
|
|
+ } else {
|
|
|
+ return getPeriodDays(contractStartDate, contractStartDate.plusMonths(3).minusDays(1));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际半年:首个周期结束日
|
|
|
+ */
|
|
|
+ private static LocalDate getActualHalfYearFirstEndDay(
|
|
|
+ LocalDate contractStartDate,
|
|
|
+ LocalDate periodBeginDate,
|
|
|
+ LocalDate periodEndDate
|
|
|
+ ) {
|
|
|
+ LocalDate endDay;
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.minusDays(1);
|
|
|
+ } else if (contractStartDate.isBefore(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.plusMonths(6).minusDays(1);
|
|
|
+ } else {
|
|
|
+ endDay = contractStartDate.plusMonths(6).minusDays(1);
|
|
|
+ }
|
|
|
+ return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际半年:完整周期总天数
|
|
|
+ */
|
|
|
+ private static int getActualHalfYearTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ return getPeriodDays(contractStartDate.minusMonths(6), contractStartDate.minusDays(1));
|
|
|
+ } else {
|
|
|
+ return getPeriodDays(contractStartDate, contractStartDate.plusMonths(6).minusDays(1));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际年:首个周期结束日
|
|
|
+ */
|
|
|
+ private static LocalDate getActualYearFirstEndDay(
|
|
|
+ LocalDate contractStartDate,
|
|
|
+ LocalDate periodBeginDate,
|
|
|
+ LocalDate periodEndDate
|
|
|
+ ) {
|
|
|
+ LocalDate endDay;
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.minusDays(1);
|
|
|
+ } else if (contractStartDate.isBefore(periodBeginDate)) {
|
|
|
+ endDay = contractStartDate.plusMonths(12).minusDays(1);
|
|
|
+ } else {
|
|
|
+ endDay = contractStartDate.plusMonths(12).minusDays(1);
|
|
|
+ }
|
|
|
+ return endDay.isAfter(periodEndDate) ? periodEndDate : endDay;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 实际年:完整周期总天数
|
|
|
+ */
|
|
|
+ private static int getActualYearTotalDays(LocalDate contractStartDate, LocalDate periodBeginDate) {
|
|
|
+ if (contractStartDate.isAfter(periodBeginDate)) {
|
|
|
+ return getPeriodDays(contractStartDate.minusMonths(12), contractStartDate.minusDays(1));
|
|
|
+ } else {
|
|
|
+ return getPeriodDays(contractStartDate, contractStartDate.plusMonths(12).minusDays(1));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ------------------------------ 私有工具方法:通用计算 ------------------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建单个周期(含费率计算)
|
|
|
+ */
|
|
|
+ private static Period buildPeriod(LocalDate beginDate, LocalDate endDate, int totalPeriodDays) {
|
|
|
+ Period period = new Period();
|
|
|
+ period.setPeriodBeginDate(beginDate);
|
|
|
+ period.setPeriodEndDate(endDate);
|
|
|
+ // 计算费率:实际天数 / 完整周期总天数
|
|
|
+ int actualDays = getPeriodDays(beginDate, endDate);
|
|
|
+ period.setRate(divideToBigDecimal(actualDays, totalPeriodDays));
|
|
|
+ return period;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取下一周期的起始日(适配 Java 8,替换不兼容的 switch 表达式)
|
|
|
+ * @param currentStart 当前周期的起始日期(如 2024-01-15)
|
|
|
+ * @param periodType 周期类型(参考类内 PERIOD_TYPE_* 常量:月/季/半年/年)
|
|
|
+ * @return 下一周期的起始日期(如当前是 2024-01-15 按月,返回 2024-02-15)
|
|
|
+ * @throws IllegalArgumentException 当传入不支持的周期类型时抛出
|
|
|
+ */
|
|
|
+ public static LocalDate getNextPeriodStart(LocalDate currentStart, String periodType) {
|
|
|
+ // Java 8 仅支持传统 switch 语句,需用 case + return 实现分支逻辑
|
|
|
+ switch (periodType) {
|
|
|
+ case PERIOD_TYPE_MONTH:
|
|
|
+ // 月周期:当前日期加 1 个月
|
|
|
+ return currentStart.plus(1, ChronoUnit.MONTHS);
|
|
|
+ case PERIOD_TYPE_QUARTER:
|
|
|
+ // 季周期:当前日期加 3 个月
|
|
|
+ return currentStart.plus(3, ChronoUnit.MONTHS);
|
|
|
+ case PERIOD_TYPE_HALFYEAR:
|
|
|
+ // 半年周期:当前日期加 6 个月
|
|
|
+ return currentStart.plus(6, ChronoUnit.MONTHS);
|
|
|
+ case PERIOD_TYPE_YEAR:
|
|
|
+ // 年周期:当前日期加 1 年
|
|
|
+ return currentStart.plus(1, ChronoUnit.YEARS);
|
|
|
+ default:
|
|
|
+ // 不支持的周期类型:抛出异常并提示具体类型
|
|
|
+ throw new IllegalArgumentException("不支持的周期类型:" + periodType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算两个日期之间的天数(包含首尾日期)
|
|
|
+ */
|
|
|
+ public static int getPeriodDays(LocalDate start, LocalDate end) {
|
|
|
+ if (start.isAfter(end)) {
|
|
|
+ throw new IllegalArgumentException("起始日期不能晚于结束日期");
|
|
|
+ }
|
|
|
+ return (int) ChronoUnit.DAYS.between(start, end) + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 整数除法转BigDecimal(保留10位小数,四舍五入)
|
|
|
+ */
|
|
|
+ public static BigDecimal divideToBigDecimal(int numerator, int denominator) {
|
|
|
+ if (denominator == 0) {
|
|
|
+ throw new ArithmeticException("分母不能为零");
|
|
|
+ }
|
|
|
+ return new BigDecimal(numerator)
|
|
|
+ .divide(new BigDecimal(denominator), 10, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ------------------------------ 私有工具方法:周期起止月计算 ------------------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取季度结束月(1-3月→3月,4-6月→6月,7-9月→9月,10-12月→12月)
|
|
|
+ * @param month 输入的月份(如 Month.JANUARY、Month.APRIL 等)
|
|
|
+ * @return 对应季度的结束月份(如输入 Month.FEBRUARY,返回 Month.MARCH)
|
|
|
+ */
|
|
|
+ private static Month getQuarterEndMonth(Month month) {
|
|
|
+ // Java 8 不支持多 case 合并(case 1,2,3)和箭头语法,需拆分为独立 case 并显式 return
|
|
|
+ switch (month.getValue()) {
|
|
|
+ case 1: // 1月属于Q1,结束月为3月
|
|
|
+ return Month.MARCH;
|
|
|
+ case 2: // 2月属于Q1,结束月为3月
|
|
|
+ return Month.MARCH;
|
|
|
+ case 3: // 3月属于Q1,结束月为3月
|
|
|
+ return Month.MARCH;
|
|
|
+ case 4: // 4月属于Q2,结束月为6月
|
|
|
+ return Month.JUNE;
|
|
|
+ case 5: // 5月属于Q2,结束月为6月
|
|
|
+ return Month.JUNE;
|
|
|
+ case 6: // 6月属于Q2,结束月为6月
|
|
|
+ return Month.JUNE;
|
|
|
+ case 7: // 7月属于Q3,结束月为9月
|
|
|
+ return Month.SEPTEMBER;
|
|
|
+ case 8: // 8月属于Q3,结束月为9月
|
|
|
+ return Month.SEPTEMBER;
|
|
|
+ case 9: // 9月属于Q3,结束月为9月
|
|
|
+ return Month.SEPTEMBER;
|
|
|
+ case 10: // 10月属于Q4,结束月为12月
|
|
|
+ return Month.DECEMBER;
|
|
|
+ case 11: // 11月属于Q4,结束月为12月
|
|
|
+ return Month.DECEMBER;
|
|
|
+ case 12: // 12月属于Q4,结束月为12月
|
|
|
+ return Month.DECEMBER;
|
|
|
+ default:
|
|
|
+ // 理论上 Month 枚举值仅 1-12,此分支为兜底,避免异常
|
|
|
+ throw new IllegalArgumentException("非法的月份值:" + month.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取季度起始月(1-3月→1月,4-6月→4月,7-9月→7月,10-12月→10月)
|
|
|
+ * @param month 输入的月份(如 Month.FEBRUARY、Month.MAY 等)
|
|
|
+ * @return 对应季度的起始月份(如输入 Month.MAY,返回 Month.APRIL)
|
|
|
+ */
|
|
|
+ private static Month getQuarterStartMonth(Month month) {
|
|
|
+ // 同上述逻辑,拆分为独立 case,每个 case 直接返回对应起始月
|
|
|
+ switch (month.getValue()) {
|
|
|
+ case 1: // 1月属于Q1,起始月为1月
|
|
|
+ return Month.JANUARY;
|
|
|
+ case 2: // 2月属于Q1,起始月为1月
|
|
|
+ return Month.JANUARY;
|
|
|
+ case 3: // 3月属于Q1,起始月为1月
|
|
|
+ return Month.JANUARY;
|
|
|
+ case 4: // 4月属于Q2,起始月为4月
|
|
|
+ return Month.APRIL;
|
|
|
+ case 5: // 5月属于Q2,起始月为4月
|
|
|
+ return Month.APRIL;
|
|
|
+ case 6: // 6月属于Q2,起始月为4月
|
|
|
+ return Month.APRIL;
|
|
|
+ case 7: // 7月属于Q3,起始月为7月
|
|
|
+ return Month.JULY;
|
|
|
+ case 8: // 8月属于Q3,起始月为7月
|
|
|
+ return Month.JULY;
|
|
|
+ case 9: // 9月属于Q3,起始月为7月
|
|
|
+ return Month.JULY;
|
|
|
+ case 10: // 10月属于Q4,起始月为10月
|
|
|
+ return Month.OCTOBER;
|
|
|
+ case 11: // 11月属于Q4,起始月为10月
|
|
|
+ return Month.OCTOBER;
|
|
|
+ case 12: // 12月属于Q4,起始月为10月
|
|
|
+ return Month.OCTOBER;
|
|
|
+ default:
|
|
|
+ throw new IllegalArgumentException("非法的月份值:" + month.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取半年结束月(1-6→6,7-12→12)
|
|
|
+ */
|
|
|
+ private static Month getHalfYearEndMonth(Month month) {
|
|
|
+ return month.getValue() <= 6 ? Month.JUNE : Month.DECEMBER;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取半年起始月(1-6→1,7-12→7)
|
|
|
+ */
|
|
|
+ private static Month getHalfYearStartMonth(Month month) {
|
|
|
+ return month.getValue() <= 6 ? Month.JANUARY : Month.JULY;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算当前季度的总天数(包含首尾月)
|
|
|
+ */
|
|
|
+ public static int getDaysInCurrentQuarter(LocalDate date) {
|
|
|
+ Month startMonth = getQuarterStartMonth(date.getMonth());
|
|
|
+ Month endMonth = getQuarterEndMonth(date.getMonth());
|
|
|
+ LocalDate quarterStart = LocalDate.of(date.getYear(), startMonth, 1);
|
|
|
+ LocalDate quarterEnd = LocalDate.of(date.getYear(), endMonth, endMonth.length(date.isLeapYear()));
|
|
|
+ return getPeriodDays(quarterStart, quarterEnd);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算当前半年的总天数(包含首尾月)
|
|
|
+ */
|
|
|
+ public static int getDaysInCurrentHalfYear(LocalDate date) {
|
|
|
+ Month startMonth = getHalfYearStartMonth(date.getMonth());
|
|
|
+ Month endMonth = getHalfYearEndMonth(date.getMonth());
|
|
|
+ LocalDate halfYearStart = LocalDate.of(date.getYear(), startMonth, 1);
|
|
|
+ LocalDate halfYearEnd = LocalDate.of(date.getYear(), endMonth, endMonth.length(date.isLeapYear()));
|
|
|
+ return getPeriodDays(halfYearStart, halfYearEnd);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ------------------------------ 周期模型:公开静态内部类(可外部访问) ------------------------------
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 周期模型:存储周期起止日期、费率、周期ID
|
|
|
+ */
|
|
|
+ public static class Period {
|
|
|
+ private LocalDate periodBeginDate;
|
|
|
+ private LocalDate periodEndDate;
|
|
|
+ private BigDecimal rate;
|
|
|
+ private String commissionPeriodId;
|
|
|
+
|
|
|
+ // Getter & Setter
|
|
|
+ public LocalDate getPeriodBeginDate() {
|
|
|
+ return periodBeginDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPeriodBeginDate(LocalDate periodBeginDate) {
|
|
|
+ this.periodBeginDate = periodBeginDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public LocalDate getPeriodEndDate() {
|
|
|
+ return periodEndDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setPeriodEndDate(LocalDate periodEndDate) {
|
|
|
+ this.periodEndDate = periodEndDate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public BigDecimal getRate() {
|
|
|
+ return rate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setRate(BigDecimal rate) {
|
|
|
+ this.rate = rate;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getCommissionPeriodId() {
|
|
|
+ return commissionPeriodId;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setCommissionPeriodId(String commissionPeriodId) {
|
|
|
+ this.commissionPeriodId = commissionPeriodId;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化日期字符串(避免外部重复创建Formatter)
|
|
|
+ public String getPeriodBeginDateStr() {
|
|
|
+ return periodBeginDate.format(DATE_FORMATTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getPeriodEndDateStr() {
|
|
|
+ return periodEndDate.format(DATE_FORMATTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getPeriodMonthStr() {
|
|
|
+ return periodBeginDate.format(MONTH_FORMATTER);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|