package cn.superdesk.uniorder.service.invoice.customer.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.superdesk.uniorder.common.bizScene.invoice.customer.InvoiceApplyCustomerScene;
import cn.superdesk.uniorder.common.enums.accounting.AccountingOrderTypeDimEnum;
import cn.superdesk.uniorder.common.enums.common.CommonAccountingStateEnum;
import cn.superdesk.uniorder.common.enums.common.CommonDirectionEnum;
import cn.superdesk.uniorder.common.enums.invoice.InvoiceApplyAuditStatusEnum;
import cn.superdesk.uniorder.common.enums.invoice.common.InvoiceTypeMoudelEnum;
import cn.superdesk.uniorder.common.enums.invoice.common.InvoiceTypeMoudelXModeEnum;
import cn.superdesk.uniorder.common.enums.invoice.customer.*;
import cn.superdesk.uniorder.common.enums.order.UnifiedOrderCreditInvoiceApplyStatusEnum;
import cn.superdesk.uniorder.common.exception.UnifiedOrderRuntimeException;
import cn.superdesk.uniorder.common.json.AnJsonUtils;
import cn.superdesk.uniorder.common.lamda.AnLamdaUtils;
import cn.superdesk.uniorder.common.numerical.AnBeanUtils;
import cn.superdesk.uniorder.common.numerical.AnBigDecimalUtils;
import cn.superdesk.uniorder.common.numerical.AnStringUtils;
import cn.superdesk.uniorder.common.rest.response.R;
import cn.superdesk.uniorder.common.rest.response.ResponseResultOuterUtils;
import cn.superdesk.uniorder.common.security.dto.LoginSysUser;
import cn.superdesk.uniorder.dao.entity.invoice.common.InvoiceBankInfo;
import cn.superdesk.uniorder.dao.entity.invoice.customer.InvoiceApply;
import cn.superdesk.uniorder.dao.entity.invoice.customer.InvoiceApplyXGoods;
import cn.superdesk.uniorder.dao.entity.organisation.SysEnterpriseExtInvoice;
import cn.superdesk.uniorder.dao.mapper.invoice.customer.InvoiceApplyMapper;
import cn.superdesk.uniorder.dao.mapper.invoice.customer.InvoiceApplyMapperExScore;
import cn.superdesk.uniorder.dao.mapper.invoice.customer.InvoiceApplyMapperExUnified;
import cn.superdesk.uniorder.dto._constant.ResourceConstant;
import cn.superdesk.uniorder.dto.param.accounting.score.customer.AccountingOrderScoreCustomerOpDetailQueryCriteria;
import cn.superdesk.uniorder.dto.param.invoice.common.InvoiceTypeModeRuleBiz;
import cn.superdesk.uniorder.dto.param.invoice.common.InvoiceTypeModeRuleSuper;
import cn.superdesk.uniorder.dto.param.invoice.customer.*;
import cn.superdesk.uniorder.dto.param.organisation.SysEnterpriseExtInvoiceParam;
import cn.superdesk.uniorder.dto.vo.accounting.score.customer.AccountingOrderScoreCustomerListItemVo;
import cn.superdesk.uniorder.dto.vo.invoice.common.InvoiceBankInfoVo;
import cn.superdesk.uniorder.dto.vo.invoice.customer.ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo;
import cn.superdesk.uniorder.dto.vo.invoice.customer.InvoiceApplyCustomerSelectScoreStruct;
import cn.superdesk.uniorder.dto.vo.invoice.customer.InvoiceApplyCustomerSelectScoreVo;
import cn.superdesk.uniorder.executor.scheduler.kafka._inner.InvoiceApplyCustomerKafkaEventPublisher;
import cn.superdesk.uniorder.executor.scheduler.kafka._inner.dto.invoice.customer.InvoiceApplyCustomerKafkaEventCheckAndUpdateAccountingOrderFieldInvoiceStatusEvent;
import cn.superdesk.uniorder.service.accounting.score.customer.AccountingScoreCustomerService;
import cn.superdesk.uniorder.service.accounting.unified.customer.AccountingUnifiedCustomerService;
import cn.superdesk.uniorder.service.order.base.UnifiedOrderCreditService;
import cn.superdesk.uniorder.service.invoice.common.InvoiceBankInfoService;
import cn.superdesk.uniorder.service.invoice.common.InvoiceTypeService;
import cn.superdesk.uniorder.service.invoice.customer.InvoiceApplyCustomerExportExcelService;
import cn.superdesk.uniorder.service.invoice.customer.InvoiceApplyCustomerOpCreateService;
import cn.superdesk.uniorder.service.invoice.customer.InvoiceApplyCustomerService;
import cn.superdesk.uniorder.service.invoice.customer.InvoiceApplyXGoodsService;
import cn.superdesk.uniorder.service.organisation.SysEnterpriseExtInvoiceService;
import cn.superdesk.uniorder.service.setting.UnifiedSnConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class InvoiceApplyCustomerOpCreateServiceImpl implements InvoiceApplyCustomerOpCreateService {

    @Autowired
    private AccountingScoreCustomerService   accountingScoreCustomerService;
    @Autowired
    private AccountingUnifiedCustomerService accountingUnifiedCustomerService;

    @Autowired
    private InvoiceApplyMapper          invoiceApplyMapper;
    @Autowired
    private InvoiceApplyMapperExUnified invoiceApplyMapperExUnified;
    @Autowired
    private InvoiceApplyMapperExScore   invoiceApplyMapperExScore;

    @Autowired
    private InvoiceApplyCustomerService invoiceApplyCustomerService;

    @Autowired
    private InvoiceApplyCustomerExportExcelService invoiceApplyCustomerExportExcelService;

    @Autowired
    private InvoiceApplyXGoodsService invoiceApplyXGoodsService;

    @Autowired
    private UnifiedOrderCreditService unifiedOrderCreditService;

    @Autowired
    private SysEnterpriseExtInvoiceService sysEnterpriseExtInvoiceService;
    @Autowired
    private InvoiceBankInfoService         invoiceBankInfoService;

    @Autowired
    private InvoiceTypeService invoiceTypeService;

    @Autowired
    private UnifiedSnConfigService unifiedSnConfigService;

    //    @Autowired
//    private InvoiceApplyCustomerTriggerEventPublisher invoiceApplyCustomerTriggerEventPublisher;
    @Autowired
    private InvoiceApplyCustomerKafkaEventPublisher invoiceApplyCustomerKafkaEventPublisher;

    @Autowired
    private ResourceConstant resourceConstant;

    /**
     * 发票申请 提交 前的 操作 ---- 订单维度
     * 过滤 已经申请开票的商品
     *
     * @param params
     * @return
     * @throws UnifiedOrderRuntimeException
     */
    @Override
    public List<InvoiceApplyCustomerSelectScoreVo> invoiceApplySelectFocusUnifiedDimOrder(Map<String, Object> params) throws UnifiedOrderRuntimeException {
        return invoiceApplyMapperExUnified.invoiceApplySelectFocusUnifiedDimOrder(params);
    }

    /**
     * 发票申请 提交 前的 操作 ---- 商品维度
     *
     * @param params
     * @return
     * @throws UnifiedOrderRuntimeException
     */
    @Override
    public List<InvoiceApplyCustomerSelectScoreVo> invoiceApplySelectFocusUnifiedDimGoods(Map<String, Object> params) throws UnifiedOrderRuntimeException {
        return invoiceApplyMapperExUnified.invoiceApplySelectFocusUnifiedDimGoods(params);
    }

    /**
     * 发票申请 提交 前的 操作 ---- 流水维度
     * 过滤 已经申请开票的流水
     *
     * @param queryCriteria
     * @return
     * @throws UnifiedOrderRuntimeException
     */
    @Override
    public List<InvoiceApplyCustomerSelectScoreVo> invoiceApplySelectFocusScoreDimCredit(InvoiceApplyCustomerQueryCriteria queryCriteria) throws UnifiedOrderRuntimeException {
        return invoiceApplyMapperExScore.invoiceApplySelectFocusScoreDimCredit(queryCriteria);
    }

    /**
     * 积分结算2.1
     *
     * @param queryCriteria
     * @param loginSysUser
     * @return
     * @throws UnifiedOrderRuntimeException
     */
    @Override
    public InvoiceApplyCustomerSelectScoreStruct invoiceApplySelectFocusScoreDimCreditV2_1(InvoiceApplyCustomerQueryCriteria queryCriteria, LoginSysUser loginSysUser) throws UnifiedOrderRuntimeException {


        /****************************************** 对账单VO start ******************************************/

        AccountingOrderScoreCustomerListItemVo accountingOrderScoreCustomerVo = accountingScoreCustomerService.getAccountingOrderScoreCustomerVoByAccountingOrderId(
                AccountingOrderScoreCustomerOpDetailQueryCriteria.builder().accountingOrderId(queryCriteria.getAccountingOrderId()).build(),
                loginSysUser
        );

        /****************************************** 对账单VO   end ******************************************/

        /****************************************** 流水List start ******************************************/

        // 积分结算2.1 添加 退单号 mark by linjinyu at 20220223
        List<InvoiceApplyCustomerSelectScoreVo> creditList = invoiceApplyMapperExScore.invoiceApplySelectFocusScoreDimCredit(queryCriteria);

        /****************************************** 流水List   end ******************************************/

        /****************************************** 销方信息 start ******************************************/

        InvoiceBankInfoVo invoiceBankInfoVo = null;
        if (StrUtil.isNotBlank(accountingOrderScoreCustomerVo.getInvoiceBankInfoSnapshot())) {
            invoiceBankInfoVo = AnJsonUtils.toObject(accountingOrderScoreCustomerVo.getInvoiceBankInfoSnapshot(), InvoiceBankInfoVo.class);
        } else {
            InvoiceBankInfo invoiceBankInfo = invoiceBankInfoService.getInvoiceBankInfoByIdOrNullByDefault(accountingOrderScoreCustomerVo.getBankInfoId());
            invoiceBankInfoVo = AnBeanUtils.copy(invoiceBankInfo, InvoiceBankInfoVo.class);
        }

        /****************************************** 销方信息   end ******************************************/

        return InvoiceApplyCustomerSelectScoreStruct
                .builder()
                .accounting(accountingOrderScoreCustomerVo)
                .creditList(creditList)
                .bankInfo(invoiceBankInfoVo)
                .build();
    }


    /**
     * opt一键分票(前端按税率分票)
     * 兼容
     * 客户结算
     * 积分结算 2.0
     *
     * @param form
     * @param loginSysUser
     * @param invoiceModeEnum
     * @return
     * @throws UnifiedOrderRuntimeException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public InvoiceApplyCustomerKafkaEventCheckAndUpdateAccountingOrderFieldInvoiceStatusEvent opInvoiceApply(
            InvoiceApplyCommonOpApplyForm form,
            LoginSysUser loginSysUser,
            InvoiceTypeMoudelXModeEnum invoiceModeEnum) throws UnifiedOrderRuntimeException {

        DateTime now = DateUtil.date();

        InvoiceApply invoiceApplyCustomerEntity = new InvoiceApply();

        invoiceApplyCustomerEntity.setInvoiceApplySn(unifiedSnConfigService.getCustomerInvoiceApplySnBySnowflake());

        // 对账单 类型:1-一般;2-积分;
        invoiceApplyCustomerEntity.setAccountingOrderType(form.getAccountingOrderTypeDim());
        // 对账单 创建维度:0-订单;1-商品;2-流水;
        invoiceApplyCustomerEntity.setDimCreateBy(form.getDimCreateBy());

        invoiceApplyCustomerEntity.setAccountingOrderId(form.getAccountingOrderId());
        invoiceApplyCustomerEntity.setAccountingOrderSn(form.getAccountingOrderSn());

        // 发票抬头 之前名称 invoiceCompanyName - 弃用 - 代之为 sysEnterpriseExtInvoiceId sys_enterprise_ext_invoice.id
        invoiceApplyCustomerEntity.setInvoiceTitle(form.getInvoiceTitle());

        // 兼容做法
        if (ObjectUtil.isNotNull(form.getSysEnterpriseExtInvoiceId())) {
            invoiceApplyCustomerEntity.setSysEnterpriseExtInvoiceId(form.getSysEnterpriseExtInvoiceId());
            SysEnterpriseExtInvoice sysEnterpriseExtInvoice = sysEnterpriseExtInvoiceService.findById(form.getSysEnterpriseExtInvoiceId());
            if (ObjectUtil.isNotNull(sysEnterpriseExtInvoice)) {
                invoiceApplyCustomerEntity.setSysEnterpriseExtInvoiceSnapshot(JSONUtil.toJsonStr(sysEnterpriseExtInvoice));

                // 补刀 mark by linjinyu at 20220224
                if (ObjectUtil.isNull(invoiceApplyCustomerEntity.getInvoiceTitle())) {
                    invoiceApplyCustomerEntity.setInvoiceTitle(sysEnterpriseExtInvoice.getCompanyName());
                }
            }


        } else {
            invoiceApplyCustomerEntity.setSysEnterpriseExtInvoiceId(0L);
        }

        // 发票种类
        invoiceApplyCustomerEntity.setInvoiceType(form.getInvoiceTypeId());
        // 开票方式 积分结算2.1 mark by linjinyu at 20220224
        invoiceApplyCustomerEntity.setInvoiceMode(form.getInvoiceMode());

        // 注意 这里是字符串,首先,本来开表的人这样定的,当初也是想记录这个票是什么税率,唯一的,
        // 但后来发现,前端拿错字段来一键分票了,所以真实的情况是,发票申请有可能数据上包含了多税率
        // 目前这个字段不可信,要重新较验一下 mark by linjinyu at 20220224 记录 再看一下上线时间, 往后推时间
        invoiceApplyCustomerEntity.setTaxRate(form.getTaxRate());

        invoiceApplyCustomerEntity.setInvoiceApplyAmount(form.getInvoiceApplyAmount());
        invoiceApplyCustomerEntity.setFreeAmount(form.getFreeAmount());
        invoiceApplyCustomerEntity.setRealAmount(form.getRealAmount());
        invoiceApplyCustomerEntity.setNoCheckCount(form.getNoCheckCount());

        /****************************************** 发票申请审核部分 start ******************************************/

        invoiceApplyCustomerEntity.setApplyUserId(loginSysUser.getSysUserId());
        invoiceApplyCustomerEntity.setApplyTime(now);

        // mark by linjinyu at 2022-06-168 01:20:06 - 【【结算中心-测试环境】积分结算-发票申请管理-“更新人”初始值需取“申请人”】https://www.tapd.cn/33003868/bugtrace/bugs/view?bug_id=1133003868001005388
        invoiceApplyCustomerEntity.setUpdateUserId(loginSysUser.getSysUserId());
        invoiceApplyCustomerEntity.setUpdateTime(now);

        /****************************************** 发票申请审核部分 start ******************************************/

        invoiceApplyCustomerEntity.setAuditStatus(InvoiceApplyAuditStatusEnum.未审核.getKey());

        /****************************************** 开票进展部分 start ******************************************/

        invoiceApplyCustomerEntity.setInvoiceStatus(InvoiceApplyCustomerInvoiceProgressingStatusEnum.待开票.getKey());

        /****************************************** 开票登记部分 start ******************************************/

        invoiceApplyCustomerEntity.setMakeInvoiceStatus(InvoiceApplyCustomerMakeInvoiceStatusEnum.待开票.getKey());

        /****************************************** 运营领票部分 start ******************************************/

        invoiceApplyCustomerEntity.setReceiveInvoiceStatus(InvoiceApplyCustomerReceiveInvoiceStatusEnum.未领票.getKey());

        /****************************************** 寄票登记部分 start ******************************************/

        invoiceApplyCustomerEntity.setMailTickStatus(InvoiceApplyCustomerMailTickStatusEnum.未寄票.getKey());

        /****************************************** 回款登记部分 start ******************************************/

        invoiceApplyCustomerEntity.setBackAccountStatus(InvoiceApplyCustomerBackAccountStatusEnum.未回款.getKey());

        invoiceApplyCustomerEntity.setRemark(form.getRemark());
//        invoiceApplyCustomerEntity.setRemark2(form.getRemark());

        /**
         * 20220516 订单来源
         */
        if (StringUtils.isNotBlank(form.getSourceFrom())) {
            invoiceApplyCustomerEntity.setSourceFrom(form.getSourceFrom());
        }
        if (StringUtils.isNotBlank(form.getServiceNo())) {
            invoiceApplyCustomerEntity.setServiceNo(form.getServiceNo());
        }

        int retMain = invoiceApplyMapper.insertSelective(invoiceApplyCustomerEntity);

        List<Long> affectedAccountingOrderIds = new ArrayList<>();

        switch (AccountingOrderTypeDimEnum.get(form.getAccountingOrderTypeDim())) {
            case 一般订单:

                List<InvoiceApplyOpCreateSelectedGoodsParams> goodsSelected = form.getGoodsSelected();

                if (CollUtil.isNotEmpty(goodsSelected)) {

                    affectedAccountingOrderIds = goodsSelected
                            .stream()
                            .filter(AnLamdaUtils.distinctByKey(o -> o.getAccountingOrderId()))
                            .map(o -> o.getAccountingOrderId())
                            .collect(Collectors.toList());

                    List<InvoiceApplyXGoods> processListDimGoods = new ArrayList<>();

                    // TODO 如果有多个对账单 最好还是 验证一下 是否同一个企业为好
                    for (InvoiceApplyOpCreateSelectedGoodsParams opCreateSelectedGoodsParams : goodsSelected) {

                        InvoiceApplyXGoods invoiceApplyXGoods = new InvoiceApplyXGoods();

                        // 反正都是 iterator 就写这里了
//						toDoUpdateAccountingOrderInvoiceStatusList.add(invoiceApplyOrderGoodsFocusUnifiedParams.getAccountingOrderId());

                        // 获取刚刚insert的Id
                        invoiceApplyXGoods.setInvoiceApplyId(invoiceApplyCustomerEntity.getId());

                        invoiceApplyXGoods.setAccountingOrderType(AccountingOrderTypeDimEnum.一般订单.getKey());
                        // 在 controller 写死了
                        invoiceApplyXGoods.setDimCreateBy(form.getDimCreateBy());

                        // unified_order_goods.id 非第三方系统传过来的goodsId 是系统自增生成的 Id
                        invoiceApplyXGoods.setUnifiedOrderGoodsId(opCreateSelectedGoodsParams.getUnifiedOrderGoodsId());
                        // accounting_unified_customer_order.id 中间表的 订单Id
                        invoiceApplyXGoods.setUnifiedOrderId(opCreateSelectedGoodsParams.getUnifiedOrderId());
                        // accounting_unified_customer.id
                        invoiceApplyXGoods.setAccountingOrderId(opCreateSelectedGoodsParams.getAccountingOrderId());

                        invoiceApplyXGoods.setAccountingState(CommonAccountingStateEnum.已对账.getKey());

                        /****************************************** TODO 性能优化 mark by linjinyu at 20220223 start ******************************************/

//						invoiceApplyXGoodsMapper.insertSelective(invoiceApplyXGoods);
                        processListDimGoods.add(invoiceApplyXGoods);

                        /****************************************** TODO 性能优化 mark by linjinyu at 20220223   end ******************************************/

                    }

                    invoiceApplyXGoodsService.insertBatch(processListDimGoods);


                }
                break;

            case 积分订单:

                List<InvoiceApplyOpCreateSelectedCreditParams> creditSelected = form.getCreditSelected();

                // TODO 这里存在传递的问题，那些 结算 那些 不结算

                if (CollUtil.isNotEmpty(creditSelected)) {

                    affectedAccountingOrderIds = creditSelected
                            .stream()
                            .filter(AnLamdaUtils.distinctByKey(o -> o.getAccountingOrderId()))
                            .map(o -> o.getAccountingOrderId())
                            .collect(Collectors.toList());

                    List<Long> processUnifiedOrderCreditIds = creditSelected
                            .stream()
                            .map(o -> o.getUnifiedOrderCreditId())
                            .collect(Collectors.toList());

                    List<InvoiceApplyXGoods> processListDimCredit = new ArrayList<>();

                    for (InvoiceApplyOpCreateSelectedCreditParams opCreateSelectedCreditParams : creditSelected) {

                        InvoiceApplyXGoods invoiceApplyXGoods = new InvoiceApplyXGoods();

                        // 反正都是 iterator 就写这里了
//						toDoUpdateAccountingOrderInvoiceStatusList.add(invoiceApplyOrderGoodsFocusScoreParams.getAccountingOrderId());

                        // 获取刚刚insert的Id
                        invoiceApplyXGoods.setInvoiceApplyId(invoiceApplyCustomerEntity.getId());

                        invoiceApplyXGoods.setAccountingOrderType(AccountingOrderTypeDimEnum.积分订单.getKey());
                        // 在 controller 写死了
                        invoiceApplyXGoods.setDimCreateBy(form.getDimCreateBy());

                        invoiceApplyXGoods.setUnifiedOrderCreditId(opCreateSelectedCreditParams.getUnifiedOrderCreditId());
                        // unified_order_goods.id 非商城传过来的goodsId 是系统自增生成的 Id
                        invoiceApplyXGoods.setUnifiedOrderGoodsId(opCreateSelectedCreditParams.getUnifiedOrderGoodsId());
                        // accounting_unified_customer_order.id 中间表的 订单Id
                        invoiceApplyXGoods.setUnifiedOrderId(opCreateSelectedCreditParams.getUnifiedOrderId());
                        // accounting_unified_customer.id
                        invoiceApplyXGoods.setAccountingOrderId(opCreateSelectedCreditParams.getAccountingOrderId());

                        // TODO
                        invoiceApplyXGoods.setAccountingState(CommonAccountingStateEnum.已对账.getKey());

                        processListDimCredit.add(invoiceApplyXGoods);

                    }

                    // 持久化
                    int ret = invoiceApplyXGoodsService.insertBatch(processListDimCredit);

                    // 更新 流水's 发票申请状态
                    unifiedOrderCreditService.updateFieldInvoiceApplyStatusByUnifiedOrderCreditIds(processUnifiedOrderCreditIds, UnifiedOrderCreditInvoiceApplyStatusEnum.已申请);

                    if (retMain > 0) {

                        if (ArrayUtil.contains(
                                InvoiceApplyCustomerScene.statusV2_1_Array_InvoiceProgressingSceneRevise(),
                                invoiceApplyCustomerEntity.getInvoiceStatus())
                        ) {
                            invoiceApplyCustomerService.patchFixInfo(invoiceApplyCustomerEntity.getId(), loginSysUser);
                            invoiceApplyCustomerService.patchFixStatistics(invoiceApplyCustomerEntity.getId(), loginSysUser);
                        }
                    }
                }

                break;
            default:
                throw new UnifiedOrderRuntimeException(500, "什么?非一般订单,积分订单,还能是什么订单?");
        }


        /**
         * 通过 对账单 ids(得出受影响的对账单 ids)
         * 再去 较验 对账单 发票状态
         */
//        return TailUpdateAccountingScoreCustomerFieldInvoiceStatusEvent
//                .builder()
//                .affectedAccountingOrderIds(affectedAccountingOrderIds)
//                .dim(AccountingOrderTypeDimEnum.get(form.getAccountingOrderTypeDim()))
//                .invoiceApplyId(invoiceApplyCustomerEntity.getId())
//                .loginSysUser(loginSysUser)
//                .build();

        return InvoiceApplyCustomerKafkaEventCheckAndUpdateAccountingOrderFieldInvoiceStatusEvent
                .builder()

                .msg("op开票申请")

                .loginSysUser(JSONUtil.toJsonStr(loginSysUser))
                .touchTime(now)

                .accountingOrderTypeDim(form.getAccountingOrderTypeDim())
                .invoiceApplyId(invoiceApplyCustomerEntity.getId())

                .build();

    }


    /**
     * opt一键分票(后端按税率分票(开关TODO))
     * 按开票方式分支(包含开票规则)
     *
     * @param form
     * @param loginSysUser
     * @return
     */
    @Override
    public R<InvoiceApplyFocusScoreOpApplyReportV2_1> opInvoiceApplyV2_1(InvoiceApplyFocusScoreOpApplyFormV2_1 form, LoginSysUser loginSysUser) {

//		线下开票(offLine)
//		深圳航信电子发票(szhtxxElectronicInvoice)

        if (StrUtil.isBlank(form.getServiceNo())) {
            return ResponseResultOuterUtils.createErrorResult("500", "ServiceNo 异常");
        }

        if (StrUtil.isBlank(form.getSourceFrom())) {
            return ResponseResultOuterUtils.createErrorResult("500", "SourceFrom 异常");
        }


        switch (InvoiceTypeMoudelXModeEnum.get(form.getInvoiceMode())) {
            case 线下开票:
                return opInvoiceApplyV2_1BranchOffLine(form, loginSysUser, InvoiceTypeMoudelXModeEnum.线下开票);
            case 深圳航信电子发票:
                return opInvoiceApplyV2_1BranchSzhtxxElectronicInvoice(form, loginSysUser, InvoiceTypeMoudelXModeEnum.深圳航信电子发票);
            case 深圳航信纸质发票:
                return opInvoiceApplyV2_2BranchSzhtxxPaperInvoice(form, loginSysUser, InvoiceTypeMoudelXModeEnum.深圳航信纸质发票);
            default:
                return ResponseResultOuterUtils.createErrorResult("500", "操作失败");
        }
    }


    /**
     * 积分结算2.1 线下开票(offLine)
     *
     * @param formV2_1
     * @param loginSysUser
     * @param invoiceModeEnum
     * @return
     */
    private R<InvoiceApplyFocusScoreOpApplyReportV2_1> opInvoiceApplyV2_1BranchOffLine(
            InvoiceApplyFocusScoreOpApplyFormV2_1 formV2_1,
            LoginSysUser loginSysUser,
            InvoiceTypeMoudelXModeEnum invoiceModeEnum) {


        //(1)该对账单存在, 否则不允许一键分票, 1.5s提示"该对账单不存在, 请返回检查";
        //(2)该对账单状态为"已确认", 否则不允许一键分票, 1.5s"该对账单状态发生变更, 请返回检查";
        //(3)该对账单申请状态为"未申请/部分申请", 否则不允许一键分票, 1.5s提示"该对账单申请状态发生变更, 请返回检查";
        //(4)该对账单"已选"开票数据数量≥1, 否则不允许一键分票, 1.5s提示"已选的开票数据不能为空, 请选择"; ----
        //(5)由于数据量大, 暂不对每条流水结算状态及申请状态检查;
        //(6)若服务端存在其他必要性判断可自补充判断;

        // TODO
        // 1.
        List<InvoiceApplyOpCreateSelectedCreditParams> creditSelected = formV2_1.getCreditSelected();

        if (CollUtil.isEmpty(creditSelected)) {
            return ResponseResultOuterUtils.createSuccesResult("已选的开票数据不能为空, 请选择", InvoiceApplyFocusScoreOpApplyReportV2_1.builder().build());
        }

        List<Long> unifiedOrderCreditIdsSelected = creditSelected
                .stream()
                .map(s -> s.getUnifiedOrderCreditId())
                .collect(Collectors.toList());

        // 对账单下选中的流水 逻辑修改 mark by linjinyu at 20220224
        List<InvoiceApplyCustomerSelectScoreVo> creditResult = invoiceApplyMapperExScore.invoiceApplySelectFocusScoreDimCredit(
                InvoiceApplyCustomerQueryCriteria
                        .builder()
                        /* 限制1 对账单 */
                        .accountingOrderId(formV2_1.getAccountingOrderId())
                        /* 限制2 对账单 下的流水 */
                        .unifiedOrderCreditIdsSelected(unifiedOrderCreditIdsSelected)
                        /* 限制3 对账单 下的流水 且 未申请 mark by linjinyu at 20220408 */
//                        .uocAccountingStateCustomer(CommonAccountingStateEnum.未对账.getKey())
                        .excludeInvoiceApplied(CommonAccountingStateEnum.未对账.getKey())
                        .build()
        );

        if (CollUtil.isEmpty(creditResult)) {
            return ResponseResultOuterUtils.createErrorResult("500", "已选的开票数据不能为空或数据状态已经变更, 请选择");
        }

        if (creditSelected.size() != creditResult.size()) {
            return ResponseResultOuterUtils.createErrorResult("500", "数据异常或数据状态已经变更, 请刷新, 重新操作");
        }

        /****************************************** 按税率分组 start ******************************************/

        /**
         * 现在处理过程假定平台税率不为空的情况
         * 在此处理为空的情况 如果为空 设定为 0税率
         */
        Map<BigDecimal, List<InvoiceApplyCustomerSelectScoreVo>> transferMapGroupByOperationTaxRate = creditResult
                .parallelStream()
                .collect(Collectors.groupingBy(e -> e.getOperationTaxRate() == null ? BigDecimal.ZERO : e.getOperationTaxRate(), Collectors.toList()));

        /****************************************** 按税率分组   end ******************************************/

        for (Map.Entry<BigDecimal, List<InvoiceApplyCustomerSelectScoreVo>> _entry : transferMapGroupByOperationTaxRate.entrySet()) {

            BigDecimal                              operationTaxRate                    = _entry.getKey();
            List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateCreditResult = _entry.getValue();

            tmpLog(formV2_1,
                    /* 过程部分 */ AnStringUtils.getString(operationTaxRate),
                    /* 过程报告 */ JSONUtil.toJsonPrettyStr(groupByOperationTaxRateCreditResult),
                    invoiceModeEnum
            );

            /****************************************** 与线上差异 start ******************************************/

            // 拆分后组装
            InvoiceApplyCommonOpApplyForm _assemblyInvoiceApply = assemblyInvoiceApply(formV2_1, operationTaxRate, groupByOperationTaxRateCreditResult, loginSysUser);

            // TODO 统一记录 event 再看一下要怎样处理吧
            InvoiceApplyCustomerKafkaEventCheckAndUpdateAccountingOrderFieldInvoiceStatusEvent event = opInvoiceApply(_assemblyInvoiceApply, loginSysUser, invoiceModeEnum);
            // 更新对账单 开票申请状态 TODO superdesk-uniorder-executor 上线后，这地方要改造
            invoiceApplyCustomerKafkaEventPublisher.checkAndUpdateAccountingOrderFieldInvoiceStatus(event);

            /****************************************** 与线上差异   end ******************************************/
        }

        return ResponseResultOuterUtils.createSuccesResult("", InvoiceApplyFocusScoreOpApplyReportV2_1.builder().build());
    }


    /**
     * 积分结算2.1 深圳航信电子发票(szhtxxElectronicInvoice)
     *
     * @param formV2_1
     * @param loginSysUser
     * @param invoiceModeEnum
     * @return
     */
    private R<InvoiceApplyFocusScoreOpApplyReportV2_1> opInvoiceApplyV2_1BranchSzhtxxElectronicInvoice(
            InvoiceApplyFocusScoreOpApplyFormV2_1 formV2_1,
            LoginSysUser loginSysUser,
            InvoiceTypeMoudelXModeEnum invoiceModeEnum) {

        //(1)该对账单存在, 否则不允许一键分票, 1.5s提示"该对账单不存在, 请返回检查";
        //(2)该对账单状态为"已确认", 否则不允许一键分票, 1.5s"该对账单状态发生变更, 请返回检查";
        //(3)该对账单申请状态为"未申请/部分申请", 否则不允许一键分票, 1.5s提示"该对账单申请状态发生变更, 请返回检查";
        //(4)该对账单"已选"开票数据数量≥1, 否则不允许一键分票, 1.5s提示"已选的开票数据不能为空, 请选择"; ----
        //(5)由于数据量大, 暂不对每条流水结算状态及申请状态检查;
        //(6)若服务端存在其他必要性判断可自补充判断;

        // TODO
        // 1.
        List<InvoiceApplyOpCreateSelectedCreditParams> creditSelected = formV2_1.getCreditSelected();

        if (CollUtil.isEmpty(creditSelected)) {
            return ResponseResultOuterUtils.createSuccesResult("已选的开票数据不能为空, 请选择", InvoiceApplyFocusScoreOpApplyReportV2_1.builder().build());
        }

        List<Long> unifiedOrderCreditIdsSelected = creditSelected.stream()
                .map(s -> s.getUnifiedOrderCreditId())
                .collect(Collectors.toList());

        // 对账单下选中的流水 逻辑修改 mark by linjinyu at 20220224
        List<InvoiceApplyCustomerSelectScoreVo> creditResult = invoiceApplyMapperExScore.invoiceApplySelectFocusScoreDimCredit(
                InvoiceApplyCustomerQueryCriteria
                        .builder()
                        /* 限制1 对账单 */
                        .accountingOrderId(formV2_1.getAccountingOrderId())
                        /* 限制2 对账单 下的流水 */
                        .unifiedOrderCreditIdsSelected(unifiedOrderCreditIdsSelected)
                        /* 限制3 对账单 下的流水 且 未申请 mark by linjinyu at 20220408 */
//                        .uocAccountingStateCustomer(CommonAccountingStateEnum.未对账.getKey())
                        .excludeInvoiceApplied(CommonAccountingStateEnum.未对账.getKey())
                        .build()
        );

        log.info("一键分票 来源数据\n{}", JSONUtil.toJsonPrettyStr(creditResult));

        if (CollUtil.isEmpty(creditResult)) {
            return ResponseResultOuterUtils.createErrorResult("500", "已选的开票数据不能为空或数据状态已经变更, 请选择");
        }

        if (creditSelected.size() != creditResult.size()) {
            return ResponseResultOuterUtils.createErrorResult("500", "数据异常或数据状态已经变更, 请刷新, 重新操作");
        }

        /****************************************** 按税率分组 start ******************************************/

        // 现在假定平台税率不为空的情况 税率为空时设置为0
        Map<BigDecimal, List<InvoiceApplyCustomerSelectScoreVo>> transferMapGroupByOperationTaxRate = creditResult.parallelStream()
                .collect(Collectors.groupingBy(e -> e.getOperationTaxRate() == null ? BigDecimal.ZERO : e.getOperationTaxRate(), Collectors.toList()));

        /****************************************** 按税率分组   end ******************************************/


        // 线上 一键分票规则 (已经先按税率拆分,现在假定平台税率不为空的情况 税率为空时设置为0 与 税率为0合并到一齐)
        // 1. 发票种类 invoiceTypeId 开票方式 invoiceMode 设置与配置
        // 2. 发票种类 开票方式 invoiceMode(里边的规则) 规则-商品行
        // 3. 销方-开票设置(收款人,复核人,开票人) 票面设置(发票种类(单张票面最大限额(不含税)))
        // 4. 购方-发票抬头 sysEnterpriseExtInvoiceId 属性-发票种类 与 传入-发票种类 要比对吗

        // 推导出 只能
        // 1. 先按 规则-最大限额
        // 2. 再按 规则-商品行 拆行时 要保证订单完整性(冲突,因为已经按商品的税率拆分)


        /****************************************** 发票种类 start ******************************************/

        /****************************************** 发票种类   end ******************************************/

        /****************************************** 发票种类 开票方式 start ******************************************/

        // 开票规则 限制行数 实时的
        InvoiceTypeModeRuleSuper invoiceTypeModeRule = invoiceTypeService.getInvoiceModeRuleByInvoiceTypeIdAndModuleAndMode(formV2_1.getInvoiceTypeId(), InvoiceTypeMoudelEnum.积分结算.getKey(), formV2_1.getInvoiceMode(), loginSysUser);

        /****************************************** 发票种类 开票方式  end ******************************************/

        /****************************************** 销方 start ******************************************/

        // 开票规则 限制单张票面最大限额(不含税) 快照的
        InvoiceBankInfo invoiceBankInfo = invoiceBankInfoService.getInvoiceBankInfoByIdOrNullByDefault(formV2_1.getSysEnterpriseExtInvoiceId());

        /****************************************** 销方   end ******************************************/

        /****************************************** 购方 start ******************************************/

        SysEnterpriseExtInvoice sysEnterpriseExtInvoice = sysEnterpriseExtInvoiceService.findById(formV2_1.getSysEnterpriseExtInvoiceId());

        /****************************************** 购方   end ******************************************/

        InvoiceTypeModeRuleBiz invoiceTypeModeRuleBiz = assemblyRuleBiz(invoiceTypeModeRule, invoiceBankInfo, formV2_1.getInvoiceTypeId());

        // 按税率分组后
        for (Map.Entry<BigDecimal, List<InvoiceApplyCustomerSelectScoreVo>> _entry : transferMapGroupByOperationTaxRate.entrySet()) {

            BigDecimal                              operationTaxRate                    = _entry.getKey();
            List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateCreditResult = _entry.getValue();

            tmpLog(formV2_1,
                    /* 过程部分 */ AnStringUtils.getString(operationTaxRate),
                    /* 过程报告 */ JSONUtil.toJsonPrettyStr(groupByOperationTaxRateCreditResult),
                    invoiceModeEnum
            );

            /****************************************** 与线下差异 start ******************************************/

            // 先转换成 税务可行的形态
            List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> convertResult = convertInvoiceApplyCustomerSelectScoreVo2ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo(groupByOperationTaxRateCreditResult);

            // 先按 金额拆分 再按 行数拆分

            /****************************************** 与线下差异   end ******************************************/

            // 按规则分片后
            Map<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> sliceMap = invoiceApplySlice(convertResult, invoiceTypeModeRuleBiz, operationTaxRate);

            for (Map.Entry<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> __entry : sliceMap.entrySet()) {

                List<Long> sliceCreditIds = __entry.getValue().stream().map(o -> o.getUnifiedOrderCreditId()).collect(Collectors.toList());


                List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateSliceCreditResult = groupByOperationTaxRateCreditResult
                        .stream()
                        .filter(o -> ArrayUtil.contains(sliceCreditIds.toArray(), o.getUnifiedOrderCreditId()))// 这个过滤是不对的
                        .collect(Collectors.toList());

                log.info("一键分票 拆分数据 税率 {} -> index({}) -> 比对数量 {}:{}\n{}\n{}",
                        operationTaxRate,
                        __entry.getKey(),
                        sliceCreditIds.size(),
                        groupByOperationTaxRateSliceCreditResult.size(),
                        JSONUtil.toJsonStr(__entry.getValue()),
                        JSONUtil.toJsonStr(groupByOperationTaxRateSliceCreditResult)
                );

                // 拆分后组装
                InvoiceApplyCommonOpApplyForm _assemblyInvoiceApply = assemblyInvoiceApply(formV2_1, operationTaxRate, groupByOperationTaxRateSliceCreditResult, loginSysUser);

                // TODO 统一记录 event 再看一下要怎样处理吧
                InvoiceApplyCustomerKafkaEventCheckAndUpdateAccountingOrderFieldInvoiceStatusEvent event = opInvoiceApply(_assemblyInvoiceApply, loginSysUser, invoiceModeEnum);
                // 更新对账单 开票申请状态 TODO superdesk-uniorder-executor 上线后，这地方要改造
                invoiceApplyCustomerKafkaEventPublisher.checkAndUpdateAccountingOrderFieldInvoiceStatus(event);


                /**************************************** 要不要在这做部分 start ****************************************/

                // 更新 流水's 发票申请状态 已在 opInvoiceApply中
                // ...

                // 更新 流水's 开票状态
                // TODO

                /**************************************** 要不要在这做部分   end ****************************************/


            }
        }

        return ResponseResultOuterUtils.createSuccesResult("", InvoiceApplyFocusScoreOpApplyReportV2_1.builder().build());
    }


    private R<InvoiceApplyFocusScoreOpApplyReportV2_1> opInvoiceApplyV2_2BranchSzhtxxPaperInvoice(
            InvoiceApplyFocusScoreOpApplyFormV2_1 formV2_1,
            LoginSysUser loginSysUser,
            InvoiceTypeMoudelXModeEnum invoiceModeEnum) {

        //(1)该对账单存在, 否则不允许一键分票, 1.5s提示"该对账单不存在, 请返回检查";
        //(2)该对账单状态为"已确认", 否则不允许一键分票, 1.5s"该对账单状态发生变更, 请返回检查";
        //(3)该对账单申请状态为"未申请/部分申请", 否则不允许一键分票, 1.5s提示"该对账单申请状态发生变更, 请返回检查";
        //(4)该对账单"已选"开票数据数量≥1, 否则不允许一键分票, 1.5s提示"已选的开票数据不能为空, 请选择"; ----
        //(5)由于数据量大, 暂不对每条流水结算状态及申请状态检查;
        //(6)若服务端存在其他必要性判断可自补充判断;

        // TODO
        // 1.
        List<InvoiceApplyOpCreateSelectedCreditParams> creditSelected = formV2_1.getCreditSelected();

        if (CollUtil.isEmpty(creditSelected)) {
            return ResponseResultOuterUtils.createSuccesResult("已选的开票数据不能为空, 请选择", InvoiceApplyFocusScoreOpApplyReportV2_1.builder().build());
        }

        List<Long> unifiedOrderCreditIdsSelected = creditSelected.stream()
                .map(s -> s.getUnifiedOrderCreditId())
                .collect(Collectors.toList());

        // 对账单下选中的流水 逻辑修改 mark by linjinyu at 20220224
        List<InvoiceApplyCustomerSelectScoreVo> creditResult = invoiceApplyMapperExScore.invoiceApplySelectFocusScoreDimCredit(
                InvoiceApplyCustomerQueryCriteria
                        .builder()
                        /* 限制1 对账单 */
                        .accountingOrderId(formV2_1.getAccountingOrderId())
                        /* 限制2 对账单 下的流水 */
                        .unifiedOrderCreditIdsSelected(unifiedOrderCreditIdsSelected)
                        /* 限制3 对账单 下的流水 且 未申请 mark by linjinyu at 20220408 */
//                        .uocAccountingStateCustomer(CommonAccountingStateEnum.未对账.getKey())
                        .excludeInvoiceApplied(CommonAccountingStateEnum.未对账.getKey())
                        .build()
        );

        log.info("一键分票 来源数据\n{}", JSONUtil.toJsonPrettyStr(creditResult));

        if (CollUtil.isEmpty(creditResult)) {
            return ResponseResultOuterUtils.createErrorResult("500", "已选的开票数据不能为空或数据状态已经变更, 请选择");
        }

        if (creditSelected.size() != creditResult.size()) {
            return ResponseResultOuterUtils.createErrorResult("500", "数据异常或数据状态已经变更, 请刷新, 重新操作");
        }

        /****************************************** 按税率分组 start ******************************************/

        // 现在假定平台税率不为空的情况 税率为空时设置为0
        Map<BigDecimal, List<InvoiceApplyCustomerSelectScoreVo>> transferMapGroupByOperationTaxRate = creditResult.parallelStream()
                .collect(Collectors.groupingBy(e -> e.getOperationTaxRate() == null ? BigDecimal.ZERO : e.getOperationTaxRate(), Collectors.toList()));

        /****************************************** 按税率分组   end ******************************************/


        // 线上 一键分票规则 (已经先按税率拆分,现在假定平台税率不为空的情况 税率为空时设置为0 与 税率为0合并到一齐)
        // 1. 发票种类 invoiceTypeId 开票方式 invoiceMode 设置与配置
        // 2. 发票种类 开票方式 invoiceMode(里边的规则) 规则-商品行
        // 3. 销方-开票设置(收款人,复核人,开票人) 票面设置(发票种类(单张票面最大限额(不含税)))
        // 4. 购方-发票抬头 sysEnterpriseExtInvoiceId 属性-发票种类 与 传入-发票种类 要比对吗

        // 推导出 只能
        // 1. 先按 规则-最大限额
        // 2. 再按 规则-商品行 拆行时 要保证订单完整性(冲突,因为已经按商品的税率拆分)


        /****************************************** 发票种类 start ******************************************/

        /****************************************** 发票种类   end ******************************************/

        /****************************************** 发票种类 开票方式 start ******************************************/

        // 开票规则 限制行数 实时的
        InvoiceTypeModeRuleSuper invoiceTypeModeRule = invoiceTypeService.getInvoiceModeRuleByInvoiceTypeIdAndModuleAndMode(
                formV2_1.getInvoiceTypeId(),
                InvoiceTypeMoudelEnum.积分结算.getKey(),
                formV2_1.getInvoiceMode(),
                loginSysUser
        );

        /****************************************** 发票种类 开票方式  end ******************************************/

        /****************************************** 销方 start ******************************************/

        // 开票规则 限制单张票面最大限额(不含税) 快照的
        InvoiceBankInfo invoiceBankInfo = invoiceBankInfoService.getInvoiceBankInfoByIdOrNullByDefault(formV2_1.getSysEnterpriseExtInvoiceId());

        /****************************************** 销方   end ******************************************/

        /****************************************** 购方 start ******************************************/

        SysEnterpriseExtInvoice sysEnterpriseExtInvoice = sysEnterpriseExtInvoiceService.findById(formV2_1.getSysEnterpriseExtInvoiceId());

        /****************************************** 购方   end ******************************************/

        InvoiceTypeModeRuleBiz invoiceTypeModeRuleBiz = assemblyRuleBiz(invoiceTypeModeRule, invoiceBankInfo, formV2_1.getInvoiceTypeId());

        // 按税率分组后
        for (Map.Entry<BigDecimal, List<InvoiceApplyCustomerSelectScoreVo>> _entry : transferMapGroupByOperationTaxRate.entrySet()) {

            BigDecimal                              operationTaxRate                    = _entry.getKey();
            List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateCreditResult = _entry.getValue();

            tmpLog(formV2_1,
                    /* 过程部分 */ AnStringUtils.getString(operationTaxRate),
                    /* 过程报告 */ JSONUtil.toJsonPrettyStr(groupByOperationTaxRateCreditResult),
                    invoiceModeEnum
            );

            /****************************************** 与线下差异 start ******************************************/

            // 先转换成 税务可行的形态
            List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> convertResult = convertInvoiceApplyCustomerSelectScoreVo2ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo(groupByOperationTaxRateCreditResult);

            // 先按 金额拆分 再按 行数拆分

            /****************************************** 与线下差异   end ******************************************/

            // 按规则分片后
            Map<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> sliceMap = invoiceApplySlice(convertResult, invoiceTypeModeRuleBiz, operationTaxRate);

            for (Map.Entry<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> __entry : sliceMap.entrySet()) {

                List<Long> sliceCreditIds = __entry.getValue().stream().map(o -> o.getUnifiedOrderCreditId()).collect(Collectors.toList());

                List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateSliceCreditResult = groupByOperationTaxRateCreditResult
                        .stream()
                        .filter(o -> ArrayUtil.contains(sliceCreditIds.toArray(), o.getUnifiedOrderCreditId()))// 这个过滤是不对的
                        .collect(Collectors.toList());

                log.info("一键分票 拆分数据 税率 {} -> index({}) -> 比对数量 {}:{}\n{}\n{}",
                        operationTaxRate,
                        __entry.getKey(),
                        sliceCreditIds.size(),
                        groupByOperationTaxRateSliceCreditResult.size(),
                        JSONUtil.toJsonStr(__entry.getValue()),
                        JSONUtil.toJsonStr(groupByOperationTaxRateSliceCreditResult)
                );

                // 拆分后组装
                InvoiceApplyCommonOpApplyForm _assemblyInvoiceApply = assemblyInvoiceApply(formV2_1, operationTaxRate, groupByOperationTaxRateSliceCreditResult, loginSysUser);

                // TODO 统一记录 event 再看一下要怎样处理吧
                InvoiceApplyCustomerKafkaEventCheckAndUpdateAccountingOrderFieldInvoiceStatusEvent event = opInvoiceApply(_assemblyInvoiceApply, loginSysUser, invoiceModeEnum);
                // 更新对账单 开票申请状态 TODO superdesk-uniorder-executor 上线后，这地方要改造
                invoiceApplyCustomerKafkaEventPublisher.checkAndUpdateAccountingOrderFieldInvoiceStatus(event);


                /**************************************** 要不要在这做部分 start ****************************************/

                // 更新 流水's 发票申请状态 已在 opInvoiceApply中
                // ...

                // 更新 流水's 开票状态
                // TODO

                /**************************************** 要不要在这做部分   end ****************************************/


            }
        }

        return ResponseResultOuterUtils.createSuccesResult("", InvoiceApplyFocusScoreOpApplyReportV2_1
                .builder()
                .build()
        );
    }

    public void tmpLog(
            InvoiceApplyFocusScoreOpApplyFormV2_1 form,
            /* 过程部分 */String part,
            /* 过程报告 */String jsonBody,
            InvoiceTypeMoudelXModeEnum invoiceModeEnum) {

        DateTime now = DateUtil.date();

        String excelExportPathRoot             = resourceConstant.getPathRoot();
        String excelExportRelativePathTemplate = resourceConstant.getPathTemplate();
        String excelExportRelativePathStorage  = resourceConstant.getPathStorage();

        String relativePath = File.separator + "middle" +
                File.separator + "oneKeySliceApply" +
                File.separator + invoiceModeEnum.getKey() +
                File.separator + form.getAccountingOrderSn() +
                File.separator;

        String targetFullPath = excelExportPathRoot + excelExportRelativePathStorage;

        String targetWriteFileName = StrUtil.format("{}_{}_{}_{}_{}.json",
                form.getAccountingOrderSn(),
                form.getInvoiceTypeId(),
                form.getInvoiceMode(),
                part,
                now
        );
        String targetWriteFileFullPath = targetFullPath + relativePath + targetWriteFileName;
        targetWriteFileFullPath = targetWriteFileFullPath.replaceAll(":", "_");
        //String targetWriteFileFullPath = "D:\\xtmp\\_storage\\middle\\oneKeySliceApply\\szhtxxPaperInvoice\\CAA20220926799545012224\\" + targetWriteFileName;
        FileUtil.touch(targetWriteFileFullPath);
        FileUtil.writeUtf8String(jsonBody, targetWriteFileFullPath);// CharsetUtil.CHARSET_UTF_8
    }

    /**
     * 组装 assembly
     * 一切为了开票
     *
     * @param form
     * @param operationTaxRate
     * @param groupByOperationTaxRateCreditResult
     * @param loginSysUser
     * @return
     */
    private InvoiceApplyCommonOpApplyForm assemblyInvoiceApply(
            InvoiceApplyFocusScoreOpApplyFormV2_1 form,
            BigDecimal operationTaxRate,
            List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateCreditResult,
            LoginSysUser loginSysUser) {

        BigDecimal invoiceApplyAmount = BigDecimal.ZERO;
        BigDecimal freeAmount         = BigDecimal.ZERO;
        BigDecimal realAmount         = BigDecimal.ZERO;
        BigDecimal noCheckCount       = BigDecimal.ZERO;

        invoiceApplyAmount = groupByOperationTaxRateCreditResult
                .stream()
                .map(item -> item.getBalance() == null ? BigDecimal.ZERO : item.getBalance())
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        realAmount = invoiceApplyAmount.subtract(freeAmount);
        noCheckCount = realAmount;

        List<InvoiceApplyOpCreateSelectedCreditParams> creditSelected = groupByOperationTaxRateCreditResult.parallelStream()
                .map(o -> InvoiceApplyOpCreateSelectedCreditParams
                        .builder()
                        .accountingOrderId(o.getAccountingOrderId())
                        .unifiedOrderCreditId(o.getUnifiedOrderCreditId())
                        .unifiedOrderGoodsId(o.getUnifiedOrderGoodsId())
                        .unifiedOrderId(o.getUnifiedOrderId())
                        .build()
                ).collect(Collectors.toList());

        return InvoiceApplyCommonOpApplyForm.builder()
                .invoiceTitle("")
                .sysEnterpriseExtInvoiceId(form.getSysEnterpriseExtInvoiceId())
                .invoiceTypeId(form.getInvoiceTypeId())
                .invoiceMode(form.getInvoiceMode())
                .taxRate(AnBigDecimalUtils.getBigDecimalFormat(operationTaxRate))
                .invoiceApplyAmount(invoiceApplyAmount)
                .freeAmount(freeAmount)
                .realAmount(realAmount)
                .noCheckCount(noCheckCount)
                .remark(form.getRemark())
                .dimCreateBy(form.getDimCreateBy())
                .accountingOrderTypeDim(form.getAccountingOrderTypeDim())
                .accountingOrderId(form.getAccountingOrderId())
                .accountingOrderSn(form.getAccountingOrderSn())
                .creditSelected(creditSelected)// 注意 这里用的不是前端传的 form.getCreditSelected()

                .serviceNo(form.getServiceNo())//服务编号
                .sourceFrom(form.getSourceFrom())//订单来源

                .build();
    }

    /**
     * 组装规则
     *
     * @param invoiceTypeModeRule
     * @param invoiceBankInfo
     * @param invoiceTypeId
     * @return
     */
    private InvoiceTypeModeRuleBiz assemblyRuleBiz(InvoiceTypeModeRuleSuper invoiceTypeModeRule, InvoiceBankInfo invoiceBankInfo, Long invoiceTypeId) {

        String _singleInvoiceMaxAmount             = StrUtil.upperFirst("singleInvoiceMaxAmount");
        String getSingleInvoiceMaxAmountMethodName = StrUtil.format("get{}{}", _singleInvoiceMaxAmount, invoiceTypeId);

        log.info("assemblyRuleBiz\n{}\n{}", getSingleInvoiceMaxAmountMethodName, JSONUtil.toJsonStr(invoiceBankInfo));

        BigDecimal singleInvoiceMaxAmount = ReflectUtil.invoke(invoiceBankInfo, getSingleInvoiceMaxAmountMethodName);

        InvoiceTypeModeRuleBiz invoiceTypeModeRuleBiz = InvoiceTypeModeRuleBiz
                .builder()
                .ruleEnable(invoiceTypeModeRule.getRuleEnable())
                .ruleGoodsLines(invoiceTypeModeRule.getRuleGoodsLines())
                .singleInvoiceMaxAmount(singleInvoiceMaxAmount)
                .build();

        return invoiceTypeModeRuleBiz;
    }

    /**
     * 转换
     * List<InvoiceApplyCustomerSelectScoreVo>
     * List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>
     *
     * @param groupByOperationTaxRateCreditResult
     * @return
     */
    private List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> convertInvoiceApplyCustomerSelectScoreVo2ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo(List<InvoiceApplyCustomerSelectScoreVo> groupByOperationTaxRateCreditResult) {

        List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> result = new ArrayList<>();

        groupByOperationTaxRateCreditResult.forEach(item -> {

            if (ObjectUtil.isNull(item.getOperationTaxRate())) {
                item.setOperationTaxRate(BigDecimal.ZERO);
            }

            BigDecimal taxRateItemOne        = item.getOperationTaxRate().divide(new BigDecimal(100L), 2, BigDecimal.ROUND_HALF_UP);// 本身税率取值(0-100)
            BigDecimal taxRateItemOneSurplus = new BigDecimal(1L).add(taxRateItemOne);// 1+ 税率


            result.add(ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo.builder()
                            .unifiedOrderCreditId(item.getUnifiedOrderCreditId())
                            .unifiedOrderGoodsId(item.getUnifiedOrderGoodsId())
                            .unifiedOrderId(item.getUnifiedOrderId())
                            .accountingOrderId(item.getAccountingOrderId())
//					        .accountingOrderType(item.g)
//					        .invoiceApplyId(item.getinvoi)  // 此时发票单还没有生成的
//					        .invoiceApplySn(item.getin)     // 此时发票单还没有生成的
//					        .applyUserId()
//					        .applyTime()
//					        .auditUserId()
//					        .auditStatus()
//					        .auditTime()
//					        .makeInvoiceUserId()
//					        .makeInvoiceTime()
//					        .makeInvoiceStatus()
//					        .sysEnterpriseExtInvoiceId(item.gete)
//					        .sysEnterpriseExtInvoiceSnapshot()
//					        .invoiceNumber()
//					        .invoiceStatus()
//					        .invoiceTitle()
//					        .invoiceType()
//					        .taxRate()
//					        .freeAmount()
//					        .realAmount()
//					        .noCheckCount()
//					        .expressCompanyId()
//					        .expressNo()
//					        .expressCompanyName()
//					        .mailTickTime()
//					        .backAccountStatus()
//					        .remark()

                            .orderSn(item.getDirection().compareTo(CommonDirectionEnum.正向.getKey()) == 0 ? item.getOrderSn() : item.getRefundOrderSn())
                            .creditType(item.getCreditType())
                            .creditName(item.getCreditName())
                            .batchs(item.getBatchs())
                            .balance(item.getBalance())
                            .balanceWithOutTax(item.getBalance().divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP))
                            .balanceTaxAmount(item.getBalance().subtract(item.getBalance().divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP)))

//					        .enterpriseId(item.get)
//					        .enterpriseName()

//					        .purchaserCompanyName(item.getp)
//					        .purchaserTaxpayersIDCode()
//					        .purchaserBankAccountNumber()
//					        .purchaserAddressPhone()

//					        .invoiceBank(item.geti)
//					        .invoiceAccount()
//					        .invoiceAddress()
//					        .invoicePhone()

//					        .memberId()
//					        .payStatus()
//					        .payType()
//					        .accountingStateCustomer()
//					        .accountingStateSupplier()
//					        .invoiceMakeStatus()
//					        .backStatus()

                            .goodsId(item.getGoodsId())// TODO 已补充
                            .goodsName(item.getGoodsName())
                            .quantity(item.getQuantity())
                            .unit(item.getUnit())
                            .price(item.getPrice())
                            .costPrice(item.getCostPrice())

                            .cateIdLv1(item.getCateIdLv1())// TODO 已补充
                            .cateNameLv1(item.getCateNameLv1())
                            .cateIdLv2(item.getCateIdLv2())
                            .cateNameLv2(item.getCateNameLv2())
                            .cateIdLv3(item.getCateIdLv3())
                            .cateNameLv3(item.getCateNameLv3())
                            .selfOption(item.getSelfOption())

//					        .unifiedOrderGoodsFiscalCode()
//					        .unifiedOrderGoodsTaxCode()
//					        .unifiedOrderGoodsTaxRate()

                            .merchTaxRate(item.getMerchTaxRate())
                            .merchTaxCode(item.getMerchTaxCode())
                            .merchTaxId(item.getMerchTaxId())

                            .operationTaxRate(item.getOperationTaxRate())
                            .operationTaxCode(item.getOperationTaxCode())
                            .operationTaxId(item.getOperationTaxId())

//					        .saleId(item.gets)
//					        .saleName()

                            .accountingOrderSn(item.getAccountingOrderSn())
//					        .applyUserName()
//					        .auditUserName()
//					        .makeUserName()
                            .build()
            );

        });

        // 按订单号先排序
        result.stream().sorted(Comparator.comparing(o -> o.getOrderSn()));

        return result;
    }


    private static String fetchGroupKeyByOrderSnGoodsId(ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo vo) {
        return StrUtil.format("{}^_^{}", vo.getOrderSn(), vo.getGoodsId());
    }

    public void setSliceMap(Map<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> sliceMap, Integer index, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> dataList) {
        if (sliceMap.get(index) == null) {
            sliceMap.put(index, new ArrayList<>());
        }

        sliceMap.get(index).addAll(dataList);
    }

    /**
     * 分片
     *
     * @param convertResult
     * @param invoiceTypeModeRuleBiz
     */
    private Map<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> invoiceApplySlice(
            /* 数据已经转换好,排序好,数据包含微信支付 */ List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> convertResult,
            /* 规则已经组装好 */ InvoiceTypeModeRuleBiz invoiceTypeModeRuleBiz,
                                       BigDecimal operationTaxRate) {

        log.info("税率 {} 开票方式-线上分票规则 {}", operationTaxRate, JSONUtil.toJsonPrettyStr(invoiceTypeModeRuleBiz));

        /****************************************** 初始化 start ******************************************/

        Map<Integer, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> sliceMap = new LinkedHashMap<>();

        /****************************************** 再分组 按 {orderSn}^_^{GoodsId} 以保证订单商品的完整性 start ******************************************/

        Map<String, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> transferMapGroupByOrderSnGoodsId = convertResult.parallelStream()
                .collect(Collectors.groupingBy(e -> fetchGroupKeyByOrderSnGoodsId(e), Collectors.toList()));

        /****************************************** 初始化 计数器 start ******************************************/

        Integer    countInvoiceApply           = 1;
        Integer    countRuleGoodsLines         = 0;
        BigDecimal countSingleInvoiceMaxAmount = BigDecimal.ZERO;
//		List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> container                   = new ArrayList<>();

        // iterator 每个为 order.goodsId
        for (Map.Entry<String, List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo>> _entry : transferMapGroupByOrderSnGoodsId.entrySet()) {

            // 试累加金额 (被加数) 以订单商品为单位 不含税
            BigDecimal _countOrderSnGoodsId = _entry.getValue()
                    .stream()
//					.map(item -> item.getPrice() == null ? BigDecimal.ZERO : item.getPrice().multiply(new BigDecimal(item.getQuantity())))
                    .map(item -> item.getBalanceWithOutTax() == null ? BigDecimal.ZERO : item.getBalanceWithOutTax())
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            // 试累加金额 试错
            BigDecimal _countSingleInvoiceMaxAmount = countSingleInvoiceMaxAmount.add(_countOrderSnGoodsId);
            Integer    _countRuleGoodsLines         = countRuleGoodsLines + _entry.getValue().size();

            // 试错后
            // 如果不超限额 满足 下一步试错
            if (invoiceTypeModeRuleBiz.getSingleInvoiceMaxAmount().compareTo(_countSingleInvoiceMaxAmount) >= 0
                    && invoiceTypeModeRuleBiz.getRuleGoodsLines().compareTo(_countRuleGoodsLines) >= 0) {

                log.info("判定(如果不超限额 满足 下一步试错)\n{}-{}={}\n{}",
                        invoiceTypeModeRuleBiz.getSingleInvoiceMaxAmount(),
                        _countSingleInvoiceMaxAmount,
                        invoiceTypeModeRuleBiz.getSingleInvoiceMaxAmount().subtract(_countSingleInvoiceMaxAmount),
                        _countRuleGoodsLines
                );

                // 分票容器 addAll
                setSliceMap(sliceMap, countInvoiceApply, _entry.getValue());

                // 计算器++
                countSingleInvoiceMaxAmount = countSingleInvoiceMaxAmount.add(_countOrderSnGoodsId);
                countRuleGoodsLines += _entry.getValue().size();

            }
            // 如果超限额 不满足 记录上一步 清空计数器 此步作为新容器
            else {

                log.info("判定(如果超限额 不满足 记录上一步 清空计数器 此步作为新容器)\n{}-{}={}\n{}",
                        invoiceTypeModeRuleBiz.getSingleInvoiceMaxAmount(),
                        _countSingleInvoiceMaxAmount,
                        invoiceTypeModeRuleBiz.getSingleInvoiceMaxAmount().subtract(_countSingleInvoiceMaxAmount),
                        _countRuleGoodsLines
                );

                // 分票容器 fetchAll
//				setSliceMap(sliceMap, countInvoiceApply, _entry.getValue());

                // 清空计数器
                countSingleInvoiceMaxAmount = BigDecimal.ZERO;
                countRuleGoodsLines = 0;

                /****************************************** 注意:这里不处理就会把试错的丢了 start ******************************************/

                // 试错的作为新票再累计 这里会有个问题,如果这个`下一次`直接就超限,这里没有作判定
                setSliceMap(sliceMap, ++countInvoiceApply, _entry.getValue());

                countSingleInvoiceMaxAmount = countSingleInvoiceMaxAmount.add(_countOrderSnGoodsId);
                countRuleGoodsLines += _entry.getValue().size();

                /****************************************** 注意:这里不处理就会把试错的丢了   end ******************************************/


            }
        }

        return sliceMap;


    }

    public void opOngoingByStateMachineHook(
            List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> resultList,
            BigDecimal taxRateHundred,
            SysEnterpriseExtInvoiceParam sysEnterpriseExtInvoiceParam,
            LoginSysUser loginSysUser) {


        /****************************************** 数据源-此做法为 开票单确认的情况 start ******************************************/

        /****************************************** 差异 范围 start ******************************************/


        /****************************************** 差异 范围   end ******************************************/

        /****************************************** 差异 变动 start ******************************************/


        /****************************************** 差异 变动   end ******************************************/

        Map<String, InvoiceApplyCustomerExportExcelFocusScoreDimCreditStruct> resultMap = invoiceApplyCustomerExportExcelService.mergeResultByExcelDebugAndCreateWorkbook(3, resultList);

        /****************************************** 数据源-此做法为 开票单确认的情况   end ******************************************/


    }
}
