Ver código fonte

计算周期修改

zhangyao 1 mês atrás
pai
commit
0dd4a0748e

+ 284 - 6
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/PeriodCalculationUtil.java

@@ -44,6 +44,8 @@ public class PeriodCalculationUtil {
      * @param periodBeginDate   服务开始日期(周期计算起始点)
      * @param periodEndDate     服务结束日期(周期计算终点)
      * @param isNaturalMonth    是否自然月模式(true=自然月,false=实际月)
+     * @param userMonthDay       是否使用当月实际天数用于计算每天费用
+     * @param monthDayType  monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
      * @return 周期列表(含起止日期、费率)
      * @throws IllegalArgumentException 当参数非法时抛出(如日期null、周期类型无效)
      */
@@ -52,11 +54,33 @@ public class PeriodCalculationUtil {
             LocalDate contractStartDate,
             LocalDate periodBeginDate,
             LocalDate periodEndDate,
-            boolean isNaturalMonth
+            boolean isNaturalMonth,
+            boolean userMonthDay,
+            int monthDayType
     ) {
         // 1. 参数校验:避免空指针和非法参数
         validateParams(ruleCate, contractStartDate, periodBeginDate, periodEndDate);
 
+        //实际月
+        if (isNaturalMonth == false && (contractStartDate.getDayOfMonth() != 1)) {
+            // monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
+            //本月1号
+            if (monthDayType == -1)
+                contractStartDate = contractStartDate.with(TemporalAdjusters.firstDayOfMonth());
+                //下月1号
+            else if (monthDayType == -2)
+                contractStartDate = contractStartDate.with(TemporalAdjusters.lastDayOfMonth());
+                //指定日期
+            else if (monthDayType > 0) {
+                int totalDays = contractStartDate.lengthOfMonth(); // 获取当月总天数
+                contractStartDate = monthDayType > totalDays ?
+                        LocalDate.of(contractStartDate.getYear(), contractStartDate.getMonth(), totalDays) :  // 超过总天数→最后一天
+                        LocalDate.of(contractStartDate.getYear(), contractStartDate.getMonth(), monthDayType);// 否则→当月第day天
+            }
+            //不修改
+        }
+
+
         List<Period> periodList = new ArrayList<>();
         // 2. 一次性收费:直接生成单个周期
         if (PERIOD_TYPE_ONCE.equals(ruleCate)) {
@@ -91,6 +115,7 @@ public class PeriodCalculationUtil {
                     totalPeriodDays = getDaysInCurrentQuarter(periodBeginDate);
                 } else {
                     currLastDay = getActualQuarterFirstEndDay(contractStartDate, periodBeginDate, periodEndDate);
+
                     totalPeriodDays = getActualQuarterTotalDays(contractStartDate, periodBeginDate);
                 }
                 break;
@@ -121,8 +146,21 @@ public class PeriodCalculationUtil {
 
         // 3.2 处理首个周期
         if (currLastDay != null) {
-            Period firstPeriod = buildPeriod(periodBeginDate, currLastDay, totalPeriodDays);
-            periodList.add(firstPeriod);
+
+            if (userMonthDay) {
+                Period period = new Period();
+                period.setPeriodBeginDate(periodBeginDate);
+                period.setPeriodEndDate(currLastDay);
+
+                //月占天数比例  整月个数
+                period.setRate(divideToBigDecimal(getDateRate(periodBeginDate, currLastDay, false), GetPeriodMonth(ruleCate)));
+
+                periodList.add(period);
+            } else {
+                Period firstPeriod = buildPeriod(periodBeginDate, currLastDay, totalPeriodDays);
+                periodList.add(firstPeriod);
+            }
+
             // 更新下一个周期的起始日(当前周期结束日+1)
             startDate = currLastDay.plusDays(1);
         } else {
@@ -138,9 +176,13 @@ public class PeriodCalculationUtil {
                 // 若周期结束日超过服务结束日,截断并计算实际费率
                 BigDecimal rate = BigDecimal.ONE;
                 if (periodEnd.isAfter(periodEndDate)) {
-                    int actualDays = getPeriodDays(startDate, periodEndDate);
-                    int fullPeriodDays = getPeriodDays(startDate, periodEnd);
-                    rate = divideToBigDecimal(actualDays, fullPeriodDays);
+                    if (userMonthDay) {
+                        rate = divideToBigDecimal(getDateRate(startDate, periodEndDate, false), GetPeriodMonth(ruleCate));
+                    } else {
+                        int actualDays = getPeriodDays(startDate, periodEndDate);
+                        int fullPeriodDays = getPeriodDays(startDate, periodEnd);
+                        rate = divideToBigDecimal(actualDays, fullPeriodDays);
+                    }
                     periodEnd = periodEndDate;
                 }
 
@@ -158,6 +200,103 @@ public class PeriodCalculationUtil {
         return periodList;
     }
 
+    /**
+     * 获取修改结束日期调整后的周期列表
+     * @param ruleCate 周期类型
+     * @param lastPeriodBeginDate 最后一个周期开始日期
+     * @param lastPeriodEndDate  最后一个周期的结束日期
+     * @param userMonthDay  是否使用当月实际天数用于计算每天费用
+     * @param newEndDate   新的结束日期  lastPeriodBeginDate<=  newEndDate
+     * @return
+     */
+    public static List<Period> getAdjustPeriodList(
+            String ruleCate,
+            LocalDate lastPeriodBeginDate,
+            LocalDate lastPeriodEndDate,
+            boolean userMonthDay,
+            LocalDate newEndDate
+    ) {
+
+        List<Period> periodList = new ArrayList<>();
+
+        if (lastPeriodEndDate.equals(newEndDate)) {
+            return null;//不需要生成新的周期,把原来周期中 lastPeriodEndDate 之后的删除就行
+        }
+
+
+        if (lastPeriodEndDate.isAfter(newEndDate)) {
+            //周期缩短
+
+            if (userMonthDay) {
+                Period period = new Period();
+                period.setPeriodBeginDate(lastPeriodBeginDate);
+                period.setPeriodEndDate(newEndDate);
+                period.setRate(divideToBigDecimal(getDateRate(lastPeriodBeginDate, newEndDate, false), GetPeriodMonth(ruleCate)));
+                periodList.add(period);
+
+            } else {
+                periodList.add(buildPeriod(lastPeriodBeginDate, lastPeriodEndDate, getPeriodDays(lastPeriodBeginDate, lastPeriodEndDate)));
+
+            }
+
+        } else {
+            //周期延长
+
+            //1、如果 lastPeriodBeginDate 到 lastPeriodEndDate  不是一个完整周期,要先修改成完整周期
+
+            LocalDate currPeriodEnd = getNextPeriodStart(lastPeriodBeginDate, ruleCate).minusDays(1);// 当前周期结束日
+
+            if (currPeriodEnd.isAfter(lastPeriodEndDate)) {
+                lastPeriodEndDate = newEndDate;
+                if (userMonthDay) {
+                    Period period = new Period();
+                    period.setPeriodBeginDate(lastPeriodBeginDate);
+                    period.setPeriodEndDate(currPeriodEnd);
+                    //月占天数比例  整月个数
+                    period.setRate(divideToBigDecimal(getDateRate(lastPeriodBeginDate, currPeriodEnd, false), GetPeriodMonth(ruleCate)));
+                    periodList.add(period);
+                } else {
+                    periodList.add(buildPeriod(lastPeriodBeginDate, lastPeriodEndDate, getPeriodDays(lastPeriodBeginDate, currPeriodEnd)));
+                }
+                return periodList;
+            } else {
+                LocalDate startDate = lastPeriodBeginDate;
+
+                if (startDate != null && !startDate.isAfter(newEndDate)) {
+                    while (!startDate.isAfter(newEndDate)) {
+
+                        LocalDate nextPeriodStart = getNextPeriodStart(startDate, ruleCate);
+                        LocalDate periodEnd = nextPeriodStart.minusDays(1); // 当前周期结束日
+
+                        // 若周期结束日超过服务结束日,截断并计算实际费率
+                        BigDecimal rate = BigDecimal.ONE;
+                        if (periodEnd.isAfter(newEndDate)) {
+                            if (userMonthDay) {
+                                rate = divideToBigDecimal(getDateRate(startDate, newEndDate, false), GetPeriodMonth(ruleCate));
+                            } else {
+                                int actualDays = getPeriodDays(startDate, newEndDate);
+                                int fullPeriodDays = getPeriodDays(startDate, periodEnd);
+                                rate = divideToBigDecimal(actualDays, fullPeriodDays);
+                            }
+                            periodEnd = newEndDate;
+                        }
+
+                        Period cyclePeriod = new Period();
+                        cyclePeriod.setPeriodBeginDate(startDate);
+                        cyclePeriod.setPeriodEndDate(periodEnd);
+                        cyclePeriod.setRate(rate);
+                        periodList.add(cyclePeriod);
+
+                        // 更新下一个周期起始日
+                        startDate = periodEnd.plusDays(1);
+                    }
+                }
+            }
+        }
+        return periodList;
+    }
+
+
     // ------------------------------ 私有工具方法:参数校验 ------------------------------
 
     /**
@@ -410,6 +549,48 @@ public class PeriodCalculationUtil {
                 .divide(new BigDecimal(denominator), 10, RoundingMode.HALF_UP);
     }
 
+    public static BigDecimal divideToBigDecimal(Object numerator, Object denominator) {
+        // 转换分子为BigDecimal
+        BigDecimal num = toBigDecimal(numerator);
+        // 转换分母为BigDecimal
+        BigDecimal den = toBigDecimal(denominator);
+
+        // 检查分母是否为0
+        if (den.compareTo(BigDecimal.ZERO) == 0) {
+            throw new ArithmeticException("分母不能为零");
+        }
+        // 精确除法(保留10位小数,四舍五入)
+        return num.divide(den, 10, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 将Object转换为BigDecimal
+     * @param obj 待转换的对象(支持Number、String等可转换为数字的类型)
+     * @return 转换后的BigDecimal
+     * @throws IllegalArgumentException 若对象无法转换为BigDecimal
+     */
+    public static BigDecimal toBigDecimal(Object obj) {
+        if (obj == null) {
+            throw new IllegalArgumentException("对象不能为null");
+        }
+
+        if (obj instanceof BigDecimal) {
+            return (BigDecimal) obj;
+        } else if (obj instanceof Number) {
+            // 处理数字类型(Integer、Double、Long等)
+            return new BigDecimal(obj.toString());
+        } else if (obj instanceof String) {
+            // 处理字符串类型(需符合数字格式)
+            try {
+                return new BigDecimal((String) obj);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("字符串无法转换为BigDecimal: " + obj, e);
+            }
+        } else {
+            throw new IllegalArgumentException("不支持的类型转换: " + obj.getClass().getName());
+        }
+    }
+
     // ------------------------------ 私有工具方法:周期起止月计算 ------------------------------
 
     /**
@@ -450,6 +631,34 @@ public class PeriodCalculationUtil {
         }
     }
 
+    /**
+     * 根据不同的周期类型转换为对应的月数
+     * 此方法用于将给定的周期类型字符串转换为相应的月数
+     * 主要用于处理不同时间周期的转换,以便统一时间单位
+     *
+     * @param period 周期类型字符串,表示不同的周期类型
+     * @return 对应的月数如果输入的周期类型不匹配已知类型,则返回0
+     */
+    private static int GetPeriodMonth(String period) {
+        switch (period) {
+            case cttConstant.PERIOD_TYPE_MONTH:
+                // 当周期类型为月时,返回1个月
+                return 1;
+            case cttConstant.PERIOD_TYPE_QUARTER:
+                // 当周期类型为季度时,返回3个月
+                return 3;
+            case cttConstant.PERIOD_TYPE_HALFYEAR:
+                // 当周期类型为半年时,返回6个月
+                return 6;
+            case cttConstant.PERIOD_TYPE_YEAR:
+                // 当周期类型为年时,返回12个月
+                return 12;
+            default:
+                // 当周期类型不匹配已知类型时,返回0
+                return 0;
+        }
+    }
+
     /**
      * 获取季度起始月(1-3月→1月,4-6月→4月,7-9月→7月,10-12月→10月)
      * @param month 输入的月份(如 Month.FEBRUARY、Month.MAY 等)
@@ -523,6 +732,75 @@ public class PeriodCalculationUtil {
         return getPeriodDays(halfYearStart, halfYearEnd);
     }
 
+
+    /**
+     * 计算日期区间的比例值
+     * @param start 起始日期
+     * @param end 结束日期
+     * @param use30DaysPerMonth 是否按每月30天计算(true=固定30天,false=实际天数)
+     * @return 比例值(起始月剩余比例 + 中间完整月数 + 结束月已过比例)
+     */
+    public static BigDecimal getDateRate(LocalDate start, LocalDate end, boolean use30DaysPerMonth) {
+        // 校验日期合法性:start不能晚于end
+        if (start.isAfter(end)) {
+            return BigDecimal.ZERO; // 或抛出异常
+        }
+
+        int startYear = start.getYear();
+        int startMonth = start.getMonthValue();
+        int endYear = end.getYear();
+        int endMonth = end.getMonthValue();
+
+        // 情况1:start和end在同一个月
+        if (startYear == endYear && startMonth == endMonth) {
+            int totalDaysInMonth = use30DaysPerMonth ? 30 : start.lengthOfMonth();
+            int daysBetween = end.getDayOfMonth() - start.getDayOfMonth() + 1; // 包含首尾
+            return BigDecimal.valueOf(daysBetween)
+                    .divide(BigDecimal.valueOf(totalDaysInMonth), 10, RoundingMode.HALF_UP);
+        }
+
+        // 情况2:start和end在不同月份
+        // 1. 起始月剩余比例:(当月剩余天数)÷ 当月总天数
+        int startMonthTotalDays = use30DaysPerMonth ? 30 : start.lengthOfMonth();
+        int startRemainingDays = startMonthTotalDays - start.getDayOfMonth() + 1; // 包含start当天
+        BigDecimal startRatio = BigDecimal.valueOf(startRemainingDays)
+                .divide(BigDecimal.valueOf(startMonthTotalDays), 10, RoundingMode.HALF_UP);
+
+        // 2. 中间完整月数:start的下一个月 到 end的上一个月之间的完整月份
+        int fullMonths = 0;
+        int currentYear = startYear;
+        int currentMonth = startMonth + 1;
+        if (currentMonth > 12) { // 处理跨年(如12月→次年1月)
+            currentMonth = 1;
+            currentYear++;
+        }
+        int endPrevYear = endYear;
+        int endPrevMonth = endMonth - 1;
+        if (endPrevMonth < 1) { // 处理跨年(如1月→上年12月)
+            endPrevMonth = 12;
+            endPrevYear--;
+        }
+        // 累加中间完整月数
+        while (currentYear < endPrevYear ||
+                (currentYear == endPrevYear && currentMonth <= endPrevMonth)) {
+            fullMonths++;
+            currentMonth++;
+            if (currentMonth > 12) {
+                currentMonth = 1;
+                currentYear++;
+            }
+        }
+
+        // 3. 结束月已过比例:(当月已过天数)÷ 当月总天数
+        int endMonthTotalDays = use30DaysPerMonth ? 30 : end.lengthOfMonth();
+        int endPassedDays = end.getDayOfMonth(); // 从1日到end当天的天数
+        BigDecimal endRatio = BigDecimal.valueOf(endPassedDays)
+                .divide(BigDecimal.valueOf(endMonthTotalDays), 10, RoundingMode.HALF_UP);
+
+        // 总和:起始比例 + 完整月数 + 结束比例
+        return startRatio.add(BigDecimal.valueOf(fullMonths)).add(endRatio);
+    }
+
     // ------------------------------ 周期模型:公开静态内部类(可外部访问) ------------------------------
 
     /**

+ 16 - 3
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/event/commissionFormAfterSave.java

@@ -8,6 +8,7 @@ import com.actionsoft.bpms.server.UserContext;
 import com.actionsoft.bpms.util.DBSql;
 import com.actionsoft.exception.BPMNError;
 import com.actionsoft.sdk.local.SDK;
+import com.awspaas.user.apps.donenow_ctt.PeriodCalculationUtil;
 import com.awspaas.user.apps.donenow_ctt.cttConstant;
 import org.apache.commons.lang3.StringUtils;
 
@@ -118,7 +119,19 @@ public class commissionFormAfterSave extends ExecuteListener {
             }
 
 
-            List<PERIOD> periodList = getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, PERIOD_END_DATE);//周期
+            //  List<PERIOD> periodList = getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, PERIOD_END_DATE);//周期
+
+
+            if (RULE_CATE.equals(cttConstant.COMMISSION_RULE_MONTH)) RULE_CATE = cttConstant.PERIOD_TYPE_ONE_TIME;
+            else if (RULE_CATE.equals(cttConstant.COMMISSION_RULE_ONE_TIME))
+                RULE_CATE = cttConstant.PERIOD_TYPE_ONE_TIME;
+            else if (RULE_CATE.equals(cttConstant.COMMISSION_RULE_QUARTER)) RULE_CATE = cttConstant.PERIOD_TYPE_QUARTER;
+            else if (RULE_CATE.equals(cttConstant.COMMISSION_RULE_YEAR)) RULE_CATE = cttConstant.PERIOD_TYPE_YEAR;
+
+
+            //获取服务周期
+            List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, PERIOD_BEGIN_DATE, PERIOD_END_DATE, true, true, -1);
+
             if (periodList.size() > 0) {
                 Connection conn = null;
                 try {
@@ -152,7 +165,7 @@ public class commissionFormAfterSave extends ExecuteListener {
                             }
                         }
 
-                        PERIOD firstPeriod = periodList.get(0);
+                        PeriodCalculationUtil.Period firstPeriod = periodList.get(0);
                         if (firstPeriod.getRate().compareTo(BigDecimal.ONE) == 0) {
                             String periodBOID = DBSql.getString(conn, "SELECT ID FROM BO_EU_DNCTT_COMMISSION_PERIOD WHERE PERIOD_BEGIN_DATE=? AND BINDID=?", new Object[]{firstPeriod.getPeriodBeginDateStr(), bindid});
 
@@ -211,7 +224,7 @@ public class commissionFormAfterSave extends ExecuteListener {
                     }
 
 
-                    for (PERIOD period : periodList) {
+                    for (PeriodCalculationUtil.Period period : periodList) {
 
                         String periodBOID = DBSql.getString(conn, "SELECT ID FROM BO_EU_DNCTT_COMMISSION_PERIOD WHERE PERIOD_BEGIN_DATE=? AND BINDID=?", new Object[]{period.getPeriodBeginDateStr(), COMMISSION_BINDID});
                         if (StringUtils.isNotBlank(periodBOID)) {

+ 10 - 2
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/event/contractCreateFormAfterSave.java

@@ -56,7 +56,10 @@ public class contractCreateFormAfterSave extends ExecuteListener {
                         contractBO = SDK.getBOAPI().getByProcess("BO_EU_DNCTT_CONTRACT_APPLY", bindid);
                     RowMap dto = new RowMap(contractBO.asMap());
 
-                    contractService.getInstance().AddServiceServiceBundleOne(processExecutionContext.getUserContext(), conn, dto, formData);//保存合同
+                    //contractService.getInstance().AddServiceServiceBundleOne(processExecutionContext.getUserContext(), conn, dto, formData);//保存合同
+
+                    //2025年10月30日 修改新增服务
+                    contractService.getInstance().AddService(processExecutionContext.getUserContext(), conn, dto, formData);
 
                     contractService.getInstance().AddContractServiceProduct(processExecutionContext.getUserContext(), dto, conn);//新增服务或者服务包--关联产品
 
@@ -92,7 +95,12 @@ public class contractCreateFormAfterSave extends ExecuteListener {
                     // SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE", formData);
 
                     RowMap dto = new RowMap(contractBO.asMap());
-                    contractService.getInstance().AddServiceServiceBundleOne(processExecutionContext.getUserContext(), conn, dto, formData);//保存合同
+
+                   // contractService.getInstance().AddServiceServiceBundleOne(processExecutionContext.getUserContext(), conn, dto, formData);//保存合同
+
+                    //2025年10月30日 修改新增服务
+                    contractService.getInstance().AddService(processExecutionContext.getUserContext(), conn, dto, formData);//保存合同
+
                     contractService.getInstance().AddContractServiceProduct(processExecutionContext.getUserContext(), dto, conn);//新增服务或者服务包--关联产品
                     conn.commit();
                 } catch (SQLException e) {

+ 2 - 2
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/job/AutoRenewJob.java

@@ -81,8 +81,8 @@ public class AutoRenewJob implements Job {
 
                         for (BO service : serviceList) {
 
-                            boolean isAutoRenew = serviceOp.ServiceAutoRenew(uc, service, END_DATE, conn);
-
+                            //   boolean isAutoRenew = serviceOp.ServiceAutoRenew(uc, service, END_DATE, conn);
+                            serviceOp.ServiceAdjustEndDate(uc, service, END_DATE, conn);
                         }
 
                         conn.commit();

+ 409 - 13
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/contractService.java

@@ -23,6 +23,7 @@ import java.time.temporal.TemporalAdjusters;
 import java.util.*;
 
 import static com.awspaas.user.apps.donenow_ctt.service.CityAbbreviationUtil.getCityAbbreviation;
+import static org.apache.fop.fonts.type1.AdobeStandardEncoding.period;
 
 
 /**
@@ -341,11 +342,16 @@ public class contractService {
             List<BO> serviceList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE").connection(conn).addQuery("CONTRACT_ID =", contract.get("ID")).list();
 
             for (BO service : serviceList) {
-                AddServiceServiceBundleOne(uc, conn, contract, service);
+                // AddServiceServiceBundleOne(uc, conn, contract, service);
 
-                ServiceAutoRenew(uc, service, END_DATE, conn);
-            }
+                //新增服务周期
+                AddService(uc, conn, contract, service);
+
+                // ServiceAutoRenew(uc, service, END_DATE, conn);
 
+                //修改合同结束日期
+                ServiceAdjustEndDate(uc, service, END_DATE, conn);
+            }
 
             conn.commit();
             return true;
@@ -444,10 +450,20 @@ public class contractService {
                             SATRT_DATE = firstPeriodEnd.plusDays(1);
                         }
                         servicePeriod.set("PERIOD_END_DATE", new_period_end_date);
+
+                        /*
                         BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, new_period_end_date), GetPeriodDays(period_begin_date, period_end_date));
                         servicePeriod.set("PERIOD_PRICE", multiply(servicePeriod.get("PERIOD_PRICE"), periodRate));
                         servicePeriod.set("PERIOD_COST", multiply(servicePeriod.get("PERIOD_COST"), periodRate));
                         servicePeriod.set("PARTYA_PERIOD_PRICE", multiply(servicePeriod.get("PARTYA_PERIOD_PRICE"), periodRate));
+                        */
+
+                        BigDecimal periodRate = divideToBigDecimal(DateRateCalculator(period_begin_date, new_period_end_date, false), (GetPeriodMonth(maxPeriod)));
+
+                        servicePeriod.set("PERIOD_PRICE", multiply(service.get("TOTAL_COST"), periodRate));
+                        servicePeriod.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRate));
+                        servicePeriod.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), periodRate));
+
                         servicePeriod.set("PERIOD_ADJUSTED_PRICE", servicePeriod.get("PERIOD_PRICE"));
                         SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriod, conn);
                     } else {
@@ -468,8 +484,10 @@ public class contractService {
                             BigDecimal periodRate = BigDecimal.ONE;
                             LocalDate periodEnd = getNextPeriodStart(SATRT_DATE, maxPeriod).minusDays(1);//本周期结束时间
                             if (periodEnd.isAfter(END_DATE)) {//如果结束日期大于合同结束日期,则计算结束日期和合同结束日期的差值,并计算该差值占整周期的比例
-                                periodRate = divideToBigDecimal(GetPeriodDays(SATRT_DATE, END_DATE), GetPeriodDays(SATRT_DATE, periodEnd));
+                                //  periodRate = divideToBigDecimal(GetPeriodDays(SATRT_DATE, END_DATE), GetPeriodDays(SATRT_DATE, periodEnd));
                                 periodEnd = END_DATE;
+
+                                periodRate = divideToBigDecimal(DateRateCalculator(SATRT_DATE, periodEnd, false), (GetPeriodMonth(maxPeriod)));
                             }
                             BO csp = new BO();//服务周期
                             csp.setAll(servicePeriod.asMap());
@@ -517,10 +535,19 @@ public class contractService {
                     servicePeriod.set("PERIOD_END_DATE", END_DATE);
                     LocalDate period_begin_date = getLocalDate(servicePeriod.get("PERIOD_BEGIN_DATE"));
                     LocalDate period_end_date = getLocalDate(servicePeriod.get("PERIOD_END_DATE"));
+                    /*
                     BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, END_DATE), GetPeriodDays(period_begin_date, period_end_date));
                     servicePeriod.set("PERIOD_PRICE", multiply(servicePeriod.get("PERIOD_PRICE"), periodRate));
                     servicePeriod.set("PERIOD_COST", multiply(servicePeriod.get("PERIOD_COST"), periodRate));
                     servicePeriod.set("PARTYA_PERIOD_PRICE", multiply(servicePeriod.get("PARTYA_PERIOD_PRICE"), periodRate));
+                    */
+
+                    BigDecimal periodRate = divideToBigDecimal(DateRateCalculator(period_begin_date, END_DATE, false), (GetPeriodMonth(service.getString("PERIOD_TYPE"))));
+
+                    servicePeriod.set("PERIOD_PRICE", multiply(service.get("TOTAL_COST"), periodRate));
+                    servicePeriod.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRate));
+                    servicePeriod.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), periodRate));
+
                     servicePeriod.set("PERIOD_ADJUSTED_PRICE", servicePeriod.get("PERIOD_PRICE"));
                     SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriod, conn);
 
@@ -559,9 +586,12 @@ public class contractService {
                             new_period_end_date = firstPeriodEnd;
                             SATRT_DATE = firstPeriodEnd.plusDays(1);
                         }
-                        BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, new_period_end_date), GetPeriodDays(period_begin_date, period_end_date));
+                        // BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, new_period_end_date), GetPeriodDays(period_begin_date, period_end_date));
+                        // payPeriod.set("PLAN_AMOUNT", multiply(payPeriod.get("PLAN_AMOUNT"), periodRate));//计划付款金额
+
+                        BigDecimal periodRate = divideToBigDecimal(DateRateCalculator(period_begin_date, new_period_end_date, false), (GetPeriodMonth(maxPeriod)));
 
-                        payPeriod.set("PLAN_AMOUNT", multiply(payPeriod.get("PLAN_AMOUNT"), periodRate));//计划付款金额
+                        payPeriod.set("PLAN_AMOUNT", multiply(SERVICE_TOTAL_COST, periodRate));//计划付款金额
                         payPeriod.set("REMAIN_AMOUNT", payPeriod.get("PLAN_AMOUNT", BigDecimal.class).subtract(payPeriod.get("PAY_AMOUNT", BigDecimal.class)));//剩余金额
                         SDK.getBOAPI().update("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", payPeriod, conn);
                     } else {
@@ -575,8 +605,11 @@ public class contractService {
                             BigDecimal periodRate = BigDecimal.ONE;
                             LocalDate periodEnd = getNextPeriodStart(SATRT_DATE, maxPeriod).minusDays(1);//本周期结束时间
                             if (periodEnd.isAfter(END_DATE)) {//如果结束日期大于合同结束日期,则计算结束日期和合同结束日期的差值,并计算该差值占整周期的比例
-                                periodRate = divideToBigDecimal(GetPeriodDays(SATRT_DATE, END_DATE), GetPeriodDays(SATRT_DATE, periodEnd));
+                                //periodRate = divideToBigDecimal(GetPeriodDays(SATRT_DATE, END_DATE), GetPeriodDays(SATRT_DATE, periodEnd));
                                 periodEnd = END_DATE;
+
+                                periodRate = divideToBigDecimal(DateRateCalculator(SATRT_DATE, END_DATE, false), (GetPeriodMonth(maxPeriod)));
+
                             }
 
                             ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_5cb4ae4a42944fd0a9a284ff4c64c65d", uc.getUID(), "付款计划");
@@ -602,8 +635,13 @@ public class contractService {
 
                     BO payPeriod = payPeriodList.get(0);
                     LocalDate period_begin_date = getLocalDate(payPeriod.get("PLAN_DATE"));
-                    BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, END_DATE), GetPeriodDays(period_begin_date, PERIOD_END));
-                    payPeriod.set("PLAN_AMOUNT", multiply(payPeriod.get("PLAN_AMOUNT"), periodRate));//计划付款金额
+
+                    // BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, END_DATE), GetPeriodDays(period_begin_date, PERIOD_END));
+                    // payPeriod.set("PLAN_AMOUNT", multiply(payPeriod.get("PLAN_AMOUNT"), periodRate));//计划付款金额
+
+                    BigDecimal periodRate = divideToBigDecimal(DateRateCalculator(period_begin_date, END_DATE, false), (GetPeriodMonth(service.getString("PURCHASE_PERIOD_TYPE"))));
+                    payPeriod.set("PLAN_AMOUNT", multiply(SERVICE_TOTAL_COST, periodRate));//计划付款金额
+
                     payPeriod.set("REMAIN_AMOUNT", payPeriod.get("PLAN_AMOUNT", BigDecimal.class).subtract(payPeriod.get("PAY_AMOUNT", BigDecimal.class)));//剩余金额
                     SDK.getBOAPI().update("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", payPeriod, conn);
 
@@ -834,8 +872,14 @@ public class contractService {
         //如果不是从一号开始,则需要调整到从1号开始
         if (StringUtils.isNotBlank(BILL_METHOD_ID) && BILL_METHOD_ID.equals("4601") && START_DATE.getDayOfMonth() != 1) {
             //本月底日期
+            /*
             LocalDate currMontLastDay = START_DATE.with(TemporalAdjusters.lastDayOfMonth());
-            if (currMontLastDay.isBefore(END_DATE)) START_DATE = currMontLastDay.plusDays(1);//周期开始日期
+            if (currMontLastDay.isBefore(END_DATE))
+                START_DATE = currMontLastDay.plusDays(1);//周期开始日期
+
+            */
+
+            START_DATE = START_DATE.with(TemporalAdjusters.firstDayOfMonth());
         }
 
         BigDecimal SERVICE_UNIT_COST = service.get("UNIT_COST", BigDecimal.class);//服务单元成本
@@ -899,12 +943,17 @@ public class contractService {
         if (!isStartOfPeriod) {
             BigDecimal periodRate;
             //如果生效日期在合同开始日期之前
+            /*
             if (EFFECTIVE_DATE.isBefore(START_DATE)) {
                 int priodDays = GetPeriodDays(getPreviousPeriodStart(end.plusDays(1), String.valueOf(maxPeriod)), end);
                 periodRate = divideToBigDecimal(GetPeriodDays(EFFECTIVE_DATE, end), priodDays);
             } else {
                 periodRate = divideToBigDecimal(GetPeriodDays(EFFECTIVE_DATE, end), GetPeriodDays(start, end));    // 首周期占整周期比例
-            }
+            }*/
+
+            //计算日期之间的比例值
+            periodRate = divideToBigDecimal(DateRateCalculator(EFFECTIVE_DATE, end, false), (GetPeriodMonth(String.valueOf(maxPeriod))));
+
             BO csa = new BO();//合同服务调整
             csa.set("CONTRACT_ID", contract.get("ID"));
             csa.set("OBJECT_ID", service.get("OBJECT_ID"));
@@ -959,8 +1008,11 @@ public class contractService {
                 BigDecimal periodRate = BigDecimal.ONE;
                 LocalDate periodEnd = getNextPeriodStart(start, String.valueOf(maxPeriod)).minusDays(1);//本周期结束时间
                 if (periodEnd.isAfter(END_DATE)) {//如果结束日期大于合同结束日期,则计算结束日期和合同结束日期的差值,并计算该差值占整周期的比例
-                    periodRate = divideToBigDecimal(GetPeriodDays(start, END_DATE), GetPeriodDays(start, periodEnd));
+                    //  periodRate = divideToBigDecimal(GetPeriodDays(start, END_DATE), GetPeriodDays(start, periodEnd));
+
                     periodEnd = END_DATE;
+                    //计算日期之间的比例值
+                    periodRate = divideToBigDecimal(DateRateCalculator(start, periodEnd, false), (GetPeriodMonth(String.valueOf(maxPeriod))));
                 }
 
                 BO csp = new BO();//服务周期
@@ -1021,6 +1073,282 @@ public class contractService {
         return true;
     }
 
+
+    /**
+     * 添加合同服务
+     * @param uc
+     * @param contract
+     * @param service
+     * @return
+     */
+    public boolean AddService(UserContext uc, Connection conn, RowMap contract, BO service) {
+        // 1、合同开始日期或结束日期变化
+        LocalDate START_DATE = getLocalDate(contract.get("START_DATE"));//服务开始日期
+        LocalDate END_DATE = getLocalDate(contract.get("END_DATE"));//合同结束日期
+        LocalDate EFFECTIVE_DATE = getLocalDate(service.get("EFFECTIVE_DATE"));//服务生效日期
+
+        //已经审批并提交则不能重新生成服务
+        if (DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE APPROVE_AND_POST_USER_ID IS NOT NULL AND LENGTH(APPROVE_AND_POST_USER_ID)>1 AND APPROVE_AND_POST_DATE IS NOT NULL AND  BINDID=? and CONTRACT_ID=? and CONTRACT_SERVICE_ID=? ", new Object[]{service.get("BINDID"), contract.get("ID"), service.get("ID")}) > 0) {
+            return true;
+        }
+
+        //删除历史数据
+        List<String> cspIdList = DBSql.getList(conn, "select ID from BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD where BINDID=? and CONTRACT_ID=? and OBJECT_ID=? and CONTRACT_SERVICE_ID=?", String.class, new Object[]{service.get("BINDID"), contract.get("ID"), service.get("OBJECT_ID"), service.get("ID")});
+        for (String cspId : cspIdList) {
+            DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE where BINDID=? and CONTRACT_SERVICE_PERIOD_ID=? ", new Object[]{service.get("BINDID"), cspId});
+        }
+        DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD where BINDID=? and CONTRACT_ID=? and OBJECT_ID=? and CONTRACT_SERVICE_ID=?", new Object[]{service.get("BINDID"), contract.get("ID"), service.get("OBJECT_ID"), service.get("ID")});
+
+
+        String PERIOD_TYPE = service.getString("PERIOD_TYPE");//服务周期类型
+        String vendor_account_id = DBSql.getString(conn, "select VENDOR_ACCOUNT_ID from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.getString("OBJECT_ID")});//供应商账号
+
+        BigDecimal SERVICE_UNIT_COST = service.get("UNIT_COST", BigDecimal.class);//服务单元成本
+        //需要采购
+        if (service.getString("NEED_PURCHASE").equals("1")) {
+            BigDecimal prate = GetPeriodRate(service.getString("PERIOD_TYPE"), service.getString("PURCHASE_PERIOD_TYPE"));
+            SERVICE_UNIT_COST = SERVICE_UNIT_COST.multiply(prate);
+        }
+        BigDecimal SERVICE_TOTAL_COST = multiply(SERVICE_UNIT_COST, service.get("QUANTITY"));//服务总成本
+
+        BigDecimal SERVICE_UNIT_PRICE = service.get("UNIT_PRICE", BigDecimal.class);
+        BigDecimal SERVICE_TOTAL_PRICE = multiply(SERVICE_UNIT_PRICE, service.get("QUANTITY"));
+
+        List<String> sbsList = null;
+        String serviceIds = DBSql.getString("select SERVICE_ID from BO_EU_DNIVT_SERVICE_BUNDLE where ID=?", new Object[]{service.get("OBJECT_ID")});
+        if (StringUtils.isNotBlank(serviceIds)) {
+            sbsList = Arrays.asList(serviceIds.split(","));
+        }
+
+        //获取服务周期
+        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(PERIOD_TYPE, START_DATE, EFFECTIVE_DATE, END_DATE, true, true, -1);
+
+        String PERIOD = periodList.get(0).getPeriodBeginDateStr() + "~" + periodList.get(periodList.size() - 1).getPeriodEndDateStr();
+
+        for (PeriodCalculationUtil.Period period : periodList) {
+
+            BO csp = new BO();//服务周期
+            csp.set("CONTRACT_ID", contract.get("ID"));
+            csp.set("OBJECT_ID", service.get("OBJECT_ID"));
+            csp.set("OBJECT_TYPE", service.get("OBJECT_TYPE"));
+            csp.set("CONTRACT_SERVICE_ID", service.get("ID"));
+
+            csp.set("PERIOD_BEGIN_DATE", period.getPeriodBeginDateStr());
+            csp.set("PERIOD_END_DATE", period.getPeriodEndDateStr());
+
+            csp.set("QUANTITY", service.get("QUANTITY"));
+
+            csp.set("PERIOD_PRICE", period.getRate().multiply(SERVICE_TOTAL_PRICE));
+
+            csp.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+
+            csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));
+
+            csp.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), period.getRate()));//三方合同甲方含税总价
+
+            csp.set("VENDOR_ACCOUNT_ID", vendor_account_id);
+            csp.setBindId(service.getString("BINDID"));
+            SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", csp, uc, conn);
+
+
+            if (service.getString("OBJECT_TYPE").equals("2"))//服务包
+            {
+                //本周期成本
+                String prorated_cost_change = DBSql.getString(conn, "select UNIT_COST from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.getString("OBJECT_ID")});//供应商账号
+                if (sbsList != null) {
+                    for (String sbs : sbsList) {
+                        BO cspbs = new BO();
+                        cspbs.set("CONTRACT_SERVICE_PERIOD_ID", csp.getId());
+                        cspbs.set("SERVICE_ID", sbs);
+                        cspbs.set("VENDOR_ACCOUNT_ID", vendor_account_id);
+                        cspbs.set("PERIOD_COST", prorated_cost_change);
+                        cspbs.setBindId(service.getString("BINDID"));
+                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", cspbs, uc, conn);
+                    }
+                }
+            }
+        }
+
+        //更新合同服务上的
+        DBSql.update(conn, "update BO_EU_DNCTT_CONTRACT_SERVICE set PERIOD=?,ADJUST_PERIOD=? where ID=? and BINDID=?", new Object[]{PERIOD, StringUtils.EMPTY, service.get("ID"), service.get("BINDID")});
+        service.set("PERIOD", PERIOD);
+        service.set("ADJUST_PERIOD", StringUtils.EMPTY);
+
+        return true;
+    }
+
+
+    /**
+     * 调整服务的结束日期(延长周期或是缩短周期)
+     * @param uc
+     * @param service
+     * @param END_DATE
+     * @param conn
+     * @return
+     */
+    public boolean ServiceAdjustEndDate(UserContext uc, BO service, LocalDate END_DATE, Connection conn) {
+
+        String PERIOD_TYPE = service.getString("PERIOD_TYPE");//服务周期类型
+        //已经审批并提交则不能重新生成服务
+        if (DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE APPROVE_AND_POST_USER_ID IS NOT NULL AND LENGTH(APPROVE_AND_POST_USER_ID)>1 AND APPROVE_AND_POST_DATE IS NOT NULL AND  BINDID=? and CONTRACT_ID=? and CONTRACT_SERVICE_ID=? AND PERIOD_END_DATE>=?", new Object[]{service.get("BINDID"), service.get("CONTRACT_ID"), service.get("ID"), LocalDateYYYYMMDD(END_DATE)}) > 0) {
+            return true;
+        }
+
+        //获取最后一笔
+        RowMap lastPeriod = DBSql.getMap(conn, "select * from BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD where CONTRACT_ID=? and CONTRACT_SERVICE_ID=? and PERIOD_BEGIN_DATE<=? order by PERIOD_BEGIN_DATE desc", new Object[]{service.get("CONTRACT_ID"), service.get("ID"), LocalDateYYYYMMDD(END_DATE)});
+
+        if (lastPeriod != null) {
+            LocalDate lastPeriodBeginDate = getLocalDate(lastPeriod.get("PERIOD_BEGIN_DATE"));
+            LocalDate lastPeriodEndDate = getLocalDate(lastPeriod.get("PERIOD_END_DATE"));
+
+            //删除 新结束日期之后的服务周期
+            List<String> periodIds = DBSql.getList(conn, "select ID from BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD where CONTRACT_ID=? and CONTRACT_SERVICE_ID=? AND PERIOD_BEGIN_DATE>?", String.class, new Object[]{service.get("CONTRACT_ID"), service.get("ID"), LocalDateYYYYMMDD(END_DATE)});
+            if (periodIds.isEmpty() == false && periodIds.size() > 0) {
+                DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD where CONTRACT_ID=? and CONTRACT_SERVICE_ID=? AND PERIOD_BEGIN_DATE>?", new Object[]{service.get("CONTRACT_ID"), service.get("ID"), LocalDateYYYYMMDD(END_DATE)});
+
+                for (String periodId : periodIds) {
+                    DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE WHERE CONTRACT_SERVICE_PERIOD_ID=?", new Object[]{periodId});
+                }
+            }
+
+            //修改周期
+            if (END_DATE.equals(lastPeriodEndDate) == false) {
+                //获取服务周期
+                List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getAdjustPeriodList(PERIOD_TYPE, lastPeriodBeginDate, lastPeriodEndDate, true, END_DATE);
+                BigDecimal SERVICE_UNIT_COST = service.get("UNIT_COST", BigDecimal.class);//服务单元成本
+                //需要采购
+                if (service.getString("NEED_PURCHASE").equals("1")) {
+                    BigDecimal prate = GetPeriodRate(service.getString("PERIOD_TYPE"), service.getString("PURCHASE_PERIOD_TYPE"));
+                    SERVICE_UNIT_COST = SERVICE_UNIT_COST.multiply(prate);
+                }
+                BigDecimal SERVICE_TOTAL_COST = multiply(SERVICE_UNIT_COST, service.get("QUANTITY"));//服务总成本
+
+                BigDecimal SERVICE_UNIT_PRICE = service.get("UNIT_PRICE", BigDecimal.class);
+                BigDecimal SERVICE_TOTAL_PRICE = multiply(SERVICE_UNIT_PRICE, service.get("QUANTITY"));
+
+                List<String> sbsList = null;
+                String serviceIds = DBSql.getString("select SERVICE_ID from BO_EU_DNIVT_SERVICE_BUNDLE where ID=?", new Object[]{service.get("OBJECT_ID")});
+                if (StringUtils.isNotBlank(serviceIds)) {
+                    sbsList = Arrays.asList(serviceIds.split(","));
+                }
+
+                String vendor_account_id = lastPeriod.getString("VENDOR_ACCOUNT_ID");
+
+                for (PeriodCalculationUtil.Period period : periodList) {
+
+                    //第一个周期 更新
+                    if (period.getPeriodBeginDateStr().equals(LocalDateYYYYMMDD(lastPeriodBeginDate))) {
+
+                        BO lastPeriodBO = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", lastPeriod.getString("ID"));
+                        lastPeriodBO.set("PERIOD_END_DATE", period.getPeriodEndDateStr());
+                        lastPeriodBO.set("PERIOD_PRICE", period.getRate().multiply(SERVICE_TOTAL_PRICE));
+                        lastPeriodBO.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+                        lastPeriodBO.set("PERIOD_ADJUSTED_PRICE", lastPeriodBO.get("PERIOD_PRICE"));
+                        lastPeriodBO.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), period.getRate()));//三方合同甲方含税总价
+                        SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", lastPeriodBO, conn);
+                    } else {
+                        //新增
+                        BO csp = new BO();//服务周期
+                        csp.set("CONTRACT_ID", service.get("CONTRACT_ID"));
+                        csp.set("OBJECT_ID", service.get("OBJECT_ID"));
+                        csp.set("OBJECT_TYPE", service.get("OBJECT_TYPE"));
+                        csp.set("CONTRACT_SERVICE_ID", service.get("ID"));
+
+                        csp.set("PERIOD_BEGIN_DATE", period.getPeriodBeginDateStr());
+                        csp.set("PERIOD_END_DATE", period.getPeriodEndDateStr());
+
+                        csp.set("QUANTITY", service.get("QUANTITY"));
+
+                        csp.set("PERIOD_PRICE", period.getRate().multiply(SERVICE_TOTAL_PRICE));
+
+                        csp.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+
+                        csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));
+
+                        csp.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), period.getRate()));//三方合同甲方含税总价
+
+                        csp.set("VENDOR_ACCOUNT_ID", vendor_account_id);
+                        csp.setBindId(service.getString("BINDID"));
+                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", csp, uc, conn);
+
+
+                        if (service.getString("OBJECT_TYPE").equals("2"))//服务包
+                        {
+                            //本周期成本
+                            String prorated_cost_change = DBSql.getString(conn, "select UNIT_COST from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.getString("OBJECT_ID")});//供应商账号
+                            if (sbsList != null) {
+                                for (String sbs : sbsList) {
+                                    BO cspbs = new BO();
+                                    cspbs.set("CONTRACT_SERVICE_PERIOD_ID", csp.getId());
+                                    cspbs.set("SERVICE_ID", sbs);
+                                    cspbs.set("VENDOR_ACCOUNT_ID", vendor_account_id);
+                                    cspbs.set("PERIOD_COST", prorated_cost_change);
+                                    cspbs.setBindId(service.getString("BINDID"));
+                                    SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", cspbs, uc, conn);
+                                }
+                            }
+                        }
+
+                    }
+
+
+                }
+            }
+        }
+
+
+        RowMap lastPayPeriod = DBSql.getMap(conn, "select * from BO_EU_DNIVT_ORDER_PAYMENT_PLAN where CONTRACT_SERVICE_ID=? and PERIOD_BEGIN_DATE<=? order by PLAN_DATE desc", new Object[]{service.get("ID"), LocalDateYYYYMMDD(END_DATE)});
+        if (lastPayPeriod != null) {
+
+            PERIOD_TYPE = service.getString("PURCHASE_PERIOD_TYPE");//采购周期类型
+            LocalDate lastPeriodBeginDate = getLocalDate(lastPayPeriod.get("PERIOD_BEGIN_DATE"));
+            LocalDate lastPeriodEndDate = getLocalDate(lastPayPeriod.get("PERIOD_END_DATE"));
+
+            DBSql.update(conn, "DELETE FROM BO_EU_DNIVT_ORDER_PAYMENT_PLAN WHERE  CONTRACT_SERVICE_ID=? and PERIOD_BEGIN_DATE>? and (PLAN_AMOUNT=0 or PLAN_AMOUNT is NULL)", new Object[]{service.getString("ID"), LocalDateYYYYMMDD(END_DATE)});
+
+            BigDecimal SERVICE_UNIT_COST = service.get("UNIT_COST", BigDecimal.class);//服务单元成本
+            BigDecimal SERVICE_TOTAL_COST = multiply(SERVICE_UNIT_COST, service.get("QUANTITY"));//服务总成本
+
+            String CONTRACT_COST_ID = lastPayPeriod.getString("CONTRACT_COST_ID");
+            String PAY_DESC = lastPayPeriod.getString("PAY_DESC");
+            String ACCOUNT_PAYEE = lastPayPeriod.getString("ACCOUNT_PAYEE");
+
+            List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getAdjustPeriodList(PERIOD_TYPE, lastPeriodBeginDate, lastPeriodEndDate, true, END_DATE);
+            for (PeriodCalculationUtil.Period period : periodList) {
+
+                //第一个周期 更新
+                if (period.getPeriodBeginDateStr().equals(LocalDateYYYYMMDD(lastPeriodBeginDate))) {
+
+                    BO lastPeriodBO = SDK.getBOAPI().get("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", lastPayPeriod.getString("ID"));
+                    lastPeriodBO.set("PERIOD_END_DATE", period.getPeriodEndDateStr());
+                    lastPeriodBO.set("PLAN_AMOUNT", period.getRate().multiply(SERVICE_TOTAL_COST));
+                    lastPeriodBO.set("REMAIN_AMOUNT", lastPeriodBO.get("PLAN_AMOUNT"));
+                    SDK.getBOAPI().update("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", lastPeriodBO, conn);
+
+                } else {
+                    ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_5cb4ae4a42944fd0a9a284ff4c64c65d", uc.getUID(), "付款计划");
+
+                    BO paymentPlan = new BO();
+                    paymentPlan.setBindId(processInstance.getId());
+                    paymentPlan.set("ORDER_ID", null);
+                    paymentPlan.set("PLAN_DATE", period.getPeriodBeginDateStr());
+                    paymentPlan.set("PLAN_AMOUNT", period.getRate().multiply(SERVICE_TOTAL_COST));
+                    paymentPlan.set("REMAIN_AMOUNT", paymentPlan.get("PLAN_AMOUNT"));
+                    paymentPlan.set("CONTRACT_COST_ID", CONTRACT_COST_ID);
+                    paymentPlan.set("CONTRACT_SERVICE_ID", service.getId());
+                    paymentPlan.set("PAY_DESC", PAY_DESC);
+                    paymentPlan.set("PERIOD_BEGIN_DATE", period.getPeriodBeginDateStr());
+                    paymentPlan.set("PERIOD_END_DATE", period.getPeriodEndDateStr());
+                    paymentPlan.set("ACCOUNT_PAYEE", ACCOUNT_PAYEE);//收款单位
+
+                    SDK.getBOAPI().create("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", paymentPlan, processInstance, uc);
+                }
+            }
+        }
+
+        return true;
+    }
+
     /**
      * 调整服务
      * @param uc
@@ -1029,6 +1357,9 @@ public class contractService {
      * @return
      */
     public boolean EditServiceServiceBundle(UserContext uc, Connection conn, RowMap contract, BO editService) {
+
+        //2025年10月30日  计算有问题  后续 在修改
+
         //2、服务调整:生效日期在合同开始日期和合同结束日期之间;已审批服务周期之后的日期;
         // 3、服务周期分为调整前和调整后两个周期;生效日期之后按照新的数量、单价重新生成服务周期;
         contractServiceLogger.info("调整服务");
@@ -1639,7 +1970,7 @@ public class contractService {
         contractServiceLogger.info("计算合同成本周期:" + service);
 
         // 需要通过采购计算总成本
-        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(service.get("PURCHASE_PERIOD_TYPE"), LocalDate.parse(service.get("CONTRACT_START_DATE").substring(0, 10)), LocalDate.parse(service.get("PURCHASE_START_DATE").substring(0, 10)), LocalDate.parse(service.get("CONTRACT_END_DATE").substring(0, 10)), service.get("PURCHASE_PERIOD_TYPE").equals("610"));
+        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(service.get("PURCHASE_PERIOD_TYPE"), LocalDate.parse(service.get("CONTRACT_START_DATE").substring(0, 10)), LocalDate.parse(service.get("PURCHASE_START_DATE").substring(0, 10)), LocalDate.parse(service.get("CONTRACT_END_DATE").substring(0, 10)), service.get("PURCHASE_PERIOD_TYPE").equals("610"), true, -1);
 
         BigDecimal rate = periodList.stream().map(period -> period.getRate()).reduce(BigDecimal.ZERO, BigDecimal::add);
         EXTENDED_COST = EXTENDED_COST.multiply(rate);
@@ -1976,6 +2307,71 @@ public class contractService {
         return num.divide(den, 10, RoundingMode.HALF_UP); // 保留 10 位小数
     }
 
+
+    /**
+     * 计算日期区间的比例值
+     * @param start 起始日期
+     * @param end 结束日期
+     * @param use30DaysPerMonth 是否按每月30天计算(true=固定30天,false=实际天数)
+     * @return 比例值(起始月剩余比例 + 中间完整月数 + 结束月已过比例)
+     */
+    public static BigDecimal DateRateCalculator(LocalDate start, LocalDate end, boolean use30DaysPerMonth) {
+        // 校验日期合法性:start不能晚于end
+        if (start.isAfter(end)) {
+            return BigDecimal.ZERO; // 或抛出异常
+        }
+
+        int startYear = start.getYear();
+        int startMonth = start.getMonthValue();
+        int endYear = end.getYear();
+        int endMonth = end.getMonthValue();
+
+        // 情况1:start和end在同一个月
+        if (startYear == endYear && startMonth == endMonth) {
+            int totalDaysInMonth = use30DaysPerMonth ? 30 : start.lengthOfMonth();
+            int daysBetween = end.getDayOfMonth() - start.getDayOfMonth() + 1; // 包含首尾
+            return BigDecimal.valueOf(daysBetween).divide(BigDecimal.valueOf(totalDaysInMonth), 10, RoundingMode.HALF_UP);
+        }
+
+        // 情况2:start和end在不同月份
+        // 1. 起始月剩余比例:(当月剩余天数)÷ 当月总天数
+        int startMonthTotalDays = use30DaysPerMonth ? 30 : start.lengthOfMonth();
+        int startRemainingDays = startMonthTotalDays - start.getDayOfMonth() + 1; // 包含start当天
+        BigDecimal startRatio = BigDecimal.valueOf(startRemainingDays).divide(BigDecimal.valueOf(startMonthTotalDays), 10, RoundingMode.HALF_UP);
+
+        // 2. 中间完整月数:start的下一个月 到 end的上一个月之间的完整月份
+        int fullMonths = 0;
+        int currentYear = startYear;
+        int currentMonth = startMonth + 1;
+        if (currentMonth > 12) { // 处理跨年(如12月→次年1月)
+            currentMonth = 1;
+            currentYear++;
+        }
+        int endPrevYear = endYear;
+        int endPrevMonth = endMonth - 1;
+        if (endPrevMonth < 1) { // 处理跨年(如1月→上年12月)
+            endPrevMonth = 12;
+            endPrevYear--;
+        }
+        // 累加中间完整月数
+        while (currentYear < endPrevYear || (currentYear == endPrevYear && currentMonth <= endPrevMonth)) {
+            fullMonths++;
+            currentMonth++;
+            if (currentMonth > 12) {
+                currentMonth = 1;
+                currentYear++;
+            }
+        }
+
+        // 3. 结束月已过比例:(当月已过天数)÷ 当月总天数
+        int endMonthTotalDays = use30DaysPerMonth ? 30 : end.lengthOfMonth();
+        int endPassedDays = end.getDayOfMonth(); // 从1日到end当天的天数
+        BigDecimal endRatio = BigDecimal.valueOf(endPassedDays).divide(BigDecimal.valueOf(endMonthTotalDays), 10, RoundingMode.HALF_UP);
+
+        // 总和:起始比例 + 完整月数 + 结束比例
+        return startRatio.add(BigDecimal.valueOf(fullMonths)).add(endRatio);
+    }
+
     /**
      * 相除
      * @param numerator

+ 13 - 2
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/service/PaymentPlanService.java

@@ -8,6 +8,7 @@ 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 com.awspaas.user.apps.donenow_ivt.utils.PeriodCalculationUtil;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
@@ -93,7 +94,11 @@ public class PaymentPlanService {
                         START_DATE = TypeUtil.convert(service.get("PURCHASE_START_DATE").toString().substring(0, 10), 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"));
+                    //  List<PERIOD> periods = getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, START_DATE, PERIOD_END_DATE, StringUtils.isNotBlank(BILL_METHOD_ID) && BILL_METHOD_ID.equals("4601"));
+
+                    //统一处理
+                    List<PeriodCalculationUtil.Period> periods = PeriodCalculationUtil.getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, START_DATE, PERIOD_END_DATE, true, true, -1);
+
 
                     if (periods.isEmpty())
                         continue;
@@ -106,7 +111,7 @@ public class PaymentPlanService {
                         continue;
 
                     BigDecimal UNIT_COST = COST_TOTAL.divide(RATE_TOTAL, 10, RoundingMode.HALF_UP);
-                    for (PERIOD period : periods) {
+                    for (PeriodCalculationUtil.Period period : periods) {
                         total++;
                         ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_5cb4ae4a42944fd0a9a284ff4c64c65d", uc.getUID(), "付款计划");
 
@@ -122,6 +127,9 @@ public class PaymentPlanService {
 
                         paymentPlan.set("ACCOUNT_PAYEE", VENDOR_ACCOUNT_ID);//收款单位
 
+                        paymentPlan.set("PERIOD_BEGIN_DATE", period.getPeriodBeginDateStr());
+                        paymentPlan.set("PERIOD_END_DATE", period.getPeriodEndDateStr());
+
                         SDK.getBOAPI().create("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", paymentPlan, processInstance, uc);
 
                         System.out.println("==== 创建付款计划:" + paymentPlan.get("PLAN_DATE") + ",金额:" + paymentPlan.get("PLAN_AMOUNT") + " ====");
@@ -190,6 +198,9 @@ public class PaymentPlanService {
             isNaturalMonth = false;
         }
 
+        if (CONTRACT_START_DATE.getDayOfMonth() != 1)// 不是1号开始,就从1号开始
+            CONTRACT_START_DATE = CONTRACT_START_DATE.with(TemporalAdjusters.firstDayOfMonth());
+
         List<PERIOD> periodList = new ArrayList<>();
         //一次性收费
         if (RULE_CATE.equals(IVTConstant.PERIOD_TYPE_ONE_TIME)) {

+ 861 - 0
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/utils/PeriodCalculationUtil.java

@@ -0,0 +1,861 @@
+package com.awspaas.user.apps.donenow_ivt.utils;
+
+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 = "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";       // 按年支付
+
+    // 日期格式化器(复用避免重复创建)
+    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=实际月)
+     * @param userMonthDay       是否使用当月实际天数用于计算每天费用
+     * @param monthDayType  monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
+     * @return 周期列表(含起止日期、费率)
+     * @throws IllegalArgumentException 当参数非法时抛出(如日期null、周期类型无效)
+     */
+    public static List<Period> getPeriodList(
+            String ruleCate,
+            LocalDate contractStartDate,
+            LocalDate periodBeginDate,
+            LocalDate periodEndDate,
+            boolean isNaturalMonth,
+            boolean userMonthDay,
+            int monthDayType
+    ) {
+        // 1. 参数校验:避免空指针和非法参数
+        validateParams(ruleCate, contractStartDate, periodBeginDate, periodEndDate);
+
+        //实际月
+        if (isNaturalMonth == false && (contractStartDate.getDayOfMonth() != 1)) {
+            // monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
+            //本月1号
+            if (monthDayType == -1)
+                contractStartDate = contractStartDate.with(TemporalAdjusters.firstDayOfMonth());
+                //下月1号
+            else if (monthDayType == -2)
+                contractStartDate = contractStartDate.with(TemporalAdjusters.lastDayOfMonth());
+                //指定日期
+            else if (monthDayType > 0) {
+                int totalDays = contractStartDate.lengthOfMonth(); // 获取当月总天数
+                contractStartDate = monthDayType > totalDays ?
+                        LocalDate.of(contractStartDate.getYear(), contractStartDate.getMonth(), totalDays) :  // 超过总天数→最后一天
+                        LocalDate.of(contractStartDate.getYear(), contractStartDate.getMonth(), monthDayType);// 否则→当月第day天
+            }
+            //不修改
+        }
+
+
+        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) {
+
+            if (userMonthDay) {
+                Period period = new Period();
+                period.setPeriodBeginDate(periodBeginDate);
+                period.setPeriodEndDate(currLastDay);
+
+                //月占天数比例  整月个数
+                period.setRate(divideToBigDecimal(getDateRate(periodBeginDate, currLastDay, false), GetPeriodMonth(ruleCate)));
+
+                periodList.add(period);
+            } else {
+                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)) {
+                    if (userMonthDay) {
+                        rate = divideToBigDecimal(getDateRate(startDate, periodEndDate, false), GetPeriodMonth(ruleCate));
+                    } else {
+                        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;
+    }
+
+    /**
+     * 获取修改结束日期调整后的周期列表
+     * @param ruleCate 周期类型
+     * @param lastPeriodBeginDate 最后一个周期开始日期
+     * @param lastPeriodEndDate  最后一个周期的结束日期
+     * @param userMonthDay  是否使用当月实际天数用于计算每天费用
+     * @param newEndDate   新的结束日期  lastPeriodBeginDate<=  newEndDate
+     * @return
+     */
+    public static List<Period> getAdjustPeriodList(
+            String ruleCate,
+            LocalDate lastPeriodBeginDate,
+            LocalDate lastPeriodEndDate,
+            boolean userMonthDay,
+            LocalDate newEndDate
+    ) {
+
+        List<Period> periodList = new ArrayList<>();
+
+        if (lastPeriodEndDate.equals(newEndDate)) {
+            return null;//不需要生成新的周期,把原来周期中 lastPeriodEndDate 之后的删除就行
+        }
+
+
+        if (lastPeriodEndDate.isAfter(newEndDate)) {
+            //周期缩短
+
+            if (userMonthDay) {
+                Period period = new Period();
+                period.setPeriodBeginDate(lastPeriodBeginDate);
+                period.setPeriodEndDate(newEndDate);
+                period.setRate(divideToBigDecimal(getDateRate(lastPeriodBeginDate, newEndDate, false), GetPeriodMonth(ruleCate)));
+                periodList.add(period);
+
+            } else {
+                periodList.add(buildPeriod(lastPeriodBeginDate, lastPeriodEndDate, getPeriodDays(lastPeriodBeginDate, lastPeriodEndDate)));
+
+            }
+
+        } else {
+            //周期延长
+
+            //1、如果 lastPeriodBeginDate 到 lastPeriodEndDate  不是一个完整周期,要先修改成完整周期
+
+            LocalDate currPeriodEnd = getNextPeriodStart(lastPeriodBeginDate, ruleCate).minusDays(1);// 当前周期结束日
+
+            if (currPeriodEnd.isAfter(lastPeriodEndDate)) {
+                lastPeriodEndDate = newEndDate;
+                if (userMonthDay) {
+                    Period period = new Period();
+                    period.setPeriodBeginDate(lastPeriodBeginDate);
+                    period.setPeriodEndDate(currPeriodEnd);
+                    //月占天数比例  整月个数
+                    period.setRate(divideToBigDecimal(getDateRate(lastPeriodBeginDate, currPeriodEnd, false), GetPeriodMonth(ruleCate)));
+                    periodList.add(period);
+                } else {
+                    periodList.add(buildPeriod(lastPeriodBeginDate, lastPeriodEndDate, getPeriodDays(lastPeriodBeginDate, currPeriodEnd)));
+                }
+                return periodList;
+            } else {
+                LocalDate startDate = lastPeriodBeginDate;
+
+                if (startDate != null && !startDate.isAfter(newEndDate)) {
+                    while (!startDate.isAfter(newEndDate)) {
+
+                        LocalDate nextPeriodStart = getNextPeriodStart(startDate, ruleCate);
+                        LocalDate periodEnd = nextPeriodStart.minusDays(1); // 当前周期结束日
+
+                        // 若周期结束日超过服务结束日,截断并计算实际费率
+                        BigDecimal rate = BigDecimal.ONE;
+                        if (periodEnd.isAfter(newEndDate)) {
+                            if (userMonthDay) {
+                                rate = divideToBigDecimal(getDateRate(startDate, newEndDate, false), GetPeriodMonth(ruleCate));
+                            } else {
+                                int actualDays = getPeriodDays(startDate, newEndDate);
+                                int fullPeriodDays = getPeriodDays(startDate, periodEnd);
+                                rate = divideToBigDecimal(actualDays, fullPeriodDays);
+                            }
+                            periodEnd = newEndDate;
+                        }
+
+                        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);
+    }
+
+    public static BigDecimal divideToBigDecimal(Object numerator, Object denominator) {
+        // 转换分子为BigDecimal
+        BigDecimal num = toBigDecimal(numerator);
+        // 转换分母为BigDecimal
+        BigDecimal den = toBigDecimal(denominator);
+
+        // 检查分母是否为0
+        if (den.compareTo(BigDecimal.ZERO) == 0) {
+            throw new ArithmeticException("分母不能为零");
+        }
+        // 精确除法(保留10位小数,四舍五入)
+        return num.divide(den, 10, RoundingMode.HALF_UP);
+    }
+
+    /**
+     * 将Object转换为BigDecimal
+     * @param obj 待转换的对象(支持Number、String等可转换为数字的类型)
+     * @return 转换后的BigDecimal
+     * @throws IllegalArgumentException 若对象无法转换为BigDecimal
+     */
+    public static BigDecimal toBigDecimal(Object obj) {
+        if (obj == null) {
+            throw new IllegalArgumentException("对象不能为null");
+        }
+
+        if (obj instanceof BigDecimal) {
+            return (BigDecimal) obj;
+        } else if (obj instanceof Number) {
+            // 处理数字类型(Integer、Double、Long等)
+            return new BigDecimal(obj.toString());
+        } else if (obj instanceof String) {
+            // 处理字符串类型(需符合数字格式)
+            try {
+                return new BigDecimal((String) obj);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("字符串无法转换为BigDecimal: " + obj, e);
+            }
+        } else {
+            throw new IllegalArgumentException("不支持的类型转换: " + obj.getClass().getName());
+        }
+    }
+
+    // ------------------------------ 私有工具方法:周期起止月计算 ------------------------------
+
+    /**
+     * 获取季度结束月(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());
+        }
+    }
+
+    /**
+     * 根据不同的周期类型转换为对应的月数
+     * 此方法用于将给定的周期类型字符串转换为相应的月数
+     * 主要用于处理不同时间周期的转换,以便统一时间单位
+     *
+     * @param period 周期类型字符串,表示不同的周期类型
+     * @return 对应的月数如果输入的周期类型不匹配已知类型,则返回0
+     */
+    private static int GetPeriodMonth(String period) {
+        switch (period) {
+            case PERIOD_TYPE_MONTH:
+                // 当周期类型为月时,返回1个月
+                return 1;
+            case PERIOD_TYPE_QUARTER:
+                // 当周期类型为季度时,返回3个月
+                return 3;
+            case PERIOD_TYPE_HALFYEAR:
+                // 当周期类型为半年时,返回6个月
+                return 6;
+            case PERIOD_TYPE_YEAR:
+                // 当周期类型为年时,返回12个月
+                return 12;
+            default:
+                // 当周期类型不匹配已知类型时,返回0
+                return 0;
+        }
+    }
+
+    /**
+     * 获取季度起始月(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);
+    }
+
+
+    /**
+     * 计算日期区间的比例值
+     * @param start 起始日期
+     * @param end 结束日期
+     * @param use30DaysPerMonth 是否按每月30天计算(true=固定30天,false=实际天数)
+     * @return 比例值(起始月剩余比例 + 中间完整月数 + 结束月已过比例)
+     */
+    public static BigDecimal getDateRate(LocalDate start, LocalDate end, boolean use30DaysPerMonth) {
+        // 校验日期合法性:start不能晚于end
+        if (start.isAfter(end)) {
+            return BigDecimal.ZERO; // 或抛出异常
+        }
+
+        int startYear = start.getYear();
+        int startMonth = start.getMonthValue();
+        int endYear = end.getYear();
+        int endMonth = end.getMonthValue();
+
+        // 情况1:start和end在同一个月
+        if (startYear == endYear && startMonth == endMonth) {
+            int totalDaysInMonth = use30DaysPerMonth ? 30 : start.lengthOfMonth();
+            int daysBetween = end.getDayOfMonth() - start.getDayOfMonth() + 1; // 包含首尾
+            return BigDecimal.valueOf(daysBetween)
+                    .divide(BigDecimal.valueOf(totalDaysInMonth), 10, RoundingMode.HALF_UP);
+        }
+
+        // 情况2:start和end在不同月份
+        // 1. 起始月剩余比例:(当月剩余天数)÷ 当月总天数
+        int startMonthTotalDays = use30DaysPerMonth ? 30 : start.lengthOfMonth();
+        int startRemainingDays = startMonthTotalDays - start.getDayOfMonth() + 1; // 包含start当天
+        BigDecimal startRatio = BigDecimal.valueOf(startRemainingDays)
+                .divide(BigDecimal.valueOf(startMonthTotalDays), 10, RoundingMode.HALF_UP);
+
+        // 2. 中间完整月数:start的下一个月 到 end的上一个月之间的完整月份
+        int fullMonths = 0;
+        int currentYear = startYear;
+        int currentMonth = startMonth + 1;
+        if (currentMonth > 12) { // 处理跨年(如12月→次年1月)
+            currentMonth = 1;
+            currentYear++;
+        }
+        int endPrevYear = endYear;
+        int endPrevMonth = endMonth - 1;
+        if (endPrevMonth < 1) { // 处理跨年(如1月→上年12月)
+            endPrevMonth = 12;
+            endPrevYear--;
+        }
+        // 累加中间完整月数
+        while (currentYear < endPrevYear ||
+                (currentYear == endPrevYear && currentMonth <= endPrevMonth)) {
+            fullMonths++;
+            currentMonth++;
+            if (currentMonth > 12) {
+                currentMonth = 1;
+                currentYear++;
+            }
+        }
+
+        // 3. 结束月已过比例:(当月已过天数)÷ 当月总天数
+        int endMonthTotalDays = use30DaysPerMonth ? 30 : end.lengthOfMonth();
+        int endPassedDays = end.getDayOfMonth(); // 从1日到end当天的天数
+        BigDecimal endRatio = BigDecimal.valueOf(endPassedDays)
+                .divide(BigDecimal.valueOf(endMonthTotalDays), 10, RoundingMode.HALF_UP);
+
+        // 总和:起始比例 + 完整月数 + 结束比例
+        return startRatio.add(BigDecimal.valueOf(fullMonths)).add(endRatio);
+    }
+
+    // ------------------------------ 周期模型:公开静态内部类(可外部访问) ------------------------------
+
+    /**
+     * 周期模型:存储周期起止日期、费率、周期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);
+        }
+    }
+}