package com.awspaas.user.apps.donenow_ctt.service;

import com.actionsoft.bpms.bo.engine.BO;
import com.actionsoft.bpms.bpmn.engine.model.run.delegate.ProcessInstance;
import com.actionsoft.bpms.commons.database.RowMap;
import com.actionsoft.bpms.server.UserContext;
import com.actionsoft.bpms.util.DBSql;
import com.actionsoft.sdk.local.SDK;
import com.actionsoft.sdk.local.api.Logger;
import com.awspaas.user.apps.donenow_ctt.PeriodCalculationUtil;
import com.awspaas.user.apps.donenow_ctt.cttConstant;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

import static com.awspaas.user.apps.donenow_ctt.service.CityAbbreviationUtil.getCityAbbreviation;


/**
 * 合同服务
 */
public class contractService {

    private static final Logger contractServiceLogger = SDK.getLogAPI().getLogger(contractService.class);//记录日志
    private final String[] excludesArray = new String[]{"ID", "ORGID", "BINDID", "CREATEDATE", "CREATEUSER", "UPDATEDATE", "UPDATEUSER", "PROCESSDEFID", "ISEND"};
    private static volatile contractService instance;

    private contractService() {
        // Constructor logic here, if any.
    }

    public static contractService getInstance() {
        if (instance == null) {
            synchronized (contractService.class) {
                if (instance == null) {
                    instance = new contractService();
                }
            }
        }
        return instance;
    }

    /**
     * 合同提交后，进行数据处理
     * @param uc
     * @param bindid
     * @param isSubmit
     * @return
     */
    public Map<String, Object> SaveContract(UserContext uc, String bindid, boolean isSubmit) {
        contractServiceLogger.info("合同提交后");
        Map<String, Object> resultDto = new HashMap<>();
        try {
            boolean idApply = true;//申请还是编辑
            BO contractBO = SDK.getBOAPI().getByProcess("BO_EU_DNCTT_CONTRACT_APPLY", bindid);//获取合同表单数据
            if (contractBO == null) {
                idApply = false;
                contractBO = SDK.getBOAPI().getByProcess("BO_EU_DNCTT_CONTRACT", bindid);//获取合同表单数据
            }
            if (contractBO.getString("IS_SUBMIT").equals("1"))//已提交
                isSubmit = true;
            String TYPE_ID = contractBO.getString("TYPE_ID");//合同类型
            if (isSubmit) contractBO.set("IS_SUBMIT", 1);
            else contractBO.set("IS_SUBMIT", 0);

            if (TYPE_ID.equals(cttConstant.CONTRACT_TYPE_SERVICE)) // 如果是服务合同
            {
                DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_SERVICE SET OLD_QUANTITY = QUANTITY, OLD_UNIT_PRICE = UNIT_PRICE, OLD_TOTAL_PRICE = TOTAL_PRICE, OLD_UNIT_COST=UNIT_COST, OLD_TOTAL_COST =TOTAL_COST WHERE BINDID=?", new Object[]{bindid});

                String PERIOD_TYPE = contractBO.getString("PERIOD_TYPE");//合同周期类型
                Integer OCCURRENCES = contractBO.get("OCCURRENCES", Integer.class);
                Date START_DATE = contractBO.get("START_DATE", Date.class);
                Date END_DATE = contractBO.get("END_DATE", Date.class);

                //通过 合同周期个数和合同周期类型计算结束日期
                if (START_DATE != null && StringUtils.isNotBlank(PERIOD_TYPE) && (END_DATE == null || END_DATE.before(START_DATE)))
                    if (OCCURRENCES != null && OCCURRENCES > 0) {
                        // 使用 Calendar 进行日期操作
                        Calendar calendar = Calendar.getInstance();
                        calendar.setTime(START_DATE);
                        if (PERIOD_TYPE.equals(cttConstant.PERIOD_TYPE_HALFYEAR)) {
                            calendar.add(Calendar.MONTH, OCCURRENCES * 6);
                            calendar.add(Calendar.DAY_OF_MONTH, -1);
                            END_DATE = calendar.getTime();
                        } else if (PERIOD_TYPE.equals(cttConstant.PERIOD_TYPE_MONTH)) {
                            calendar.add(Calendar.MONTH, OCCURRENCES);
                            calendar.add(Calendar.DAY_OF_MONTH, -1);
                            END_DATE = calendar.getTime();
                        } else if (PERIOD_TYPE.equals(cttConstant.PERIOD_TYPE_QUARTER)) {
                            calendar.add(Calendar.MONTH, OCCURRENCES * 3);
                            calendar.add(Calendar.DAY_OF_MONTH, -1);
                            END_DATE = calendar.getTime();
                        } else if (PERIOD_TYPE.equals(cttConstant.PERIOD_TYPE_YEAR)) {
                            calendar.add(Calendar.MONTH, OCCURRENCES * 12);
                            calendar.add(Calendar.DAY_OF_MONTH, -1);
                            END_DATE = calendar.getTime();
                        } else {
                            resultDto.put("success", false);
                            resultDto.put("msg", "周期类型输入错误！");
                            return resultDto;
                        }
                        contractBO.set("END_DATE", END_DATE);
                    }
            }

            contractBO.set("ADJUST_SETUP_FEE", contractBO.get("SETUP_FEE"));

            //计费客户
            if (StringUtils.isBlank(contractBO.getString("BILL_TO_ACCOUNT_ID"))) {
                contractBO.set("BILL_TO_ACCOUNT_ID", contractBO.getString("ACCOUNT_ID"));
                contractBO.set("BILL_TO_ACCOUNT_NAME", contractBO.getString("ACCOUNT_NAME"));
            }

            //合同：合同新增/编辑页面，默认运维合同字段，如果选中---需要将本客户的其他默认合同改为非默认合同，即一个客户最多只能有一个默认合同--张耀
            if (contractBO.getString("IS_SDT_DEFAULT").equals("1")) {
                DBSql.update("update BO_EU_DNCTT_CONTRACT set IS_SDT_DEFAULT=0 where ACCOUNT_ID=?", new Object[]{contractBO.getString("ACCOUNT_ID")});
            }

            //2025年10月25日  生成合同编号
            contractServiceLogger.info("生成合同编号");
            String contractNo = contractBO.getString("NO");
            if (StringUtils.isBlank(contractNo) || contractNo.indexOf("XHT") != 0) {
                //框架协议子合同编号：框架协议合同编码-S001
                String FRAMEWORK = contractBO.getString("FRAMEWORK");
                if (StringUtils.isNotBlank(FRAMEWORK)) {
                    String orgContractNo = DBSql.getString("select NO from BO_EU_DNCTT_CONTRACT where ID=? AND NO LIKE '%XHT%'  ORDER BY NO DESC", new Object[]{FRAMEWORK});
                    List<String> noList = DBSql.getList("select NO from BO_EU_DNCTT_CONTRACT where FRAMEWORK=? AND NO LIKE ? order by NO desc", String.class, new Object[]{FRAMEWORK, orgContractNo + "-S%"});
                    if (noList.size() > 0) {
                        String lastNo = noList.get(0);
                        lastNo = lastNo.replace(orgContractNo + "-S", "");
                        // lastNo 去除前面的0
                        lastNo = lastNo.replaceAll("^0+", "");
                        contractNo = orgContractNo + "-S" + String.format("%03d", Integer.parseInt(lastNo) + 1);
                        contractBO.set("NO", contractNo);
                    } else {
                        contractNo = orgContractNo + "-S001";
                        contractBO.set("NO", contractNo);
                    }
                } else {
                    // contractNo = SDK.getRuleAPI().executeAtScript("@sequence(@companyId_contract_@year,5)");
                    String varname = uc.getCompanyModel().getId() + "_contract_" + LocalDate.now().getYear();
                    contractNo = SDK.getRuleAPI().executeAtScript("@sequence(" + varname + ",5)");

                    String DISTRICT_ID = DBSql.getString("select DISTRICT_ID from BO_EU_DNCRM_ACCOUNT where ID=?", new Object[]{contractBO.getString("ACCOUNT_ID")});
                    String cssx = "SH";//城市缩写
                    if (StringUtils.isNotBlank(DISTRICT_ID)) {
                        RowMap city = DBSql.getMap("SELECT EXTTEXT1,EXTTEXT2 FROM BO_ACT_DICT_KV_ITEM WHERE DICTKEY = 'GB.ADDR' AND ITEMNO= ?", new Object[]{DISTRICT_ID});
                        if (city != null) {
                            cssx = city.getString("EXTTEXT1");
                            if (!cssx.contains("市")) {
                                cssx = city.getString("EXTTEXT2");
                            }
                            //如果 cssx="上海市" 则输出 SH
                            cssx = getCityAbbreviation(cssx);
                        }
                    }
                    contractNo = "XHT-" + cssx + "-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-" + contractNo;

                    contractBO.set("NO", contractNo);
                }
            }

            contractServiceLogger.info("生成合同编号" + contractNo);

            if (idApply) SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_APPLY", contractBO);//更新合同
            else {
                dn.recordFormChanges.record(uc, contractBO, "合同编辑");
                SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT", contractBO);
                ContractExcManage(uc, bindid);//更新合同例外因素
            }

            //  if (!isSubmit)
            //     return resultDto;

            contractServiceLogger.info("合同提交后，进行数据处理");

            RowMap dto = new RowMap(contractBO.asMap());//合同对象

            if (idApply) insertContract(uc, dto);//创建合同详情，用于提交后查看和修改相关配置项、工单、服务等
            else {
                //更新
                DBSql.update("update BO_EU_DNCTT_CONTRACT_RATE set CONTRACT_ID=? where BINDID=?", new Object[]{dto.getString("ID"), dto.getString("BINDID")});
                DBSql.update("update BO_EU_DNCTT_CONTRACT_MILESTONE set CONTRACT_ID=? where BINDID=?", new Object[]{dto.getString("ID"), dto.getString("BINDID")});
                DBSql.update("update BO_EU_DNCTT_CONTRACT_SERVICE set CONTRACT_ID=? where BINDID=?", new Object[]{dto.getString("ID"), dto.getString("BINDID")});

                DBSql.update("update BO_EU_DNCTT_CONTRACT_COST_DEFAULT set CONTRACT_ID=? where BINDID=?", new Object[]{dto.getString("ID"), dto.getString("BINDID")});

                DBSql.update("update BO_EU_DNCTT_CONTRACT_BLOCK set CONTRACT_ID=? where BINDID=?", new Object[]{dto.getString("ID"), dto.getString("BINDID")});

            }

            //更新合同产品上面的客户ID
            DBSql.update("update BO_EU_DNCTT_CONTRACT_COST set ACCOUNT_ID=?,BILL_ACCOUNT_ID=? where CONTRACT_ID=?", new Object[]{dto.getString("ACCOUNT_ID"), dto.getString("BILL_ACCOUNT_ID"), dto.getString("ID")});

            DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT A JOIN BO_EU_DNCTT_CONTRACT_SERVICE B ON A.ID=B.CONTRACT_ID SET B.END_DATE=A.END_DATE WHERE A.ID=?   AND A.END_DATE IS NOT NULL", new Object[]{dto.getString("ID")});


            if (TYPE_ID.equals(cttConstant.CONTRACT_TYPE_SERVICE)) // 如果是服务合同
            {
                try {
                    contractServiceLogger.info("服务---服务");
                    boolean flag = AddServiceServiceBundle(uc, dto);//新增服务或者服务包
                    contractServiceLogger.info("服务产品---服务产品");
                    AddContractServiceProduct(uc, dto);//新增服务或者服务包--关联产品
                    //更新服务产品价格
                    if (!flag) {
                        resultDto.put("success", false);
                        resultDto.put("msg", "新增服务或服务包失败！");
                        return resultDto;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    contractServiceLogger.info("新增服务--info--失败。。。" + e.getMessage());
                }
            }

            resultDto.put("success", true);

            return resultDto;
        } catch (Exception e) {
            e.printStackTrace();
            contractServiceLogger.info("合同报错-info--失败。。。" + e.getMessage());
        }
        resultDto.put("success", false);
        return resultDto;
    }

    /**
     * 插入合同详情
     * @param uc
     * @param contract
     */
    public void insertContract(UserContext uc, RowMap contract) throws SQLException {
        String contractBINDID = null;
        String APPLY_ID = contract.getString("ID");
        String APPLY_BINDID = contract.getString("BINDID");
        String exitsBOID = DBSql.getString("select ID from BO_EU_DNCTT_CONTRACT where APPLY_ID=?", new Object[]{APPLY_ID});
        String processDefId = "obj_4bac3fd23814406c8d224ef2094ac274";
        if (StringUtils.isNotBlank(exitsBOID)) {
            BO contractBO = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT", exitsBOID);
            for (String key : excludesArray) {
                contract.remove(key);
            }
            contractBO.setAll(contract);
            contractBO.set("APPLY_ID", APPLY_ID);

            dn.recordFormChanges.record(uc, contractBO, "合同编辑");

            SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT", contractBO);
            contract.put("BINDID", contractBO.getBindId());
            contract.put("ID", contractBO.getId());
            contract.put("CONTRACT_ID", contractBO.getId());
            contractBINDID = contractBO.getBindId();
        } else {
            //新增
            ProcessInstance contractInstance = SDK.getProcessAPI().createBOProcessInstance(processDefId, uc.getUID(), contract.getString("NAME"));

            BO contractBO = new BO();
            for (String key : excludesArray) {
                contract.remove(key);
            }
            contractBO.setAll(contract);
            contractBO.set("APPLY_ID", APPLY_ID);
            contractBO.setBindId(contractInstance.getId());

            //我现在用的规则是 销售合同以XHT-SH（以公司所在城市拼音首位字母缩写）-20251013(录入日期的年月)-00001(以保存先后顺序从1开始排以此类推）  XHT-SH-20220624-00636像这样的
            //2025年10月25日  生成合同编号

            String contractNo = SDK.getRuleAPI().executeAtScript("@sequenceYear(@companyId_contract,5)");

            String DISTRICT_ID = DBSql.getString("select DISTRICT_ID from BO_EU_DNCRM_ACCOUNT where ID=?", new Object[]{contract.getString("ACCOUNT_ID")});

            String cssx = "SH";//城市缩写

            if (StringUtils.isNotBlank(DISTRICT_ID)) {
                RowMap city = DBSql.getMap("SELECT EXTTEXT1,EXTTEXT2 FROM BO_ACT_DICT_KV_ITEM WHERE DICTKEY = 'GB.ADDR' AND ITEMNO= ?", new Object[]{DISTRICT_ID});
                if (city != null) {
                    cssx = city.getString("EXTTEXT1");
                    if (!cssx.contains("市")) {
                        cssx = city.getString("EXTTEXT2");
                    }
                    //如果 cssx="上海市" 则输出 SH
                    cssx = getCityAbbreviation(cssx);
                }
            }
            contractNo = "XHT-" + cssx + "-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + "-" + contractNo;
            contractBO.set("NO", contractNo);

            SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT", contractBO, uc);
            dn.recordFormChanges.record(uc, contractBO, "合同新增");

            exitsBOID = contractBO.getId();
            contract.put("BINDID", contractInstance.getId());
            contract.put("ID", contractBO.getId());
            contract.put("CONTRACT_ID", contractBO.getId());
            contractBINDID = contractBO.getBindId();
        }

        contract.put("PROCESSDEFID", processDefId);
        //更新
        DBSql.update("update BO_EU_DNCTT_CONTRACT_RATE set PROCESSDEFID=?,BINDID=?,CONTRACT_ID=? where BINDID=?", new Object[]{processDefId, contractBINDID, exitsBOID, APPLY_BINDID});
        DBSql.update("update BO_EU_DNCTT_CONTRACT_MILESTONE set PROCESSDEFID=?,BINDID=?,CONTRACT_ID=? where BINDID=?", new Object[]{processDefId, contractBINDID, exitsBOID, APPLY_BINDID});

        // DBSql.update("update BO_EU_DNCTT_CONTRACT_SERVICE set PROCESSDEFID=?,BINDID=?,CONTRACT_ID=? where BINDID=?", new Object[]{processDefId, contractBINDID, exitsBOID, APPLY_BINDID});

        // 合同服务
        List<BO> serviceList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE").addQuery("BINDID =", APPLY_BINDID).list();
        for (BO service : serviceList) {

            ProcessInstance costServiceProcessInst = SDK.getProcessAPI().createBOProcessInstance("obj_58693e03feac4879901430440b42efd9", uc.getUID(), "合同服务管理");

            Map<String, Object> serviceMap = service.asMap();

            BO serviceBO = new BO();
            for (String key : excludesArray) {
                serviceMap.remove(key);
            }
            serviceBO.setAll(serviceMap);

            serviceBO.setBindId(costServiceProcessInst.getId());
            serviceBO.set("CONTRACT_ID", exitsBOID);
            dn.recordFormChanges.record(uc, serviceBO, "合同服务新增");

            SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE", serviceBO, uc);

        }

        DBSql.update("update BO_EU_DNCTT_CONTRACT_BLOCK set PROCESSDEFID=?,BINDID=?,CONTRACT_ID=? where BINDID=?", new Object[]{processDefId, contractBINDID, exitsBOID, APPLY_BINDID});
    }

    /**
     * 新增服务或者服务包
     * @param uc
     * @param contract
     * @return
     */
    public boolean AddServiceServiceBundle(UserContext uc, RowMap contract) throws SQLException {
        contractServiceLogger.info("新增服务或服务包");
        LocalDate START_DATE = getLocalDate(contract.get("START_DATE"));//服务开始日期
        LocalDate END_DATE = getLocalDate(contract.get("END_DATE"));//合同结束日期
        Connection conn = null;
        try {
            conn = DBSql.open();
            conn.setAutoCommit(false);
            //获取服务列表
            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);

                //新增服务周期
                AddService(uc, conn, contract, service);

                // ServiceAutoRenew(uc, service, END_DATE, conn);

                //修改合同结束日期
                //ServiceAdjustEndDate(uc, service, END_DATE, conn);

            }
            conn.commit();

        } catch (SQLException e) {

            contractServiceLogger.info("AddServiceServiceBundle--error--失败。。。" + e.getMessage());

            e.printStackTrace();

            conn.rollback();

        } finally {
            if (conn != null)
                DBSql.close(conn);
        }

        try {
            List<String> serviceIdList = DBSql.getList("select ID from BO_EU_DNCTT_CONTRACT_SERVICE where CONTRACT_ID=?", String.class, new Object[]{contract.get("ID")});

            for (String contractServiceId : serviceIdList) {

                //销售服务
                contractServiceAdj.getInstance().editServiceSaleEndDate(uc, contractServiceId, END_DATE);

                //采购--付款计划
                contractServiceAdj.getInstance().editServicePurchaseEndDate(uc, contractServiceId, END_DATE);


                //分摊成本
                contractServiceAdj.getInstance().paySplitServiceMonth(uc, contractServiceId);

            }
        } catch (Exception e) {
            e.printStackTrace();
            contractServiceLogger.info("调整服务结束日期失败" + e.getMessage());
        }

        return true;
    }


    /**
     * 服务自动线续约
     * @param uc
     * @param service
     * @param END_DATE
     * @param conn
     * @return
     * @throws SQLException
     */
    public boolean ServiceAutoRenew(UserContext uc, BO service, LocalDate END_DATE, Connection conn) {
        try {

            //一次性收费服务 不受开始日期或结束日期影响
            if (service.getString("PERIOD_TYPE").equals(cttConstant.PERIOD_TYPE_ONE_TIME)) {
                return true;
            }
            contractServiceLogger.info("判断合同结束日期是否修改");
            //如果服务周期的结束时间不等于合同的结束时间，则需要延长服务周期或缩短
            String PERIOD = service.getString("PERIOD");//服务起止日期
            String ADJUST_PERIOD = service.getString("ADJUST_PERIOD");//服务调整的起止日期

            contractServiceLogger.info("PERIOD---" + PERIOD);
            contractServiceLogger.info("ADJUST_PERIOD---" + ADJUST_PERIOD);

            LocalDate PERIOD_END;
            if (StringUtils.isNotBlank(PERIOD)) {
                PERIOD_END = getLocalDate(PERIOD.split("~")[1]);
                PERIOD = PERIOD.split("~")[0] + "~" + LocalDateYYYYMMDD(END_DATE);
            } else {
                PERIOD_END = getLocalDate(ADJUST_PERIOD.split("~")[1]);
                ADJUST_PERIOD = ADJUST_PERIOD.split("~")[0] + "~" + LocalDateYYYYMMDD(END_DATE);
            }

            //缩短
            if (PERIOD_END.isAfter(END_DATE)) {
                if (StringUtils.isNotBlank(ADJUST_PERIOD)) {
                    LocalDate ADJUST_PERIOD_END = getLocalDate(ADJUST_PERIOD.split("~")[1]);
                    if (ADJUST_PERIOD_END.isAfter(END_DATE)) {
                        PERIOD = null;
                        ADJUST_PERIOD = ADJUST_PERIOD.split("~")[0] + "~" + LocalDateYYYYMMDD(END_DATE);
                    }
                }
            }

            contractServiceLogger.info("服务周期结束日期--" + LocalDateYYYYMMDD(PERIOD_END));
            contractServiceLogger.info("合同结束日期--" + LocalDateYYYYMMDD(END_DATE));


            if (!PERIOD_END.isEqual(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"));//服务总成本

                if (PERIOD_END.isBefore(END_DATE)) {
                    contractServiceLogger.info("服务周期延长--" + service.getString("NAME"));
                    //延长
                    List<BO> servicePeriodList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").connection(conn).addQuery("CONTRACT_SERVICE_ID =", service.getId()).addQuery("PERIOD_END_DATE >=", LocalDateYYYYMMDD(PERIOD_END)).orderBy("PERIOD_BEGIN_DATE").desc().list();

                    BO servicePeriod = servicePeriodList.get(0);
                    LocalDate period_begin_date = getLocalDate(servicePeriod.get("PERIOD_BEGIN_DATE"));
                    LocalDate period_end_date = getLocalDate(servicePeriod.get("PERIOD_END_DATE"));

                    String maxPeriod = service.getString("PERIOD_TYPE");

                    LocalDate firstPeriodEnd = getNextPeriodStart(period_begin_date, maxPeriod).minusDays(1);//本周期结束时间
                    LocalDate SATRT_DATE = null;
                    if (period_end_date.isBefore(firstPeriodEnd)) {
                        LocalDate new_period_end_date;
                        if (END_DATE.isBefore(firstPeriodEnd)) {
                            new_period_end_date = END_DATE;
                        } else {
                            new_period_end_date = firstPeriodEnd;
                            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 {
                        SATRT_DATE = period_end_date.plusDays(1);
                    }

                    if (SATRT_DATE != null && SATRT_DATE.isBefore(END_DATE)) {

                        //服务包上的服务
                        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(","));
                        }

                        //循环添加全部周期
                        while (!SATRT_DATE.isAfter(END_DATE)) {
                            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));
                                periodEnd = END_DATE;

                                periodRate = divideToBigDecimal(DateRateCalculator(SATRT_DATE, periodEnd, false), (GetPeriodMonth(maxPeriod)));
                            }
                            BO csp = new BO();//服务周期
                            csp.setAll(servicePeriod.asMap());
                            csp.set("PERIOD_BEGIN_DATE", SATRT_DATE);
                            csp.set("PERIOD_END_DATE", periodEnd);
                            BigDecimal period_price = multiply(service.get("UNIT_PRICE"), service.get("QUANTITY"));
                            period_price = periodRate.multiply(period_price);
                            csp.set("PERIOD_PRICE", period_price);

                            csp.set("PERIOD_COST", periodRate.multiply(SERVICE_TOTAL_COST));

                            csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));

                            csp.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), periodRate));

                            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", csp.get("VENDOR_ACCOUNT_ID"));
                                        cspbs.set("PERIOD_COST", prorated_cost_change);
                                        cspbs.setBindId(csp.getString("BINDID"));
                                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", cspbs, uc, conn);
                                    }
                                }
                            }
                            if (periodEnd != null) {
                                SATRT_DATE = periodEnd.plusDays(1);
                            }
                        }
                    }
                } else if (PERIOD_END.isAfter(END_DATE)) {
                    contractServiceLogger.info("服务周期缩短--" + service.getString("NAME"));
                    //缩短
                    List<BO> servicePeriodList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").connection(conn).addQuery("CONTRACT_SERVICE_ID =", service.getId()).addQuery("PERIOD_END_DATE >=", LocalDateYYYYMMDD(END_DATE)).orderBy("PERIOD_BEGIN_DATE").asc().list();

                    BO servicePeriod = servicePeriodList.get(0);
                    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);

                    for (BO period : servicePeriodList) {
                        //删除
                        if (period.getString("ID").equals(servicePeriod.getString("ID")) == false) {

                            DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE WHERE CONTRACT_SERVICE_PERIOD_ID=?", new Object[]{period.getString("ID")});
                            DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE ID=?", new Object[]{period.getString("ID")});
                        }
                    }
                }

                DBSql.update(conn, "update BO_EU_DNCTT_CONTRACT_SERVICE set PERIOD=?,ADJUST_PERIOD=? where ID=? and BINDID=?", new Object[]{PERIOD, ADJUST_PERIOD, service.get("ID"), service.get("BINDID")});


                //付款计划延期
                if (PERIOD_END.isBefore(END_DATE)) {
                    contractServiceLogger.info("服务周期延长--" + service.getString("NAME"));
                    //延长
                    List<BO> payPeriodList = SDK.getBOAPI().query("BO_EU_DNIVT_ORDER_PAYMENT_PLAN").connection(conn).addQuery("CONTRACT_SERVICE_ID =", service.getId()).addQuery("PLAN_DATE >=", LocalDateYYYYMMDD(PERIOD_END)).orderBy("PLAN_DATE").desc().list();

                    BO payPeriod = payPeriodList.get(0);
                    LocalDate period_begin_date = getLocalDate(payPeriod.get("PLAN_DATE"));
                    LocalDate period_end_date = getLocalDate(PERIOD_END);//上次结束日期

                    String maxPeriod = service.getString("PURCHASE_PERIOD_TYPE");//采购周期类型

                    LocalDate firstPeriodEnd = getNextPeriodStart(period_begin_date, maxPeriod).minusDays(1);//本周期结束时间
                    LocalDate SATRT_DATE = null;
                    if (period_end_date.isBefore(firstPeriodEnd)) {
                        LocalDate new_period_end_date;
                        if (END_DATE.isBefore(firstPeriodEnd)) {
                            new_period_end_date = END_DATE;
                        } else {
                            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));
                        // 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(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 {
                        SATRT_DATE = period_end_date.plusDays(1);
                    }

                    if (SATRT_DATE != null && SATRT_DATE.isBefore(END_DATE)) {

                        //循环添加全部周期
                        while (!SATRT_DATE.isAfter(END_DATE)) {
                            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));
                                periodEnd = END_DATE;

                                periodRate = divideToBigDecimal(DateRateCalculator(SATRT_DATE, END_DATE, false), (GetPeriodMonth(maxPeriod)));

                            }

                            ProcessInstance processInstance = SDK.getProcessAPI().createProcessInstance("obj_5cb4ae4a42944fd0a9a284ff4c64c65d", uc.getUID(), "付款计划");
                            BO csp = new BO();//服务周期
                            csp.setAll(payPeriod.asMap());
                            csp.setBindId(processInstance.getId());
                            csp.set("PLAN_DATE", SATRT_DATE);
                            csp.set("PLAN_AMOUNT", periodRate.multiply(SERVICE_TOTAL_COST));
                            csp.set("REMAIN_AMOUNT", csp.get("PLAN_AMOUNT"));
                            SDK.getBOAPI().create("BO_EU_DNIVT_ORDER_PAYMENT_PLAN", csp, processInstance, uc);


                            if (periodEnd != null) {
                                SATRT_DATE = periodEnd.plusDays(1);
                            }
                        }
                    }
                } else if (PERIOD_END.isAfter(END_DATE)) {
                    contractServiceLogger.info("服务周期缩短--" + service.getString("NAME"));
                    //缩短

                    List<BO> payPeriodList = SDK.getBOAPI().query("BO_EU_DNIVT_ORDER_PAYMENT_PLAN").connection(conn).addQuery("CONTRACT_SERVICE_ID =", service.getId()).addQuery("PLAN_DATE >=", LocalDateYYYYMMDD(PERIOD_END)).orderBy("PLAN_DATE").asc().list();

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

                    for (BO period : payPeriodList) {
                        //删除
                        if (period.getString("ID").equals(payPeriod.getString("ID")) == false) {

                            DBSql.update(conn, "DELETE FROM BO_EU_DNIVT_ORDER_PAYMENT_PLAN WHERE ID=?", new Object[]{period.getString("ID")});
                        }
                    }
                }
            }

        } catch (Exception e) {

        }
        return true;
    }


    /**
     * 添加服务
     * @param uc
     * @param conn
     * @param contract
     * @param service
     * @return
     */
    public boolean AddServiceServiceBundleOne(UserContext uc, Connection conn, RowMap contract, BO service) {
        if (service == null) return false;

        contractServiceLogger.info("AddServiceServiceBundleOne--开始");

        boolean isCreate = false;
        // 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"));//服务生效日期
        String PERIOD = service.getString("PERIOD");//服务起止日期
        String ADJUST_PERIOD = service.getString("ADJUST_PERIOD");//服务调整的起止日期
        if (EFFECTIVE_DATE == null || (StringUtils.isBlank(PERIOD) && StringUtils.isBlank(ADJUST_PERIOD)))
            isCreate = true;

        if (EFFECTIVE_DATE == null) {
            EFFECTIVE_DATE = START_DATE;
            isCreate = true;
        }

        if (StringUtils.isNotBlank(ADJUST_PERIOD)) {
            LocalDate ADJUST_PERIOD_START = getLocalDate(ADJUST_PERIOD.split("~")[0]);
            if (!ADJUST_PERIOD_START.isEqual(EFFECTIVE_DATE)) {
                isCreate = true;
            }
            if (StringUtils.isBlank(PERIOD)) {
                LocalDate ADJUST_PERIOD_END = getLocalDate(ADJUST_PERIOD.split("~")[1]);
                if (!ADJUST_PERIOD_END.isEqual(END_DATE)) {
                    isCreate = true;
                }
            }
        }

        if (StringUtils.isNotBlank(PERIOD)) {
            LocalDate PERIOD_END = getLocalDate(PERIOD.split("~")[1]);
            if (!PERIOD_END.isEqual(END_DATE)) {
                isCreate = true;
            }
            if (StringUtils.isBlank(ADJUST_PERIOD)) {
                LocalDate PERIOD_START = getLocalDate(PERIOD.split("~")[0]);
                if (!PERIOD_START.isEqual(EFFECTIVE_DATE)) {
                    isCreate = true;
                }
            }
        }

        /*
        String effectiveDateStr = EFFECTIVE_DATE.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        effectiveDateStr = "%" + effectiveDateStr + "%";
        //生效日期修改了
        if (DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE CONTRACT_SERVICE_ID=? AND EFFECTIVE_DATE LIKE ?", new Object[]{service.get("ID"), effectiveDateStr}) == 0 && DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE CONTRACT_SERVICE_ID=? AND PERIOD_BEGIN_DATE LIKE ?", new Object[]{service.get("ID"), effectiveDateStr}) == 0) {
            isCreate = true;
        }


        //2、服务单价或数量变化
        if (service.get("ADJUSTED_PRICE") == null || StringUtils.isBlank(service.getString("ADJUSTED_PRICE"))) {
            isCreate = true;
        } else {
            BigDecimal ADJUSTED_PRICE = new BigDecimal(service.getString("ADJUSTED_PRICE"));

            ADJUSTED_PRICE = ADJUSTED_PRICE.setScale(8, RoundingMode.HALF_UP);

            if (ADJUSTED_PRICE.compareTo(multiply(service.get("QUANTITY"), service.get("UNIT_PRICE"))) != 0) {
                isCreate = true;
            }
        }
         */

        if (!isCreate) return true;

        //服务已经审批并提交则不需要重新生成
        // 暂时不考虑 审批并提交之后修改服务，不需要重新生成
        if (DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST 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;
        }

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


      /*
        if (DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST WHERE BINDID=? and CONTRACT_ID=? and CONTRACT_SERVICE_ID=? ", new Object[]{service.get("BINDID"), contract.get("ID"), service.get("ID")}) > 0) {
            return true;
        }
        if (DBSql.getInt("SELECT count(1) FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE BINDID=? and CONTRACT_ID=? and CONTRACT_SERVICE_ID=? ", new Object[]{service.get("BINDID"), contract.get("ID"), service.get("ID")}) > 0) {
            return true;
        }
       */

        //删除历史数据
        if (StringUtils.isNotBlank(ADJUST_PERIOD)) {

            List<String> csaIdList = DBSql.getList(conn, "select ID from BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST where BINDID=? and CONTRACT_ID=? and CONTRACT_SERVICE_ID=?", String.class, new Object[]{service.get("BINDID"), contract.get("ID"), service.get("ID")});

            for (String csaId : csaIdList) {
                DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST_BUNDLE_SERVICE where BINDID=? and CONTRACT_SERVICE_ADJUST_ID=? ", new Object[]{service.get("BINDID"), csaId});
            }

            DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_SERVICE_ADJUST where BINDID=? and CONTRACT_ID=? and CONTRACT_SERVICE_ID=?", new Object[]{service.get("BINDID"), contract.get("ID"), service.get("ID")});

            DBSql.update("delete from BO_EU_DNCTT_CONTRACT_SERVICE_EDIT where  CONTRACT_SERVICE_ID=?", new Object[]{service.get("ID")});
        }

        if (StringUtils.isNotBlank(PERIOD)) {
            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")});
        }

        service.set("CONTRACT_ID", contract.get("ID"));
        service.set("EFFECTIVE_DATE", EFFECTIVE_DATE);//生效日期

        if (service.getString("OBJECT_TYPE").equals("1"))//服务
        {
            int NEED_PURCHASE = DBSql.getInt(conn, "select NEED_PURCHASE from BO_EU_DNIVT_SERVICE where ID=? ", new Object[]{service.getString("OBJECT_ID")});

            if (NEED_PURCHASE == 1)//需要购买
            {
                service.set("STATUS_ID", cttConstant.CONTRACT_SERVICE_STATUS_WAIT_INSTALL);//待安装
            } else {
                service.set("STATUS_ID", cttConstant.CONTRACT_SERVICE_STATUS_NO_INSTALL);//不需要购买
            }
        }

        int maxPeriod = contract.getInt("PERIOD_TYPE") > service.get("PERIOD_TYPE", Integer.class) ? contract.getInt("PERIOD_TYPE") : service.get("PERIOD_TYPE", Integer.class);  // 合同与服务周期类型中较大的周期类型

        BigDecimal rate = GetPeriodRate(contract.getString("PERIOD_TYPE"), service.getString("PERIOD_TYPE"));

        BigDecimal unit_cost = service.get("UNIT_COST", BigDecimal.class);
        BigDecimal unit_price = service.get("UNIT_PRICE", BigDecimal.class);

        // 合同周期更长，服务周期使用合同周期，单价和成本根据周期倍数翻倍
        if (rate.compareTo(BigDecimal.ONE) > 0) {

            //是否需要采购
            if (service.getString("NEED_PURCHASE").equals("1") == false) {
                unit_cost = unit_cost.multiply(rate);
                service.set("UNIT_COST", unit_cost);
            }

            unit_price = unit_price.multiply(rate);
            service.set("UNIT_PRICE", unit_price);
            // service.set("PERIOD_TYPE", contract.getString("PERIOD_TYPE"));//计费周期更新
            service.set("PERIOD_TYPE", maxPeriod);

            //2025年10月24日 新增 三方合同甲方含税单价
            service.set("UNIT_PRICE_NOTAX", multiply(service.get("UNIT_PRICE_NOTAX"), rate));
            service.set("TOTAL_PRICE_NOTAX", multiply(service.get("TOTAL_PRICE_NOTAX"), rate));
            service.set("PARTYA_UNIT_PRICE", multiply(service.get("PARTYA_UNIT_PRICE"), rate));
            service.set("PARTYA_UNIT_PRICE_NOTAX", multiply(service.get("PARTYA_UNIT_PRICE_NOTAX"), rate));
            service.set("PARTYA_TOTAL_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), rate));//三方合同甲方含税总价
            service.set("PARTYA_TOTAL_PRICE_NOTAX", multiply(service.get("PARTYA_TOTAL_PRICE_NOTAX"), rate));
        }

        contractServiceLogger.info("计算-service-ADJUSTED_PRICE");
        service.set("TOTAL_PRICE", multiply(service.get("QUANTITY"), service.get("UNIT_PRICE")));//总价
        service.set("ADJUSTED_PRICE", multiply(service.get("QUANTITY"), service.get("UNIT_PRICE")));//总价

        //是否需要采购
        if (service.getString("NEED_PURCHASE").equals("1") == false)
            service.set("TOTAL_COST", multiply(service.get("QUANTITY"), service.get("UNIT_COST")));//总价

        SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE", service, conn);//保存

        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(","));
        }
                /*
                if (service.getString("OBJECT_TYPE").equals("2"))//服务包
                    sbsList = DBSql.getMaps(conn, "SELECT * FROM BO_EU_DNIVT_SERVICE_BUNDLE_SERVICE WHERE SERVICE_BUNDLE_ID=?", new Object[]{service.getString("OBJECT_ID")});
                 */
        String BILL_METHOD_ID = null;//计费方式  4601:自然月  4602:实际月
        if (service.getString("OBJECT_TYPE").equals("2"))//服务包
        {
            BILL_METHOD_ID = DBSql.getString(conn, "select BILL_METHOD_ID from BO_EU_DNIVT_SERVICE_BUNDLE where ID=?", new Object[]{service.getString("OBJECT_ID")});
            // 插入合同服务包对应的服务 ctt_contract_service_bundle_service
            if (sbsList != null) {
                for (String sbs : sbsList) {
                    if (DBSql.getInt(conn, "select count(1) from BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE where SERVICE_ID=? and CONTRACT_SERVICE_ID=?", new Object[]{sbs, service.get("ID")}) == 0) {
                        BO csbs = new BO();
                        csbs.set("SERVICE_ID", sbs);
                        csbs.set("CONTRACT_SERVICE_ID", service.get("ID"));
                        csbs.setBindId(service.getString("BINDID"));
                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_BUNDLE_SERVICE", csbs, uc, conn);
                    }

                }
            }
        } else {
            BILL_METHOD_ID = DBSql.getString(conn, "select BILL_METHOD_ID from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.getString("OBJECT_ID")});
        }

        String vendor_account_id = DBSql.getString(conn, "select VENDOR_ACCOUNT_ID from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.getString("OBJECT_ID")});//供应商账号
        //如果不是从一号开始，则需要调整到从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);//周期开始日期

            */

            START_DATE = START_DATE.with(TemporalAdjusters.firstDayOfMonth());
        }

        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"));//服务总成本

        //2025年10月15日 一次性收费服务
        if (service.getString("PERIOD_TYPE").equals(cttConstant.PERIOD_TYPE_ONE_TIME)) {
            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", EFFECTIVE_DATE);
            csp.set("PERIOD_END_DATE", END_DATE);
            csp.set("QUANTITY", service.get("QUANTITY"));
            BigDecimal period_price = multiply(service.get("UNIT_PRICE"), service.get("QUANTITY"));
            csp.set("PERIOD_PRICE", period_price);
            BigDecimal period_cost = multiply(SERVICE_UNIT_COST, service.get("QUANTITY"));
            csp.set("PERIOD_COST", period_cost);
            csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));
            csp.set("PARTYA_PERIOD_PRICE", service.get("PARTYA_TOTAL_PRICE"));//三方合同甲方含税总价
            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);
                    }
                }
            }
            return true;
        }

        // 计算首周期起止日期，并判断是否需要新增服务调整记录
        Map<String, Object> firstPeriodDate = getFirstPeriodDate(START_DATE, END_DATE, EFFECTIVE_DATE, contract.getString("PERIOD_TYPE"), service.getString("PERIOD_TYPE"));
        boolean isStartOfPeriod = (boolean) firstPeriodDate.get("isStartOfPeriod");//是否需要服务调整
        // 合同服务的周期起止日期
        LocalDate start = getLocalDate(firstPeriodDate.get("periodStart"));
        LocalDate end = getLocalDate(firstPeriodDate.get("periodEnd"));
        ADJUST_PERIOD = null;

        contractServiceLogger.info("getFirstPeriodDate" + firstPeriodDate.toString());

        //增加服务周期调整
        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"));
            csa.set("OBJECT_TYPE", service.get("OBJECT_TYPE"));
            csa.set("QUANTITY", service.get("QUANTITY"));
            csa.set("CONTRACT_SERVICE_ID", service.get("ID"));
            csa.set("PERIOD_BEGIN_DATE", EFFECTIVE_DATE);
            csa.set("PERIOD_END_DATE", end);

            ADJUST_PERIOD = LocalDateYYYYMMDD(EFFECTIVE_DATE) + "~" + LocalDateYYYYMMDD(end);

            csa.set("PERIOD_ADJUSTED_PRICE", multiply(service.get("ADJUSTED_PRICE"), periodRate));
            csa.set("PERIOD_PRICE", csa.get("PERIOD_ADJUSTED_PRICE"));

            csa.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), periodRate));//三方合同甲方含税总价

            csa.set("VENDOR_ACCOUNT_ID", vendor_account_id);
            csa.setBindId(service.getString("BINDID"));

            //服务周期成本
            csa.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRate));
            SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", csa, uc, conn);

            // 插入服务调整表包含的服务ctt_contract_service_adjust_bundle_service
            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 csabs = new BO();
                        csabs.set("CONTRACT_SERVICE_PERIOD_ID", csa.getId());
                        csabs.set("SERVICE_ID", sbs);
                        csabs.set("VENDOR_ACCOUNT_ID", vendor_account_id);
                        csabs.set("PERIOD_COST", prorated_cost_change);
                        csabs.setBindId(service.getString("BINDID"));
                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", csabs, uc, conn);
                    }
                }
            }

            start = end.plusDays(1);     // 周期开始日期指
        }

        PERIOD = null;
        //生成服务周期
        if (start.isBefore(END_DATE)) {
            PERIOD = LocalDateYYYYMMDD(start);
            //循环添加全部周期
            while (!start.isAfter(END_DATE)) {
                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));

                    periodEnd = END_DATE;
                    //计算日期之间的比例值
                    periodRate = divideToBigDecimal(DateRateCalculator(start, periodEnd, false), (GetPeriodMonth(String.valueOf(maxPeriod))));
                }

                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", start);
                csp.set("PERIOD_END_DATE", periodEnd);
                csp.set("QUANTITY", service.get("QUANTITY"));

                contractServiceLogger.info("计算-csp-PERIOD_PRICE");
                BigDecimal period_price = multiply(service.get("UNIT_PRICE"), service.get("QUANTITY"));
                period_price = periodRate.multiply(period_price);
                csp.set("PERIOD_PRICE", period_price);

                csp.set("PERIOD_COST", periodRate.multiply(SERVICE_TOTAL_COST));

                csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));

                csp.set("PARTYA_PERIOD_PRICE", multiply(service.get("PARTYA_TOTAL_PRICE"), periodRate));//三方合同甲方含税总价

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

                if (periodEnd != null) {
                    start = periodEnd.plusDays(1);
                }
            }

            PERIOD = PERIOD + "~" + LocalDateYYYYMMDD(END_DATE);
        }

        //更新合同服务上的
        DBSql.update(conn, "update BO_EU_DNCTT_CONTRACT_SERVICE set PERIOD=?,ADJUST_PERIOD=? where ID=? and BINDID=?", new Object[]{PERIOD, ADJUST_PERIOD, service.get("ID"), service.get("BINDID")});
        service.set("PERIOD", PERIOD);
        service.set("ADJUST_PERIOD", ADJUST_PERIOD);

        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"));//服务生效日期
        LocalDate EFFECTIVE_END_DATE = getLocalDate(service.get("END_DATE"));//服务结束日期
        if (END_DATE == null && EFFECTIVE_END_DATE == null) {
            return false;
        }
        if (EFFECTIVE_END_DATE != null)
            END_DATE = EFFECTIVE_END_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 false;
        }

        //删除历史数据
        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 = BigDecimal.ZERO;//服务单元成本
        BigDecimal SERVICE_TOTAL_COST = BigDecimal.ZERO;//服务总成本
        //无需采购
        if (service.getString("NEED_PURCHASE").equals("1") == false) {
            SERVICE_UNIT_COST = service.get("UNIT_COST", BigDecimal.class);//服务单元成本
            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 CALC_METHOD_1STPERIOD = service.getString("CALC_METHOD_1STPERIOD");
        List<PeriodCalculationUtil.Period> periodList = PeriodCalculationUtil.getPeriodListByConfig(PERIOD_TYPE, EFFECTIVE_DATE, EFFECTIVE_DATE, END_DATE, CALC_METHOD_1STPERIOD, service.getString("CALC_METHOD_1STYEAR"), service.getString("MONTH_1STQUARTER"));

        if (periodList != null && periodList.size() > 0) {
            String PERIOD = periodList.get(0).getPeriodBeginDateStr() + "~" + periodList.get(periodList.size() - 1).getPeriodEndDateStr();

            //首月+3个月、首月+6个月、首月+12个月 的情况，需要把前两条 合并成一条
            if (periodList.size() > 1) {
                if (CALC_METHOD_1STPERIOD.equals("4597") || CALC_METHOD_1STPERIOD.equals("4594") || CALC_METHOD_1STPERIOD.equals("4591")) {
                    PeriodCalculationUtil.Period period1 = periodList.get(0);
                    PeriodCalculationUtil.Period period2 = periodList.get(1);
                    //小于1
                    if (period1.getRate().compareTo(BigDecimal.ONE) < 0) {
                        //合并
                        period2.setRate(period1.getRate().add(period2.getRate()));
                        period2.setPeriodBeginDate(period1.getPeriodBeginDate());
                        //移除第一条
                        periodList.remove(0);
                    }
                }
            }

            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) {
        if (END_DATE == null)
            return true;

        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
     * @param contract
     * @param editService
     * @return
     */
    public boolean EditServiceServiceBundle(UserContext uc, Connection conn, RowMap contract, BO editService) {

        //2025年10月30日  计算有问题  后续 在修改

        //2、服务调整:生效日期在合同开始日期和合同结束日期之间；已审批服务周期之后的日期；
        // 3、服务周期分为调整前和调整后两个周期；生效日期之后按照新的数量、单价重新生成服务周期；
        contractServiceLogger.info("调整服务");

        String IS_EFFECTIVE = editService.getString("IS_EFFECTIVE");
        if (!IS_EFFECTIVE.equals("未执行")) return false;
        LocalDate EFFECTIVE_DATE = getLocalDate(editService.get("EFFECTIVE_DATE"));//服务生效日期
        String EFFECTIVE_DATE_STR = EFFECTIVE_DATE.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        //2025年10月15日 一次性收费服务
        if (editService.getString("PERIOD_TYPE").equals(cttConstant.PERIOD_TYPE_ONE_TIME)) {

            //有审批通过的；
            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 CONTRACT_ID=? and CONTRACT_SERVICE_ID=? ", new Object[]{contract.get("ID"), editService.get("CONTRACT_SERVICE_ID")}) > 0) {
                return false;
            }
        } else {
            //生效日期之后有审批通过的；
            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 CONTRACT_ID=? and CONTRACT_SERVICE_ID=? AND  PERIOD_END_DATE>=?", new Object[]{contract.get("ID"), editService.get("CONTRACT_SERVICE_ID"), EFFECTIVE_DATE_STR}) > 0) {
                return false;
            }
        }

        //原合同服务
        BO contractServiceBO = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_SERVICE", editService.getString("CONTRACT_SERVICE_ID"));

        String maxPeriod = editService.getString("PERIOD_TYPE");

        BigDecimal SERVICE_UNIT_COST = editService.get("UNIT_COST", BigDecimal.class);//服务单元成本
        //需要采购
        if (editService.getString("NEED_PURCHASE").equals("1")) {
            BigDecimal prate = GetPeriodRate(editService.getString("PERIOD_TYPE"), editService.getString("PURCHASE_PERIOD_TYPE"));
            SERVICE_UNIT_COST = SERVICE_UNIT_COST.multiply(prate);
        }
        BigDecimal SERVICE_TOTAL_COST = multiply(SERVICE_UNIT_COST, editService.get("QUANTITY"));//服务总成本


        //一次性收费服务
        if (editService.getString("PERIOD_TYPE").equals(cttConstant.PERIOD_TYPE_ONE_TIME)) {
            BO servicePeriod = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").connection(conn).addQuery("CONTRACT_SERVICE_ID =", editService.getString("CONTRACT_SERVICE_ID")).detail();
            servicePeriod.set("PERIOD_BEGIN_DATE", EFFECTIVE_DATE);
            servicePeriod.set("QUANTITY", editService.get("QUANTITY"));
            servicePeriod.set("PERIOD_PRICE", editService.get("TOTAL_PRICE"));
            servicePeriod.set("PERIOD_COST", SERVICE_TOTAL_COST);

            servicePeriod.set("PARTYA_PERIOD_PRICE", editService.get("PARTYA_TOTAL_PRICE"));//三方合同甲方含税总价

            servicePeriod.set("PERIOD_ADJUSTED_PRICE", servicePeriod.get("PERIOD_PRICE"));
            SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriod, conn);

        } else {


            List<BO> editServiceList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_EDIT").connection(conn).addQuery("CONTRACT_SERVICE_ID =", editService.getString("CONTRACT_SERVICE_ID")).addQuery("IS_EFFECTIVE =", "已执行").addQuery("EFFECTIVE_DATE >", EFFECTIVE_DATE_STR).orderBy("EFFECTIVE_DATE").asc().list();

            boolean needDel = false;//是否需要删除重新生成
            if (editServiceList != null && editServiceList.size() > 0) {
                needDel = true;
                //在此次调整之后有新的
                for (BO editServiceOld : editServiceList) {
                    editServiceOld.set("IS_EFFECTIVE", "作废");
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_EDIT", editServiceOld, conn);
                }
            }

            //服务调整的生效日期，是按照日期顺序创建的
            if (needDel == false) {

                contractServiceLogger.info("服务调整的生效日期，是按照日期顺序创建的");

                List<BO> servicePeriodList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").connection(conn).addQuery("CONTRACT_SERVICE_ID =", editService.getString("CONTRACT_SERVICE_ID")).orderBy("PERIOD_BEGIN_DATE").asc().list();

                if (servicePeriodList != null && servicePeriodList.size() > 0) {

                    for (BO servicePeriod : servicePeriodList) {
                        LocalDate period_begin_date = getLocalDate(servicePeriod.get("PERIOD_BEGIN_DATE"));
                        LocalDate period_end_date = getLocalDate(servicePeriod.get("PERIOD_END_DATE"));

                        //调整日期之前，不用修改
                        if (period_end_date.isBefore(EFFECTIVE_DATE)) {
                            continue;
                        }
                        //处于调整日期之后的
                        if (period_begin_date.isBefore(EFFECTIVE_DATE) && !period_end_date.isBefore(EFFECTIVE_DATE)) {
                            //分成调整前和调整后 两条计费周期
                            //新的计费周期
                            BO servicePeriodAdj = new BO();
                            servicePeriodAdj.setAll(servicePeriod.asMap());
                            servicePeriodAdj.setId(null);
                            servicePeriodAdj.set("PERIOD_BEGIN_DATE", EFFECTIVE_DATE);
                            servicePeriodAdj.set("PERIOD_END_DATE", period_end_date);
                            servicePeriodAdj.set("QUANTITY", editService.get("QUANTITY"));

                            String ORG_PERIOD_ID = servicePeriod.getString("ORG_PERIOD_ID");
                            if (StringUtils.isBlank(ORG_PERIOD_ID)) ORG_PERIOD_ID = servicePeriod.getId();
                            servicePeriodAdj.set("ORG_PERIOD_ID", ORG_PERIOD_ID);


                            int priodDays = GetPeriodDays(getPreviousPeriodStart(period_end_date.plusDays(1), editService.getString("PERIOD_TYPE")), period_end_date);
                            BigDecimal periodRateAdj = divideToBigDecimal(GetPeriodDays(EFFECTIVE_DATE, period_end_date), priodDays);

                            servicePeriodAdj.set("PERIOD_PRICE", multiply(editService.get("TOTAL_PRICE"), periodRateAdj));
                            servicePeriodAdj.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRateAdj));
                            servicePeriodAdj.set("PERIOD_ADJUSTED_PRICE", servicePeriodAdj.get("PERIOD_PRICE"));

                            //三方合同甲方含税总价
                            servicePeriodAdj.set("PARTYA_PERIOD_PRICE", multiply(editService.get("PARTYA_TOTAL_PRICE"), periodRateAdj));

                            SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriodAdj, uc, conn);


                            List<BO> sbsList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE").connection(conn).addQuery("CONTRACT_SERVICE_PERIOD_ID =", ORG_PERIOD_ID).list();
                            if (sbsList != null && sbsList.size() > 0) for (BO sbs : sbsList) {
                                BO newSbs = new BO();
                                newSbs.setAll(sbs.asMap());
                                newSbs.setId(null);
                                newSbs.set("CONTRACT_SERVICE_PERIOD_ID", servicePeriodAdj.getId());
                                SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", newSbs, uc, conn);
                            }

                            //旧的计费周期更新
                            servicePeriod.set("PERIOD_END_DATE", EFFECTIVE_DATE.minusDays(1));

                            BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, EFFECTIVE_DATE.minusDays(1)), 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));

                            servicePeriod.set("PERIOD_ADJUSTED_PRICE", servicePeriod.get("PERIOD_PRICE"));
                            servicePeriod.set("ORG_PERIOD_ID", ORG_PERIOD_ID);
                            SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriod, conn);
                        } else {
                            //重新计算
                            /*
                            BigDecimal priceRate = editService.get("TOTAL_PRICE", BigDecimal.class).divide(editService.get("OLD_TOTAL_PRICE", BigDecimal.class), 10, RoundingMode.HALF_UP);
                            BigDecimal costRate = editService.get("TOTAL_COST", BigDecimal.class).divide(editService.get("OLD_TOTAL_COST", BigDecimal.class), 10, RoundingMode.HALF_UP);
                            servicePeriod.set("QUANTITY", editService.get("QUANTITY"));
                            servicePeriod.set("PERIOD_PRICE", multiply(servicePeriod.get("PERIOD_PRICE"), priceRate));
                            servicePeriod.set("PERIOD_COST", multiply(servicePeriod.get("PERIOD_COST"), costRate));

                             */

                            BigDecimal periodRate = BigDecimal.ONE;
                            LocalDate periodEnd = getNextPeriodStart(period_begin_date, maxPeriod).minusDays(1);//本周期结束时间
                            if (periodEnd.isAfter(period_end_date)) {//如果结束日期大于合同结束日期，则计算结束日期和合同结束日期的差值，并计算该差值占整周期的比例
                                periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, period_end_date), GetPeriodDays(period_begin_date, periodEnd));
                            }

                            servicePeriod.set("QUANTITY", editService.get("QUANTITY"));
                            servicePeriod.set("PERIOD_PRICE", multiply(editService.get("TOTAL_PRICE"), periodRate));
                            servicePeriod.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRate));
                            servicePeriod.set("PERIOD_ADJUSTED_PRICE", servicePeriod.get("PERIOD_PRICE"));
                            //三方合同甲方含税总价
                            servicePeriod.set("PARTYA_PERIOD_PRICE", multiply(editService.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 {
                contractServiceLogger.info("本次服务调整生效日期之后的服务调整全部作废");

                //生效日期之后的
                List<BO> servicePeriodList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").connection(conn).addQuery("CONTRACT_SERVICE_ID =", editService.getString("CONTRACT_SERVICE_ID")).addQuery("PERIOD_END_DATE >=", EFFECTIVE_DATE_STR).orderBy("PERIOD_BEGIN_DATE").asc().list();

                BO firstServicePeriod = servicePeriodList.get(0);
                //判断是不是个整周期
                String ORG_PERIOD_ID = firstServicePeriod.getString("ORG_PERIOD_ID");

                LocalDate SATRT_DATE = null;
                if (StringUtils.isBlank(ORG_PERIOD_ID)) {
                    ORG_PERIOD_ID = firstServicePeriod.getString("ORG_PERIOD_ID");
                    LocalDate period_begin_date = getLocalDate(firstServicePeriod.get("PERIOD_BEGIN_DATE"));
                    LocalDate period_end_date = getLocalDate(firstServicePeriod.get("PERIOD_END_DATE"));

                    //新的计费周期
                    BO servicePeriodAdj = new BO();

                    servicePeriodAdj.setAll(firstServicePeriod.asMap());
                    servicePeriodAdj.setId(null);

                    servicePeriodAdj.set("PERIOD_BEGIN_DATE", EFFECTIVE_DATE);
                    servicePeriodAdj.set("PERIOD_END_DATE", period_end_date);
                    servicePeriodAdj.set("QUANTITY", editService.get("QUANTITY"));
                    servicePeriodAdj.set("ORG_PERIOD_ID", ORG_PERIOD_ID);

                    /*
                    BigDecimal periodRateAdj = divideToBigDecimal(GetPeriodDays(EFFECTIVE_DATE, period_end_date), GetPeriodDays(period_begin_date, period_end_date));

                    BigDecimal priceRate = editService.get("TOTAL_PRICE", BigDecimal.class).divide(editService.get("OLD_TOTAL_PRICE", BigDecimal.class), 10, RoundingMode.HALF_UP);
                    BigDecimal costRate = editService.get("TOTAL_COST", BigDecimal.class).divide(editService.get("OLD_TOTAL_COST", BigDecimal.class), 10, RoundingMode.HALF_UP);

                    servicePeriodAdj.set("PERIOD_PRICE", multiply(firstServicePeriod.get("PERIOD_PRICE"), multiply(periodRateAdj, priceRate)));
                    servicePeriodAdj.set("PERIOD_COST", multiply(firstServicePeriod.get("PERIOD_COST"), multiply(periodRateAdj, costRate)));
                    servicePeriodAdj.set("PERIOD_ADJUSTED_PRICE", servicePeriodAdj.get("PERIOD_PRICE"));
                    SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriodAdj, uc, conn);
                     */

                    int priodDays = GetPeriodDays(getPreviousPeriodStart(period_end_date.plusDays(1), editService.getString("PERIOD_TYPE")), period_end_date);
                    BigDecimal periodRateAdj = divideToBigDecimal(GetPeriodDays(EFFECTIVE_DATE, period_end_date), priodDays);
                    servicePeriodAdj.set("PERIOD_PRICE", multiply(editService.get("TOTAL_PRICE"), periodRateAdj));
                    servicePeriodAdj.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRateAdj));
                    servicePeriodAdj.set("PERIOD_ADJUSTED_PRICE", servicePeriodAdj.get("PERIOD_PRICE"));
                    //三方合同甲方含税总价
                    servicePeriodAdj.set("PARTYA_PERIOD_PRICE", multiply(editService.get("PARTYA_TOTAL_PRICE"), periodRateAdj));
                    SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriodAdj, uc, conn);


                    List<BO> sbsList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE").connection(conn).addQuery("CONTRACT_SERVICE_PERIOD_ID =", ORG_PERIOD_ID).list();
                    if (sbsList != null && sbsList.size() > 0) for (BO sbs : sbsList) {
                        BO newSbs = new BO();
                        newSbs.setAll(sbs.asMap());
                        newSbs.setId(null);
                        newSbs.set("CONTRACT_SERVICE_PERIOD_ID", servicePeriodAdj.getId());
                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", newSbs, uc, conn);
                    }


                    //旧的计费周期更新
                    firstServicePeriod.set("PERIOD_END_DATE", EFFECTIVE_DATE.minusDays(1));
                    BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, EFFECTIVE_DATE.minusDays(1)), GetPeriodDays(period_begin_date, period_end_date));
                    firstServicePeriod.set("PERIOD_PRICE", multiply(firstServicePeriod.get("PERIOD_PRICE"), periodRate));
                    firstServicePeriod.set("PERIOD_COST", multiply(firstServicePeriod.get("PERIOD_COST"), periodRate));
                    firstServicePeriod.set("PERIOD_ADJUSTED_PRICE", firstServicePeriod.get("PERIOD_PRICE"));
                    firstServicePeriod.set("PARTYA_PERIOD_PRICE", multiply(firstServicePeriod.get("PARTYA_PERIOD_PRICE"), periodRate));
                    firstServicePeriod.set("ORG_PERIOD_ID", ORG_PERIOD_ID);
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", firstServicePeriod, conn);

                    SATRT_DATE = period_end_date.plusDays(1);
                } else {

                    LocalDate period_begin_date = getLocalDate(firstServicePeriod.get("PERIOD_BEGIN_DATE"));
                    LocalDate period_end_date = getLocalDate(firstServicePeriod.get("PERIOD_END_DATE"));
                    firstServicePeriod.set("PERIOD_END_DATE", EFFECTIVE_DATE.minusDays(1));
                    BigDecimal periodRate = divideToBigDecimal(GetPeriodDays(period_begin_date, EFFECTIVE_DATE.minusDays(1)), GetPeriodDays(period_begin_date, period_end_date));
                    firstServicePeriod.set("PERIOD_PRICE", multiply(firstServicePeriod.get("PERIOD_PRICE"), periodRate));
                    firstServicePeriod.set("PERIOD_COST", multiply(firstServicePeriod.get("PERIOD_COST"), periodRate));

                    firstServicePeriod.set("PARTYA_PERIOD_PRICE", multiply(firstServicePeriod.get("PARTYA_PERIOD_PRICE"), periodRate));

                    firstServicePeriod.set("PERIOD_ADJUSTED_PRICE", firstServicePeriod.get("PERIOD_PRICE"));
                    firstServicePeriod.set("ORG_PERIOD_ID", ORG_PERIOD_ID);
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", firstServicePeriod, conn);


                    List<BO> servicePeriodList2 = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").connection(conn).addQuery("CONTRACT_SERVICE_ID =", editService.getString("CONTRACT_SERVICE_ID")).addQuery("ORG_PERIOD_ID =", ORG_PERIOD_ID).orderBy("PERIOD_BEGIN_DATE").desc().list();

                    DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE WHERE CONTRACT_SERVICE_PERIOD_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE ORG_PERIOD_ID=? AND PERIOD_END_DATE>=?)", new Object[]{ORG_PERIOD_ID, EFFECTIVE_DATE_STR});

                    DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE ORG_PERIOD_ID=? AND PERIOD_END_DATE>=?", new Object[]{ORG_PERIOD_ID, EFFECTIVE_DATE_STR});


                    BO lastServicePeriod = servicePeriodList2.get(0);
                    LocalDate last_period_begin_date = getLocalDate(lastServicePeriod.get("PERIOD_BEGIN_DATE"));
                    LocalDate last_period_end_date = getLocalDate(lastServicePeriod.get("PERIOD_END_DATE"));

                    //新的计费周期
                    BO servicePeriodAdj = new BO();

                    servicePeriodAdj.setAll(firstServicePeriod.asMap());
                    servicePeriodAdj.setId(null);

                    servicePeriodAdj.set("PERIOD_BEGIN_DATE", EFFECTIVE_DATE);
                    servicePeriodAdj.set("PERIOD_END_DATE", last_period_end_date);
                    servicePeriodAdj.set("QUANTITY", editService.get("QUANTITY"));
                    servicePeriodAdj.set("ORG_PERIOD_ID", ORG_PERIOD_ID);

                    LocalDate start = getPreviousPeriodStart(last_period_end_date.plusDays(1), editService.getString("PERIOD_TYPE"));
                    BigDecimal periodRateAdj = divideToBigDecimal(GetPeriodDays(EFFECTIVE_DATE, last_period_end_date), GetPeriodDays(start, last_period_end_date));    // 首周期占整周期比例
                    servicePeriodAdj.set("PERIOD_PRICE", multiply(editService.get("TOTAL_PRICE"), periodRateAdj));
                    servicePeriodAdj.set("PERIOD_COST", multiply(SERVICE_TOTAL_COST, periodRateAdj));

                    servicePeriodAdj.set("PARTYA_PERIOD_PRICE", multiply(editService.get("PARTYA_TOTAL_PRICE"), periodRateAdj));

                    servicePeriodAdj.set("PERIOD_ADJUSTED_PRICE", servicePeriodAdj.get("PERIOD_PRICE"));
                    SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", servicePeriodAdj, uc, conn);


                    List<BO> sbsList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE").connection(conn).addQuery("CONTRACT_SERVICE_PERIOD_ID =", ORG_PERIOD_ID).list();
                    if (sbsList != null && sbsList.size() > 0) for (BO sbs : sbsList) {
                        BO newSbs = new BO();
                        newSbs.setAll(sbs.asMap());
                        newSbs.setId(null);
                        newSbs.set("CONTRACT_SERVICE_PERIOD_ID", servicePeriodAdj.getId());
                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", newSbs, uc, conn);
                    }

                    SATRT_DATE = last_period_end_date.plusDays(1);
                }

                String SATRT_DATE_STR = SATRT_DATE.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                //SATRT_DATE 之后，全部删除，重新计算
                DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE WHERE CONTRACT_SERVICE_PERIOD_ID IN (SELECT ID FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE CONTRACT_SERVICE_ID=? AND PERIOD_END_DATE>=?)", new Object[]{editService.getString("CONTRACT_SERVICE_ID"), SATRT_DATE_STR});

                DBSql.update(conn, "DELETE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE CONTRACT_SERVICE_ID=? AND PERIOD_END_DATE>=?", new Object[]{editService.getString("CONTRACT_SERVICE_ID"), SATRT_DATE_STR});

                LocalDate END_DATE = getLocalDate(contract.get("END_DATE"));//合同结束日期

                //服务包上的服务
                List<String> sbsList = null;
                String serviceIds = DBSql.getString("select SERVICE_ID from BO_EU_DNIVT_SERVICE_BUNDLE where ID=?", new Object[]{editService.get("OBJECT_ID")});
                if (StringUtils.isNotBlank(serviceIds)) {
                    sbsList = Arrays.asList(serviceIds.split(","));
                }

                //生成服务周期
                if (SATRT_DATE.isBefore(END_DATE)) {
                    //循环添加全部周期
                    while (!SATRT_DATE.isAfter(END_DATE)) {

                        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));
                            periodEnd = END_DATE;
                        }

                        BO csp = new BO();//服务周期

                        csp.setAll(firstServicePeriod.asMap());
                        csp.set("PERIOD_BEGIN_DATE", SATRT_DATE);
                        csp.set("PERIOD_END_DATE", periodEnd);
                        csp.set("QUANTITY", editService.get("QUANTITY"));

                        BigDecimal period_price = multiply(editService.get("UNIT_PRICE"), editService.get("QUANTITY"));
                        period_price = periodRate.multiply(period_price);
                        csp.set("PERIOD_PRICE", period_price);
                        contractServiceLogger.info("计算-csp-PERIOD_COST");

                        csp.set("PERIOD_COST", periodRate.multiply(SERVICE_TOTAL_COST));
                        csp.set("PERIOD_ADJUSTED_PRICE", csp.get("PERIOD_PRICE"));

                        csp.set("PARTYA_PERIOD_PRICE", multiply(editService.get("PARTYA_TOTAL_PRICE"), periodRate));//三方合同甲方含税总价

                        SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", csp, uc, conn);

                        if (editService.getString("OBJECT_TYPE").equals("2"))//服务包
                        {
                            //本周期成本
                            String prorated_cost_change = DBSql.getString(conn, "select UNIT_COST from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{editService.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", csp.get("VENDOR_ACCOUNT_ID"));
                                    cspbs.set("PERIOD_COST", prorated_cost_change);
                                    cspbs.setBindId(csp.getString("BINDID"));
                                    SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD_BUNDLE_SERVICE", cspbs, uc, conn);
                                }
                            }
                        }
                        if (periodEnd != null) {
                            SATRT_DATE = periodEnd.plusDays(1);
                        }
                    }
                }

                String PERIOD = editService.getString("PERIOD");//服务起止日期
                String ADJUST_PERIOD = editService.getString("ADJUST_PERIOD");//服务调整的起止日期
                LocalDate PERIOD_END;
                if (StringUtils.isNotBlank(PERIOD)) {
                    PERIOD_END = getLocalDate(PERIOD.split("~")[1]);
                    PERIOD = PERIOD.split("~")[0] + "~" + LocalDateYYYYMMDD(END_DATE);
                } else {
                    PERIOD_END = getLocalDate(ADJUST_PERIOD.split("~")[1]);
                    ADJUST_PERIOD = ADJUST_PERIOD.split("~")[0] + "~" + LocalDateYYYYMMDD(END_DATE);
                }

                //缩短
                if (PERIOD_END.isAfter(END_DATE)) {
                    if (StringUtils.isNotBlank(ADJUST_PERIOD)) {
                        LocalDate ADJUST_PERIOD_END = getLocalDate(ADJUST_PERIOD.split("~")[1]);
                        if (ADJUST_PERIOD_END.isAfter(END_DATE)) {
                            PERIOD = null;
                            ADJUST_PERIOD = ADJUST_PERIOD.split("~")[0] + "~" + LocalDateYYYYMMDD(END_DATE);
                        }
                    }
                }
                DBSql.update(conn, "update BO_EU_DNCTT_CONTRACT_SERVICE set PERIOD=?,ADJUST_PERIOD=? where ID=? and BINDID=?", new Object[]{PERIOD, ADJUST_PERIOD, editService.get("CONTRACT_SERVICE_ID"), editService.get("BINDID")});
            }
        }
        editService.set("IS_EFFECTIVE", "已执行");
        SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_EDIT", editService, conn);

        //如果在此次调整之后还有调整，则设为失效状态

        contractServiceBO.set("OLD_QUANTITY", editService.get("OLD_QUANTITY"));
        contractServiceBO.set("OLD_UNIT_PRICE", editService.get("OLD_UNIT_PRICE"));
        contractServiceBO.set("OLD_TOTAL_PRICE", editService.get("OLD_TOTAL_PRICE"));
        contractServiceBO.set("OLD_UNIT_COST", editService.get("OLD_UNIT_COST"));
        contractServiceBO.set("OLD_TOTAL_COST", editService.get("OLD_TOTAL_COST"));

        contractServiceBO.set("QUANTITY", editService.get("QUANTITY"));
        contractServiceBO.set("UNIT_PRICE", editService.get("UNIT_PRICE"));
        contractServiceBO.set("TOTAL_PRICE", editService.get("TOTAL_PRICE"));
        contractServiceBO.set("UNIT_COST", editService.get("UNIT_COST"));
        contractServiceBO.set("TOTAL_COST", editService.get("TOTAL_COST"));

        contractServiceBO.set("PARTYA_TOTAL_PRICE", editService.get("PARTYA_TOTAL_PRICE"));

        SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE", contractServiceBO, conn);

        contractServiceLogger.info("服务调整成功");

        return true;
    }

    /**
     * 一个服务对应添加一个合同产品
     * @param uc
     * @param contract
     * @param conn
     * @return
     * @throws SQLException
     */
    public boolean AddContractServiceProduct(UserContext uc, RowMap contract, Connection conn) throws SQLException {
        //获取服务列表
        List<BO> serviceList = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE").connection(conn).addQuery("BINDID =", contract.get("BINDID")).list();
        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);
        }

        for (BO service : serviceList) {
            AddContractServiceProductOne(uc, contract, service, conn);
        }
        return true;
    }


    /**
     * 增加服务产品
     * @param uc
     * @param contract
     * @param service
     * @param conn
     * @return
     * @throws SQLException
     */
    public boolean AddContractServiceProductOne(UserContext uc, RowMap contract, BO service, Connection conn) throws SQLException {

        String PRODUCT_ID = null;
        if (service.getString("OBJECT_TYPE").equals("2"))//服务包
        {
            // String serviceIDS = DBSql.getString(conn, "select SERVICE_ID from BO_EU_DNIVT_SERVICE_BUNDLE where ID=?", new Object[]{service.getString("OBJECT_ID")});
            RowMap serviceBUNDLE = DBSql.getMap(conn, "SELECT SERVICE_ID,UNIT_COST,UNIT_PRICE FROM BO_EU_DNIVT_SERVICE_BUNDLE WHERE ID=?", new Object[]{service.getString("OBJECT_ID")});
            String serviceIDS = serviceBUNDLE.getString("SERVICE_ID");

            Double BUNDLE_UNIT_COST = serviceBUNDLE.getDouble("UNIT_COST");
            Double BUNDLE_UNIT_PRICE = serviceBUNDLE.getDouble("UNIT_PRICE");

            List<RowMap> ivtServiceList = DBSql.getMaps(conn, "select ID,UNIT_COST,UNIT_PRICE from BO_EU_DNIVT_SERVICE where FIND_IN_SET(ID,?)", new Object[]{serviceIDS});

            RowMap ivtServiceSum = DBSql.getMap(conn, "SELECT SUM(UNIT_COST) AS SUM_COST,SUM(UNIT_PRICE) AS SUM_PRICE  FROM BO_EU_DNIVT_SERVICE where FIND_IN_SET(ID,?)", new Object[]{serviceIDS});

            Map<String, Double> unitPriceMap = new HashMap<String, Double>();
            Map<String, Double> unitCostMap = new HashMap<String, Double>();
            for (RowMap ivtService : ivtServiceList) {
                Double UNIT_PRICE = ivtService.getDouble("UNIT_PRICE");
                Double UNIT_COST = ivtService.getDouble("UNIT_COST");

                UNIT_PRICE = ivtServiceSum.getDouble("SUM_PRICE") == 0 ? 0.0 : BUNDLE_UNIT_PRICE * UNIT_PRICE / ivtServiceSum.getDouble("SUM_PRICE");
                unitPriceMap.put(ivtService.getString("ID"), UNIT_PRICE);

                UNIT_COST = ivtServiceSum.getDouble("SUM_COST") == 0 ? 0.0 : BUNDLE_UNIT_COST * UNIT_COST / ivtServiceSum.getDouble("SUM_COST");
                unitCostMap.put(ivtService.getString("ID"), UNIT_COST);
            }

            for (String serviceID : serviceIDS.split(",")) {
                String contractCostId = DBSql.getString(conn, "select ID from BO_EU_DNCTT_CONTRACT_COST where IS_SERVICE_PRODUCT=1 AND  CONTRACT_ID=? and SERVICE_ID=? and PRODUCT_ID in(select PRODUCT_ID from BO_EU_DNIVT_SERVICE where ID=?)", new Object[]{contract.getString("ID"), service.getString("ID"), serviceID});

                String NEED_PURCHASE = service.getString("NEED_PURCHASE");
                if (NEED_PURCHASE.equals("1")) {
                    Map<String, String> contractCost = new HashMap<String, String>();
                    contractCost.put("ID", contractCostId);
                    contractCost.put("SERVICE_ID", service.getString("ID"));
                    contractCost.put("SERVICE_NAME", service.getString("NAME"));
                    contractCost.put("serviceID", serviceID);
                    contractCost.put("CONTRACT_ID", contract.getString("ID"));
                    contractCost.put("QUANTITY", service.getString("QUANTITY"));

                    contractCost.put("UNIT_PRICE", String.valueOf(unitPriceMap.get(serviceID)));
                    contractCost.put("UNIT_COST", String.valueOf(unitCostMap.get(serviceID)));

                    //采购
                    contractCost.put("PURCHASE_PERIOD_TYPE", service.getString("PURCHASE_PERIOD_TYPE"));//采购周期
                    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, service.asMap());
                } else {
                    //是否 删除 contractCostId  没有采购 没有拣货时可删除
                    if (DBSql.getInt(conn, "select count(*) from BO_EU_DNCTT_CONTRACT_COST_PRODUCT where CONTRACT_COST_ID=?", new Object[]{contractCostId}) == 0 && DBSql.getInt(conn, "select count(*) from BO_EU_DNIVT_ORDER_PRODUCT where CONTRACT_COST_ID=?", new Object[]{contractCostId}) == 0) {
                        String contractCostBINDId = DBSql.getString(conn, "select BINDID from BO_EU_DNCTT_CONTRACT_COST where ID=?", new Object[]{contractCostId});
                        SDK.getProcessAPI().deleteById(contractCostBINDId, uc.getUID());
                        DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_COST where ID=?", new Object[]{contractCostId});
                    }
                }

            }
        } else {
            String contractCostId = DBSql.getString(conn, "select ID from BO_EU_DNCTT_CONTRACT_COST where IS_SERVICE_PRODUCT=1 AND CONTRACT_ID=? and SERVICE_ID=?", new Object[]{contract.getString("ID"), service.getString("ID")});
            String NEED_PURCHASE = service.getString("NEED_PURCHASE");
            if (NEED_PURCHASE.equals("1")) {
                Map<String, String> contractCost = new HashMap<String, String>();
                contractCost.put("ID", contractCostId);
                contractCost.put("SERVICE_ID", service.getString("ID"));
                contractCost.put("SERVICE_NAME", service.getString("NAME"));
                contractCost.put("serviceID", service.getString("OBJECT_ID"));
                contractCost.put("CONTRACT_ID", contract.getString("ID"));
                contractCost.put("QUANTITY", service.getString("QUANTITY"));

                contractCost.put("UNIT_PRICE", service.getString("UNIT_PRICE"));
                contractCost.put("UNIT_COST", service.getString("UNIT_COST"));

                //采购
                contractCost.put("PURCHASE_PERIOD_TYPE", service.getString("PURCHASE_PERIOD_TYPE"));//采购周期

                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, service.asMap());
            } else {
                //是否 删除 contractCostId
                //是否 删除 contractCostId  没有采购 没有拣货时可删除
                if (DBSql.getInt(conn, "select count(*) from BO_EU_DNCTT_CONTRACT_COST_PRODUCT where CONTRACT_COST_ID=?", new Object[]{contractCostId}) == 0 && DBSql.getInt(conn, "select count(*) from BO_EU_DNIVT_ORDER_PRODUCT where CONTRACT_COST_ID=?", new Object[]{contractCostId}) == 0) {
                    String contractCostBINDId = DBSql.getString(conn, "select BINDID from BO_EU_DNCTT_CONTRACT_COST where ID=?", new Object[]{contractCostId});
                    SDK.getProcessAPI().deleteById(contractCostBINDId, uc.getUID());
                    DBSql.update(conn, "delete from BO_EU_DNCTT_CONTRACT_COST where ID=?", new Object[]{contractCostId});
                }
            }
        }
        return true;
    }


    /**
     * 一个服务对应添加一个合同产品
     * @param uc
     * @return
     */
    public boolean AddContractServiceProduct(UserContext uc, RowMap contract) throws SQLException {
        contractServiceLogger.info("一个服务对应添加一个合同产品");

        Connection conn = null;
        try {
            conn = DBSql.open();
            conn.setAutoCommit(false);
            AddContractServiceProduct(uc, contract, conn);
            conn.commit();
            return true;
        } catch (SQLException e) {
            e.printStackTrace();
            conn.rollback();
        } finally {
            if (conn != null) DBSql.close(conn);
        }
        return false;
    }


    /**
     * 新增合同产品
     * @param uc
     * @param service
     * @return
     */
    private String AddContractServiceProduct(UserContext uc, Connection conn, Map<String, String> service, Map<String, Object> serviceBO) {
        contractServiceLogger.info("新增合同产品");
        System.out.println(service);
        BO contractCost = null;
        if (service.get("ID") != null && StringUtils.isNotBlank(service.get("ID").toString())) {
            contractCost = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_COST", service.get("ID"));
        }
        if (contractCost == null) {
            ProcessInstance contractInstance = SDK.getProcessAPI().createBOProcessInstance("obj_87c6ef75d18f44cc8e85d3d4e818c303", uc.getUID(), "新增合同产品");
            contractCost = new BO();
            contractCost.setBindId(contractInstance.getId());
        }
        contractCost.set("CONTRACT_ID", service.get("CONTRACT_ID"));
        contractCost.set("SERVICE_ID", service.get("SERVICE_ID"));
        contractCost.set("QUANTITY", service.get("QUANTITY"));
        contractCost.set("NAME", service.get("SERVICE_NAME"));

        contractCost.set("PRODUCT_ID", DBSql.getString(conn, "select PRODUCT_ID from BO_EU_DNIVT_SERVICE where ID=?", new Object[]{service.get("serviceID")}));

        if (StringUtils.isNotBlank(contractCost.getString("PRODUCT_ID"))) {
            BO product = SDK.getBOAPI().get("BO_EU_DNIVT_PRODUCT", contractCost.getString("PRODUCT_ID"));

            if (product != null) {
                contractCost.set("DESCRIPTION", product.getString("DESCRIPTION"));
                contractCost.set("COST_CODE_ID", product.getString("COST_CODE_ID"));
                contractCost.set("PRODUCT_NAME", product.getString("NAME"));
                contractCost.set("TAX_CATEGORY_ID", DBSql.getString("select TAX_CATEGORY_ID from BO_EU_DND_COST_CODE where OID=?", new Object[]{product.getString("COST_CODE_ID")}));
                contractCost.set("COST_CODE_NAME", DBSql.getString("select NAME from BO_EU_DND_COST_CODE where OID=?", new Object[]{product.getString("COST_CODE_ID")}));
            }
        }


        //服务表上面 采购税种
        /*
        String PURCHASE_TAX_CATEGORY_ID = serviceBO.get("PURCHASE_TAX_CATEGORY_ID") == null ? null : serviceBO.get("PURCHASE_TAX_CATEGORY_ID").toString();
        if (StringUtils.isBlank(PURCHASE_TAX_CATEGORY_ID))
            PURCHASE_TAX_CATEGORY_ID = serviceBO.get("TAX_CATEGORY_ID") == null ? null : serviceBO.get("TAX_CATEGORY_ID").toString();

        contractCost.set("TAX_CATEGORY_ID", PURCHASE_TAX_CATEGORY_ID);
        */

        if (serviceBO.get("VAT_TAX").toString().equals("1")) {
            contractCost.set("TAX_CATEGORY_ID", serviceBO.get("PURCHASE_TAX_CATEGORY_ID"));
        } else {
            contractCost.set("TAX_CATEGORY_ID", DBSql.getString("SELECT OID,NAME,ORGID,GENERAL_TABLE_ID,EXT1,CLOSED,IS_ACTIVE FROM BO_EU_DND_GENERAL WHERE  GENERAL_TABLE_ID=44 AND ORGID=? AND EXT1=0", new Object[]{uc.getCompanyModel().getId()}));
        }

        contractCost.set("IS_SERVICE_PRODUCT", 1);
        //单价
        contractCost.set("UNIT_COST", service.get("UNIT_COST"));
        //成本
        contractCost.set("UNIT_PRICE", service.get("UNIT_PRICE"));

        contractCost.set("EXTENDED_COST", multiply(service.get("UNIT_COST"), service.get("QUANTITY")));//总成本
        contractCost.set("EXTENDED_PRICE", multiply(service.get("UNIT_PRICE"), service.get("QUANTITY")));//总价

        //计费客户
        contractCost.set("BILL_ACCOUNT_ID", serviceBO.get("BILL_TO_ACCOUNT_ID"));

        // 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);
//2025年11月5日 服务产品的 单价取采购单价、采购总价
        /*
        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"));

        if (periodList != null && periodList.size() > 0) {
            //首月成本单独计算  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().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);
            }
            //单元成本
            contractCost.set("UNIT_COST", divideToBigDecimal(contractCost.get("EXTENDED_COST"), contractCost.get("QUANTITY")));
            contractServiceLogger.info("计算合同成本周期结束：" + EXTENDED_COST);
        }


        try {
            //总价格
            contractCost.set("EXTENDED_PRICE", DBSql.getDouble(conn, "SELECT SUM(PERIOD_ADJUSTED_PRICE) as PRICE FROM BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD WHERE CONTRACT_SERVICE_ID='" + service.get("SERVICE_ID") + "'", "PRICE"));

            contractCost.set("UNIT_PRICE", divideToBigDecimal(contractCost.get("EXTENDED_PRICE"), contractCost.get("QUANTITY")));
        } catch (Exception e) {

        }
*/

        if (contractCost.isNew()) {
            //成本子类:合同成本
            contractCost.set("SUB_CATE_ID", "1314");
            SDK.getBOAPI().create("BO_EU_DNCTT_CONTRACT_COST", contractCost, contractCost.getBindId(), uc.getUID(), conn);
        } else {
            SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_COST", contractCost, conn);
        }

        return contractCost.getId();
    }

    public static String LocalDateYYYYMMDD(LocalDate date) {
        return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
    }

    /**
     * 数值相乘
     * @param obj1
     * @param obj2
     * @return
     */
    public static BigDecimal multiply(Object obj1, Object obj2) {
        try {
            if (obj1 == null || obj2 == null) return BigDecimal.ZERO;

            BigDecimal num1 = new BigDecimal(String.valueOf(obj1));
            BigDecimal num2 = new BigDecimal(String.valueOf(obj2));
            // 执行乘法运算
            BigDecimal result = num1.multiply(num2).setScale(8, RoundingMode.HALF_UP);
            ;
            // 输出结果
            contractServiceLogger.info("Result: " + result);
            return result;
        } catch (Exception e) {
            contractServiceLogger.error("multiply--info--失败。。。" + obj1 + "*" + obj2);
        }
        return BigDecimal.ZERO; // 返回默认值 0
    }

    // 误差范围：0.00001
    private static final BigDecimal EPSILON = new BigDecimal("0.00001");

    /**
     * 将Object转换为BigDecimal
     * @param obj 待转换的对象（支持Number、String等可转换为数字的类型）
     * @return 转换后的BigDecimal
     * @throws IllegalArgumentException 若对象无法转换为BigDecimal
     */
    public static BigDecimal toBigDecimal(Object obj) {
        if (obj == null) {
            // throw new IllegalArgumentException("对象不能为null");
            return BigDecimal.ZERO;
        }

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

    /**
     * 判断两个Object转换后的BigDecimal是否在0.00001误差范围内相等
     * @param obj1 第一个对象
     * @param obj2 第二个对象
     * @return true（误差范围内相等），false（不相等）
     */
    public static boolean isApproximatelyEqual(Object obj1, Object obj2) {
        BigDecimal bd1 = toBigDecimal(obj1);
        BigDecimal bd2 = toBigDecimal(obj2);

        // 计算两个数的差值绝对值
        BigDecimal difference = bd1.subtract(bd2).abs();

        // 比较差值是否小于等于误差范围
        return difference.compareTo(EPSILON) <= 0;
    }

    /**
     * 返回合同周期天数除以服务周期天数的比例，保留8位小数
     * @param contractPeriod
     * @param servicePeriod
     * @return
     */
    public BigDecimal GetPeriodRate(String contractPeriod, String servicePeriod) {
        if (Objects.equals(contractPeriod, servicePeriod)) {
            return BigDecimal.valueOf(1);
        }
        if (contractPeriod.equals(cttConstant.PERIOD_TYPE_ONE_TIME) || servicePeriod.equals(cttConstant.PERIOD_TYPE_ONE_TIME)) {
            return BigDecimal.valueOf(0);
        }
        int ctt = GetPeriodMonth(contractPeriod);
        int svc = GetPeriodMonth(servicePeriod);
        if (ctt == 0 || svc == 0) {
            return BigDecimal.valueOf(0);
        }

        // 将 ctt 和 svc 转换为 BigDecimal
        BigDecimal cttDecimal = BigDecimal.valueOf(ctt);
        BigDecimal svcDecimal = BigDecimal.valueOf(svc);

        // 执行除法并保留 8 位小数，使用四舍五入
        return cttDecimal.divide(svcDecimal, 8, RoundingMode.HALF_UP);
    }


    /**
     * 获取合同服务首周期的开始和结束日期
     *
     * @param contractStart  合同开始日期
     * @param contractEnd    合同结束日期
     * @param serviceEffect  服务生效日期
     * @param contractPeriod 合同周期
     * @param servicePeriod  服务周期
     * @return 包含首周期开始日期、结束日期以及是否为周期起始日期的结果对象
     */
    public Map<String, Object> getFirstPeriodDate(LocalDate contractStart, LocalDate contractEnd, LocalDate serviceEffect, String contractPeriod, String servicePeriod) {
        // 创建一个Map对象用于存储计算结果
        Map<String, Object> dto = new HashMap<>();

        // 将合同周期和服务周期转换为月数
        int cttPeriod = GetPeriodMonth(contractPeriod);
        int svcPeriod = GetPeriodMonth(servicePeriod);

        // 初始化开始日期为合同开始日期
        LocalDate start = contractStart;
        LocalDate end;

        if (serviceEffect.isBefore(start)) {
            dto.put("periodStart", serviceEffect);
            dto.put("periodEnd", start.minusDays(1));
            dto.put("isStartOfPeriod", false);
            // 返回结果
            return dto;
        }

        // 遍历每个合同周期，直到合同结束日期
        while (!start.isAfter(contractEnd)) {
            // 计算当前周期的结束日期
            end = getNextPeriodStart(start, contractPeriod).minusDays(1);

            // 检查服务是否在当前合同周期内生效
            if (!serviceEffect.isBefore(start) && !serviceEffect.isAfter(end)) {
                // 服务生效，计算服务周期的开始和结束日期
                if (end.isAfter(contractEnd)) {
                    end = contractEnd;
                }
                LocalDate periodEnd;
                if (svcPeriod > cttPeriod) {
                    // 如果服务周期大于合同周期，计算服务周期的结束日期
                    periodEnd = getNextPeriodStart(start, servicePeriod).minusDays(1);
                } else {
                    // 否则，服务周期结束日期与合同周期相同
                    periodEnd = end;
                }

                //不能超过合同结束日期
                if (periodEnd.isAfter(contractEnd)) {
                    periodEnd = contractEnd;
                }

                // 判断是否为服务周期的起始日期
                dto.put("periodStart", start);
                dto.put("periodEnd", periodEnd);
                dto.put("isStartOfPeriod", start.isEqual(serviceEffect));
                // 返回结果
                return dto;
            } else {
                // 服务未生效，移动到下一个合同周期
                start = end.plusDays(1);
            }
        }
        // 如果没有找到服务周期，返回默认值
        dto.put("periodStart", LocalDate.MIN);
        dto.put("periodEnd", LocalDate.MIN);
        dto.put("isStartOfPeriod", false);
        // 返回结果
        return dto;
    }


    /**
     * 根据不同的周期类型转换为对应的月数
     * 此方法用于将给定的周期类型字符串转换为相应的月数
     * 主要用于处理不同时间周期的转换，以便统一时间单位
     *
     * @param period 周期类型字符串，表示不同的周期类型
     * @return 对应的月数如果输入的周期类型不匹配已知类型，则返回0
     */
    private 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;
        }
    }

    /**
     * 获取下一周期开始时间
     * @param start 当前周期开始时间
     * @param periodType 周期类型
     * @return 下一周期开始时间
     */
    public static LocalDate getNextPeriodStart(LocalDate start, String periodType) {
        // 根据不同的周期类型计算下一周期的开始时间
        switch (periodType) {
            case cttConstant.PERIOD_TYPE_MONTH:
                // 如果是月周期，则在当前时间基础上增加1个月
                return start.plus(1, ChronoUnit.MONTHS);
            case cttConstant.PERIOD_TYPE_QUARTER:
                // 如果是季度周期，则在当前时间基础上增加3个月
                return start.plus(3, ChronoUnit.MONTHS);
            case cttConstant.PERIOD_TYPE_HALFYEAR:
                // 如果是半年周期，则在当前时间基础上增加6个月
                return start.plus(6, ChronoUnit.MONTHS);
            case cttConstant.PERIOD_TYPE_YEAR:
                // 如果是年周期，则在当前时间基础上增加1年
                return start.plus(1, ChronoUnit.YEARS);
            default:
                // 如果周期类型不匹配任何已知类型，则返回原始开始时间
                return start;
        }
    }

    /**
     * 计算上一个周期开始时间
     * @param end
     * @param periodType
     * @return
     */
    public static LocalDate getPreviousPeriodStart(LocalDate end, String periodType) {
        switch (periodType) {
            case cttConstant.PERIOD_TYPE_MONTH:
                // 如果是月周期，则在当前时间基础上减少1个月
                return end.minus(1, ChronoUnit.MONTHS);
            case cttConstant.PERIOD_TYPE_QUARTER:
                // 如果是季度周期，则在当前时间基础上减少3个月
                return end.minus(3, ChronoUnit.MONTHS);
            case cttConstant.PERIOD_TYPE_HALFYEAR:
                // 如果是半年周期，则在当前时间基础上减少6个月
                return end.minus(6, ChronoUnit.MONTHS);
            case cttConstant.PERIOD_TYPE_YEAR:
                // 如果是年周期，则在当前时间基础上减少1年
                return end.minus(1, ChronoUnit.YEARS);
            default:
                // 如果周期类型不匹配任何已知类型，则返回原始开始时间
                return end;
        }
    }

    /**
     * object 转换成LocalDate
     */
    public static LocalDate getLocalDate(Object obj) {
        try {
            if (obj == null) return null;
            if (obj instanceof LocalDate) {
                return (LocalDate) obj;
            } else if (obj instanceof String) {
                if (StringUtils.isBlank(obj.toString())) return null;
                // 定义日期格式
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                // 解析字符串为 LocalDate
                contractServiceLogger.info("getLocalDate" + obj.toString());
                return LocalDate.parse(obj.toString(), formatter);
            } else if (obj instanceof Date) {
                return ((Date) obj).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            } else {
                contractServiceLogger.info("Invalid object type: " + obj.getClass().getName());
                //throw new IllegalArgumentException("Invalid object type: " + obj.getClass().getName());
                return null;
            }
        } catch (Exception e) {
            contractServiceLogger.info("getLocalDate" + e.getMessage());
        }
        return null;
    }

    /**
     * 计算两个日期之间的天数
     * @param start 开始日期
     * @param end   结束日期
     * @return 两个日期之间的天数
     */
    public static int GetPeriodDays(LocalDate start, LocalDate end) {
        return (int) ChronoUnit.DAYS.between(start, end) + 1;
    }

    // 定义一个方法，将两个 int 相除并返回 BigDecimal
    public static BigDecimal divideToBigDecimal(int numerator, int denominator) {
        if (denominator == 0) {
            throw new ArithmeticException("分母不能为零");
        }

        // 使用 BigDecimal 进行精确计算
        BigDecimal num = new BigDecimal(numerator);
        BigDecimal den = new BigDecimal(denominator);

        // 设置小数点后的位数和舍入模式
        return num.divide(den, 10, RoundingMode.HALF_UP); // 保留 10 位小数
    }


    /**
     * 计算日期区间的比例值
     * @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
     * @param denominator
     * @return
     */
    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);
    }

    /**
     * 合同例外因素更新（添加和删除）
     * @param uc
     * @param bindid
     * @return
     */
    public boolean ContractExcManage(UserContext uc, String bindid) {

        RowMap rowMap = DBSql.getMap("SELECT ID,BINDID,EXCLUSION_CONTRACT_ID,EXCLUSION_COST_CODE,EXCLUSION_ROLE FROM BO_EU_DNCTT_CONTRACT WHERE BINDID= ?", bindid);

        String exclusionContractId = rowMap.getString("EXCLUSION_CONTRACT_ID");// 获取排除合同ID
        String exclusionCostCode = rowMap.getString("EXCLUSION_COST_CODE");// 获取排除费用代码
        String exclusionRole = rowMap.getString("EXCLUSION_ROLE");// 获取排除角色

        // 判断排除费用代码是否为空，如果为空，则清空排除费用代码
        if (StringUtils.isBlank(exclusionCostCode)) {
            //清空
            //DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE SET CLOSED =1 WHERE BINDID = ? OR CONTRACT_ID=?", new Object[]{rowMap.getString("ID"), rowMap.getString("BINDID")});
            DBSql.update("DELETE FROM BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE WHERE BINDID = ? OR CONTRACT_ID=?", new Object[]{rowMap.getString("ID"), rowMap.getString("BINDID")});
        } else {
            String[] exclusionCostCodeArr = exclusionCostCode.split(",");
            for (String costCode : exclusionCostCodeArr) {
                /*
                String ccID = DBSql.getString("SELECT ID FROM BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE WHERE (BINDID =? OR CONTRACT_ID=?) AND COST_CODE_ID = ?", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), costCode});
                //更新
                if (StringUtils.isNotBlank(ccID)) {
                    DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE SET CLOSED =0,BINDID =?,CONTRACT_ID=? WHERE ID = ?", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), ccID});
                    continue;
                }
                 */
                //新增
                BO bo = new BO();
                bo.set("BINDID", rowMap.getString("BINDID"));
                bo.set("CONTRACT_ID", rowMap.getString("ID"));
                bo.set("COST_CODE_ID", costCode);
                SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE", bo, uc);
            }

            //删除
            //DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE SET CLOSED =1 WHERE (BINDID = ? OR CONTRACT_ID=?) AND COST_CODE_ID NOT IN (?)", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), exclusionCostCode});

            DBSql.update("DELETE FROM BO_EU_DNCTT_CONTRACT_EXCLUSION_COST_CODE WHERE (BINDID = ? OR CONTRACT_ID=?) AND COST_CODE_ID NOT IN (?)", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), exclusionCostCode});
        }

        if (StringUtils.isBlank(exclusionRole)) {
            //清空
            // DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE SET CLOSED =1 WHERE BINDID = ? OR CONTRACT_ID=?", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID")});
            DBSql.update("DELETE FROM BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE WHERE BINDID = ? OR CONTRACT_ID=?", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID")});
        } else {
            String[] exclusionRoleArr = exclusionRole.split(",");
            for (String role : exclusionRoleArr) {
                /*
                String roleID = DBSql.getString("SELECT ID FROM BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE WHERE (BINDID =? OR CONTRACT_ID=?) AND ROLE_ID = ?", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), role});
                //更新
                if (StringUtils.isNotBlank(roleID)) {
                    DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE SET CLOSED =0,BINDID =?,CONTRACT_ID=? WHERE ID = ?", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), roleID});
                    continue;
                }

                 */
                //新增
                BO bo = new BO();
                bo.set("BINDID", rowMap.getString("BINDID"));
                bo.set("CONTRACT_ID", rowMap.getString("ID"));
                bo.set("ROLE_ID", role);
                SDK.getBOAPI().createDataBO("BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE", bo, uc);
            }

            //删除
            // DBSql.update("UPDATE BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE SET CLOSED =1 WHERE (BINDID = ? OR CONTRACT_ID=?) AND ROLE_ID NOT IN (?)", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), exclusionRole});
            DBSql.update("DELETE FROM BO_EU_DNCTT_CONTRACT_EXCLUSION_ROLE WHERE (BINDID = ? OR CONTRACT_ID=?) AND ROLE_ID NOT IN (?)", new Object[]{rowMap.getString("BINDID"), rowMap.getString("ID"), exclusionRole});
        }
        return true;
    }


    /**
     * 提交服务费费用
     * @param uc
     * @param approveDate
     * @param approveIds
     * @return
     */
    public boolean postRecurringServices(UserContext uc, String approveDate, String approveIds) throws SQLException {

        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);

            String feeDate = null;
            if (StringUtils.isNotBlank(approveDate)) {
                // 简单替换分隔符后拼接（如2025-11-19 → 202511）
                feeDate = approveDate.replace("-", "").substring(0, 6);
            }
            for (String approveId : approveIds.split(",")) {

                BO cad = new BO();// crm_account_deduction();//审批并提交表实体对象
                String TAX_CATEGORY_ID = null;//物料计费代码
                String CONTRACT_SERVICE_ID = null;//合同服务ID
                String CONTRACT_ID = null;//合同ID

                String ORGID = uc.getCompanyModel().getId();

                //BO_EU_DNCTT_CONTRACT
                BO bo = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT", approveId);//合同初始费用
                if (bo != null) {
                    CONTRACT_ID = bo.getId();
                    bo.set("APPROVE_AND_POST_DATE", approveDate);
                    bo.set("SETUP_FEE_APPROVE_POST_USER_ID", uc.getUID());
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT", bo, connUpdate);

                    cad.set("TYPE_ID", cttConstant.ACCOUNT_DEDUCTION_TYPE_INITIAL_COST);//类型(合同初始费用)
                    cad.set("OBJECT_ID", approveId);
                    cad.set("CONTRACT_ID", approveId);
                    cad.set("QUANTITY", 1);
                    cad.set("BILL_CREATE_USER_ID", bo.getCreateUser());//创建人
                    cad.set("ACCOUNT_ID", bo.getString("ACCOUNT_ID"));
                    cad.set("BILL_ACCOUNT_ID", bo.getString("BILL_TO_ACCOUNT_ID"));
                    cad.set("EXTENDED_PRICE", bo.get("ADJUST_SETUP_FEE"));

                    if (StringUtils.isNotBlank(bo.getString("SETUP_FEE_COST_CODE_ID"))) {
                        TAX_CATEGORY_ID = DBSql.getString("select TAX_CATEGORY_ID from BO_EU_DND_COST_CODE where OID=? and ORGID=?", new Object[]{bo.getString("SETUP_FEE_COST_CODE_ID"), ORGID});
                        //SDK.getBOAPI().get("BO_EU_DND_COST_CODE", bo.getString("SETUP_FEE_COST_CODE_ID"), "TAX_CATEGORY_ID").toString();
                    }
                } else {

                    //BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD
                    bo = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", approveId);
                    if (bo != null) {
                        CONTRACT_SERVICE_ID = bo.getString("CONTRACT_SERVICE_ID");
                        CONTRACT_ID = bo.getString("CONTRACT_ID");
                        cad.set("CONTRACT_ID", CONTRACT_ID);
                        bo.set("APPROVE_AND_POST_DATE", approveDate);
                        bo.set("APPROVE_AND_POST_USER_ID", uc.getUID());
                        SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", bo, connUpdate);

                        cad.set("TYPE_ID", cttConstant.ACCOUNT_DEDUCTION_TYPE_SERVICE);//类型(服务)
                        cad.set("OBJECT_ID", approveId);
                        cad.set("CONTRACT_ID", bo.getString("CONTRACT_ID"));
                        cad.set("QUANTITY", bo.get("QUANTITY"));

                        cad.set("BILL_CREATE_USER_ID", bo.getCreateUser());//创建人
                        cad.set("ACCOUNT_ID", DBSql.getString("SELECT ACCOUNT_ID FROM BO_EU_DNCTT_CONTRACT WHERE ID = ?", new Object[]{bo.getString("CONTRACT_ID")}));
                        cad.set("BILL_ACCOUNT_ID", DBSql.getString("SELECT BILL_TO_ACCOUNT_ID FROM BO_EU_DNCTT_CONTRACT WHERE ID = ?", new Object[]{bo.getString("CONTRACT_ID")}));

                        cad.set("EXTENDED_PRICE", bo.get("PERIOD_ADJUSTED_PRICE"));
                        cad.set("EXTENDED_COST", bo.get("PERIOD_COST"));
 /*
                        String costCodeId = null;
                        if (bo.getString("OBJECT_TYPE").equals("1")) {
                            costCodeId = DBSql.getString("SELECT COST_CODE_ID FROM BO_EU_DNIVT_SERVICE WHERE ID = ?", new Object[]{bo.getString("OBJECT_ID")});
                        } else {
                            costCodeId = DBSql.getString("SELECT COST_CODE_ID FROM BO_EU_DNIVT_SERVICE_BUNDLE WHERE ID = ?", new Object[]{bo.getString("OBJECT_ID")});
                        }

                        Object taxCategoryId = DBSql.getString("select TAX_CATEGORY_ID from BO_EU_DND_COST_CODE where OID=? and ORGID=?", new Object[]{costCodeId, ORGID}); //SDK.getBOAPI().get("BO_EU_DND_COST_CODE", costCodeId, "TAX_CATEGORY_ID");
                        if (taxCategoryId != null)
                            TAX_CATEGORY_ID = taxCategoryId.toString();
                        */
                    }

                }


                if (StringUtils.isNotBlank(CONTRACT_SERVICE_ID)) {
                    //按照合同税率
                    if (SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_SERVICE", CONTRACT_SERVICE_ID, "IS_CTRTAX_APPLIED").equals("1")) {
                        TAX_CATEGORY_ID = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT", CONTRACT_ID, "TAX_CATEGORY_ID").toString();
                    } else {
                        //使用服务上面的税率
                        TAX_CATEGORY_ID = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_SERVICE", CONTRACT_SERVICE_ID, "TAX_CATEGORY_ID").toString();
                    }
                }

                contractServiceLogger.info("TAX_CATEGORY_ID:" + TAX_CATEGORY_ID);

                RowMap ca = DBSql.getMap("select NAME,TAX_REGION_ID,IS_TAX_EXEMPT from BO_EU_DNCRM_ACCOUNT where ID=?", new Object[]{cad.get("ACCOUNT_ID")});
                String tax_category_name = null;//税收种类
                String tax_region_name = null;//税区
                double tax_rate = 0;//税率

                if (StringUtils.isNotBlank(TAX_CATEGORY_ID)) {

                    RowMap taxcate = DBSql.getMap("SELECT NAME,EXT1 FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{TAX_CATEGORY_ID, uc.getCompanyModel().getId()});

                    tax_category_name = taxcate.getString("NAME");

                    tax_rate = taxcate.getDouble("EXT1");
                }

                if (ca.containsKey("TAX_REGION_ID") && StringUtils.isNotBlank(ca.getString("TAX_REGION_ID"))) {
                    tax_region_name = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{ca.getString("TAX_REGION_ID"), uc.getCompanyModel().getId()});
                }

                //2025年11月19日 税率不需要再从税区上面获取
                /*
                if (tax_category_name != null && tax_region_name != null)
                    tax_rate = DBSql.getDouble("SELECT TOTAL_EFFECTIVE_TAX_RATE FROM BO_EU_DND_TAX_REGION_CATE WHERE TAX_REGION_ID='" + ca.getString("TAX_REGION_ID") + "' AND TAX_CATE_ID='" + TAX_CATEGORY_ID + "' AND ORGID='" + uc.getCompanyModel().getId() + "' ", "TOTAL_EFFECTIVE_TAX_RATE");
                */

                cad.set("POSTED_DATE", approveDate);
                cad.set("TAX_CATEGORY_NAME", tax_category_name);
                cad.set("TAX_CATEGORY_ID", TAX_CATEGORY_ID);
                cad.set("TAX_REGION_ID", ca.getString("TAX_REGION_ID"));
                cad.set("TAX_REGION_NAME", tax_region_name);
                cad.set("EFFECTIVE_TAX_RATE", tax_rate);

                // cad.set("EXTENDED_COST", tax_rate);
                // cad.extended_cost = GetCost(id);

                contractServiceLogger.info("定期服务计算税额");
                contractServiceLogger.info("是否免税" + ca.get("IS_TAX_EXEMPT"));
                contractServiceLogger.info(ca.getString("NAME") + cad.get("ACCOUNT_ID") + "是否免税" + ca.get("IS_TAX_EXEMPT"));
                //是否免税
                if (ca != null && ca.get("IS_TAX_EXEMPT") != null && ca.getString("IS_TAX_EXEMPT").equals("1")) {

                } else {

                    contractServiceLogger.info("cad.extended_price:" + cad.get("EXTENDED_PRICE"));
                    contractServiceLogger.info("tax_rate:" + tax_rate);

                    if (cad.get("EXTENDED_PRICE") != null && tax_rate > 0) {
                        double tax_dollars = cad.get("EXTENDED_PRICE", Double.class) * (1 - 1 / (1 + tax_rate));//税额
                        contractServiceLogger.info("cad.tax_dollars:" + tax_dollars);
                        //截取前四位
                        tax_dollars = Math.round(tax_dollars * 10000) / 10000.0;
                        cad.set("TAX_DOLLARS", tax_dollars);
                    }
                }

                //计费客户
                if (StringUtils.isBlank(cad.getString("BILL_ACCOUNT_ID")) && StringUtils.isNotBlank(cad.getString("ACCOUNT_ID")))
                    cad.set("BILL_ACCOUNT_ID", cad.get("ACCOUNT_ID"));

                if (feeDate != null) {
                    cad.set("FEE_DATE", feeDate);
                }

                SDK.getBOAPI().createDataBO("BO_EU_DNCRM_ACCOUNT_DEDUCTION", cad, uc, connUpdate);
                dn.recordFormChanges.record(uc, cad, "提交服务费费用");
            }
            connUpdate.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }

        return true;
    }

    /**
     * 提交里程碑
     * @param uc
     * @param approveIds
     * @return
     */
    public boolean postMilestone(UserContext uc, String approveDate, String approveIds) throws SQLException {
        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);
            for (String approveId : approveIds.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_MILESTONE", approveId);
                if (bo != null) {
                    bo.set("STATUS_ID", 1267);//进行中
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_MILESTONE", bo, connUpdate);

                    BO cad = new BO();
                    cad.set("TYPE_ID", cttConstant.ACCOUNT_DEDUCTION_TYPE_MILESTONES);//类型(里程碑)

                    String TAX_CATEGORY_ID = null;
                    //按照合同税率
                    if (bo.getString("IS_CTRTAX_APPLIED").equals("1")) {
                        TAX_CATEGORY_ID = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT", bo.getString("CONTRACT_ID"), "TAX_CATEGORY_ID").toString();
                    } else {
                        TAX_CATEGORY_ID = DBSql.getString("SELECT TAX_CATEGORY_ID FROM BO_EU_DND_COST_CODE WHERE ID=?", new Object[]{bo.getString("COST_CODE_ID")});
                    }

                    cad.set("OBJECT_ID", approveId);
                    cad.set("TAX_CATEGORY_ID", TAX_CATEGORY_ID);

                    cad.set("TAX_REGION_ID", DBSql.getString("SELECT TAX_REGION_ID FROM BO_EU_DNCRM_ACCOUNT WHERE ID=(SELECT ACCOUNT_ID FROM BO_EU_DNCTT_CONTRACT WHERE ID=?)", new Object[]{bo.getString("CONTRACT_ID")}));

                    cad.set("TAX_REGION_NAME", DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{cad.get("TAX_REGION_ID"), uc.getCompanyModel().getId()}));

                    cad.set("ACCOUNT_ID", DBSql.getString("SELECT ACCOUNT_ID FROM BO_EU_DNCTT_CONTRACT WHERE ID=?", new Object[]{bo.getString("CONTRACT_ID")}));
                    cad.set("BILL_ACCOUNT_ID", DBSql.getString("SELECT BILL_TO_ACCOUNT_ID FROM BO_EU_DNCTT_CONTRACT WHERE ID = ?", new Object[]{bo.getString("CONTRACT_ID")}));
                    cad.set("POSTED_DATE", approveDate);

                    cad.set("CONTRACT_ID", bo.get("CONTRACT_ID"));

                    // double tax_rate = DBSql.getDouble("SELECT TOTAL_EFFECTIVE_TAX_RATE FROM BO_EU_DND_TAX_REGION_CATE WHERE TAX_REGION_ID='" + cad.getString("TAX_REGION_ID") + "' AND TAX_CATE_ID='" + TAX_CATEGORY_ID + "' ", "TOTAL_EFFECTIVE_TAX_RATE");

                    RowMap taxcate = DBSql.getMap("SELECT NAME,EXT1 FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{TAX_CATEGORY_ID, uc.getCompanyModel().getId()});

                    cad.set("TAX_CATEGORY_NAME", taxcate.getString("NAME"));

                    double tax_rate = taxcate.getDouble("EXT1");

                    cad.set("EFFECTIVE_TAX_RATE", tax_rate);

                    cad.set("EXTENDED_PRICE", bo.get("DOLLARS"));

                    //是否免税
                    if (tax_rate > 0 && cad.get("EXTENDED_PRICE") != null && DBSql.getInt("select COUNT(1) cnt from BO_EU_DNCRM_ACCOUNT WHERE ID=? AND IS_TAX_EXEMPT<>1 ", new Object[]{cad.getString("ACCOUNT_ID")}) > 0) {
                        double tax_dollars = cad.get("EXTENDED_PRICE", Double.class) - (1 - 1 / (1 + tax_rate));//税额
                        cad.set("TAX_DOLLARS", tax_dollars);
                    }

                    //计费客户
                    if (StringUtils.isBlank(cad.getString("BILL_ACCOUNT_ID")) && StringUtils.isNotBlank(cad.getString("ACCOUNT_ID")))
                        cad.set("BILL_ACCOUNT_ID", cad.get("ACCOUNT_ID"));
                    SDK.getBOAPI().createDataBO("BO_EU_DNCRM_ACCOUNT_DEDUCTION", cad, uc, connUpdate);
                    dn.recordFormChanges.record(uc, cad, "审批提交里程碑");

                }
            }
            connUpdate.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }
        return true;
    }

    /**
     * 审批并提交合同产品
     * @param uc
     * @param approveDate
     * @param approveIds
     * @return
     * @throws SQLException
     */
    public boolean postCost(UserContext uc, String approveDate, String approveIds) throws SQLException {

        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);

            for (String approveId : approveIds.split(",")) {

                BO ccc = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_COST", approveId);//合同成本
                BO cc = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT", ccc.getString("CONTRACT_ID"));//合同
                BO ca = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT", ccc.getString("ACCOUNT_ID"));//客户
                // BO dcc = SDK.getBOAPI().get("BO_EU_DND_COST_CODE", ccc.getString("COST_CODE_ID"));//物料计费代码

                String tax_category_name = null;//税收种类
                String tax_region_name = null;//税区
                double tax_rate = 0;//税率

                String TAX_CATEGORY_ID = null;
                //按照合同税率
                if (ccc.getString("IS_CTRTAX_APPLIED").equals("1")) {
                    TAX_CATEGORY_ID = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT", ccc.getString("CONTRACT_ID"), "TAX_CATEGORY_ID").toString();
                } else {
                    TAX_CATEGORY_ID = ccc.getString("TAX_CATEGORY_ID");
                }

                if (StringUtils.isNotBlank(TAX_CATEGORY_ID)) {

                    RowMap taxcate = DBSql.getMap("SELECT NAME,EXT1 FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{TAX_CATEGORY_ID, uc.getCompanyModel().getId()});
                    tax_category_name = taxcate.getString("NAME");
                    tax_rate = taxcate.getDouble("EXT1");
                }

                if (ca.get("TAX_REGION_ID") != null)
                    tax_region_name = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{ca.get("TAX_REGION_ID"), uc.getCompanyModel().getId()});

                /*
                if (StringUtils.isNotBlank(TAX_CATEGORY_ID) && ca.get("TAX_REGION_ID") != null)
                    tax_rate = DBSql.getDouble("SELECT TOTAL_EFFECTIVE_TAX_RATE FROM BO_EU_DND_TAX_REGION_CATE WHERE TAX_REGION_ID='" + ca.getString("TAX_REGION_ID") + "' AND TAX_CATE_ID='" + TAX_CATEGORY_ID + "' ", "TOTAL_EFFECTIVE_TAX_RATE");
                */

                BO cad = new BO();
                cad.set("OBJECT_ID", ccc.getString("ID"));//成本表id
                cad.set("TYPE_ID", cttConstant.ACCOUNT_DEDUCTION_TYPE_CHARGE);
                cad.set("POSTED_DATE", getLocalDate(approveDate));//审批时间
                cad.set("BILL_ACCOUNT_ID", cc.getString("BILL_ACCOUNT_ID"));//计费客户
                cad.set("TASK_ID", ccc.getString("TASK_ID"));
                cad.set("CONTRACT_ID", ccc.getString("CONTRACT_ID"));
                cad.set("BILL_CREATE_USER_ID", ccc.getCreateUser());//创建人
                cad.set("ACCOUNT_ID", ca.getString("ID"));//客户
                cad.set("QUANTITY", ccc.get("QUANTITY"));//数量
                cad.set("TAX_CATEGORY_NAME", tax_category_name);
                cad.set("TAX_CATEGORY_ID", TAX_CATEGORY_ID);
                cad.set("TAX_REGION_NAME", tax_region_name);
                cad.set("EFFECTIVE_TAX_RATE", tax_rate);
                cad.set("PURCHASE_ORDER_NO", ccc.getString("PURCHASE_ORDER_NO"));//采购订单号
                cad.set("EXTENDED_COST", multiply(ccc.get("UNIT_COST"), ccc.get("QUANTITY")));//总成本

                //关联预付费合同
                //暂不实现


                //不是 关联预付费合同
                cad.set("EXTENDED_PRICE", ccc.get("EXTENDED_PRICE"));//总价
                if (!ca.getString("IS_TAX_EXEMPT").equals("1") && tax_rate > 0 && cad.get("EXTENDED_PRICE") != null) {
                    double tax_dollars = cad.get("EXTENDED_PRICE", Double.class) - (1 - 1 / (1 + tax_rate));//税额
                    cad.set("TAX_DOLLARS", tax_dollars);
                }

                //计费客户
                if (StringUtils.isBlank(cad.getString("BILL_ACCOUNT_ID")) && StringUtils.isNotBlank(cad.getString("ACCOUNT_ID")))
                    cad.set("BILL_ACCOUNT_ID", cad.get("ACCOUNT_ID"));
                SDK.getBOAPI().createDataBO("BO_EU_DNCRM_ACCOUNT_DEDUCTION", cad, uc, connUpdate);
                dn.recordFormChanges.record(uc, cad, "审批并提交合同产品");
                //改变状态
                ccc.set("BILL_STATUS", "1");
                SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_COST", ccc, connUpdate);

            }
            connUpdate.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }
        return true;
    }

    /**
     * 撤销定期服务
     * @param uc
     * @param ids
     * @return
     */
    public String revokeRecurringServices(UserContext uc, String ids) throws SQLException {

        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);

            for (String id : ids.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", id);

                //判断是否已经生成 发票
                if (StringUtils.isNotBlank(bo.getString("INVOICE_ID"))) {
                    if (!DBSql.getString("select IS_VOIDED from BO_EU_DNCTT_INVOICE where ID=?", new Object[]{bo.getString("INVOICE_ID")}).equals("1"))
                        throw new RuntimeException("该服务已经生成发票，请先删除发票");
                }

                if (bo.getString("TYPE_ID").equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_SERVICE))) //类型：服务
                {
                    /*
                    BO ccsp = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD").addQuery("CONTRACT_ID =", bo.getString("CONTRACT_ID")).addQuery("ID =", bo.getString("OBJECT_ID")).detail();
                    ccsp.set("APPROVE_AND_POST_DATE", null);
                    ccsp.set("APPROVE_AND_POST_USER_ID", null);
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD", ccsp, connUpdate);
                    */
                    DBSql.update(connUpdate, "update BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD set APPROVE_AND_POST_DATE=null,APPROVE_AND_POST_USER_ID=null where ID=?", new Object[]{bo.getString("OBJECT_ID")});

                } else if (bo.getString("TYPE_ID").equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_INITIAL_COST))) //类型：初始费用
                {
                    /*
                    BO ccc = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT").addQuery("ID =", bo.getString("CONTRACT_ID")).detail();
                    ccc.set("SETUP_FEE_APPROVE_AND_POST_USER_ID", null);
                    ccc.set("SETUP_FEE_APPROVE_AND_POST_DATE", null);
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT", ccc, connUpdate);

                     */

                    DBSql.update(connUpdate, "update BO_EU_DNCTT_CONTRACT set SETUP_FEE_APPROVE_AND_POST_USER_ID=null,SETUP_FEE_APPROVE_AND_POST_DATE=null where ID=?", new Object[]{bo.getString("OBJECT_ID")});
                }
                //bo.set("CLOSED", 1);
                dn.recordFormChanges.record(uc, bo, "撤销定期服务");
                SDK.getBOAPI().remove("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo.getId(), connUpdate);
                //SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo, connUpdate);
            }
            connUpdate.commit();
        } catch (SQLException e) {
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            return e.getMessage();
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }

        return null;
    }

    /**
     * 撤销里程碑审批
     * @param uc
     * @param ids
     * @return
     * @throws SQLException
     */
    public String revokeMilestones(UserContext uc, String ids) throws SQLException {
        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);

            for (String id : ids.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", id);

                //判断是否已经生成 发票
                if (StringUtils.isNotBlank(bo.getString("INVOICE_ID"))) {
                    if (!DBSql.getString("select IS_VOIDED from BO_EU_DNCTT_INVOICE where ID=?", new Object[]{bo.getString("INVOICE_ID")}).equals("1"))
                        throw new RuntimeException("该服务已经生成发票，请先删除发票");
                }

                if (bo.get("TYPE_ID").equals(cttConstant.ACCOUNT_DEDUCTION_TYPE_MILESTONES)) //类型：里程碑
                {
                    BO ccm = SDK.getBOAPI().query("BO_EU_DNCTT_CONTRACT_MILESTONES").addQuery("CONTRACT_ID =", bo.getString("CONTRACT_ID")).addQuery("ID =", bo.getString("OBJECT_ID")).detail();
                    ccm.set("STATUS_ID", 1266);
                    SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_MILESTONES", ccm, connUpdate);
                }
                bo.set("CLOSED", 1);
                dn.recordFormChanges.record(uc, bo, "撤销里程碑审批");
                //SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo, connUpdate);
                SDK.getBOAPI().remove("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo.getId(), connUpdate);
            }

            connUpdate.commit();
        } catch (SQLException e) {
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            return e.getMessage();
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }
        return null;
    }


    /**
     * 撤销成本审批
     * @param uc
     * @param ids
     * @return
     * @throws SQLException
     */
    public String revokeCost(UserContext uc, String ids) throws SQLException {
        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);
            for (String id : ids.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", id);

                //判断是否已经生成 发票
                if (StringUtils.isNotBlank(bo.getString("INVOICE_ID"))) {
                    if (!DBSql.getString("select IS_VOIDED from BO_EU_DNCTT_INVOICE where ID=?", new Object[]{bo.getString("INVOICE_ID")}).equals("1"))
                        throw new RuntimeException("该服务已经生成发票，请先删除发票");
                }

                bo.set("CLOSED", 1);
                dn.recordFormChanges.record(uc, bo, "撤销成本审批");

                SDK.getBOAPI().remove("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo.getId(), connUpdate);
                //SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo, connUpdate);

                BO ccc = SDK.getBOAPI().get("BO_EU_DNCTT_CONTRACT_COST", bo.getString("OBJECT_ID"));//合同成本
                ccc.set("BILL_STATUS", 0);
                ccc.set("EXTENDED_PRICE", multiply(ccc.get("UNIT_PRICE"), ccc.get("QUANTITY")));//总成本
                SDK.getBOAPI().update("BO_EU_DNCTT_CONTRACT_COST", ccc, connUpdate);

            }
            connUpdate.commit();
        } catch (SQLException e) {
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            return e.getMessage();
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }
        return null;
    }


    /**
     * 根据 物料计费代码Id 和 角色ID返回相关费率
     * @param cost_code_id
     * @param role_id
     * @return
     */
    public static Double GetRateByCodeAndRole(String cost_code_id, String role_id) {
        Double rate = null;

        RowMap thisCostCode = DBSql.getMap("SELECT * FROM BO_EU_DND_COST_CODE WHERE ID=?", new Object[]{cost_code_id});
        RowMap thisRole = DBSql.getMap("SELECT * FROM BO_EU_DNSYS_ROLE WHERE ID=?", new Object[]{role_id});
        if (thisCostCode != null && thisRole != null) {
            switch (thisCostCode.getString("BILLING_METHOD_ID")) {
                case "2166":
                    rate = thisRole.getDouble("hourly_rate");
                    break;
                case "2167":
                    if (StringUtils.isNotBlank(thisCostCode.getString("RATE_ADJUSTMENT"))) {
                        rate = (thisRole.getDouble("hourly_rate") + thisCostCode.getDouble("RATE_ADJUSTMENT"));
                    }
                    break;
                case "2168":
                    if (StringUtils.isNotBlank(thisCostCode.getString("RATE_MULTIPLIER"))) {
                        rate = (thisRole.getDouble("hourly_rate") * thisCostCode.getDouble("RATE_MULTIPLIER"));
                    }
                    break;
                case "2169":
                    if (StringUtils.isNotBlank(thisCostCode.getString("CUSTOM_RATE"))) {
                        rate = thisCostCode.getDouble("CUSTOM_RATE");
                    }
                    break;
                case "2170":
                    if (StringUtils.isNotBlank(thisCostCode.getString("FLAT_RATE"))) {
                        rate = thisCostCode.getDouble("FLAT_RATE");
                    }
                    break;
                default:
                    break;
            }
        }
        return rate;
    }

    /**
     * 获取工时成本
     * @param id
     * @return
     */
    public static Double GetEntryCost(String id) {
        return DBSql.getDouble("SELECT( IFNULL(( SELECT rate FROM BO_EU_DNCTT_CONTRACT_INTERNAL_COST WHERE contract_id = p.contract_id AND resource_id = p.resource_id AND role_id = p.role_id LIMIT 1), ( SELECT hourly_rate FROM bo_eu_dnsys_resource_internal_cost WHERE resource_id = p.resource_id AND IFNULL(start_date, '1970-01-01') <= FROM_UNIXTIME(p.start_time / 1000) AND IFNULL(end_date, '9999-01-01') > FROM_UNIXTIME(p.start_time / 1000)) ) * p.hours_billed ) AS COST FROM bo_eu_dnsdk_work_entry p JOIN bo_eu_dnd_cost_code cc ON p.cost_code_id = cc.id JOIN bo_eu_dnsdk_task t ON p.task_id = t.id LEFT JOIN bo_eu_dnctt_contract c ON p.contract_id = c.id WHERE p.id = '" + id + "'", "COST");
    }


    /**
     * 提交佣金
     * @param uc
     * @param approveDate
     * @param approveIds
     * @return
     */
    public boolean approveCommission(UserContext uc, String approveDate, String approveIds) throws SQLException {

        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);

            for (String approveId : approveIds.split(",")) {

                BO cad = new BO();// crm_account_deduction();//审批并提交表实体对象
                String TAX_CATEGORY_ID = null;//物料计费代码

                String ORGID = uc.getCompanyModel().getId();

                //BO_EU_DNCTT_COMMISSION_PERIOD

                RowMap period = DBSql.getMap("select * from BO_EU_DNCTT_COMMISSION_PERIOD where ID=?", new Object[]{approveId});
                String COST_CODE_ID = DBSql.getString("select COST_CODE_ID from BO_EU_DNCTT_COMMISSION where BINDID=?", new Object[]{period.getString("BINDID")});

                //BO bo = SDK.getBOAPI().get("BO_EU_DNCTT_COMMISSION_PERIOD", approveId);//佣金
                //BO commissionBO = SDK.getBOAPI().getByProcess("BO_EU_DNCTT_COMMISSION", bo.getBindId());
                if (period != null) {
                    //bo.set("APPROVE_AND_POST_DATE", approveDate);
                    //bo.set("APPROVE_AND_POST_USER_ID", uc.getUID());
                    //SDK.getBOAPI().update("BO_EU_DNCTT_COMMISSION_PERIOD", bo, connUpdate);
                    DBSql.update(connUpdate, "update BO_EU_DNCTT_COMMISSION_PERIOD set APPROVE_AND_POST_DATE=?,APPROVE_AND_POST_USER_ID=? where ID=?", new Object[]{approveDate, uc.getUID(), approveId});

                    cad.set("TYPE_ID", cttConstant.ACCOUNT_DEDUCTION_TYPE_COMMISSION);//类型(佣金)
                    cad.set("OBJECT_ID", approveId);
                    cad.set("CONTRACT_ID", approveId);
                    cad.set("QUANTITY", 1);
                    cad.set("BILL_CREATE_USER_ID", period.getString("CREATEUSER"));//创建人
                    String ISP = DBSql.getString(connUpdate, "select ISP from BO_EU_DNCTT_COMMISSION where BINDID=?", new Object[]{period.getString("BINDID")});
                    cad.set("ACCOUNT_ID", ISP);
                    cad.set("BILL_ACCOUNT_ID", ISP);
                    cad.set("EXTENDED_PRICE", period.get("PERIOD_ADJUSTED_PRICE"));

                    if (StringUtils.isNotBlank(COST_CODE_ID)) {
                        TAX_CATEGORY_ID = DBSql.getString("select TAX_CATEGORY_ID from BO_EU_DND_COST_CODE where OID=? and ORGID=?", new Object[]{period.getString("COST_CODE_ID"), ORGID});
                    }
                }

                RowMap ca = DBSql.getMap("select TAX_REGION_ID,IS_TAX_EXEMPT from BO_EU_DNCRM_ACCOUNT where ID=?", new Object[]{cad.get("ACCOUNT_ID")});
                if (ca == null) {
                    Map<String, Object> caM = new HashMap<>();
                    caM.put("TAX_REGION_ID", "13");
                    caM.put("IS_TAX_EXEMPT", "0");
                    ca = new RowMap(caM);
                }

                String tax_category_name = null;//税收种类
                String tax_region_name = null;//税区
                double tax_rate = 0;//税率

                if (StringUtils.isNotBlank(TAX_CATEGORY_ID)) {
                    tax_category_name = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{TAX_CATEGORY_ID, uc.getCompanyModel().getId()});
                }

                if (ca.containsKey("TAX_REGION_ID") && StringUtils.isNotBlank(ca.getString("TAX_REGION_ID"))) {
                    tax_region_name = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{ca.getString("TAX_REGION_ID"), uc.getCompanyModel().getId()});
                }

                if (tax_category_name != null && tax_region_name != null)
                    tax_rate = DBSql.getDouble("SELECT TOTAL_EFFECTIVE_TAX_RATE FROM BO_EU_DND_TAX_REGION_CATE WHERE TAX_REGION_ID='" + ca.getString("TAX_REGION_ID") + "' AND TAX_CATE_ID='" + TAX_CATEGORY_ID + "' AND ORGID='" + uc.getCompanyModel().getId() + "' ", "TOTAL_EFFECTIVE_TAX_RATE");

                cad.set("POSTED_DATE", approveDate);
                cad.set("TAX_CATEGORY_NAME", tax_category_name);
                cad.set("TAX_CATEGORY_ID", TAX_CATEGORY_ID);
                cad.set("TAX_REGION_ID", ca.getString("TAX_REGION_ID"));
                cad.set("TAX_REGION_NAME", tax_region_name);
                cad.set("EFFECTIVE_TAX_RATE", tax_rate);

                //是否免税
                if (!ca.getString("IS_TAX_EXEMPT").equals("1") && ca.get("EXTENDED_PRICE") != null && tax_rate > 0) {
                    double tax_dollars = cad.get("EXTENDED_PRICE", Double.class) * (1 - 1 / (1 + tax_rate));//税额
                    cad.set("TAX_DOLLARS", tax_dollars);
                }

                //计费客户
                if (StringUtils.isBlank(cad.getString("BILL_ACCOUNT_ID")) && StringUtils.isNotBlank(cad.getString("ACCOUNT_ID")))
                    cad.set("BILL_ACCOUNT_ID", cad.get("ACCOUNT_ID"));

                SDK.getBOAPI().createDataBO("BO_EU_DNCRM_ACCOUNT_DEDUCTION", cad, uc, connUpdate);
                dn.recordFormChanges.record(uc, cad, "提交佣金");
            }
            connUpdate.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            if (connUpdate != null && !connUpdate.isClosed()) try {
                connUpdate.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed()) DBSql.close(connUpdate);
        }

        return true;
    }


    /**
     * 计算周期
     * @param purchasePeriodType
     * @param beginDate
     * @param endDate
     * @return
     */
    public int calculatePeriods(String purchasePeriodType, LocalDate beginDate, LocalDate endDate) {
        // 校验日期合法性
        if (beginDate == null || endDate == null) {
            throw new IllegalArgumentException("开始日期或结束日期不能为空");
        }
        if (beginDate.isAfter(endDate)) {
            return 0; // 开始日期晚于结束日期，周期数为0
        }

        // 一次性收费直接返回1
        if ("609".equals(purchasePeriodType)) {
            return 1;
        }

        // 确定周期对应的月数
        int periodMonths;
        switch (purchasePeriodType) {
            case "610":
                periodMonths = 1; // 按月
                break;
            case "611":
                periodMonths = 3; // 按季度
                break;
            case "612":
                periodMonths = 6; // 按半年
                break;
            case "613":
                periodMonths = 12; // 按年
                break;
            default:
                throw new IllegalArgumentException("未知的采购周期类型：" + purchasePeriodType);
        }

        // 开始日期和结束日期相同，算1个周期
        if (beginDate.equals(endDate)) {
            return 1;
        }

        // 计算两个日期之间的总月数
        long monthsBetween = ChronoUnit.MONTHS.between(beginDate, endDate);

        // 不足1个月的情况，算1个周期
        if (monthsBetween == 0) {
            return 1;
        }

        // 计算完整周期数
        int fullPeriods = (int) (monthsBetween / periodMonths);
        // 计算完整周期后的截止日期
        LocalDate endOfFullPeriods = beginDate.plusMonths(fullPeriods * periodMonths);

        // 若结束日期超过完整周期的截止日期，周期数+1
        return endDate.isAfter(endOfFullPeriods) ? fullPeriods + 1 : fullPeriods;
    }


    /**
     * 撤销费用审批
     * @param uc
     * @param ids
     * @return
     * @throws SQLException
     */
    public String revokeExpense(UserContext uc, String ids) throws SQLException {
        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);
            for (String id : ids.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", id);

                //判断是否已经生成 发票
                if (StringUtils.isNotBlank(bo.getString("INVOICE_ID"))) {
                    if (!DBSql.getString("select IS_VOIDED from BO_EU_DNCTT_INVOICE where ID=?", new Object[]{bo.getString("INVOICE_ID")}).equals("1"))
                        return "该服务已经生成发票，请先删除发票";
                }

                bo.set("CLOSED", 1);
                dn.recordFormChanges.record(uc, bo, "撤销费用审批");
                SDK.getBOAPI().remove("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo.getId(), connUpdate);
                BO oldEntry = SDK.getBOAPI().get("BO_EU_DNSDK_EXPENSE", bo.getString("OBJECT_ID"));
                oldEntry.set("APPROVE_AND_POST_DATE", null);
                oldEntry.set("APPROVE_AND_POST_USER_ID", null);
                dn.recordFormChanges.record(uc, oldEntry, "撤销费用审批");
                SDK.getBOAPI().update("BO_EU_DNSDK_EXPENSE", oldEntry);

            }
            connUpdate.commit();
        } catch (SQLException e) {
            if (connUpdate != null && !connUpdate.isClosed())
                try {
                    connUpdate.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed())
                DBSql.close(connUpdate);
        }
        return null;
    }


    /**
     * 撤销工时审批
     * @param uc
     * @param ids
     * @return
     * @throws SQLException
     */
    public String revokeLabor(UserContext uc, String ids) throws SQLException {
        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);
            for (String id : ids.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", id);

                //判断是否已经生成 发票
                if (StringUtils.isNotBlank(bo.getString("INVOICE_ID"))) {
                    if (!DBSql.getString("select IS_VOIDED from BO_EU_DNCTT_INVOICE where ID=?", new Object[]{bo.getString("INVOICE_ID")}).equals("1"))
                        return "该服务已经生成发票，请先删除发票";
                }

                bo.set("CLOSED", 1);
                dn.recordFormChanges.record(uc, bo, "撤销工时审批");

                SDK.getBOAPI().remove("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo.getId(), connUpdate);

                BO oldEntry = SDK.getBOAPI().get("BO_EU_DNSDK_WORK_ENTRY", bo.getString("OBJECT_ID"));
                oldEntry.set("APPROVE_AND_POST_DATE", null);
                oldEntry.set("APPROVE_AND_POST_USER_ID", null);
                dn.recordFormChanges.record(uc, oldEntry, "撤销工时审批");
                SDK.getBOAPI().update("BO_EU_DNSDK_WORK_ENTRY", oldEntry);

            }
            connUpdate.commit();
        } catch (SQLException e) {
            if (connUpdate != null && !connUpdate.isClosed())
                try {
                    connUpdate.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed())
                DBSql.close(connUpdate);
        }
        return null;
    }


    /**
     * 撤销佣金审批
     * @param uc
     * @param ids
     * @return
     * @throws SQLException
     */
    public String revokeCommission(UserContext uc, String ids) throws SQLException {
        Connection connUpdate = null;
        try {
            connUpdate = DBSql.open();
            connUpdate.setAutoCommit(false);
            for (String id : ids.split(",")) {
                BO bo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", id);
                bo.set("CLOSED", 1);
                dn.recordFormChanges.record(uc, bo, "撤销佣金审批");
                SDK.getBOAPI().remove("BO_EU_DNCRM_ACCOUNT_DEDUCTION", bo.getId(), connUpdate);
                DBSql.update("update BO_EU_DNCTT_COMMISSION_PERIOD set APPROVE_AND_POST_DATE=null,APPROVE_AND_POST_USER_ID=null where ID=?", new Object[]{bo.getString("OBJECT_ID")});
            }
            connUpdate.commit();
        } catch (SQLException e) {
            if (connUpdate != null && !connUpdate.isClosed())
                try {
                    connUpdate.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
        } finally {
            if (connUpdate != null && !connUpdate.isClosed())
                DBSql.close(connUpdate);
        }
        return null;
    }
}
