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.bpms.util.TypeUtil;
import com.actionsoft.sdk.local.SDK;
import com.actionsoft.sdk.local.api.Logger;
import com.awspaas.user.apps.donenow_ctt.cttConstant;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 发票服务
 */
public class InvoiceService {

    private static final Logger logger = SDK.getLogAPI().getLogger(InvoiceService.class);
    // 单例模式：双重校验锁保证线程安全
    private static volatile InvoiceService instance;

    private InvoiceService() {
    }

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

    // ====================== 1. 核心入口方法：处理发票生成 ======================
    public String processInvoice(UserContext uc, String accDeduIds, InvoiceDealDto param, boolean isDeal) {

        logger.info("处理发票生成");
        logger.info("accDeduIds:" + accDeduIds);
        logger.info("日期范围:" + param.getDateRangeFrom() + "--" + param.getDateRangeTo());

        // 1. 基础校验：空值过滤
        if (StringUtils.isBlank(accDeduIds))
            return "参数错误";

        String[] dedIdArr = accDeduIds.split(",");

        StringBuilder where = new StringBuilder("(");
        for (int i = 0; i < dedIdArr.length; i++) {
            where.append("?").append(i < dedIdArr.length - 1 ? "," : ")");
        }
        String sqlWhere = where.toString(); // 复用：构建IN条件

        // 2. 初始化基础数据（3张核心表查询）
        List<RowMap> postedDataList = DBSql.getMaps("SELECT * FROM VIEW_EU_DNV_POSTED_ALL d WHERE d.ID IN " + sqlWhere + " AND (d.invoice_id is null OR d.invoice_id='' OR NOT EXISTS(SELECT 1 FROM bo_eu_dnctt_invoice i WHERE i.id=d.invoice_id AND IS_VOIDED=0))  AND  d.bill_account_id is NOT NULL AND d.ORGID='" + uc.getCompanyModel().getId() + "' ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID", dedIdArr);

        logger.info("账单个数:" + postedDataList.size());

        if (postedDataList.isEmpty() || postedDataList.size() != dedIdArr.length)
            return "所选条目有账单状态必须为未生成生成账单";

        // 统一Key为大写，避免字段名大小写不一致导致的NPE
        postedDataList.forEach(row -> row.forEach((key, value) -> {
            if (key.toUpperCase().equals("BILL_ACCOUNT_ID")) {
                if (value == null || StringUtils.isBlank(value.toString())) {
                    row.put(key.toUpperCase(), row.get("ACCOUNT_ID"));
                } else {
                    row.put(key.toUpperCase(), value);
                }
            } else {
                row.put(key.toUpperCase(), value);
            }
        }));

        //ITEM_DATE   i) 弹出窗口输入账单起止日期，保存时需要校验所选条目是否在账单起止日期内
        //            ii) 批量选择条目：计费客户相同、状态必须为----未生成生成账单

        org.joda.time.format.DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");

        String BILL_ACCOUNT_ID = null;
        for (RowMap row : postedDataList) {
            String ITEM_DATE = row.getString("ITEM_DATE");
            if (StringUtils.isNotBlank(ITEM_DATE)) {

                LocalDate itemDate = LocalDate.parse(ITEM_DATE, formatter);

                //i) 弹出窗口输入账单起止日期，保存时需要校验所选条目是否在账单起止日期内
                /*
                if (itemDate.isBefore(param.getDateRangeFrom()) || itemDate.isAfter(param.getDateRangeTo())) {
                    return "所选条目计费日期必须在输入的账单起止日期内";
                }
                 */

                //  ii) 批量选择条目：计费客户相同、状态必须为----未生成生成账单
                if (StringUtils.isBlank(BILL_ACCOUNT_ID)) BILL_ACCOUNT_ID = row.getString("BILL_ACCOUNT_ID");
                else {
                    if (!BILL_ACCOUNT_ID.equals(row.getString("BILL_ACCOUNT_ID"))) {
                        return "所选条目有账单客户必须相同";
                    }
                }

                //RECEIVE_STATUS  4661 未生成生成账单
                String RECEIVE_STATUS = row.getString("RECEIVE_STATUS");
                if (RECEIVE_STATUS.equals("4661") == false) return "所选条目有账单状态必须为未生成生成账单";
            }

        }


        List<RowMap> accountDedList = DBSql.getMaps("SELECT * FROM BO_EU_DNCRM_ACCOUNT_DEDUCTION WHERE ID IN " + sqlWhere + " ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID", dedIdArr);
        List<RowMap> itemTypeDict = DBSql.getMaps("SELECT OID,NAME FROM BO_EU_DND_GENERAL WHERE GENERAL_TABLE_ID='121' AND ORGID=?", new Object[]{uc.getCompanyModel().getId()});

        // 3. 按「账单客户ID」分组抵扣数据（核心分组逻辑）
        Map<String, List<RowMap>> customerDedMap = accountDedList.stream().filter(ded -> ded.get("BILL_ACCOUNT_ID") != null).collect(Collectors.groupingBy(ded -> ded.get("BILL_ACCOUNT_ID").toString()));
        if (customerDedMap.isEmpty()) return null;


        // 4. 循环处理每个客户的发票（拆分「有无采购订单」逻辑）
        List<String> invoiceIds = new ArrayList<>();
        for (Map.Entry<String, List<RowMap>> entry : customerDedMap.entrySet()) {
            String ACCOUNT_ID = entry.getKey();
            List<RowMap> customerDedList = entry.getValue();

            // 4.1 客户信息校验：必须有客户顾问（否则跳过）

            RowMap customer = DBSql.getMap("SELECT * FROM BO_EU_DNCRM_ACCOUNT WHERE ID = ?", ACCOUNT_ID);

            logger.info("处理客户ID:" + ACCOUNT_ID);
            logger.info("RESOURCE_ID--" + customer.getString("RESOURCE_ID"));

            if (customer == null || StringUtils.isBlank(customer.getString("RESOURCE_ID"))) {
                continue;
            }

            // 4.2 处理「无采购订单」的发票
            List<RowMap> noPoDedList = customerDedList.stream().filter(ded -> StringUtils.isBlank(ded.getString("PURCHASE_ORDER_NO"))).collect(Collectors.toList());
            if (!noPoDedList.isEmpty()) {
                List<RowMap> noPoPostedList = postedDataList.stream().filter(p -> StringUtils.isBlank(p.getString("PURCHASE_ORDER_NO"))).filter(p -> ACCOUNT_ID.equals(p.get("ACCOUNT_ID")) || ACCOUNT_ID.equals(p.get("BILL_ACCOUNT_ID"))).collect(Collectors.toList());
                logger.info("无PO账单数量:" + noPoPostedList.size());
                String noPoInvoiceId = createInvoiceAndDetails(uc, customer, noPoPostedList, noPoDedList, itemTypeDict, param, isDeal, "");
                if (StringUtils.isNotBlank(noPoInvoiceId)) {
                    invoiceIds.add(noPoInvoiceId);
                }
            }

            // 4.3 处理「有采购订单」的发票（按PO分组）
            Map<String, List<RowMap>> poDedMap = customerDedList.stream().filter(ded -> StringUtils.isNotBlank(ded.getString("PURCHASE_ORDER_NO"))).collect(Collectors.groupingBy(ded -> ded.getString("PURCHASE_ORDER_NO")));
            for (Map.Entry<String, List<RowMap>> poEntry : poDedMap.entrySet()) {
                String poNo = poEntry.getKey();
                List<RowMap> poDedList = poEntry.getValue();
                List<RowMap> poPostedList = postedDataList.stream().filter(p -> poNo.equals(p.getString("PURCHASE_ORDER_NO"))).filter(p -> ACCOUNT_ID.equals(p.get("ACCOUNT_ID")) || ACCOUNT_ID.equals(p.get("BILL_ACCOUNT_ID"))).collect(Collectors.toList());
                logger.info("处理采购订单：" + poNo);

                String poInvoiceId = createInvoiceAndDetails(uc, customer, poPostedList, poDedList, itemTypeDict, param, isDeal, poNo);
                if (StringUtils.isNotBlank(poInvoiceId)) {
                    invoiceIds.add(poInvoiceId);
                }
            }
        }

        logger.info("发票ID:" + invoiceIds);

        String invoiceBINDID = DBSql.getString("SELECT BINDID FROM BO_EU_DNCTT_INVOICE WHERE ID=?", new Object[]{invoiceIds.get(0)});

        return "成功--" + invoiceBINDID;
    }


    // ====================== 4. 计算发票汇总数据（金额/工时） ======================
    private InvoiceSummary calculateSummary(List<RowMap> postedList) {
        InvoiceSummary summary = new InvoiceSummary();
        // 金额汇总（含税总额、税额、不含税总价）
        summary.totalMoney = postedList.stream().mapToDouble(p -> p.getDouble("DOLLARS")).sum();
        summary.totalTax = postedList.stream().mapToDouble(p -> p.getDouble("TAX_DOLLARS")).sum();
        summary.totalPrice = summary.totalMoney - summary.totalTax;
        // 工时汇总（不计费、计费、预付）
        summary.noBillHours = postedList.stream().filter(p -> StringUtils.isBlank(p.getString("BILLABLE"))).mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
        summary.billHours = postedList.stream().filter(p -> StringUtils.isNotBlank(p.getString("BILLABLE"))).mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
        summary.prepaidHours = postedList.stream().filter(p -> StringUtils.isNotBlank(p.getString("CONTRACT_BLOCK_ID"))).mapToDouble(p -> p.getDouble("BILLABLE_HOURS")).sum();
        return summary;
    }

    // ====================== 5. 核心复用方法4：创建发票头+明细+更新抵扣 ======================
    private String createInvoiceAndDetails(UserContext uc, RowMap customer, List<RowMap> postedList, List<RowMap> dedList, List<RowMap> itemTypeDict, InvoiceDealDto param, boolean isDeal, String poNo) {
        try {
            // 1. 计算汇总数据
            InvoiceSummary summary = calculateSummary(postedList);
            // 2. 生成发票编号+创建流程实例
            String invoiceNo = SDK.getRuleAPI().executeAtScript("B@left(@date,4)@mid(@date,6,2).@mid(@sequenceMonth(@companyId_invoice,4,0,1),7)", uc);
            ProcessInstance pi = SDK.getProcessAPI().createProcessInstance("obj_a7d46a9dd4df40b899ddcf02113fe0d2", uc.getUID(), "新增账单" + invoiceNo);
            // 3. 构建并保存发票头
            BO invoice = new BO();
            invoice.set("INVOICE_NO", invoiceNo);
            invoice.set("ACCOUNT_ID", customer.getString("ID"));
            invoice.set("ACCOUNT_NAME", customer.getString("NAME"));
            invoice.set("OWNER_RESOURCE_NAME", DBSql.getString("SELECT USERNAME FROM orguser WHERE USERID=?", new Object[]{customer.getString("RESOURCE_ID")}));

            invoice.set("INVOICE_DATE", param.getInvoiceDate().toString());
            invoice.set("DATE_RANGE_FROM", param.getDateRangeFrom().toString());
            invoice.set("DATE_RANGE_TO", param.getDateRangeTo().toString());

            invoice.set("TOTAL", summary.totalMoney);
            invoice.set("TAX_VALUE", summary.totalTax);
            invoice.set("TOTAL_PRICE", summary.totalPrice);
            invoice.set("NOBILL_HOURS", summary.noBillHours);
            invoice.set("BILL_HOURS", summary.billHours);
            invoice.set("PREPAID_HOURS", summary.prepaidHours);
            invoice.set("PAYMENT_TERM_ID", param.getPaymentTermId());
            invoice.set("PURCHASE_ORDER_NO", poNo); // 有PO则赋值，无PO为空
            invoice.set("NOTES", param.getNotes());
            // 税区名称查询
            String taxRegionName = StringUtils.isBlank(customer.getString("TAX_REGION_ID")) || customer.getString("TAX_REGION_ID").equals("0") ? "" : DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ?", new Object[]{customer.getString("TAX_REGION_ID")});

            invoice.set("TAX_REGION_NAME", taxRegionName);
            invoice.setBindId(pi.getId());

            // 记录变更+保存发票BO
            SDK.getBOAPI().create("BO_EU_DNCTT_INVOICE", invoice, pi, uc);
            dn.recordFormChanges.record(uc, invoice, "保存发票：" + invoiceNo);
            // 4. 构建并保存发票明细（批量处理）
            Map<String, RowMap> dedIdMap = dedList.stream().collect(Collectors.toMap(ded -> ded.getString("ID"), ded -> ded));
            int lineNo = 1;
            for (RowMap posted : postedList) {
                String dedId = posted.getString("ID");
                RowMap ded = dedIdMap.get(dedId);

                // 4.1 构建明细BO
                BO detail = new BO();
                detail.setBindId(invoice.getBindId());
                detail.set("INVOICE_ID", invoice.getId());
                detail.set("ITEM_DATE", posted.get("ITEM_DATE"));
                detail.set("ITEM_DESC", StringUtils.isBlank(posted.getString("ITEM_DESC")) ? posted.get("ITEM_NAME") : posted.get("ITEM_DESC"));
                detail.set("RESOURCE_NAME", posted.get("RESOURCE_NAME"));
                detail.set("QUANTITY", posted.get("QUANTITY"));
                detail.set("RATE", posted.get("RATE"));
                detail.set("DOLLARS", posted.get("DOLLARS"));//含税总价
                detail.set("TAX_DOLLARS", posted.get("TAX_DOLLARS"));//税额

                //不含税总价=含税总价-税额
                BigDecimal DOLLARS_NOTAX = (posted.get("DOLLARS") == null ? BigDecimal.ZERO : TypeUtil.convert(posted.get("DOLLARS"), BigDecimal.class)).subtract((posted.get("TAX_DOLLARS") == null ? BigDecimal.ZERO : TypeUtil.convert(posted.get("TAX_DOLLARS"), BigDecimal.class)));

                detail.set("DOLLARS_NOTAX", DOLLARS_NOTAX);//不含税总价

                detail.set("HOURLY_RATE", posted.get("HOURLY_RATE"));
                detail.set("ROLE_NAME", posted.get("ROLE_NAME"));
                detail.set("WORK_TYPE", posted.get("WORK_TYPE"));
                detail.set("BILL_ACCOUNT_ID", ded.getString("BILL_ACCOUNT_ID"));//计费客户
                detail.set("ACCOUNT_ID", ded.getString("ACCOUNT_ID"));//客户
                detail.set("ACCOUNT_DEDUCTION_ID", dedId);

                // 4.2 特殊处理：工时显示（按合同类型）
                String contractTypeId = posted.getString("CONTRACT_TYPE_ID");
                String billableHours = posted.getString("BILLABLE_HOURS");
                if ("1199".equals(contractTypeId) || "1201".equals(contractTypeId)) {
                    billableHours = "合同已包";
                } else if (("1202".equals(contractTypeId) || "1203".equals(contractTypeId)) && StringUtils.isNotBlank(posted.getString("BILLABLE"))) {
                    billableHours = "预支付";
                }
                detail.set("BILLABLE_HOURS", billableHours);

                // 4.3 特殊处理：条目类型（OID转名称）
                String itemTypeOid = posted.getString("ITEM_TYPE");
                String itemTypeName = itemTypeDict.stream().filter(type -> itemTypeOid.equals(type.getString("OID"))).findFirst().map(type -> type.getString("NAME")).orElse(null);
                if (itemTypeName == null)
                    itemTypeName = DBSql.getString("SELECT NAME FROM BO_EU_DND_GENERAL WHERE OID = ? AND ORGID=?", new Object[]{itemTypeOid, uc.getCompanyModel().getId()});

                detail.set("ITEM_TYPE", itemTypeName);

                // 4.4 税务相关字段
                detail.set("EFFECTIVE_TAX_RATE", ded.getDouble("EFFECTIVE_TAX_RATE") * 100);
                detail.set("TAX_CATEGORY_NAME", ded.get("TAX_CATEGORY_NAME"));
                detail.set("TAX_CATEGORY_ID", ded.get("TAX_CATEGORY_ID"));
                detail.set("CONTRACT_ID", ded.get("CONTRACT_ID"));
                // 4.5 保存明细
                SDK.getBOAPI().createDataBO("BO_EU_DNCTT_INVOICE_DETAIL", detail, uc);
                dn.recordFormChanges.record(uc, detail, "保存账单明细：" + detail.getId());

                // 5. 实际处理时：更新账户抵扣（关联发票ID+行号）
                if (isDeal) {
                    BO dedBo = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", dedId);
                    dedBo.set("INVOICE_ID", invoice.getId());
                    dedBo.set("INVOICE_LINE_ITEM_NO", lineNo);
                    dn.recordFormChanges.record(uc, dedBo, "生成账单");
                    SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", dedBo);
                }

                lineNo++;
            }

            return invoice.getId(); // 返回生成的发票ID
        } catch (Exception e) {
            logger.info("错误---" + e.getMessage());
            logger.error("错误--111-", e);
            e.printStackTrace();
            return null;
        }
    }

    // ====================== 内部辅助类：发票汇总数据载体 ======================
    private static class InvoiceSummary {
        double totalMoney;    // 含税总额
        double totalTax;      // 税额
        double totalPrice;    // 不含税总价
        double noBillHours;   // 不计费工时
        double billHours;     // 计费工时
        double prepaidHours;  // 预付工时
    }


    /**
     * 发票处理 1、生成发票 2、作废发票
     * @param uc
     * @param invoiceIds
     * @param dealStatus
     * @return
     */
    public boolean invoiceUpdate(UserContext uc, String invoiceIds, String dealStatus) throws SQLException {
        for (String invoiceId : invoiceIds.split(",")) {
            BO invoice = SDK.getBOAPI().get("BO_EU_DNCTT_INVOICE", invoiceId);
            List<BO> invoiceDetails = SDK.getBOAPI().query("BO_EU_DNCTT_INVOICE_DETAIL").bindId(invoice.getBindId()).addQuery("INVOICE_ID =", invoiceId).orderBy("ACCOUNT_ID").orderBy("BILL_ACCOUNT_ID").list();

            Map<String, HashSet<String>> TypeDedIds = new HashMap<>();

            int lineNo = 1;
            for (BO invoiceDetail : invoiceDetails) {

                String accountDeductionId = invoiceDetail.getString("ACCOUNT_DEDUCTION_ID");
                if (StringUtils.isBlank(accountDeductionId))
                    continue;
                BO accountDeduction = SDK.getBOAPI().get("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accountDeductionId);
                if (accountDeduction == null)
                    continue;

                String typeId = accountDeduction.getString("TYPE_ID");
                if (TypeDedIds.containsKey(typeId)) {
                    TypeDedIds.get(typeId).add(accountDeductionId);
                } else {
                    TypeDedIds.put(typeId, new HashSet<String>());
                    TypeDedIds.get(typeId).add(accountDeductionId);
                }

                if (dealStatus.equals("生成发票")) {
                    if (StringUtils.isBlank(accountDeduction.getString("INVOICE_ID"))) {
                        accountDeduction.set("INVOICE_ID", invoice.getId());
                        accountDeduction.set("INVOICE_LINE_ITEM_NO", lineNo);

                        dn.recordFormChanges.record(uc, accountDeduction, "生成发票");

                        SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accountDeduction);
                        lineNo++;
                    }

                } else if (dealStatus.contains("作废发票")) {
                    if (StringUtils.isNotBlank(accountDeduction.getString("INVOICE_ID"))) {
                        accountDeduction.set("INVOICE_ID", null);
                        accountDeduction.set("INVOICE_LINE_ITEM_NO", 0);

                        dn.recordFormChanges.record(uc, accountDeduction, "作废发票");

                        SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", accountDeduction);
                        lineNo++;
                    }
                }
            }

            if (dealStatus.contains("作废发票")) {
                SDK.getProcessAPI().deleteById(invoice.getBindId(), uc.getUID());
                SDK.getBOAPI().remove("BO_EU_DNCTT_INVOICE", invoiceId);
                SDK.getBOAPI().removeByBindId("BO_EU_DNCTT_INVOICE_DETAIL", invoice.getBindId());
                DBSql.update("DELETE FROM BO_EU_DNCTT_INVOICE_DETAIL WHERE BINDID=? OR INVOICE_ID=?", new Object[]{invoice.getBindId(), invoiceId});
            } else if (dealStatus.equals("生成发票")) {
                invoice.set("IS_VOIDED", 0);
                invoice.set("VOIDED_TIME", null);
                invoice.set("VOIDED_RESOURCE_ID", null);

                dn.recordFormChanges.record(uc, invoice, "生成发票");

                SDK.getBOAPI().update("BO_EU_DNCTT_INVOICE", invoice);
            } else if (dealStatus.equals("作废发票")) {
                invoice.set("IS_VOIDED", 1);//作废
                invoice.set("VOIDED_TIME", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                invoice.set("VOIDED_RESOURCE_ID", uc.getUID());
                dn.recordFormChanges.record(uc, invoice, "作废发票");
                SDK.getBOAPI().update("BO_EU_DNCTT_INVOICE", invoice);
            }

            if (dealStatus.contains("作废发票")) {
                List<BO> deductions = SDK.getBOAPI().query("BO_EU_DNCRM_ACCOUNT_DEDUCTION").addQuery("INVOICE_ID =", invoiceId).list();
                for (BO deduction : deductions) {

                    String typeId = deduction.getString("TYPE_ID");

                    if (TypeDedIds.containsKey(typeId) == false) {
                        TypeDedIds.put(typeId, new HashSet<String>());
                    }
                    TypeDedIds.get(typeId).add(deduction.getId());

                    deduction.set("INVOICE_ID", null);
                    deduction.set("INVOICE_LINE_ITEM_NO", 0);
                    dn.recordFormChanges.record(uc, deduction, "作废发票");
                    SDK.getBOAPI().update("BO_EU_DNCRM_ACCOUNT_DEDUCTION", deduction);


                    DBSql.update("UPDATE BO_EU_DNCRM_ACCOUNT_DEDUCTION SET INVOICE_ID=null,INVOICE_LINE_ITEM_NO=0 WHERE INVOICE_ID=?", new Object[]{invoiceId});
                }


                if (dealStatus.contains("取消审批") && TypeDedIds.isEmpty() == false) {
                    for (String typeId : TypeDedIds.keySet()) {
                        String ids = StringUtils.join(TypeDedIds.get(typeId), ",");
                        //定期服务
                        if (typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_SERVICE)) || typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_INITIAL_COST))) {
                            //撤销定期服务  初始费用
                            String result = contractService.getInstance().revokeRecurringServices(uc, ids);
                        } else if (typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_CHARGE))) {
                            //撤销合同成本成功
                            String result = contractService.getInstance().revokeCost(uc, ids);
                        } else if (typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_MILESTONES))) {
                            //撤销合同里程碑成功
                            String result = contractService.getInstance().revokeMilestones(uc, ids);
                        } else if (typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_EXPENSES))) {
                            //撤销合同费用成功
                            String result = contractService.getInstance().revokeExpense(uc, ids);
                        } else if (typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_LABOUR))) {
                            //撤销合同人工成功
                            String result = contractService.getInstance().revokeLabor(uc, ids);
                        } else if (typeId.equals(String.valueOf(cttConstant.ACCOUNT_DEDUCTION_TYPE_COMMISSION))) {
                            //撤销合同佣金成功
                            String result = contractService.getInstance().revokeCommission(uc, ids);
                        }
                    }
                }
            }
        }
        return true;

    }


    /**
     * 创建发票计划
     */
    public String invoicePlan(UserContext uc, String accDeduIds) {
        // 1. 基础校验：空值过滤
        if (StringUtils.isBlank(accDeduIds))
            return "参数错误";
        String[] dedIdArr = accDeduIds.split(",");

        StringBuilder where = new StringBuilder("(");
        for (int i = 0; i < dedIdArr.length; i++) {
            where.append("?").append(i < dedIdArr.length - 1 ? "," : ")");
        }
        String sqlWhere = where.toString(); // 复用：构建IN条件

        // 2. 初始化基础数据（3张核心表查询）
        List<RowMap> postedDataList = DBSql.getMaps("SELECT * FROM VIEW_EU_DNV_POSTED_ALL d WHERE d.ID IN " + sqlWhere + "  AND  d.bill_account_id is NOT NULL AND d.ORGID='" + uc.getCompanyModel().getId() + "' ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID", dedIdArr);

        logger.info("账单个数:" + postedDataList.size());

        postedDataList.forEach(row -> row.forEach((key, value) -> {
            if (key.toUpperCase().equals("BILL_ACCOUNT_ID")) {
                if (value == null || StringUtils.isBlank(value.toString())) {
                    row.put(key.toUpperCase(), row.get("ACCOUNT_ID"));
                } else {
                    row.put(key.toUpperCase(), value);
                }
            } else {
                row.put(key.toUpperCase(), value);
            }
        }));

        String BILL_ACCOUNT_ID = null;
        for (RowMap row : postedDataList) {
            String ITEM_DATE = row.getString("ITEM_DATE");
            if (StringUtils.isNotBlank(ITEM_DATE)) {


                //  ii) 批量选择条目：计费客户相同、状态必须为----未生成生成账单
                if (StringUtils.isBlank(BILL_ACCOUNT_ID)) BILL_ACCOUNT_ID = row.getString("BILL_ACCOUNT_ID");
                else {
                    if (!BILL_ACCOUNT_ID.equals(row.getString("BILL_ACCOUNT_ID"))) {
                        return "所选条目有账单客户必须相同";
                    }
                }

                //RECEIVE_STATUS  4661 未生成生成账单
                String RECEIVE_STATUS = row.getString("RECEIVE_STATUS");
                if (RECEIVE_STATUS.equals("4662") == false)
                    return "所选条目有账单状态必须为已生成生成账单";
            }
        }


        List<RowMap> accountDedList = DBSql.getMaps("SELECT * FROM VIEW_EU_DNCTT_INVOICE_DETAIL WHERE ACCOUNT_DEDUCTION_ID IN " + sqlWhere + " ORDER BY ACCOUNT_ID,BILL_ACCOUNT_ID", dedIdArr);

        if (accountDedList.size() == 0)
            return "所选条目无账单";

        ProcessInstance pi = SDK.getProcessAPI().createProcessInstance("obj_a1a00d7c7c0e4eb69acd57291557521d", uc.getUID(), "开票申请");

        BO invoice = new BO();
        invoice.setBindId(pi.getId());
        invoice.set("BILL_ACCOUNT_ID", BILL_ACCOUNT_ID);
        invoice.set("BILL_ACCOUNT_NAME", DBSql.getString("select NAME from bo_eu_dncrm_account where ID=?", new Object[]{BILL_ACCOUNT_ID}));
        invoice.set("INVOICE_AMOUNT", 0);
        invoice.set("STATUS_ID", "4471");

        SDK.getBOAPI().create("BO_EU_DNCTT_INVOICE_PLAN", invoice, pi, uc);

        SDK.getProcessAPI().start(pi);

        BigDecimal invoiceAmount = BigDecimal.ZERO;

        for (RowMap row : accountDedList) {

            invoiceAmount = invoiceAmount.add(TypeUtil.convert(row.get("INVOICE_AMOUNT"), BigDecimal.class));

            BO dedBO = new BO();
            dedBO.setAll(row);
            dedBO.setBindId(pi.getId());
            dedBO.set("INVOICE_DETAIL_ID", row.getString("ID"));
            SDK.getBOAPI().create("BO_EU_DNCTT_INVOICE_PLAN_DETAIL", dedBO, pi, uc);
        }

        DBSql.update("UPDATE BO_EU_DNCTT_INVOICE_PLAN SET INVOICE_AMOUNT=? WHERE ID=?", new Object[]{invoiceAmount, invoice.getId()});

        return "成功--" + invoice.getBindId();
    }


}