Ver código fonte

生成付款计划

zhangyao 2 meses atrás
pai
commit
619f45a19d

+ 582 - 0
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/PeriodCalculationUtil.java

@@ -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);
+        }
+    }
+}

+ 0 - 2
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/contractService.java

@@ -21,8 +21,6 @@ import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.*;
 
-import static com.awspaas.user.apps.donenow_ctt.service.contractService.divideToBigDecimal;
-
 
 /**
  * 合同服务

+ 22 - 0
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/constant/IVTConstant.java

@@ -135,4 +135,26 @@ public class IVTConstant {
 	 * 已配送
 	 */
 	public static String STATUS_1237="1237";
+
+
+	/**
+	 * 一次性收费
+	 */
+	public static final String PERIOD_TYPE_ONE_TIME = "609";
+	/**
+	 * 按月收费
+	 */
+	public static final String PERIOD_TYPE_MONTH = "610";
+	/**
+	 * 按季度收费
+	 */
+	public static final String PERIOD_TYPE_QUARTER = "611";
+	/**
+	 * 半年收费
+	 */
+	public static final String PERIOD_TYPE_HALFYEAR = "612";
+	/**
+	 * 按年收费
+	 */
+	public static final String PERIOD_TYPE_YEAR = "613";
 }

+ 481 - 1
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/event/ivtOrderProcessAfterComplete.java

@@ -7,10 +7,19 @@ import com.actionsoft.bpms.bpmn.engine.model.run.delegate.ProcessInstance;
 import com.actionsoft.bpms.commons.database.RowMap;
 import com.actionsoft.bpms.server.UserContext;
 import com.actionsoft.bpms.util.DBSql;
+import com.actionsoft.bpms.util.TypeUtil;
 import com.actionsoft.sdk.local.SDK;
+import com.actionsoft.sdk.local.api.Logger;
 import com.awspaas.user.apps.donenow_ivt.constant.IVTConstant;
 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.*;
 
 public class ivtOrderProcessAfterComplete extends ExecuteListener {
@@ -18,6 +27,7 @@ public class ivtOrderProcessAfterComplete extends ExecuteListener {
         return "流程结束事件:采购订单审批完成后触发";
     }
 
+    private static final Logger logger = SDK.getLogAPI().getLogger(ivtOrderProcessAfterComplete.class);//记录日志
     private final String[] excludesArray = new String[]{"ID", "ORGID", "BINDID", "CREATEDATE", "CREATEUSER", "UPDATEDATE", "UPDATEUSER"};
 
     /**
@@ -90,6 +100,77 @@ public class ivtOrderProcessAfterComplete extends ExecuteListener {
 
         }
 
+        //生成付款计划-- 胡磊 旧版
+        //createPaymentPlan(uc,orderId);
+
+/*****************************************2025年10月24日  张耀 生成付款计划********************************************/
+        logger.info("生成付款计划");
+        int total = 0;
+        for (BO orderProduct : orderProducts) {
+            if (StringUtils.isNotBlank(orderProduct.getString("CONTRACT_COST_ID"))) {
+                String SERVICE_ID = DBSql.getString("select SERVICE_ID from BO_EU_DNCTT_CONTRACT_COST where ID=?", new Object[]{orderProduct.getString("CONTRACT_COST_ID")});
+                if (StringUtils.isNotBlank(SERVICE_ID)) {
+
+                    logger.info("生成付款计划SERVICE_ID--" + SERVICE_ID);
+
+                    BO service = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_SERVICE", SERVICE_ID);
+                    String BILL_METHOD_ID = null;//计费方式  4601:自然月  4602:实际月
+                    if (service.getString("OBJECT_TYPE").equals("2"))//服务包
+                    {
+                        BILL_METHOD_ID = DBSql.getString("select BILL_METHOD_ID from BO_EU_DNIVT_SERVICE_BUNDLE where ID=?", new Object[]{service.getString("OBJECT_ID")});
+                    } else {
+                        BILL_METHOD_ID = DBSql.getString("select BILL_METHOD_ID from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.getString("OBJECT_ID")});
+                    }
+
+                    String RULE_CATE = service.getString("PURCHASE_PERIOD_TYPE");//采购周期
+                    LocalDate PERIOD_BEGIN_DATE = service.get("EFFECTIVE_DATE", LocalDate.class);
+                    LocalDate START_DATE = TypeUtil.convert(DBSql.getString("select START_DATE from BO_EU_DNCTT_CONTRACT where ID=?", new Object[]{service.getString("CONTRACT_ID")}), LocalDate.class);
+                    LocalDate PERIOD_END_DATE = TypeUtil.convert(DBSql.getString("select END_DATE from BO_EU_DNCTT_CONTRACT where ID=?", new Object[]{service.getString("CONTRACT_ID")}), LocalDate.class);
+
+                    List<PERIOD> periods = getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, START_DATE, PERIOD_END_DATE, StringUtils.isNotBlank(BILL_METHOD_ID) && BILL_METHOD_ID.equals("4601"));
+
+                    if (periods.isEmpty())
+                        continue;
+
+                    BigDecimal COST_TOTAL = service.get("COST_TOTAL", BigDecimal.class);//总金额
+
+                    BigDecimal RATE_TOTAL = periods.stream().map(period -> period.getRate()).reduce(BigDecimal.ZERO, BigDecimal::add);
+
+                    if (RATE_TOTAL.equals(BigDecimal.ZERO))
+                        continue;
+
+                    BigDecimal UNIT_COST = COST_TOTAL.divide(RATE_TOTAL, 10, RoundingMode.HALF_UP);
+                    for (PERIOD period : periods) {
+                        total++;
+                        ProcessInstance processInstance = SDK.getProcessAPI()
+                                .createProcessInstance("obj_5cb4ae4a42944fd0a9a284ff4c64c65d",
+                                        uc.getUID(),
+                                        "付款计划");
+
+                        BO paymentPlan = new BO();
+                        paymentPlan.setBindId(processInstance.getId());
+                        paymentPlan.set("ORDER_ID", orderId);
+                        paymentPlan.set("PLAN_DATE", period.getPeriodBeginDateStr());
+                        paymentPlan.set("PAY_AMOUNT", UNIT_COST.multiply(period.getRate()));
+                        paymentPlan.set("REMAIN_AMOUNT", paymentPlan.get("PAY_AMOUNT"));
+                        SDK.getBOAPI().create("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", paymentPlan, processInstance, uc);
+
+                        System.out.println("==== 创建付款计划:" + paymentPlan.get("PLAN_DATE") + ",金额:" + paymentPlan.get("PAY_AMOUNT") + " ====");
+                    }
+
+                }
+            }
+        }
+        logger.info("付款计划生成完成,生成付款计划数量:" + total);
+    }
+
+    /**
+     * 生成采购计划
+     * @param uc
+     * @param orderId
+     */
+    private void createPaymentPlan(UserContext uc, String orderId) {
+
         System.out.println("==== 开始执行自动生成付款计划逻辑 ====");
         try {
 
@@ -133,7 +214,7 @@ public class ivtOrderProcessAfterComplete extends ExecuteListener {
                     String planDate = (String) dateInfo.get("EXPECTED_SHIP_DATE");
                     Double planAmount = (Double) priceInfo.get("cost_total");
 
-                    if (StringUtils.isNotBlank(planDate) ) {
+                    if (StringUtils.isNotBlank(planDate)) {
                         createPaymentPlan(uc, orderId, planDate, planAmount);
                         System.out.println("==== 非服务类付款计划创建完成,日期:" + planDate + ",金额:" + planAmount + " ====");
                     } else {
@@ -332,4 +413,403 @@ public class ivtOrderProcessAfterComplete extends ExecuteListener {
         return priceInfo;
     }
 
+
+    /*************************************2025年10月23日**************************************************/
+
+    /**
+     * 获取周期列表
+     * @param RULE_CATE
+     * @param CONTRACT_START_DATE
+     * @param PERIOD_BEGIN_DATE
+     * @param PERIOD_END_DATE
+     * @param isNaturalMonth //自然月和实际月
+     * @return
+     */
+    private List<PERIOD> getPeriodList(String RULE_CATE, LocalDate CONTRACT_START_DATE, LocalDate PERIOD_BEGIN_DATE, LocalDate PERIOD_END_DATE, boolean isNaturalMonth) {
+        List<PERIOD> periodList = new ArrayList<>();
+        //一次性收费
+        if (RULE_CATE.equals(IVTConstant.PERIOD_TYPE_MONTH)) {
+            PERIOD period = new PERIOD();
+            period.setPeriodBeginDate(PERIOD_BEGIN_DATE);
+            period.setPeriodEndDate(PERIOD_END_DATE);
+            period.setRate(BigDecimal.ONE);
+            periodList.add(period);
+        } else {
+            LocalDate START_DATE = null;
+            LocalDate currLastDay = null;//当前周期的结束日
+            int periodDays = 0;//当前周期天数
+            switch (RULE_CATE) {
+                case IVTConstant.PERIOD_TYPE_MONTH://按月支付
+                {
+                    if (isNaturalMonth) {
+                        //自然月
+                        if (PERIOD_BEGIN_DATE.getDayOfMonth() != 1) {
+                            currLastDay = PERIOD_BEGIN_DATE.with(TemporalAdjusters.lastDayOfMonth());
+                            periodDays = PERIOD_BEGIN_DATE.lengthOfMonth();
+                        }
+                    } else {
+                        //实际月
+                        if (CONTRACT_START_DATE.isAfter(PERIOD_BEGIN_DATE))//合同开始日期在 服务开始日期之后
+                        {
+                            currLastDay = CONTRACT_START_DATE.minusDays(1);//获取合同开始日期的前一日
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE.minusMonths(1), currLastDay);
+
+                        } else if (CONTRACT_START_DATE.isBefore(PERIOD_BEGIN_DATE)) {//合同开始日期在 服务开始日期之前
+                            currLastDay = CONTRACT_START_DATE.plusMonths(1).minusDays(1);
+
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE, currLastDay);
+
+                            if (currLastDay.isAfter(PERIOD_END_DATE))//如果当前周期结束日大于服务结束日
+                            {
+                                currLastDay = PERIOD_END_DATE;
+                            }
+
+                        } else {
+
+
+                        }
+                    }
+                }
+                break;
+                case IVTConstant.PERIOD_TYPE_QUARTER://按季支付
+                {
+                    if (isNaturalMonth) {
+                        //如果不是季度第一天
+                        if (PERIOD_BEGIN_DATE.getDayOfMonth() != 1 || (PERIOD_BEGIN_DATE.getMonthValue() - 1) % 3 != 0) {
+                            Month endMonth = getQuarterEndMonth(PERIOD_BEGIN_DATE.getMonth());
+                            currLastDay = LocalDate.of(PERIOD_BEGIN_DATE.getYear(), endMonth, endMonth.length(PERIOD_BEGIN_DATE.isLeapYear()) // 考虑闰年2月的天数
+                            );
+                            periodDays = getDaysInCurrentQuarter(PERIOD_BEGIN_DATE);
+                        }
+                    } else {
+                        //实际月
+                        if (CONTRACT_START_DATE.isAfter(PERIOD_BEGIN_DATE))//合同开始日期在 服务开始日期之后
+                        {
+                            currLastDay = CONTRACT_START_DATE.minusDays(1);//获取合同开始日期的前一日
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE.minusMonths(3), currLastDay);
+
+                        } else if (CONTRACT_START_DATE.isBefore(PERIOD_BEGIN_DATE)) {//合同开始日期在 服务开始日期之前
+                            currLastDay = CONTRACT_START_DATE.plusMonths(3).minusDays(1);
+
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE, currLastDay);
+
+                            if (currLastDay.isAfter(PERIOD_END_DATE))//如果当前周期结束日大于服务结束日
+                            {
+                                currLastDay = PERIOD_END_DATE;
+                            }
+
+                        } else {
+
+
+                        }
+                    }
+
+                }
+                break;
+                case IVTConstant.PERIOD_TYPE_HALFYEAR://按半年支付
+                    if (isNaturalMonth) {
+                        // 若不是半年第一天,计算本半年剩余天数作为首个周期
+                        if (PERIOD_BEGIN_DATE.getDayOfMonth() != 1 || (PERIOD_BEGIN_DATE.getMonthValue() - 1) % 6 != 0) {
+                            Month endMonth = getHalfYearEndMonth(PERIOD_BEGIN_DATE.getMonth());
+                            currLastDay = LocalDate.of(
+                                    PERIOD_BEGIN_DATE.getYear(),
+                                    endMonth,
+                                    endMonth.length(PERIOD_BEGIN_DATE.isLeapYear())
+                            );
+                            periodDays = getDaysInCurrentHalfYear(PERIOD_BEGIN_DATE);
+                        }
+                    } else {
+                        //实际月
+                        if (CONTRACT_START_DATE.isAfter(PERIOD_BEGIN_DATE))//合同开始日期在 服务开始日期之后
+                        {
+                            currLastDay = CONTRACT_START_DATE.minusDays(1);//获取合同开始日期的前一日
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE.minusMonths(6), currLastDay);
+
+                        } else if (CONTRACT_START_DATE.isBefore(PERIOD_BEGIN_DATE)) {//合同开始日期在 服务开始日期之前
+                            currLastDay = CONTRACT_START_DATE.plusMonths(6).minusDays(1);
+
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE, currLastDay);
+
+                            if (currLastDay.isAfter(PERIOD_END_DATE))//如果当前周期结束日大于服务结束日
+                            {
+                                currLastDay = PERIOD_END_DATE;
+                            }
+
+                        } else {
+
+
+                        }
+                    }
+                    break;
+                case IVTConstant.PERIOD_TYPE_YEAR://按年支付
+                {
+                    if (isNaturalMonth) {
+                        //如果不是年第一天
+                        if (PERIOD_BEGIN_DATE.getDayOfMonth() != 1 || PERIOD_BEGIN_DATE.getMonthValue() != 1) {
+                            //所在年最后一天
+                            currLastDay = LocalDate.of(PERIOD_BEGIN_DATE.getYear(), 12, 31);
+                            periodDays = PERIOD_BEGIN_DATE.lengthOfYear();
+                        }
+
+                    } else {
+                        //实际月
+                        if (CONTRACT_START_DATE.isAfter(PERIOD_BEGIN_DATE))//合同开始日期在 服务开始日期之后
+                        {
+                            currLastDay = CONTRACT_START_DATE.minusDays(1);//获取合同开始日期的前一日
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE.minusMonths(12), currLastDay);
+
+                        } else if (CONTRACT_START_DATE.isBefore(PERIOD_BEGIN_DATE)) {//合同开始日期在 服务开始日期之前
+                            currLastDay = CONTRACT_START_DATE.plusMonths(12).minusDays(1);
+
+                            periodDays = GetPeriodDays(CONTRACT_START_DATE, currLastDay);
+
+                            if (currLastDay.isAfter(PERIOD_END_DATE))//如果当前周期结束日大于服务结束日
+                            {
+                                currLastDay = PERIOD_END_DATE;
+                            }
+                        } else {
+
+
+                        }
+                    }
+                }
+                break;
+                default: {
+                    //报错,类型异常
+                    return null;
+                }
+            }
+
+            if (currLastDay != null) {
+                if (currLastDay.isBefore(PERIOD_END_DATE)) {
+                    PERIOD period = new PERIOD();
+                    period.setPeriodBeginDate(PERIOD_BEGIN_DATE);
+                    period.setPeriodEndDate(currLastDay);
+
+                    BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(PERIOD_BEGIN_DATE, currLastDay), periodDays);
+                    period.setRate(periodRate);
+
+                    periodList.add(period);
+
+                    START_DATE = currLastDay.plusDays(1);//周期开始日期
+                } else {
+                    PERIOD period = new PERIOD();
+                    period.setPeriodBeginDate(PERIOD_BEGIN_DATE);
+                    period.setPeriodEndDate(PERIOD_END_DATE);
+
+                    BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(PERIOD_BEGIN_DATE, PERIOD_END_DATE), periodDays);
+                    period.setRate(periodRate);
+
+                    periodList.add(period);
+                }
+            } else {
+                START_DATE = PERIOD_BEGIN_DATE;
+            }
+            //循环
+            if (START_DATE != null && START_DATE.isBefore(PERIOD_END_DATE)) {
+
+                while (!START_DATE.isAfter(PERIOD_END_DATE)) {
+                    LocalDate periodEnd = getNextPeriodStart(START_DATE, RULE_CATE).minusDays(1);//本周期结束时间
+
+                    BigDecimal periodRate = BigDecimal.ONE;
+
+                    if (periodEnd.isAfter(PERIOD_END_DATE)) {//如果结束日期大于合同结束日期,则计算结束日期和合同结束日期的差值,并计算该差值占整周期的比例
+                        periodRate = divideToBigDecimal(GetPeriodDays(START_DATE, PERIOD_END_DATE), GetPeriodDays(START_DATE, periodEnd));
+                        periodEnd = PERIOD_END_DATE;
+                    }
+
+                    PERIOD period = new PERIOD();
+                    period.setPeriodBeginDate(START_DATE);
+                    period.setPeriodEndDate(periodEnd);
+                    period.setRate(periodRate);
+                    periodList.add(period);
+
+                    if (periodEnd != null) {
+                        START_DATE = periodEnd.plusDays(1);
+                    }
+                }
+            }
+        }
+        return periodList;
+    }
+
+    /**
+     * 获取下一周期开始时间
+     * @param start 当前周期开始时间
+     * @param periodType 周期类型
+     * @return 下一周期开始时间
+     */
+    public static LocalDate getNextPeriodStart(LocalDate start, String periodType) {
+        // 根据不同的周期类型计算下一周期的开始时间
+        switch (periodType) {
+            case IVTConstant.PERIOD_TYPE_MONTH:
+                // 如果是月周期,则在当前时间基础上增加1个月
+                return start.plus(1, ChronoUnit.MONTHS);
+            case IVTConstant.PERIOD_TYPE_QUARTER:
+                // 如果是季度周期,则在当前时间基础上增加3个月
+                return start.plus(3, ChronoUnit.MONTHS);
+            case IVTConstant.PERIOD_TYPE_HALFYEAR:
+                // 如果是半年周期,则在当前时间基础上增加6个月
+                return start.plus(6, ChronoUnit.MONTHS);
+            case IVTConstant.PERIOD_TYPE_YEAR:
+                // 如果是年周期,则在当前时间基础上增加1年
+                return start.plus(1, ChronoUnit.YEARS);
+            default:
+                // 如果周期类型不匹配任何已知类型,则返回原始开始时间
+                return start;
+        }
+    }
+
+    /**
+     * 获取当前季度的结束月份
+     */
+    private static Month getQuarterEndMonth(Month month) {
+        int monthValue = month.getValue();
+        if (monthValue <= 3) {
+            return Month.MARCH;   // 第一季度结束月
+        } else if (monthValue <= 6) {
+            return Month.JUNE;    // 第二季度结束月
+        } else if (monthValue <= 9) {
+            return Month.SEPTEMBER; // 第三季度结束月
+        } else {
+            return Month.DECEMBER;  // 第四季度结束月
+        }
+    }
+
+    /**
+     * 计算当前季度的总天数
+     */
+    private static int getDaysInCurrentQuarter(LocalDate date) {
+        // 获取当前月份
+        Month currentMonth = date.getMonth();
+        // 确定当前季度的起始月份和结束月份
+        Month startMonth = getQuarterStartMonth(currentMonth);
+        Month endMonth = getQuarterEndMonth(currentMonth);
+
+        // 构建季度的第一天(当月第一天)
+        LocalDate quarterStart = LocalDate.of(date.getYear(), startMonth, 1);
+
+        // 构建季度的最后一天(当月最后一天)
+        LocalDate quarterEnd = LocalDate.of(date.getYear(), endMonth, endMonth.length(date.isLeapYear()) // 考虑闰年2月的天数
+        );
+
+        // 计算两个日期之间的天数(包含首尾两天)
+        return (int) ChronoUnit.DAYS.between(quarterStart, quarterEnd) + 1;
+    }
+
+    /**
+     * 获取当前季度的起始月份
+     */
+    private static Month getQuarterStartMonth(Month month) {
+        int monthValue = month.getValue();
+        if (monthValue <= 3) {
+            return Month.JANUARY; // 第一季度:1-3月
+        } else if (monthValue <= 6) {
+            return Month.APRIL;   // 第二季度:4-6月
+        } else if (monthValue <= 9) {
+            return Month.JULY;    // 第三季度:7-9月
+        } else {
+            return Month.OCTOBER; // 第四季度:10-12月
+        }
+    }
+
+    /**
+     * 获取当前半年的结束月份
+     */
+    private static Month getHalfYearEndMonth(Month month) {
+        int monthValue = month.getValue();
+        return monthValue <= 6 ? Month.JUNE : Month.DECEMBER;
+    }
+
+    /**
+     * 获取当前半年的起始月份
+     */
+    private static Month getHalfYearStartMonth(Month month) {
+        int monthValue = month.getValue();
+        return monthValue <= 6 ? Month.JANUARY : Month.JULY;
+    }
+
+    /**
+     * 计算当前半年的总天数
+     */
+    private static int getDaysInCurrentHalfYear(LocalDate date) {
+        Month currentMonth = date.getMonth();
+        Month startMonth = getHalfYearStartMonth(currentMonth);
+        Month endMonth = getHalfYearEndMonth(currentMonth);
+
+        LocalDate halfYearStart = LocalDate.of(date.getYear(), startMonth, 1);
+        LocalDate halfYearEnd = LocalDate.of(
+                date.getYear(),
+                endMonth,
+                endMonth.length(date.isLeapYear())
+        );
+
+        return GetPeriodDays(halfYearStart, halfYearEnd);
+    }
+
+    private static int GetPeriodDays(LocalDate start, LocalDate end) {
+        return (int) ChronoUnit.DAYS.between(start, end) + 1;
+    }
+
+    // 定义一个方法,将两个 int 相除并返回 BigDecimal
+    public static BigDecimal divideToBigDecimal(int numerator, int denominator) {
+        if (denominator == 0) {
+            throw new ArithmeticException("分母不能为零");
+        }
+
+        // 使用 BigDecimal 进行精确计算
+        BigDecimal num = new BigDecimal(numerator);
+        BigDecimal den = new BigDecimal(denominator);
+
+        // 设置小数点后的位数和舍入模式
+        return num.divide(den, 10, RoundingMode.HALF_UP); // 保留 10 位小数
+    }
+
+    /**
+     * 周期
+     */
+    private class PERIOD {
+        private LocalDate PERIOD_BEGIN_DATE;
+        private LocalDate PERIOD_END_DATE;
+        private BigDecimal RATE;
+
+        public void setPeriodBeginDate(LocalDate periodBeginDate) {
+            this.PERIOD_BEGIN_DATE = periodBeginDate;
+        }
+
+        public void setPeriodEndDate(LocalDate periodEndDate) {
+            this.PERIOD_END_DATE = periodEndDate;
+        }
+
+        public void setRate(BigDecimal rate) {
+            this.RATE = rate;
+        }
+
+        public LocalDate getPeriodBeginDate() {
+            return PERIOD_BEGIN_DATE;
+        }
+
+        public LocalDate getPeriodEndDate() {
+            return PERIOD_END_DATE;
+        }
+
+        public String getPeriodBeginDateStr() {
+            return PERIOD_BEGIN_DATE.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        }
+
+        public String getPeriodMonthStr() {
+            return PERIOD_BEGIN_DATE.format(DateTimeFormatter.ofPattern("yyyyMM"));
+        }
+
+        public String getPeriodEndDateStr() {
+            return PERIOD_END_DATE.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        }
+
+        public BigDecimal getRate() {
+            return RATE;
+        }
+
+    }
+
+
 }