Ver código fonte

Merge branch 'master' of http://210.51.45.41:3000/itcat_admin/aws_donenow

HULEI 1 mês atrás
pai
commit
56972110bb

+ 170 - 15
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/PeriodCalculationUtil.java

@@ -1,5 +1,7 @@
 package com.awspaas.user.apps.donenow_ctt;
 
+import com.actionsoft.sdk.local.SDK;
+import com.actionsoft.sdk.local.api.Logger;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
@@ -11,12 +13,14 @@ import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * 周期计算工具类
  * 支持:月/季/半年/年周期、自然月/实际月模式、周期列表生成与费率计算
  */
 public class PeriodCalculationUtil {
+    private static final Logger LOGGERU = SDK.getLogAPI().getLogger(PeriodCalculationUtil.class);//记录日志
     // 周期类型常量(与原IVTConstant对齐,可根据实际常量类调整)
     public static final String PERIOD_TYPE_ONCE = "609";       // 一次性收费
     public static final String PERIOD_TYPE_MONTH = "610";     // 按月支付
@@ -36,6 +40,110 @@ public class PeriodCalculationUtil {
     }
 
 
+    /**
+     * CALC_METHOD_1STPERIOD/PURCHASE_CALC_METHOD_1STPERIOD
+     * 610	按月支付	4601	自然月 isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
+     * 610	按月支付	4602	开通日期后的完整1个月  isNaturalMonth=false userMonthDay=true   monthDayType=0 startMonth=0
+     * 611	按季度支付	4596	开通日期所在月+后续2个自然月 isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
+     *
+     * 611	按季度支付	4597	开通日期所在月 isNaturalMonth=true userMonthDay=true  monthDayType=-2 startMonth
+     *
+     * 611	按季度支付	4598	开通日期后的完整一个季度 isNaturalMonth=false userMonthDay=true  monthDayType=0 startMonth=0
+     * 611	按季度支付	4599	首季度根据固定季度计算 isNaturalMonth=true userMonthDay=true  monthDayType=-3  startMonth=0
+     *
+     *
+     * 612	按半年支付	4593	开通日期所在月+后续5个自然月   isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
+     * 612	按半年支付	4594	开通日期所在月  isNaturalMonth=true userMonthDay=true  monthDayType=-2 startMonth=0
+     * 612	按半年支付	4595	开通日期后的完整半年 isNaturalMonth=false userMonthDay=true monthDayType=0 startMonth=0
+     *
+     * 613	按年支付	4590	开通日期所在月+后续11个自然月  isNaturalMonth=true  CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true monthDayType=-1 startMonth=0
+     * 613	按年支付	4591	开通日期所在月  isNaturalMonth=true CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true  monthDayType=-2 startMonth=0
+     * 613	按年支付	4592	开通日期后的完整一年 isNaturalMonth=false CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true monthDayType=0 startMonth=0
+     *
+     *
+     *CALC_METHOD_1STYEAR
+     * 4637	(拆机时实际计费天数/当年天数(如365))*年费用
+     * 4638	先算月再算天
+     *
+     *
+     * MONTH_1STQUARTER
+     * 4642	1月 startMonth=1
+     * 4643	11月 startMonth=11
+     * 4644	12月 startMonth=12
+     */
+
+    /**
+     * 获取周期列表通过配置
+     * @param ruleCate 周期类型
+     * @param contractStartDate 计费开始日期
+     * @param periodBeginDate 周期开始日期
+     * @param periodEndDate 周期结束日期
+     * @param CALC_METHOD_1STPERIOD
+     * @param CALC_METHOD_1STYEAR
+     * @param MONTH_1STQUARTER
+     * @return
+     */
+    public static List<Period> getPeriodListByConfig(String ruleCate, LocalDate contractStartDate, LocalDate periodBeginDate, LocalDate periodEndDate, String CALC_METHOD_1STPERIOD, String CALC_METHOD_1STYEAR, String MONTH_1STQUARTER) {
+        // 初始化默认值
+        boolean isNaturalMonth = true;
+        boolean userMonthDay = true;
+        int monthDayType = 0;
+        int startMonth = 0;
+
+        //611	按季度支付	4599	首季度根据固定季度计算
+        if (CALC_METHOD_1STPERIOD.equals("4599")) {
+            switch (MONTH_1STQUARTER) {
+                case "4642":
+                    startMonth = 1;
+                    break;
+                case "4643":
+                    startMonth = 11;
+                    break;
+                case "4644":
+                    startMonth = 12;
+                    break;
+                default:
+                    startMonth = 0;
+                    break;
+            }
+        }
+
+        if (ruleCate.equals("613")) {
+            userMonthDay = !"4637".equals(CALC_METHOD_1STYEAR);
+        }
+
+        switch (CALC_METHOD_1STPERIOD) {
+            // 合并:isNaturalMonth=true, userMonthDay=true, monthDayType=-1 的情况
+            case "4601":
+            case "4596":
+            case "4593":
+            case "4590":
+                monthDayType = -1;
+                break;
+
+            // 合并:isNaturalMonth=true, userMonthDay=true, monthDayType=-2 的情况
+            case "4597":
+            case "4594":
+            case "4591":
+                monthDayType = -2;
+                break;
+
+            // 合并:isNaturalMonth=false, userMonthDay=true, monthDayType=0 的情况
+            case "4602":
+            case "4598":
+            case "4595":
+            case "4599":
+            case "4592":
+                isNaturalMonth = false;
+                monthDayType = 0;
+                break;
+        }
+
+        return getPeriodList(ruleCate, contractStartDate, periodBeginDate, periodEndDate,
+                isNaturalMonth, userMonthDay, monthDayType, startMonth);
+    }
+
+
     /**
      * 核心方法:生成周期列表(含费率)
      *
@@ -46,6 +154,7 @@ public class PeriodCalculationUtil {
      * @param isNaturalMonth    是否自然月模式(true=自然月,false=实际月)
      * @param userMonthDay       是否使用当月实际天数用于计算每天费用
      * @param monthDayType  monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
+     * @param startMonth  指定开始月份
      * @return 周期列表(含起止日期、费率)
      * @throws IllegalArgumentException 当参数非法时抛出(如日期null、周期类型无效)
      */
@@ -56,11 +165,29 @@ public class PeriodCalculationUtil {
             LocalDate periodEndDate,
             boolean isNaturalMonth,
             boolean userMonthDay,
-            int monthDayType
+            int monthDayType,
+            int startMonth
     ) {
+        LOGGERU.info("【周期计算】开始生成周期列表");
+
+        LOGGERU.info("【周期计算】参数:ruleCate=" + ruleCate + ", contractStartDate=" + contractStartDate + ", periodBeginDate=" + periodBeginDate + ", periodEndDate=" + periodEndDate + ", isNaturalMonth=" + isNaturalMonth + ", userMonthDay=" + userMonthDay + ", monthDayType=" + monthDayType + ",startMonth=" + startMonth);
+
+        LOGGERU.info("周期开始日期--计算前:" + contractStartDate);
+
         // 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;
+        }
+
         //实际月
         if (isNaturalMonth == false && (contractStartDate.getDayOfMonth() != 1)) {
             // monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
@@ -80,18 +207,13 @@ public class PeriodCalculationUtil {
             //不修改
         }
 
-
-        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;
+        //指定开始月份  11、12、1
+        if (startMonth > 0) {
+            contractStartDate = getClosestPeriodStartDate(contractStartDate, startMonth);
         }
 
+        LOGGERU.info("周期开始日期--计算后:" + contractStartDate);
+
         // 3. 非一次性收费:按周期类型计算(月/季/半年/年)
         LocalDate startDate = null;
         LocalDate currLastDay = null; // 当前周期结束日
@@ -200,6 +322,39 @@ public class PeriodCalculationUtil {
         return periodList;
     }
 
+    /**
+     * 计算最接近的周期开始日期(支持1-12月所有起始月份)
+     * 每个起始月份对应4个周期,每个周期包含3个连续月份
+     * @param contractStartDate 合同开始日期
+     * @param startMonth 指定的起始月份(1-12)
+     * @return 对应的周期开始日期(每月1日)
+     */
+    public static LocalDate getClosestPeriodStartDate(LocalDate contractStartDate, int startMonth) {
+        Objects.requireNonNull(contractStartDate, "合同开始日期不能为空");
+        if (startMonth < 1 || startMonth > 12) {
+            throw new IllegalArgumentException("起始月份必须在1-12之间");
+        }
+
+        // 计算合同日期的总月数(从1970年1月1日开始的月数)
+        long totalMonths = (contractStartDate.getYear() - 1970) * 12L + (contractStartDate.getMonthValue() - 1);
+        // 起始月份的基准月(0-based)
+        long baseMonth = startMonth - 1;
+
+        long periodIndex = (totalMonths - baseMonth) / 3;
+        if ((totalMonths - baseMonth) < 0 && (totalMonths - baseMonth) % 3 != 0) {
+            periodIndex--;
+        }
+
+        // 计算周期起始的总月数
+        long periodStartTotal = baseMonth + 3 * periodIndex;
+
+        // 转换为年月
+        int year = 1970 + (int) (periodStartTotal / 12);
+        int month = (int) (periodStartTotal % 12) + 1;
+
+        return LocalDate.of(year, month, 1);
+    }
+
     /**
      * 获取修改结束日期调整后的周期列表
      * @param ruleCate 周期类型
@@ -641,16 +796,16 @@ public class PeriodCalculationUtil {
      */
     private static int GetPeriodMonth(String period) {
         switch (period) {
-            case cttConstant.PERIOD_TYPE_MONTH:
+            case PERIOD_TYPE_MONTH:
                 // 当周期类型为月时,返回1个月
                 return 1;
-            case cttConstant.PERIOD_TYPE_QUARTER:
+            case PERIOD_TYPE_QUARTER:
                 // 当周期类型为季度时,返回3个月
                 return 3;
-            case cttConstant.PERIOD_TYPE_HALFYEAR:
+            case PERIOD_TYPE_HALFYEAR:
                 // 当周期类型为半年时,返回6个月
                 return 6;
-            case cttConstant.PERIOD_TYPE_YEAR:
+            case PERIOD_TYPE_YEAR:
                 // 当周期类型为年时,返回12个月
                 return 12;
             default:

+ 1 - 2
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/event/commissionFormAfterSave.java

@@ -128,9 +128,8 @@ public class commissionFormAfterSave extends ExecuteListener {
             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);
+            List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, PERIOD_BEGIN_DATE, PERIOD_END_DATE, true, true, -1,0);
 
             if (periodList.size() > 0) {
                 Connection conn = null;

+ 83 - 10
com.awspaas.user.apps.donenow_ctt/src/com/awspaas/user/apps/donenow_ctt/service/contractService.java

@@ -1141,12 +1141,17 @@ public class contractService {
         }
 
         //获取服务周期
-        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(PERIOD_TYPE, START_DATE, EFFECTIVE_DATE, END_DATE, true, true, -1);
+        //List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodList(PERIOD_TYPE, START_DATE, EFFECTIVE_DATE, END_DATE, true, true, -1);
+
+        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodListByConfig(PERIOD_TYPE, START_DATE, EFFECTIVE_DATE, END_DATE, service.getString("CALC_METHOD_1STPERIOD"), service.getString("CALC_METHOD_1STYEAR"), service.getString("MONTH_1STQUARTER"));
+
 
         String PERIOD = periodList.get(0).getPeriodBeginDateStr() + "~" + periodList.get(periodList.size() - 1).getPeriodEndDateStr();
 
+        int i = 0;
         for (PeriodCalculationUtil.Period period : periodList) {
 
+            i++;
             BO csp = new BO();//服务周期
             csp.set("CONTRACT_ID", contract.get("ID"));
             csp.set("OBJECT_ID", service.get("OBJECT_ID"));
@@ -1158,9 +1163,36 @@ public class contractService {
 
             csp.set("QUANTITY", service.get("QUANTITY"));
 
-            csp.set("PERIOD_PRICE", period.getRate().multiply(SERVICE_TOTAL_PRICE));
+            //首月总价需要单独计算   613	按年支付	4591	开通日期所在月
+            if (i == 1 && service.get("CALC_METHOD_1STPERIOD").equals("4591")) {
+                //firstRate<1
+                if (period.getRate().compareTo(BigDecimal.ONE) < 0) {
+
+                    //首月成本需要单独计算
+                    BigDecimal UNIT_PRICE_1STMONTH = toBigDecimal(service.get("UNIT_PRICE_1STMONTH"));
+                    UNIT_PRICE_1STMONTH = period.getRate().multiply(BigDecimal.valueOf(12)).multiply(UNIT_PRICE_1STMONTH);
+                    csp.set("PERIOD_PRICE", UNIT_PRICE_1STMONTH);
+
+                    //首期成本怎么算?  在同一个月 和不在 同一个月
+                    LocalDate PURCHASE_START_DATE = service.get("PURCHASE_START_DATE", LocalDate.class);
+
+                    if (EFFECTIVE_DATE.format(DateTimeFormatter.ofPattern("yyyyMM")).equals(PURCHASE_START_DATE.format(DateTimeFormatter.ofPattern("yyyyMM")))) {
+                        BigDecimal UNIT_COST_1STMONTH = toBigDecimal(service.get("UNIT_COST_1STMONTH"));
+                        UNIT_COST_1STMONTH = period.getRate().multiply(BigDecimal.valueOf(12)).multiply(UNIT_COST_1STMONTH);
+                        csp.set("PERIOD_COST", period.getRate().multiply(UNIT_COST_1STMONTH));
+                    } else {
+                        csp.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+                    }
+                } else {
+                    csp.set("PERIOD_PRICE", period.getRate().multiply(SERVICE_TOTAL_PRICE));
+                    csp.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+                }
+
+            } else {
+                csp.set("PERIOD_PRICE", period.getRate().multiply(SERVICE_TOTAL_PRICE));
 
-            csp.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+                csp.set("PERIOD_COST", period.getRate().multiply(SERVICE_TOTAL_COST));
+            }
 
             csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));
 
@@ -1806,9 +1838,10 @@ public class contractService {
         List<BO> serviceList2 = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE").connection(conn).addQuery("BINDID !=", contract.get("BINDID")).addQuery("CONTRACT_ID =", contract.get("ID")).list();
 
         if (serviceList == null) serviceList = new ArrayList<BO>();
-        if (serviceList2 != null && serviceList2.size() > 0) for (BO service : serviceList2) {
-            serviceList.add(service);
-        }
+        if (serviceList2 != null && serviceList2.size() > 0)
+            for (BO service : serviceList2) {
+                serviceList.add(service);
+            }
 
         for (BO service : serviceList) {
 
@@ -1860,10 +1893,14 @@ public class contractService {
                         String PURCHASE_START_DATE = service.getString("PURCHASE_START_DATE");
                         if (StringUtils.isBlank(PURCHASE_START_DATE))
                             PURCHASE_START_DATE = contract.getString("START_DATE");
+
                         contractCost.put("PURCHASE_START_DATE", PURCHASE_START_DATE);//采购开始日期
                         contractCost.put("CONTRACT_START_DATE", contract.getString("START_DATE"));
                         contractCost.put("CONTRACT_END_DATE", contract.getString("END_DATE"));//采购结束日期
 
+                        contractCost.put("PURCHASE_CALC_METHOD_1STPERIOD", service.getString("PURCHASE_CALC_METHOD_1STPERIOD"));
+                        contractCost.put("CALC_METHOD_1STYEAR", service.getString("CALC_METHOD_1STYEAR"));
+                        contractCost.put("MONTH_1STQUARTER", service.getString("MONTH_1STQUARTER"));
 
                         PRODUCT_ID = AddContractServiceProduct(uc, conn, contractCost);
                     } else {
@@ -1897,10 +1934,15 @@ public class contractService {
                     String PURCHASE_START_DATE = service.getString("PURCHASE_START_DATE");
                     if (StringUtils.isBlank(PURCHASE_START_DATE))
                         PURCHASE_START_DATE = contract.getString("START_DATE");
+                    contractCost.put("PURCHASE_START_DATE", PURCHASE_START_DATE);//采购开始日期
 
                     contractCost.put("CONTRACT_START_DATE", contract.getString("START_DATE"));
                     contractCost.put("CONTRACT_END_DATE", contract.getString("END_DATE"));//采购结束日期
 
+                    contractCost.put("PURCHASE_CALC_METHOD_1STPERIOD", service.getString("PURCHASE_CALC_METHOD_1STPERIOD"));
+                    contractCost.put("CALC_METHOD_1STYEAR", service.getString("CALC_METHOD_1STYEAR"));
+                    contractCost.put("MONTH_1STQUARTER", service.getString("MONTH_1STQUARTER"));
+
                     PRODUCT_ID = AddContractServiceProduct(uc, conn, contractCost);
                 } else {
                     //是否 删除 contractCostId
@@ -1990,11 +2032,42 @@ 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"), true, -1);
+        // 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);
+
+        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodListByConfig(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_CALC_METHOD_1STPERIOD"), service.get("CALC_METHOD_1STYEAR"), service.get("MONTH_1STQUARTER"));
+
+
+        //首月成本单独计算  613	按年支付	4591	开通日期所在月
+        if (service.get("PURCHASE_CALC_METHOD_1STPERIOD").equals("4591")) {
+            BigDecimal firstRate = periodList.get(0).getRate();
+            //firstRate<1
+            if (firstRate.compareTo(BigDecimal.ONE) < 0) {
+
+                //首月成本需要单独计算
+                BigDecimal UNIT_COST_1STMONTH = toBigDecimal(service.get("UNIT_COST_1STMONTH"));
+
+                UNIT_COST_1STMONTH = firstRate.multiply(BigDecimal.valueOf(12)).multiply(UNIT_COST_1STMONTH);
+
+                BigDecimal rate = periodList.stream().skip(1).map(period -> period.getRate()).reduce(BigDecimal.ZERO, BigDecimal::add);
+                EXTENDED_COST = EXTENDED_COST.multiply(rate);
+
+                EXTENDED_COST = EXTENDED_COST.add(UNIT_COST_1STMONTH);
+
+                contractCost.set("EXTENDED_COST", EXTENDED_COST);
+
+
+            } else {
+                BigDecimal rate = periodList.stream().skip(1).map(period -> period.getRate()).reduce(BigDecimal.ZERO, BigDecimal::add);
+                EXTENDED_COST = EXTENDED_COST.multiply(rate);
+                contractCost.set("EXTENDED_COST", EXTENDED_COST);
+            }
+
+        } else {
+            BigDecimal rate = periodList.stream().map(period -> period.getRate()).reduce(BigDecimal.ZERO, BigDecimal::add);
+            EXTENDED_COST = EXTENDED_COST.multiply(rate);
+            contractCost.set("EXTENDED_COST", EXTENDED_COST);
+        }
 
-        BigDecimal rate = periodList.stream().map(period -> period.getRate()).reduce(BigDecimal.ZERO, BigDecimal::add);
-        EXTENDED_COST = EXTENDED_COST.multiply(rate);
-        contractCost.set("EXTENDED_COST", EXTENDED_COST);
 
         //单元成本
         contractCost.set("UNIT_COST", divideToBigDecimal(contractCost.get("EXTENDED_COST"), contractCost.get("QUANTITY")));

+ 50 - 7
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/service/PaymentPlanService.java

@@ -83,7 +83,7 @@ public class PaymentPlanService {
                     }
 
                     String RULE_CATE = service.getString("PURCHASE_PERIOD_TYPE");//采购周期
-                    LocalDate PERIOD_BEGIN_DATE = service.get("EFFECTIVE_DATE", LocalDate.class);
+                    LocalDate PERIOD_BEGIN_DATE = service.get("PURCHASE_START_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")}).substring(0, 10), LocalDate.class);
 
@@ -97,8 +97,9 @@ public class PaymentPlanService {
                     //  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);
+                    // List<PeriodCalculationUtil.Period> periods = PeriodCalculationUtil.getPeriodList(RULE_CATE, PERIOD_BEGIN_DATE, START_DATE, PERIOD_END_DATE, true, true, -1);
 
+                    List<PeriodCalculationUtil.Period> periods = PeriodCalculationUtil.getPeriodListByConfig(RULE_CATE, PERIOD_BEGIN_DATE, START_DATE, PERIOD_END_DATE, service.getString("PURCHASE_CALC_METHOD_1STPERIOD"), service.getString("CALC_METHOD_1STYEAR"), service.getString("MONTH_1STQUARTER"));
 
                     if (periods.isEmpty())
                         continue;
@@ -111,6 +112,7 @@ public class PaymentPlanService {
                         continue;
 
                     BigDecimal UNIT_COST = COST_TOTAL.divide(RATE_TOTAL, 10, RoundingMode.HALF_UP);
+                    int i=0;
                     for (PeriodCalculationUtil.Period period : periods) {
                         total++;
                         ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_5cb4ae4a42944fd0a9a284ff4c64c65d", uc.getUID(), "付款计划");
@@ -119,7 +121,21 @@ public class PaymentPlanService {
                         paymentPlan.setBindId(processInstance.getId());
                         paymentPlan.set("ORDER_ID", orderId);
                         paymentPlan.set("PLAN_DATE", period.getPeriodBeginDateStr());
-                        paymentPlan.set("PLAN_AMOUNT", UNIT_COST.multiply(period.getRate()));
+
+                        //首月总价需要单独计算   613	按年支付	4591	开通日期所在月
+                        if (i == 1 && service.get("PURCHASE_CALC_METHOD_1STPERIOD").equals("4591")) {
+                            //firstRate<1
+                            if (period.getRate().compareTo(BigDecimal.ONE) < 0) {
+                                BigDecimal UNIT_COST_1STMONTH = toBigDecimal(service.get("UNIT_COST_1STMONTH"));
+                                UNIT_COST_1STMONTH = period.getRate().multiply(BigDecimal.valueOf(12)).multiply(UNIT_COST_1STMONTH);
+                                paymentPlan.set("PLAN_AMOUNT",  period.getRate().multiply(UNIT_COST_1STMONTH));
+                            } else {
+                                paymentPlan.set("PLAN_AMOUNT", UNIT_COST.multiply(period.getRate()));
+                            }
+                        }else{
+                            paymentPlan.set("PLAN_AMOUNT", UNIT_COST.multiply(period.getRate()));
+                        }
+
                         paymentPlan.set("REMAIN_AMOUNT", paymentPlan.get("PLAN_AMOUNT"));
                         paymentPlan.set("CONTRACT_COST_ID", orderProduct.getString("CONTRACT_COST_ID"));
                         paymentPlan.set("CONTRACT_SERVICE_ID", service.getId());
@@ -149,15 +165,13 @@ public class PaymentPlanService {
             String PLAN_DATE = null;
             if (purchaseOrder.get("EXPECTED_SHIP_DATE") != null)
                 PLAN_DATE = purchaseOrder.get("EXPECTED_SHIP_DATE").toString().substring(0, 10);
-            else
-                PLAN_DATE = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+            else PLAN_DATE = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
 
             BigDecimal PLAN_AMOUNT = orderProducts.stream().map(orderProduct -> orderProduct.get("COST_TOTAL", BigDecimal.class)).reduce(BigDecimal.ZERO, BigDecimal::add);
 
             String PAY_DESC = orderProducts.stream().map(orderProduct -> orderProduct.getString("NAME")).collect(Collectors.joining(","));
 
-            if (StringUtils.isNotBlank(PAY_DESC) && PAY_DESC.length() > 300)
-                PAY_DESC = PAY_DESC.substring(0, 300);
+            if (StringUtils.isNotBlank(PAY_DESC) && PAY_DESC.length() > 300) PAY_DESC = PAY_DESC.substring(0, 300);
 
             BO paymentPlan = new BO();
             paymentPlan.setBindId(processInstance.getId());
@@ -537,6 +551,35 @@ public class PaymentPlanService {
         return num.divide(den, 10, RoundingMode.HALF_UP); // 保留 10 位小数
     }
 
+
+    /**
+     * 将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());
+        }
+    }
+
     /**
      * 周期
      */

+ 167 - 11
com.awspaas.user.apps.donenow_ivt/src/com/awspaas/user/apps/donenow_ivt/utils/PeriodCalculationUtil.java

@@ -1,5 +1,8 @@
 package com.awspaas.user.apps.donenow_ivt.utils;
 
+
+import com.actionsoft.sdk.local.SDK;
+import com.actionsoft.sdk.local.api.Logger;
 import org.apache.commons.lang3.StringUtils;
 
 import java.math.BigDecimal;
@@ -11,12 +14,14 @@ import java.time.temporal.ChronoUnit;
 import java.time.temporal.TemporalAdjusters;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * 周期计算工具类
  * 支持:月/季/半年/年周期、自然月/实际月模式、周期列表生成与费率计算
  */
 public class PeriodCalculationUtil {
+    private static final Logger LOGGERU = SDK.getLogAPI().getLogger(PeriodCalculationUtil.class);//记录日志
     // 周期类型常量(与原IVTConstant对齐,可根据实际常量类调整)
     public static final String PERIOD_TYPE_ONCE = "609";       // 一次性收费
     public static final String PERIOD_TYPE_MONTH = "610";     // 按月支付
@@ -36,6 +41,110 @@ public class PeriodCalculationUtil {
     }
 
 
+    /**
+     * CALC_METHOD_1STPERIOD/PURCHASE_CALC_METHOD_1STPERIOD
+     * 610	按月支付	4601	自然月 isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
+     * 610	按月支付	4602	开通日期后的完整1个月  isNaturalMonth=false userMonthDay=true   monthDayType=0 startMonth=0
+     * 611	按季度支付	4596	开通日期所在月+后续2个自然月 isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
+     *
+     * 611	按季度支付	4597	开通日期所在月 isNaturalMonth=true userMonthDay=true  monthDayType=-2 startMonth
+     *
+     * 611	按季度支付	4598	开通日期后的完整一个季度 isNaturalMonth=false userMonthDay=true  monthDayType=0 startMonth=0
+     * 611	按季度支付	4599	首季度根据固定季度计算 isNaturalMonth=true userMonthDay=true  monthDayType=-3  startMonth=0
+     *
+     *
+     * 612	按半年支付	4593	开通日期所在月+后续5个自然月   isNaturalMonth=true userMonthDay=true  monthDayType=-1 startMonth=0
+     * 612	按半年支付	4594	开通日期所在月  isNaturalMonth=true userMonthDay=true  monthDayType=-2 startMonth=0
+     * 612	按半年支付	4595	开通日期后的完整半年 isNaturalMonth=false userMonthDay=true monthDayType=0 startMonth=0
+     *
+     * 613	按年支付	4590	开通日期所在月+后续11个自然月  isNaturalMonth=true  CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true monthDayType=-1 startMonth=0
+     * 613	按年支付	4591	开通日期所在月  isNaturalMonth=true CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true  monthDayType=-2 startMonth=0
+     * 613	按年支付	4592	开通日期后的完整一年 isNaturalMonth=false CALC_METHOD_1STYEAR=4637 userMonthDay=false 或者 CALC_METHOD_1STYEAR=4638 userMonthDay=true monthDayType=0 startMonth=0
+     *
+     *
+     *CALC_METHOD_1STYEAR
+     * 4637	(拆机时实际计费天数/当年天数(如365))*年费用
+     * 4638	先算月再算天
+     *
+     *
+     * MONTH_1STQUARTER
+     * 4642	1月 startMonth=1
+     * 4643	11月 startMonth=11
+     * 4644	12月 startMonth=12
+     */
+
+    /**
+     * 获取周期列表通过配置
+     * @param ruleCate 周期类型
+     * @param contractStartDate 计费开始日期
+     * @param periodBeginDate 周期开始日期
+     * @param periodEndDate 周期结束日期
+     * @param CALC_METHOD_1STPERIOD
+     * @param CALC_METHOD_1STYEAR
+     * @param MONTH_1STQUARTER
+     * @return
+     */
+    public static List<Period> getPeriodListByConfig(String ruleCate, LocalDate contractStartDate, LocalDate periodBeginDate, LocalDate periodEndDate, String CALC_METHOD_1STPERIOD, String CALC_METHOD_1STYEAR, String MONTH_1STQUARTER) {
+        // 初始化默认值
+        boolean isNaturalMonth = true;
+        boolean userMonthDay = true;
+        int monthDayType = 0;
+        int startMonth = 0;
+
+        //611	按季度支付	4599	首季度根据固定季度计算
+        if (CALC_METHOD_1STPERIOD.equals("4599")) {
+            switch (MONTH_1STQUARTER) {
+                case "4642":
+                    startMonth = 1;
+                    break;
+                case "4643":
+                    startMonth = 11;
+                    break;
+                case "4644":
+                    startMonth = 12;
+                    break;
+                default:
+                    startMonth = 0;
+                    break;
+            }
+        }
+
+        if (ruleCate.equals("613")) {
+            userMonthDay = !"4637".equals(CALC_METHOD_1STYEAR);
+        }
+
+        switch (CALC_METHOD_1STPERIOD) {
+            // 合并:isNaturalMonth=true, userMonthDay=true, monthDayType=-1 的情况
+            case "4601":
+            case "4596":
+            case "4593":
+            case "4590":
+                monthDayType = -1;
+                break;
+
+            // 合并:isNaturalMonth=true, userMonthDay=true, monthDayType=-2 的情况
+            case "4597":
+            case "4594":
+            case "4591":
+                monthDayType = -2;
+                break;
+
+            // 合并:isNaturalMonth=false, userMonthDay=true, monthDayType=0 的情况
+            case "4602":
+            case "4598":
+            case "4595":
+            case "4599":
+            case "4592":
+                isNaturalMonth = false;
+                monthDayType = 0;
+                break;
+        }
+
+        return getPeriodList(ruleCate, contractStartDate, periodBeginDate, periodEndDate,
+                isNaturalMonth, userMonthDay, monthDayType, startMonth);
+    }
+
+
     /**
      * 核心方法:生成周期列表(含费率)
      *
@@ -46,6 +155,7 @@ public class PeriodCalculationUtil {
      * @param isNaturalMonth    是否自然月模式(true=自然月,false=实际月)
      * @param userMonthDay       是否使用当月实际天数用于计算每天费用
      * @param monthDayType  monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
+     * @param startMonth  指定开始月份
      * @return 周期列表(含起止日期、费率)
      * @throws IllegalArgumentException 当参数非法时抛出(如日期null、周期类型无效)
      */
@@ -56,11 +166,29 @@ public class PeriodCalculationUtil {
             LocalDate periodEndDate,
             boolean isNaturalMonth,
             boolean userMonthDay,
-            int monthDayType
+            int monthDayType,
+            int startMonth
     ) {
+        LOGGERU.info("【周期计算】开始生成周期列表");
+
+        LOGGERU.info("【周期计算】参数:ruleCate=" + ruleCate + ", contractStartDate=" + contractStartDate + ", periodBeginDate=" + periodBeginDate + ", periodEndDate=" + periodEndDate + ", isNaturalMonth=" + isNaturalMonth + ", userMonthDay=" + userMonthDay + ", monthDayType=" + monthDayType + ",startMonth=" + startMonth);
+
+        LOGGERU.info("周期开始日期--计算前:" + contractStartDate);
+
         // 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;
+        }
+
         //实际月
         if (isNaturalMonth == false && (contractStartDate.getDayOfMonth() != 1)) {
             // monthDayType  -1 本月1号  -2 下月1号 0 不修改  大于 则是 contractStartDate 所在月的monthDayType 天,大于这个天数则使用这个天数
@@ -80,18 +208,13 @@ public class PeriodCalculationUtil {
             //不修改
         }
 
-
-        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;
+        //指定开始月份  11、12、1
+        if (startMonth > 0) {
+            contractStartDate = getClosestPeriodStartDate(contractStartDate, startMonth);
         }
 
+        LOGGERU.info("周期开始日期--计算后:" + contractStartDate);
+
         // 3. 非一次性收费:按周期类型计算(月/季/半年/年)
         LocalDate startDate = null;
         LocalDate currLastDay = null; // 当前周期结束日
@@ -200,6 +323,39 @@ public class PeriodCalculationUtil {
         return periodList;
     }
 
+    /**
+     * 计算最接近的周期开始日期(支持1-12月所有起始月份)
+     * 每个起始月份对应4个周期,每个周期包含3个连续月份
+     * @param contractStartDate 合同开始日期
+     * @param startMonth 指定的起始月份(1-12)
+     * @return 对应的周期开始日期(每月1日)
+     */
+    public static LocalDate getClosestPeriodStartDate(LocalDate contractStartDate, int startMonth) {
+        Objects.requireNonNull(contractStartDate, "合同开始日期不能为空");
+        if (startMonth < 1 || startMonth > 12) {
+            throw new IllegalArgumentException("起始月份必须在1-12之间");
+        }
+
+        // 计算合同日期的总月数(从1970年1月1日开始的月数)
+        long totalMonths = (contractStartDate.getYear() - 1970) * 12L + (contractStartDate.getMonthValue() - 1);
+        // 起始月份的基准月(0-based)
+        long baseMonth = startMonth - 1;
+
+        long periodIndex = (totalMonths - baseMonth) / 3;
+        if ((totalMonths - baseMonth) < 0 && (totalMonths - baseMonth) % 3 != 0) {
+            periodIndex--;
+        }
+
+        // 计算周期起始的总月数
+        long periodStartTotal = baseMonth + 3 * periodIndex;
+
+        // 转换为年月
+        int year = 1970 + (int) (periodStartTotal / 12);
+        int month = (int) (periodStartTotal % 12) + 1;
+
+        return LocalDate.of(year, month, 1);
+    }
+
     /**
      * 获取修改结束日期调整后的周期列表
      * @param ruleCate 周期类型