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

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.superdesk.uniorder.common.enums.common.CommonAccountingStateEnum;
import cn.superdesk.uniorder.common.enums.common.CommonDirectionEnum;
import cn.superdesk.uniorder.common.enums.common.CommonFormStateEnum;
import cn.superdesk.uniorder.common.enums.invoice.common.InvoiceTypeEnum;
import cn.superdesk.uniorder.common.enums.invoice.common.InvoiceTypeMoudelXModeEnum;
import cn.superdesk.uniorder.common.enums.invoice.common.PurchaserCompanyTypeEnum;
import cn.superdesk.uniorder.common.enums.invoice.customer.InvoiceApplyCustomerInvoiceProgressingStatusEnum;
import cn.superdesk.uniorder.common.enums.invoice.customer.InvoiceApplyCustomerMailTickStatusEnum;
import cn.superdesk.uniorder.common.enums.invoice.customer.InvoiceApplyCustomerMakeInvoiceStatusEnum;
import cn.superdesk.uniorder.common.enums.invoice.customer.InvoiceApplyCustomerReceiveInvoiceStatusEnum;
import cn.superdesk.uniorder.common.enums.order.UnifiedOrderCreditInvoiceMakeStatusEnum;
import cn.superdesk.uniorder.common.enums.thirdpart.szhtxx.electronicInvoice.SzhtxxElectronicInvoiceFptxxBillSentSignEnum;
import cn.superdesk.uniorder.common.enums.thirdpart.szhtxx.electronicInvoice.SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum;
import cn.superdesk.uniorder.common.enums.thirdpart.szhtxx.electronicInvoice.SzhtxxElectronicInvoiceFptxxKplxEnum;
import cn.superdesk.uniorder.common.enums.thirdpart.szhtxx.electronicInvoice.SzhtxxElectronicInvoiceResponseCodeEnum;
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.AnBigDecimalUtils;
import cn.superdesk.uniorder.common.numerical.AnStringUtils;
import cn.superdesk.uniorder.common.security.dto.LoginSysUser;
import cn.superdesk.uniorder.dao.entity.invoice.common.InvoiceType;
import cn.superdesk.uniorder.dao.entity.invoice.customer.InvoiceApply;
import cn.superdesk.uniorder.dao.entity.sys.notify.SysNotify;
import cn.superdesk.uniorder.dto._constant.ResourceConstant;
import cn.superdesk.uniorder.dto.param.invoice.customer.InvoiceApplyCustomerQueryCriteria;
import cn.superdesk.uniorder.dto.param.organisation.SysEnterpriseExtInvoiceParam;
import cn.superdesk.uniorder.dto.param.thirdpart.szhtxx.common.singleBilling.SzhtxxInvoiceFptxxOpSingleBillingForm;
import cn.superdesk.uniorder.dto.vo.invoice.common.InvoiceBankInfoVo;
import cn.superdesk.uniorder.dto.vo.invoice.customer.ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo;
import cn.superdesk.uniorder.dto.vo.thirdpart.szhtxx.electronicInvoice.SzhtxxElectronicInvoiceFptxxListItemVo;
import cn.superdesk.uniorder.executor.scheduler.kafka._thirdpart.qchjc.QchjcStatementKafkaEventPublisher;
import cn.superdesk.uniorder.executor.scheduler.kafka._thirdpart.qchjc.dto.accounting.customer.QchjcCustomerStatementEvent;
import cn.superdesk.uniorder.executor.scheduler.kafka._thirdpart.qchjc.dto.accounting.customer.QchjcCustomerStatementEventSingleStructVo;
import cn.superdesk.uniorder.openapicall.thirdpart.szhtxx.electronicInvoice.dto.request.SingleBillingRequest;
import cn.superdesk.uniorder.openapicall.thirdpart.szhtxx.electronicInvoice.dto.request.SingleBillingRequestBodyKpContent;
import cn.superdesk.uniorder.openapicall.thirdpart.szhtxx.electronicInvoice.dto.request.SingleBillingRequestBodyKpContentFptxxVo;
import cn.superdesk.uniorder.openapicall.thirdpart.szhtxx.electronicInvoice.dto.request.SingleBillingRequestBodyKpContentXmxxItemVo;
import cn.superdesk.uniorder.openapicall.thirdpart.szhtxx.electronicInvoice.dto.response.SingleBillingResponse;
import cn.superdesk.uniorder.openapicall.thirdpart.szhtxx.electronicInvoice.service.SingleBillingCallService;
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.InvoiceApplyCustomerService;
import cn.superdesk.uniorder.service.invoice.customer.InvoiceApplyXGoodsService;
import cn.superdesk.uniorder.service.invoice.customer.impl.InvoiceApplyCustomerExportExcelFocusScoreDimCreditStruct;
import cn.superdesk.uniorder.service.invoice.customer.state.InvoiceApplyCustomerFieldMakeInvoiceStatusOpOngoingSzhtxxElectronicInvoiceService;
import cn.superdesk.uniorder.service.organisation.SysEnterpriseExtInvoiceService;
import cn.superdesk.uniorder.service.thirdpart.szhtxx.electronicInvoice.SzhtxxElectronicInvoiceFptxxService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Description: (电子普通发票)(深圳航信电子票)
 * @Author: linjinyu
 * @CreateDate: 2022/4/26 10:12 上午
 * @UpdateUser: linjinyu
 * @UpdateDate: 2022/4/26 10:12 上午
 * @UpdateRemark: 修改内容
 * @Version: 1.0
 */
@Slf4j
@Service
public class InvoiceApplyCustomerFieldMakeInvoiceStatusOpOngoingSzhtxxElectronicInvoiceServiceImpl implements InvoiceApplyCustomerFieldMakeInvoiceStatusOpOngoingSzhtxxElectronicInvoiceService {

    @Autowired
    private InvoiceTypeService invoiceTypeService;

    @Autowired
    private InvoiceApplyCustomerService            invoiceApplyCustomerService;
    @Autowired
    private InvoiceApplyCustomerExportExcelService invoiceApplyCustomerExportExcelService;

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

    @Autowired
    private SingleBillingCallService singleBillingCallService;

    @Autowired
    private SzhtxxElectronicInvoiceFptxxService szhtxxElectronicInvoiceFptxxService;

    @Autowired
    private InvoiceApplyXGoodsService invoiceApplyXGoodsService;
    @Autowired
    private UnifiedOrderCreditService unifiedOrderCreditService;

    @Autowired
    private ResourceConstant resourceConstant;

    /**************************************** superdesk-uniorder-executor start ****************************************/

    @Autowired
    private QchjcStatementKafkaEventPublisher qchjcStatementEventPublisher;

    /**************************************** superdesk-uniorder-executor   end ****************************************/

    /**
     * op确认发起(电子普通发票)(深圳航信电子票) ---- op申请开票预览页 op确认发起
     * op确认开票(电子普通发票)(深圳航信电子票) ---- 列表上是没有 op确认开票
     *
     * @param form
     * @param invoiceApplyCustomerEntity
     * @param loginSysUser
     * @return
     */
    @Override
    public SysNotify submitAndRecord(SzhtxxInvoiceFptxxOpSingleBillingForm form, InvoiceApply invoiceApplyCustomerEntity, LoginSysUser loginSysUser) {

        DateTime               now            = DateUtil.date();
        Map<Long, InvoiceType> invoiceTypeMap = invoiceTypeService.convertToMapFieldId2Entity();

        switch (InvoiceTypeEnum.get(invoiceApplyCustomerEntity.getInvoiceType())) {
            case 电子普通发票:

                log.info("opOngoingByStateMachine\n{}\n{}",
                        invoiceTypeMap.get(invoiceApplyCustomerEntity.getInvoiceType()).getTypeName(),
                        InvoiceTypeMoudelXModeEnum.get(invoiceApplyCustomerEntity.getInvoiceMode()).getDesc()
                );

                SingleBillingRequestBodyKpContent kpContent = opOngoingInvoiceTypeEq1InvoiceModeEqSzhtxxElectronicInvoice(form, invoiceApplyCustomerEntity, loginSysUser);

                SingleBillingRequest request = SingleBillingRequest
                        .builder()
                        .kpContent(kpContent)
                        .build();

                SingleBillingResponse response = singleBillingCallService.callSingleBillingApi(request);

//			            {"returnCode":"1007","returnMsg":"000000100_CIA2022030892364288税额计算有误！","datas":null}
                if (ObjectUtil.isNotNull(response)
                        && response.getReturnCode().equals(SzhtxxElectronicInvoiceResponseCodeEnum.SUCCESS.getReturnCode())) {

                    // 记录
                    szhtxxElectronicInvoiceFptxxService.record(form, kpContent, response, loginSysUser);

                    InvoiceApply forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice = new InvoiceApply();
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setId(invoiceApplyCustomerEntity.getId());

                    // TODO 更新状态逻辑...

                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setInvoiceStatus(InvoiceApplyCustomerInvoiceProgressingStatusEnum.待回款.getKey());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setInvoiceProgressingUserId(loginSysUser.getSysUserId());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setInvoiceProgressingTime(now);

                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setMakeInvoiceStatus(InvoiceApplyCustomerMakeInvoiceStatusEnum.已开票.getKey());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setMakeInvoiceUserId(loginSysUser.getSysUserId());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setMakeInvoiceTime(now);

                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setReceiveInvoiceStatus(InvoiceApplyCustomerReceiveInvoiceStatusEnum.已领票.getKey());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setReceiveInvoiceUserId(loginSysUser.getSysUserId());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setReceiveInvoiceTime(now);
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setInvoiceNumber(response.getDatas().getFphm());

                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setMailTickStatus(InvoiceApplyCustomerMailTickStatusEnum.已寄票.getKey());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setMailTickUserId(loginSysUser.getSysUserId());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setMailTickTime(now);

                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setUpdateUserId(loginSysUser.getSysUserId());
                    forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice.setUpdateTime(now);

                    invoiceApplyCustomerService.updateInvoiceApplyByPrimaryKeySelective(forUpdateInvoiceTypeEq1InvoiceModeSzhtxxElectronicInvoice);


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

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


                    // 从 op发起开票 -> 深圳航息电子票 returnCode = 0000 -> ... -> 待回款
                    List<Long> processUnifiedOrderCreditIds = invoiceApplyXGoodsService.findCreditSetByInvoicApplyId(invoiceApplyCustomerEntity.getId());
                    unifiedOrderCreditService.updateFieldInvoiceMakeStatusByUnifiedOrderCreditIds(processUnifiedOrderCreditIds, UnifiedOrderCreditInvoiceMakeStatusEnum.已开票);

                    // 触发事件点;集采账单状态推送 #此处只是触点，触点背后会有逻辑判定要不要调用~集采的接口 特此说明# at 20220616
                    // 1.来源于“企业商城”的开票申请在**发起开票成功时**：若该对账单的申请状态为“全部申请”**&**所有有效的开票申请的申请状态均为“待回款”时
                    // 2.来源于“企业商城”的对账单在**“积分结算-发票管理-【运营寄票】”成功时**：若该对账单的申请状态为“全部申请”**&**所有有效的开票申请的申请状态均为“待回款”时；
                    qchjcStatementEventPublisher.customerStatementEventMakeInvoiceStatusSync(
                            QchjcCustomerStatementEvent
                                    .builder()
                                    .msg("积分结算v2.3-发票申请管理-op发起开票-op确认发起(发票种类::电子普通发票)(开票方式::深圳航信电子票)")

                                    .singleStructVo(
                                            QchjcCustomerStatementEventSingleStructVo
                                                    .builder()
                                                    .accountingOrderId(0L)
                                                    .accountingOrderSn("")

                                                    .invoiceApplyId(invoiceApplyCustomerEntity.getId())
                                                    .invoiceApplySn(invoiceApplyCustomerEntity.getInvoiceApplySn())
                                                    .build()
                                    )


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

                                    .build()
                    );


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

                    return SysNotify.builder()
                            .title(response.getReturnCode())
                            .content(response.getReturnMsg())
//									.messageBody(response.getReturnMsg())
                            .build();
                } else {
                    return SysNotify.builder()
                            .title(response.getReturnCode())
                            .content(response.getReturnMsg())
//									.messageBody(response.getReturnMsg())
                            .build();
                }

            case 纸质专用发票:

                return SysNotify.builder()
                        .title("0001")
                        .content("该发票种类[纸质专用发票]无此开票方式")
                        .build();
            case 纸质普通发票:
                return SysNotify.builder()
                        .title("0001")
                        .content("该发票种类[纸质普通发票]无此开票方式")
                        .build();
            case 电子专用发票:
                return SysNotify.builder()
                        .title("0001")
                        .content("该发票种类[电子专用发票]无此开票方式")
                        .build();
            default:
                return SysNotify.builder()
                        .title("0001")
                        .content("该发票种类[]无此开票方式")
                        .build();
        }
    }


    /**
     * op发起开票(电子普通发票)(深圳航信电子票)
     *
     * @param form
     * @param invoiceApplyCustomer
     * @param loginSysUser
     * @retur
     * @link opOngoingByStateMachine 分发
     */
    @Override
    public SingleBillingRequestBodyKpContent opOngoingInvoiceTypeEq1InvoiceModeEqSzhtxxElectronicInvoice(
            SzhtxxInvoiceFptxxOpSingleBillingForm form,
            InvoiceApply invoiceApplyCustomer,
            LoginSysUser loginSysUser) {

        log.info("\n财务-发起开票 -> 状态 -> {} -> 开票中(10, \"开票中\")\n{}\n{}", getClass().getName(), JSONUtil.toJsonPrettyStr(invoiceApplyCustomer), JSONUtil.toJsonPrettyStr(loginSysUser));


        StringBuffer report      = new StringBuffer();
        StringBuffer reportWhich = new StringBuffer();

        List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> resultList = invoiceApplyCustomerService.findInvoiceApplyFocusScoreDimCredit(
                InvoiceApplyCustomerQueryCriteria.builder()
                        .invoiceApplyIdsSelected(Arrays.asList(invoiceApplyCustomer.getId()))
                        .creditAccountingState(CommonAccountingStateEnum.已对账.getKey()) // TODO 现在是有作用，但不是最终的办法
                        .build()
        );

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

        SingleBillingRequestBodyKpContentFptxxVo          fptxx = null;
        List<SingleBillingRequestBodyKpContentXmxxItemVo> xmxx  = new ArrayList<>();

        for (Map.Entry<String, InvoiceApplyCustomerExportExcelFocusScoreDimCreditStruct> _entry : resultMap.entrySet()) {

//			log.info("\n财务-发起开票 -> 状态 -> {} -> 开票中(10, \"开票中\")\n{}\n{}", getClass().getName(), _entry.getKey(), JSONUtil.toJsonPrettyStr(_entry.getValue()));

            String purchaseVirtName = AnStringUtils.stringListConvertToString(_entry.getValue()
                    .getTransferListBySheet()
                    .stream()
                    .map(ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo::getEnterpriseName)
                    .distinct()
                    .collect(Collectors.toList())
            );
            String invoiceApplySn = _entry.getKey();
            String accountingOrderSn = AnStringUtils.stringListConvertToString(_entry.getValue()
                    .getTransferListBySheet()
                    .stream()
                    .map(ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo::getAccountingOrderSn)
                    .distinct()
                    .collect(Collectors.toList())
            );

            /****************************************** 侨城汇需求 20210922 start ******************************************/

            invoiceApplyCustomerExportExcelService.qchNeed20210922(_entry.getValue().getTransferListBySheetMerge());

            /****************************************** 侨城汇需求 20210922   end ******************************************/


            /****************************************** 面单 start ******************************************/


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

            InvoiceBankInfoVo invoiceBankInfoVo = invoiceBankInfoService.getInvoiceBankInfoByInvoiceApplyIdOrNullByDefault(invoiceApplyCustomer.getId());


            /****************************************** 购方信息 start ******************************************/

            SysEnterpriseExtInvoiceParam sysEnterpriseExtInvoiceParam = null;

            if (invoiceApplyCustomer.getSysEnterpriseExtInvoiceId().compareTo(0L) != 0) {
                sysEnterpriseExtInvoiceParam = AnJsonUtils.toObject(invoiceApplyCustomer.getSysEnterpriseExtInvoiceSnapshot(), SysEnterpriseExtInvoiceParam.class);
//				sysEnterpriseExtInvoiceParam.setTaxpayersIDCode("1111111111");
            } else {
                sysEnterpriseExtInvoiceParam = new SysEnterpriseExtInvoiceParam();
                // 异常
            }


            BigDecimal taxRateHundred        = new BigDecimal(invoiceApplyCustomer.getTaxRate());
            BigDecimal taxRateHundredSurplus = new BigDecimal(100L).add(taxRateHundred);// 100+税率(0-100)

            BigDecimal taxRateOne        = taxRateHundred.divide(new BigDecimal(100L), 2, BigDecimal.ROUND_HALF_UP);
            BigDecimal taxRateOneSurplus = new BigDecimal(1L).add(taxRateOne); // 1+税率(0-1)

            /**
             * 前置条件为 此票有统一税率 侨城汇要求
             */

//            BigDecimal invoiceApplyAmountIncludeTax = invoiceApplyCustomer.getInvoiceApplyAmount();// 错 这个是包含结算与不结算
            BigDecimal invoiceApplyAmountIncludeTax = invoiceApplyCustomer.getRealAmount();

//			BigDecimal invoiceApplyAmountWithOutTax = invoiceApplyAmountIncludeTax.subtract(invoiceApplyTaxAmount);
//			BigDecimal invoiceApplyTaxAmount        = invoiceApplyAmountIncludeTax.multiply(taxRateOne); // 税额

            // 0-1 test1
//            BigDecimal invoiceApplyAmountWithOutTax = invoiceApplyAmountIncludeTax.divide(taxRateOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
//            BigDecimal invoiceApplyTaxAmount        = invoiceApplyAmountIncludeTax.subtract(invoiceApplyAmountWithOutTax); // 税额

            BigDecimal invoiceApplyAmountWithOutTax = invoiceApplyAmountIncludeTax.divide(taxRateOneSurplus, 2, BigDecimal.ROUND_HALF_DOWN);
            BigDecimal invoiceApplyTaxAmount        = invoiceApplyAmountIncludeTax.subtract(invoiceApplyAmountWithOutTax); // 税额

            BigDecimal invoiceApplyTaxAmountCount = BigDecimal.ZERO;


            report.append(StrUtil.format("\n总数项::金额(含税):{} 总数项::金额(不含税):{} 总数项::税额:{} 累计税额:{} 税率: {}",
                    invoiceApplyAmountIncludeTax,
                    invoiceApplyAmountWithOutTax,
                    invoiceApplyTaxAmount,
                    invoiceApplyTaxAmountCount,
                    taxRateOneSurplus)
            );

            List<BigDecimal> invoiceApplyTaxAmountList      = new ArrayList<>();
            List<BigDecimal> invoiceApplyTaxAmountWhichList = new ArrayList<>();


            /****************************************** 项目信息 明细 start ******************************************/

            xmxx = new ArrayList<>();

//			_entry.getValue().getTransferListBySheetMerge().forEach(item -> {
            int i    = 0;
            int size = _entry.getValue().getTransferListBySheetMerge().size();
            for (ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo item : _entry.getValue().getTransferListBySheetMerge()) {

                i++;
//				BigDecimal taxRateItemHundred    = item.getOperationTaxRate();
//				BigDecimal taxRateItemOne        = taxRateItemHundred.divide(new BigDecimal(100L), 2, BigDecimal.ROUND_HALF_UP);

                BigDecimal taxRateItemHundred    = item.getOperationTaxRate();
                BigDecimal taxRateItemOne        = item.getOperationTaxRate();
                BigDecimal taxRateItemOneSurplus = new BigDecimal(1L).add(taxRateItemOne);


                /****************************************** ?? start ******************************************/
//				BigDecimal goodsItemAmount           = item.getPrice().multiply(new BigDecimal(item.getQuantity())); // 正项额
//				BigDecimal goodsItemTaxAmount        = goodsItemAmount.multiply(taxRateItemOne); // 正项额 税额
//				BigDecimal goodsItemAmountWithOutTax = goodsItemAmount.subtract(goodsItemTaxAmount); // 正项额 不包含 税
//
//
//				BigDecimal goodsItemDiffAmount           = goodsItemAmount.subtract(item.getBalance());// 扣除额
//				BigDecimal goodsItemDiffTaxAmount        = goodsItemDiffAmount.multiply(taxRateItemOne);// 扣除额 税额
//				BigDecimal goodsItemDiffAmountWithOutTax = goodsItemDiffAmount.subtract(goodsItemDiffTaxAmount);// 扣除额 不包含 税
//
//				BigDecimal goodsItemDiffAmountMinus           = goodsItemDiffAmount.multiply(new BigDecimal(-1L)); // 扣除额 转换成 -扣除额
//				BigDecimal goodsItemDiffTaxAmountMinus        = goodsItemDiffTaxAmount.multiply(new BigDecimal(-1L));// -扣除额 税额
//				BigDecimal goodsItemDiffAmountWithOutTaxMinus = goodsItemDiffAmountWithOutTax.multiply(new BigDecimal(-1L));// -扣除额 不包含 税
                /****************************************** ?? start ******************************************/

                // 正常项::金额(含税) = 单价(含税) * 数量
                BigDecimal goodsItemAmount = item.getPrice().multiply(new BigDecimal(item.getQuantity()));
                // 正常项::金额(不含税) = 金额(含税) 除以 (1+税率(0-1))
                BigDecimal goodsItemAmountWithOutTax = goodsItemAmount.divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
                // 正常项::税额 = 金额(含税) - 金额(不含税)
                BigDecimal goodsItemTaxAmount = goodsItemAmount.subtract(goodsItemAmountWithOutTax);

                // 扣减项::扣除额(含税) = 正常项::金额(含税) - 实际金额(含税)
                BigDecimal goodsItemDiffAmount = goodsItemAmount.subtract(item.getBalance());
                // 扣减项::扣除额(不含税) = 扣除额(含税) 除以 (1+税率(0-1))
                BigDecimal goodsItemDiffAmountWithOutTax = goodsItemDiffAmount.divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
                // 扣减项::扣除额(税额) = 扣除额(含税) - 扣除额(不含税)
                BigDecimal goodsItemDiffTaxAmount = goodsItemDiffAmount.subtract(goodsItemDiffAmountWithOutTax);// 扣除额 税额

                BigDecimal goodsItemDiffAmountMinus           = goodsItemDiffAmount.multiply(new BigDecimal(-1L)); // 扣除额 转换成 -扣除额
                BigDecimal goodsItemDiffAmountWithOutTaxMinus = goodsItemDiffAmountWithOutTax.multiply(new BigDecimal(-1L));// -扣除额 不包含 税
                BigDecimal goodsItemDiffTaxAmountMinus        = goodsItemDiffTaxAmount.multiply(new BigDecimal(-1L));// -扣除额 税额


                // 最后一项
                if (size == i) {

                    log.info("\n最后一项\n正常项::金额(含税):{} 税率:{} 税额:{} \n扣减项::扣除额(含税):{} 税率:{} 税额:{} \n累计税额:{}",
                            goodsItemAmount, taxRateItemOne, goodsItemTaxAmount,
                            goodsItemDiffAmountMinus, taxRateItemOne, goodsItemDiffTaxAmountMinus,
                            invoiceApplyTaxAmountCount);

                    // 最后一项 有扣减项
                    if (goodsItemDiffAmount.compareTo(BigDecimal.ZERO) != 0) {

                        /**************************************** 修正最后一项 mark by linjinyu at 20220412 不修改 改摊分 start ****************************************/

                        // 单项税额(正常项-扣除项)
//                        goodsItemTaxAmount = invoiceApplyTaxAmount
//                                .subtract(invoiceApplyTaxAmountCount)
//                                .subtract(goodsItemDiffTaxAmountMinus);
//                        goodsItemDiffTaxAmountMinus

                        /**************************************** 修正最后一项 mark by linjinyu at 20220412 不修改 改摊分 start ****************************************/


                        invoiceApplyTaxAmountCount = invoiceApplyTaxAmountCount
                                /* 正常项-税额(加正数) */.add(goodsItemTaxAmount)
                                /* 扣除项-税额(加负数) */.add(goodsItemDiffTaxAmountMinus);

                        report.append(StrUtil.format("\n正常项::金额(含税):{} 正常项::金额(不含税):{} 正常项::税额:{} 累计税额:{} 税率: {} 订单号: {} 订单id: {} 商品id: {} 流水Id: {}",
                                goodsItemAmount, goodsItemAmountWithOutTax, goodsItemTaxAmount, invoiceApplyTaxAmountCount, taxRateOneSurplus, item.getOrderSn(), item.getUnifiedOrderId(), item.getUnifiedOrderGoodsId(), item.getUnifiedOrderCreditId()));

                        report.append(StrUtil.format("\n扣减项::金额(含税):{} 扣减项::金额(不含税):{} 扣减项::税额:{} 累计税额:{} 税率: {}",
                                goodsItemDiffAmountMinus, goodsItemDiffAmountWithOutTaxMinus, goodsItemDiffTaxAmountMinus, invoiceApplyTaxAmountCount, taxRateOneSurplus));
                    }
                    // 最后一项 无扣减项
                    else {

                        /**************************************** 修正最后一项 mark by linjinyu at 20220412 不修改 改摊分 start ****************************************/

                        // 单项税额(正常项-扣除项(无))
//                        goodsItemTaxAmount = invoiceApplyTaxAmount
//                                .subtract(invoiceApplyTaxAmountCount);
//                        goodsItemDiffTaxAmountMinus

                        /**************************************** 修正最后一项 mark by linjinyu at 20220412 不修改 改摊分 start ****************************************/


                        invoiceApplyTaxAmountCount = invoiceApplyTaxAmountCount
                                /* 正常项-税额(加正数) */.add(goodsItemTaxAmount)
                                /* 扣除项-税额(加负数) */.add(goodsItemDiffTaxAmountMinus);

                        report.append(StrUtil.format("\n正常项::金额(含税):{} 正常项::金额(不含税):{} 正常项::税额:{} 累计税额:{} 税率: {} 订单号: {} 订单id: {} 商品id: {} 流水Id: {}",
                                goodsItemAmount, goodsItemAmountWithOutTax, goodsItemTaxAmount, invoiceApplyTaxAmountCount, taxRateOneSurplus, item.getOrderSn(), item.getUnifiedOrderId(), item.getUnifiedOrderGoodsId(), item.getUnifiedOrderCreditId()));

                        report.append(StrUtil.format("\n扣减项::金额(含税):{} 扣减项::金额(不含税):{} 扣减项::税额:{} 累计税额:{} 税率: {}",
                                goodsItemDiffAmountMinus, goodsItemDiffAmountWithOutTaxMinus, goodsItemDiffTaxAmountMinus, invoiceApplyTaxAmountCount, taxRateOneSurplus));
                    }

                } else {

                    invoiceApplyTaxAmountCount = invoiceApplyTaxAmountCount
                            /* 正常项-税额(加正数) */.add(goodsItemTaxAmount)
                            /* 扣除项-税额(加负数) */.add(goodsItemDiffTaxAmountMinus);

//                    log.info("\n正常项::金额(含税):{} 税率:{} 税额:{} \n扣减项::扣除额(含税):{} 税率:{} 税额:{} \n累计税额:{}",
//                            goodsItemAmount, taxRateItemOne, goodsItemTaxAmount,
//                            goodsItemDiffAmountMinus, taxRateItemOne, goodsItemDiffTaxAmountMinus,
//                            invoiceApplyTaxAmountCount);

                    report.append(StrUtil.format("\n正常项::金额(含税):{} 正常项::金额(不含税):{} 正常项::税额:{} 累计税额:{} 税率: {} 订单号: {} 订单id: {} 商品id: {} 流水Id: {}",
                            goodsItemAmount, goodsItemAmountWithOutTax, goodsItemTaxAmount, invoiceApplyTaxAmountCount, taxRateOneSurplus, item.getOrderSn(), item.getUnifiedOrderId(), item.getUnifiedOrderGoodsId(), item.getUnifiedOrderCreditId()));

                    report.append(StrUtil.format("\n扣减项::金额(含税):{} 扣减项::金额(不含税):{} 扣减项::税额:{} 累计税额:{} 税率: {}",
                            goodsItemDiffAmountMinus, goodsItemDiffAmountWithOutTaxMinus, goodsItemDiffTaxAmountMinus, invoiceApplyTaxAmountCount, taxRateOneSurplus));
                }


//				plus-minus


                SingleBillingRequestBodyKpContentXmxxItemVo plus  = null;
                SingleBillingRequestBodyKpContentXmxxItemVo minus = null;

                plus = SingleBillingRequestBodyKpContentXmxxItemVo.builder()
                        .xmmc(AnStringUtils.szhtxx(item.getGoodsName(), AnStringUtils.GOODS_NAME_LENGHT)) // 商品名称; 如 FPHXZ=1,则此商 品行为 折扣行,此 版本折扣行不允 许 多行折扣,折扣行 必须紧 邻被折扣 行,项目名称必须 与被折扣行一致。
//						.xmmc(item.getGoodsName()) // 商品名称; 如 FPHXZ=1,则此商 品行为 折扣行,此版本折扣行不允许多行折扣,折扣行必须紧邻被折扣行,项目名称必须与被折扣行一致。
                        .xmdw(AnStringUtils.szhtxx(item.getUnit(), AnStringUtils.UNIT_LENGHT)) // 单位; 项目单位
                        .ggxh(AnStringUtils.szhtxx(item.getSelfOption(), AnStringUtils.SELF_OPTION_LENGHT)) // 型号; 规格型号 // 第13行:GGXH  非通行费发票 字符串长度小于等于40" // 单价 数量 规格型号 计量单位这些可以不传

                        .hsbz("1") // 含税标志;表示项目单价和项 目金额是 否含税。0 表示都不含税,1 表 示都含税。 item.getFlagTaxIncluded()
                        .yhzcbs("0") // 优惠政策标识;0:不使用,1:使 用 item.getFlagTaxIncentives()

                        .fphxz("0") // 发票行性质;0正常行、1折扣行、 2被折扣行

                        .lslbs("") // 零税率标识;空:非零税率, 1: 免税, 2:不征税, 3 普通零税率
                        .zzstsgl("") // 增值税特殊管理;当 YHZCBS(优惠政策标识) 为 1 时必 填

                        .xmsl(item.getQuantity()) // 数量; 小数点后 8 位, 小 数点后都是 0 时, PDF 上只显示整数 (特殊情况:如果不传单价, 数量。则含税标志选 0,项目 金额传不含税)
                        .xmdj(item.getPrice()) // 单价; 小数点后 8 位小数 点后都是 0 时,PDF 上只显 示2 位小数; 否则只显示至 最后 一位不为0 的数字 (特殊情况:如果不传,需同时 不传单价和数量)

//						.kce(NumberUtil.roundStr(goodsItemDiffAmount.doubleValue(), 2)) // 扣除额;小数点 2 位小数,不能大于不 含税金额 说明如下: 1、差额征税的发票如果没有折扣的话,只能允许一条商 品行。 2、具体差额征税发票的计算 方法如下: 不含税差额 = 不含税金额 - 扣 除额; 税额 = 不含税差额 * 税率;
                        .xmje(NumberUtil.roundStr(goodsItemAmount.doubleValue(), 2)) // 项目金额;小数点后 2 位,以 元为单位 精确到 分。 等于=单价*数 量, 根据含税标志,确 定此 金额是否为含 税金额。
                        .se(NumberUtil.roundStr(goodsItemTaxAmount.doubleValue(), 2)) // 税额;小数点后 2 位,以 元为单位 精确到分

                        .spbm(item.getOperationTaxCode()) // 税收分类编码;商品编码?
                        .sl(item.getOperationTaxRate().toString()) // 税率;如果税率为 0,表示免税

                        /**************************************** 校验用 转json 已作屏蔽 start ****************************************/

                        .xmjeBigDecimal(goodsItemAmount) // 项目金额;小数点后 2 位,以 元为单位 精确到 分。 等于=单价*数 量, 根据含税标志,确 定此 金额是否为含 税金额。
                        .seBigDecimal(goodsItemTaxAmount) // 税额;小数点后 2 位,以 元为单位 精确到分
                        .seBigDecimalOriginal(goodsItemTaxAmount)
                        .slBigDecimal(item.getOperationTaxRate()) // 税率;如果税率为 0,表示免税

                        /**************************************** 校验用 转json 已作屏蔽   end ****************************************/

                        .build();


                invoiceApplyTaxAmountList.add(goodsItemTaxAmount);

                xmxx.add(plus);

                log.info("观 扣减项 {} 比 {} => {}", goodsItemDiffAmount, new BigDecimal(0.00), goodsItemDiffAmount.compareTo(new BigDecimal(0.00)));

                /**************************************** 特殊数据 start ****************************************/

//                总数项::金额(含税):100.00 总数项::金额(不含税):88.50 总数项::税额:11.50 累计税额:0 税率: 1.13
//                正常项::金额(含税):0.4080 正常项::金额(不含税):0.36 正常项::税额:0.0480 累计税额:0.0500 税率: 1.13 订单号: ME20220419174838242473 订单id: 313491 商品id: 1043135 流水Id: 718281
//                扣减项::金额(含税):0.0020 扣减项::金额(不含税):0.00 扣减项::税额:0.0020 累计税额:0.0500 税率: 1.13
//                正常项::金额(含税):198.0000 正常项::金额(不含税):175.22 正常项::税额:22.7800 累计税额:11.5100 税率: 1.13 订单号: ME20220419174838230483 订单id: 313492 商品id: 1043139 流水Id: 718285
//                扣减项::金额(含税):-98.4100 扣减项::金额(不含税):-87.09 扣减项::税额:-11.3200 累计税额:11.5100 税率: 1.13

//                观 扣减项 -0.0020 比 0 => -1
//                观 扣减项 98.4100 比 0 => 1

                /**************************************** 特殊数据 start ****************************************/

                if (goodsItemDiffAmount.compareTo(new BigDecimal(0.00)) > 0) {

                    plus.setFphxz("2");

                    minus = SingleBillingRequestBodyKpContentXmxxItemVo.builder()
                            .xmmc(AnStringUtils.szhtxx(item.getGoodsName(), AnStringUtils.GOODS_NAME_LENGHT)) // 商品名称; 如 FPHXZ=1,则此商 品行为 折扣行,此 版本折扣行不允 许 多行折扣,折扣行 必须紧 邻被折扣 行,项目名称必须 与被折扣行一致。
//							.xmmc(item.getGoodsName()) // 商品名称; 如 FPHXZ=1,则此商 品行为 折扣行,此 版本折扣行不允 许 多行折扣,折扣行 必须紧 邻被折扣 行,项目名称必须 与被折扣行一致。
                            .xmdw("") // item.getUnit() // 单位; 项目单位
//							.ggxh(item.getSelfOption()) // 型号; 规格型号 // 第13行:GGXH  非通行费发票 字符串长度小于等于40" // 单价 数量 规格型号 计量单位这些可以不传

                            .yhzcbs("0") // 优惠政策标识;0:不使用,1:使 用 item.getFlagTaxIncentives()

                            .fphxz("1") // 发票行性质;0正常行、1折扣行、 2被折扣行

                            .lslbs("") // 零税率标识;空:非零税率, 1: 免税, 2:不征税, 3 普通零税率
                            .zzstsgl("") // 增值税特殊管理;当 YHZCBS(优惠政策标识) 为 1 时必 填


                            // (特殊情况:如果不传单价, 数量。则含税标志选 0,项目 金额传不含税)
                            .xmsl(null) // item.getQuantity() 数量; 小数点后 8 位, 小 数点后都是 0 时, PDF 上只显示整数 (特殊情况:如果不传单价, 数量。则含税标志选 0,项目 金额传不含税)
                            .xmdj(null) // item.getPrice() 单价; 小数点后 8 位小数 点后都是 0 时,PDF 上只显 示2 位小数; 否则只显示至 最后 一位不为0 的数字 (特殊情况:如果不传,需同时 不传单价和数量)

                            .hsbz("1") // 含税标志;表示项目单价和项 目金额是 否含税。0 表示都不含税,1 表 示都含税。 item.getFlagTaxIncluded()
//							.xmje(NumberUtil.roundStr(goodsItemDiffAmountWithOutTaxMinus.doubleValue(), 2))

//							.kce("0.00") // 扣除额;小数点 2 位小数,不能大于不 含税金额 说明如下: 1、差额征税的发票如果没有折扣的话,只能允许一条商 品行。 2、具体差额征税发票的计算 方法如下: 不含税差额 = 不含税金额 - 扣 除额; 税额 = 不含税差额 * 税率;
                            .xmje(NumberUtil.roundStr(goodsItemDiffAmountMinus.doubleValue(), 2)) // 项目金额;小数点后 2 位,以 元为单位 精确到 分。 等于=单价*数 量, 根据含税标志,确 定此 金额是否为含 税金额。
                            .se(NumberUtil.roundStr(goodsItemDiffTaxAmountMinus.doubleValue(), 2)) // 税额;小数点后 2 位,以 元为单位 精确到分

                            .spbm(item.getOperationTaxCode()) // 税收分类编码;商品编码?
                            .sl(item.getOperationTaxRate().toString()) // 税率;如果税率为 0,表示免税

                            /**************************************** 校验用 转json 已作屏蔽 start ****************************************/

                            .xmjeBigDecimal(goodsItemDiffAmountMinus) // 项目金额;小数点后 2 位,以 元为单位 精确到 分。 等于=单价*数 量, 根据含税标志,确 定此 金额是否为含 税金额。
                            .seBigDecimal(goodsItemDiffTaxAmountMinus) // 税额;小数点后 2 位,以 元为单位 精确到分
                            .seBigDecimalOriginal(goodsItemDiffTaxAmountMinus)
                            .slBigDecimal(item.getOperationTaxRate()) // 税率;如果税率为 0,表示免税

                            /**************************************** 校验用 转json 已作屏蔽   end ****************************************/

                            .build();

                    invoiceApplyTaxAmountList.add(goodsItemDiffTaxAmountMinus);

                    xmxx.add(minus);
                }

            }


//            先用总金额计算出一个税额，然后每行明细按正常的逻辑去算一个税额，两个税额如果一致就不需要调整，如果不一致就调整这个误差，然后把这个误差摊到明细行上去，同时要保证摊后的明细行的误差小于0.06，
//            每一行发票是允许有6分钱的误差的（误差=含税金额/（1+税率）*税率-保留两位小数后的税额的值），直到把这个误差分摊完即可。

            // 注意
            BigDecimal diffTaxAmount      = invoiceApplyTaxAmount.subtract(invoiceApplyTaxAmountCount);
            BigDecimal diffTaxAmountCount = BigDecimal.ZERO;

            // 得出方向 大于为1 小于为-1
            int direction = invoiceApplyTaxAmount.compareTo(invoiceApplyTaxAmountCount);

            if (direction != 0) {

                reportWhich.append(StrUtil.format("\n税额不等 -> 触发摊分 总计 {} 累计 {} 差额 {} {} {}", invoiceApplyTaxAmount, invoiceApplyTaxAmountCount, diffTaxAmount, diffTaxAmountCount, direction));

                // 步进(摊分基数) 未考虑大于0.06的情况
                BigDecimal stepTaxAmount = diffTaxAmount.divide(AnBigDecimalUtils.getBigDecimal(xmxx.size()), 2, BigDecimal.ROUND_HALF_UP);

                reportWhich.append(StrUtil.format("\n步进计算 -> 初始 {} {}", stepTaxAmount, xmxx.size()));

                // 步进修订
                if (stepTaxAmount.compareTo(BigDecimal.ZERO) == 0) {

                    if (direction == 1) {
                        stepTaxAmount = new BigDecimal("0.01");
                    }
                    if (direction == -1) {
                        stepTaxAmount = new BigDecimal("-0.01");
                    }

                }

                reportWhich.append(StrUtil.format("\n步进计算 -> 修订 {} {}", stepTaxAmount, direction));

                // 摊分
                for (BigDecimal se : invoiceApplyTaxAmountList) {

                    if (diffTaxAmount.compareTo(diffTaxAmountCount) != 0) {


                        // 计数器先加 步进
                        diffTaxAmountCount = diffTaxAmountCount.add(stepTaxAmount);

                        reportWhich.append(StrUtil.format("\n未达 -> 试错 累计差额税额 {} 步进 {} ", diffTaxAmountCount, stepTaxAmount));


                        int compareTo = diffTaxAmount.compareTo(diffTaxAmountCount);

                        // 加步进后 相等了?
                        if (compareTo == 0) {

                            BigDecimal seWhich = se.add(stepTaxAmount);
                            invoiceApplyTaxAmountWhichList.add(seWhich);

                            reportWhich.append(StrUtil.format("加步进后 相等了  {}比{}={} ? {} + {} = {}", diffTaxAmount, diffTaxAmountCount, compareTo, se, stepTaxAmount, se.add(stepTaxAmount)));

                        }


                        /**************************************** 走向 start ****************************************/

                        if (direction == -1) {

                        }

                        switch (CommonDirectionEnum.get(direction)) {
                            case 正向:

                                // 加步进后 达不到?
                                if (compareTo > 0) {

                                    BigDecimal seWhich = se.add(stepTaxAmount);
                                    invoiceApplyTaxAmountWhichList.add(seWhich);

                                    reportWhich.append(StrUtil.format("加步进后 达不到  {}比{}={} ? {} + {} = {}", diffTaxAmount, diffTaxAmountCount, compareTo, se, stepTaxAmount, se.add(stepTaxAmount)));
                                }

                                // 加步进后 超额了?
                                if (compareTo < 0) {

                                    BigDecimal seWhich = se.add(diffTaxAmount.subtract(diffTaxAmountCount));
                                    invoiceApplyTaxAmountWhichList.add(seWhich);

                                    reportWhich.append(StrUtil.format("加步进后 超额了  {}比{}={} ? {} -> {}", diffTaxAmount, diffTaxAmountCount, compareTo, se, seWhich));
                                }

                                break;
                            case 负向:

                                // 加步进后 达不到?
                                if (compareTo < 0) {

                                    BigDecimal seWhich = se.add(stepTaxAmount);
                                    invoiceApplyTaxAmountWhichList.add(seWhich);

                                    reportWhich.append(StrUtil.format("加步进后 达不到  {}比{}={} ? {} + {} = {}", diffTaxAmount, diffTaxAmountCount, compareTo, se, stepTaxAmount, se.add(stepTaxAmount)));
                                }

                                // 加步进后 超额了?
                                if (compareTo > 0) {

                                    BigDecimal seWhich = se.add(diffTaxAmount.subtract(diffTaxAmountCount));
                                    invoiceApplyTaxAmountWhichList.add(seWhich);

                                    reportWhich.append(StrUtil.format("加步进后 超额了  {}比{}={} ? {} -> {}", diffTaxAmount, diffTaxAmountCount, compareTo, se, seWhich));
                                }

                                break;

                        }

                        /**************************************** 走向 start ****************************************/

                    } else {

                        reportWhich.append(StrUtil.format("\n已达 -> 平移 累计差额税额 {}", diffTaxAmountCount));

                        invoiceApplyTaxAmountWhichList.add(se);
                    }
                }


                xmxx.forEach(AnLamdaUtils.consumerWithIndex((xmxxItem, index) -> {
                    xmxxItem.setSe(NumberUtil.roundStr(invoiceApplyTaxAmountWhichList.get(index).doubleValue(), 2));
                    xmxxItem.setSeBigDecimal(invoiceApplyTaxAmountWhichList.get(index));
                }));
            }


            /****************************************** 项目信息 明细   end ******************************************/


            /**************************************** 发票体信息 start ****************************************/

            fptxx = SingleBillingRequestBodyKpContentFptxxVo
                    .builder()

                    .fpqqlsh(invoiceApplyCustomer.getInvoiceApplySn()) // 发票请求唯一流水号;

                    .kpxm("办公用品") // 开票项目名称; 主要开票商品,或者第一条 商品,取项目信息中第一条 数据的项目名称(或传递大 类例 如:办公用品)
                    .dkbz("0") // 代开标志; 0:自开 1:代开 默认:0
                    .fjh("") // 分机号; 为空时随机获取分机盘开 票,不为空是指定分机盘开 具(A9 暂时不支持,C48 支 持)

                    .xhfmc(invoiceBankInfoVo.getCompanyName()) // 销货方名称;
                    .xhfNsrsbh(invoiceBankInfoVo.getTaxpayersIDCode()) // 销货方纳税人识别号;
                    .xhfYhzh(StrUtil.format("{}{}", invoiceBankInfoVo.getBankName(), invoiceBankInfoVo.getBankAccount())) // 销货方银行账号;
                    .xhfDzdh(StrUtil.format("{}{}", invoiceBankInfoVo.getAddress(), invoiceBankInfoVo.getTel())) // 销货方地址电话;

                    .ghfmc(sysEnterpriseExtInvoiceParam.getCompanyName()) // 购货方名称;
                    .ghfNsrsbh(sysEnterpriseExtInvoiceParam.getTaxpayersIDCode()) // 购货方纳税人识别号;
                    .ghfYhzh(StrUtil.format("{}{}", sysEnterpriseExtInvoiceParam.getInvoiceBank(), sysEnterpriseExtInvoiceParam.getInvoiceAccount())) // 购货方银行账号;
                    .ghfDzdh(StrUtil.format("{}{}", sysEnterpriseExtInvoiceParam.getInvoiceAddress(), sysEnterpriseExtInvoiceParam.getInvoicePhone())) // 购货方地址电话;
                    .ghfqylx(PurchaserCompanyTypeEnum.getPurchaserCompanyType(sysEnterpriseExtInvoiceParam.getSelectedInvoiceTitle())) // 购货方企业类型; 购货方企业类型 01 02 代表企业,两者没区别,03 代表个人

//                    .ghfSj(sysEnterpriseExtInvoiceParam.getInvoicePhone()) // 购货方手机(发票交付手机号);
                    .ghfSj("") // 购货方手机(发票交付手机号); mark by linjinyu at 20220614 按道理是设置为不交付的，这个设置没作用？按这样说，这个手机就是不能填充罗 【【结算中心-测试环境】积分结算-发票申请管理-【深圳航信电子发票】不用交付手机号】https://www.tapd.cn/33003868/bugtrace/bugs/view?bug_id=1133003868001005367
                    .ghfEmail(sysEnterpriseExtInvoiceParam.getInvoiceEmail()) // 购货方邮箱(发票交付邮箱); // sysEnterpriseExtInvoiceParam.getInvoiceEmail()

                    .kpy(invoiceBankInfoVo.getPersonNameInvoice()) // 开票人;
                    .sky(invoiceBankInfoVo.getPersonNameCashier()) // 收款人;
                    .fhr(invoiceBankInfoVo.getPersonNameReview()) // 复核人;

//                    .billSentSign("1") // 交付标志; 0:不交付, 1:交付邮箱, 2 交付手机, 3:手机邮箱都交付 默认:0
                    .billSentSign(StrUtil.isBlank(sysEnterpriseExtInvoiceParam.getInvoiceEmail()) ?
                            SzhtxxElectronicInvoiceFptxxBillSentSignEnum.不交付.getKey().toString() : SzhtxxElectronicInvoiceFptxxBillSentSignEnum.交付邮箱.getKey().toString())
                    // 产品说 默认交付为 1:交付邮箱
                    // 产品说 如果没有邮箱 就不交付 修改 by linjinyu at 20220418 1755
                    // SzhtxxElectronicInvoiceFptxxBillSentSignEnum

                    .qdBz("0") // 清单标志;
                    // 0-普通电子发票
                    // 1-普通电子发票(清单)
                    // 2-收购电子发票 3-收购电子发票(清单)
                    // 4-成品油电子发票(成品油无 清单)
                    // 备注:默认为 0
                    // 1、3 清单是指强制清单
                    // 0、2 如果超出 8 行自动打印 清单。
                    // 4 成品油,则商品明细行不能 有其他商品,一个商品是成 品油,另一个不是成品油, 开不出来票。并且成品油的 发票的商品行中,单价、数 量、单位必填,其中单位为 升或者吨
                    .qdxmmc("") // 清单项目名称; 清单标志为 1 时必填

                    .kplx(AnStringUtils.getString(SzhtxxElectronicInvoiceFptxxKplxEnum.正常发票.getKey())) // 开票类型; 开票类型(1 是蓝票,2 是红票,10 特殊冲红)

                    .yfpDm("") // 原发票代码; 开红票时必填
                    .yfpHm("") // 原发票号码; 开红票时必填
//                    .redReason(fptxxListItemVo.getRedReason()) // 需求变动-接口变动-冲红原因,普通发票、电子普通发票、卷票冲红需给出冲红原因。^_^1-销货退回;2-开票有误;3-服务中止;4-销售折让;对于冲红原因为 1、2、3 的，会执行本地校验和联网校验两项检查，只要其中一项检查不通过，不允许开具红票，其它:为空; 引用 enum SzhtxxElectronicInvoiceFptxxRedReasonEnum
//                    .blueInvDate(fptxxListItemVo.getKprq()) // 需求变动-接口变动-原蓝字发票的开具日期^_^redReason不为空时，则要求填写原蓝字发票的开具日期，格式:yyyyMMdd, 如:20211230注:如果原开票软件中原蓝票的开票时间不一致时，则以开票软件的为准，不作不一致的提示。
//                    .blueInvKind(AnStringUtils.getString(SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum.电子票.getKey())) // 需求变动-接口变动-原蓝字发票的发票类型^_^redReason不为空时，则要求填写原蓝字发票的发票类型，对应蓝字发票种类主要包括以下4类:2-普通发票;41-卷票;51-电子普通发票;999-其他普通发票(如:通行费发票)

                    .invoiceType(SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum.电子票.getKey()) // 需求变动-接口变动-发票类型^_^0-专票;2-普票;41-卷票;42-二手车;51-电子票;52-电子专票;12-机动车发票;不传默认电子发票; 引用 enum SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum
//                    .invReason() // 需求变动-接口变动-开具理由^_^规模纳税人(含转登记)、代开、委托代开企业，自 2022-01-01 至 2022-12-31 起 3%、1%、0.5%税率降为免税，此期间依旧开具 3% 或 1% 税率的，需要传入开具理由。; 引用 enum SzhtxxElectronicInvoiceFptxxInvReasonEnum

                    /**************************************** 存在漏洞 这样的算法的前题为 这个税率是一致的情况下 start ****************************************/

//                    .kphjje(NumberUtil.roundStr(invoiceApplyAmountIncludeTax.doubleValue(), 2)) // 价税合计; 小数点后 2 位,以 元为单位 精确到分
//                    .hjbhsje(NumberUtil.roundStr(invoiceApplyAmountWithOutTax.doubleValue(), 2)) // 合计不含税金额; 小数点后 2 位,以 元为单位 精确到分 (单行商品金额之 和)。
//                    .hjse(NumberUtil.roundStr(invoiceApplyTaxAmount.doubleValue(), 2)) // 合计税额; 小数点后 2 位,以 元为单位 精确到分 (单行商品税额之和)

                    /**************************************** 存在漏洞 这样的算法的前题为 这个税率是一致的情况下 start ****************************************/

                    /**************************************** 修一下 start ****************************************/

                    .kphjje(NumberUtil.roundStr(invoiceApplyAmountIncludeTax.doubleValue(), 2)) // 价税合计; 小数点后 2 位,以 元为单位 精确到分
                    .hjbhsje(NumberUtil.roundStr(invoiceApplyAmountIncludeTax.doubleValue() - invoiceApplyTaxAmount.doubleValue(), 2)) // 合计不含税金额; 小数点后 2 位,以 元为单位 精确到分 (单行商品金额之 和)。
                    .hjse(NumberUtil.roundStr(invoiceApplyTaxAmount.doubleValue(), 2)) // 合计税额; 小数点后 2 位,以 元为单位 精确到分 (单行商品税额之和)

                    /**************************************** 修一下   end ****************************************/

                    // 因为这个方法是同时两种情况用的，所以现在默认为先从开票申请过来，再按提交的修正
//                    .bz(form.getBz()) // 备注" 增值税发票红字发票开具时,备注要求: 开具负数发票,必须在备注中注明"对应正数发票代码:XXXXXXXXX 号码:YYYYYYYY"字 样,其中"X"为 发票代码,"Y"为发票号码。
                    .bz(invoiceApplyCustomer.getRemark()) // 备注" 增值税发票红字发票开具时,备注要求: 开具负数发票,必须在备注中注明"对应正数发票代码:XXXXXXXXX 号码:YYYYYYYY"字 样,其中"X"为 发票代码,"Y"为发票号码。

                    .callBackUrl("") // pdf 生成回调标志;

                    .build();

            if (form.getFormState() == CommonFormStateEnum.提交) {
                fptxx.setBz(form.getBz());
            }


            /**************************************** 发票体信息   end ****************************************/


            log.info("\n{}" +
                            "\n税额1 总计 {} √" +
                            "\n税额2 累计 {}" +

//                            "\n税额3 累计列表 {}" +
//                            "\n税额4 摊分列表 {}" +

                            "\n{}" +
                            "\n{}" +
                            "\n{}" +
                            "\n税额1 总计 {} √" +
                            "\n∑税额3 累计列表 {}" +
                            "\n∑税额4 摊分列表 {}",
                    report.toString(),
                    invoiceApplyTaxAmount,
                    invoiceApplyTaxAmountCount,


//                    NumberUtil.roundStr(invoiceApplyTaxAmountList.stream().reduce(BigDecimal.ZERO, BigDecimal::add).doubleValue(), 2),
//                    NumberUtil.roundStr(invoiceApplyTaxAmountWhichList.stream().reduce(BigDecimal.ZERO, BigDecimal::add).doubleValue(), 2),

                    reportWhich.toString(),
                    JSONUtil.toJsonStr(invoiceApplyTaxAmountList),
                    JSONUtil.toJsonStr(invoiceApplyTaxAmountWhichList),

                    invoiceApplyTaxAmount,
                    invoiceApplyTaxAmountList.stream().reduce(BigDecimal.ZERO, BigDecimal::add),
                    invoiceApplyTaxAmountWhichList.stream().reduce(BigDecimal.ZERO, BigDecimal::add)
            );

            /**
             * 1. api前 ---- 当前
             * 2. api
             * 3. excel
             */
            String now     = DateUtil.now();
            String fpqqlsh = invoiceApplyCustomer.getInvoiceApplySn();

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

            String relativePath = File.separator + "thirdpart" +
                    File.separator + "szhtxx" +
                    File.separator + "electronicInvoice" +
                    File.separator + "1.1.1_SingleBilling" +
                    File.separator + fpqqlsh +
                    File.separator;

            String targetFullPath          = excelExportPathRoot + excelExportRelativePathStorage;
            String targetFileName          = StrUtil.format("SingleBilling_{}_{}_CheckLog.json", fpqqlsh, now);
            String targetWriteFileFullPath = targetFullPath + relativePath + targetFileName;
            targetWriteFileFullPath = targetWriteFileFullPath.replaceAll(":","_");
            FileUtil.touch(targetWriteFileFullPath);
            FileUtil.writeUtf8String(StrUtil.format(
                    "\n{}" +
                            "\n税额1 总计 {} √" +
                            "\n税额2 累计 {}" +

                            "\n{}" +
                            "\n{}" +
                            "\n{}" +

                            "\n税额1 总计 {} √" +
                            "\n∑税额3 累计列表 {}" +
                            "\n∑税额4 摊分列表 {}",
                    report.toString(),
                    invoiceApplyTaxAmount,
                    invoiceApplyTaxAmountCount,

                    reportWhich.toString(),
                    JSONUtil.toJsonStr(invoiceApplyTaxAmountList),
                    JSONUtil.toJsonStr(invoiceApplyTaxAmountWhichList),

                    invoiceApplyTaxAmount,
                    invoiceApplyTaxAmountList.stream().reduce(BigDecimal.ZERO, BigDecimal::add),
                    invoiceApplyTaxAmountWhichList.stream().reduce(BigDecimal.ZERO, BigDecimal::add)
            ), targetWriteFileFullPath);// CharsetUtil.CHARSET_UTF_8

        }

        return SingleBillingRequestBodyKpContent
                .builder()
                .fptxx(fptxx)
                .xmxx(xmxx)
                .build();
    }

    /**
     * op红冲发票(电子普通发票)(深圳航信电子票)
     *
     * @param form
     * @param fptxxListItemVo
     * @param invoiceApplyCustomer
     * @param loginSysUser
     * @return
     * @throws UnifiedOrderRuntimeException
     */
    @Override
    public SingleBillingRequestBodyKpContent opOngoingInvoiceTypeEq1InvoiceModeEqSzhtxxElectronicInvoiceRedWrite(
            SzhtxxInvoiceFptxxOpSingleBillingForm form,
            SzhtxxElectronicInvoiceFptxxListItemVo fptxxListItemVo,
            InvoiceApply invoiceApplyCustomer,
            LoginSysUser loginSysUser) throws UnifiedOrderRuntimeException {

        log.info("\n红冲开票 -> 状态 -> {} -> 开票中(10, \"开票中\")\n{}\n{}", getClass().getName(), JSONUtil.toJsonPrettyStr(invoiceApplyCustomer), JSONUtil.toJsonPrettyStr(loginSysUser));

        StringBuffer report = new StringBuffer();

        List<ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo> resultList = invoiceApplyCustomerService.findInvoiceApplyFocusScoreDimCredit(
                InvoiceApplyCustomerQueryCriteria.builder()
                        .creditAccountingState(CommonAccountingStateEnum.已对账.getKey())
                        .invoiceApplyIdsSelected(Arrays.asList(invoiceApplyCustomer.getId()))
                        .build()
        );

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


        SingleBillingRequestBodyKpContentFptxxVo          fptxx = null;
        List<SingleBillingRequestBodyKpContentXmxxItemVo> xmxx  = new ArrayList<>();

        for (Map.Entry<String, InvoiceApplyCustomerExportExcelFocusScoreDimCreditStruct> _entry : resultMap.entrySet()) {

            String purchaseVirtName = AnStringUtils.stringListConvertToString(_entry.getValue()
                    .getTransferListBySheet()
                    .stream()
                    .map(ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo::getEnterpriseName)
                    .distinct()
                    .collect(Collectors.toList())
            );
            String invoiceApplySn = _entry.getKey();
            String accountingOrderSn = AnStringUtils.stringListConvertToString(_entry.getValue()
                    .getTransferListBySheet()
                    .stream()
                    .map(ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo::getAccountingOrderSn)
                    .distinct()
                    .collect(Collectors.toList())
            );

            /****************************************** 侨城汇需求 20210922 start ******************************************/

            invoiceApplyCustomerExportExcelService.qchNeed20210922(_entry.getValue().getTransferListBySheetMerge());

            /****************************************** 侨城汇需求 20210922   end ******************************************/


            /****************************************** 面单 start ******************************************/


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

            InvoiceBankInfoVo invoiceBankInfoVo = invoiceBankInfoService.getInvoiceBankInfoByInvoiceApplyIdOrNullByDefault(invoiceApplyCustomer.getId());

            log.info("opOngoingInvoiceTypeEq1InvoiceModeEqSzhtxxElectronicInvoiceRedWrite->销方信息 \n{}", JSONUtil.toJsonStr(invoiceBankInfoVo));

            /****************************************** 购方信息 start ******************************************/

            SysEnterpriseExtInvoiceParam sysEnterpriseExtInvoiceParam = null;


            if (invoiceApplyCustomer.getSysEnterpriseExtInvoiceId().compareTo(0L) != 0) {

                if (StrUtil.isBlank(invoiceApplyCustomer.getSysEnterpriseExtInvoiceSnapshot())) {
                    throw new UnifiedOrderRuntimeException(500, "购方快照信息异常");
                }

                sysEnterpriseExtInvoiceParam = AnJsonUtils.toObject(invoiceApplyCustomer.getSysEnterpriseExtInvoiceSnapshot(), SysEnterpriseExtInvoiceParam.class);
//				sysEnterpriseExtInvoiceParam.setTaxpayersIDCode("1111111111");
            } else {
                sysEnterpriseExtInvoiceParam = new SysEnterpriseExtInvoiceParam();
                // 异常
            }

            log.info("opOngoingInvoiceTypeEq1InvoiceModeEqSzhtxxElectronicInvoiceRedWrite->购方信息 \nSysEnterpriseExtInvoiceId : {}\nsysEnterpriseExtInvoiceParam : {}", invoiceApplyCustomer.getSysEnterpriseExtInvoiceId(), JSONUtil.toJsonStr(sysEnterpriseExtInvoiceParam));


            BigDecimal taxRateHundred = new BigDecimal(invoiceApplyCustomer.getTaxRate());
            //			BigDecimal taxRateOneSurplus = new BigDecimal(1L).subtract(taxRateOne);

            BigDecimal taxRateOne        = taxRateHundred.divide(new BigDecimal(100L), 2, BigDecimal.ROUND_HALF_UP);
            BigDecimal taxRateOneSurplus = new BigDecimal(1L).add(taxRateOne); // 1+税率

            /**
             * 前置条件为 此票有统一税率 侨城汇要求
             */

//            BigDecimal invoiceApplyAmountIncludeTax = invoiceApplyCustomer.getInvoiceApplyAmount();// 错误
            BigDecimal invoiceApplyAmountIncludeTax = invoiceApplyCustomer.getRealAmount();

//			BigDecimal invoiceApplyAmountWithOutTax = invoiceApplyAmountIncludeTax.subtract(invoiceApplyTaxAmount);
//			BigDecimal invoiceApplyTaxAmount        = invoiceApplyAmountIncludeTax.multiply(taxRateOne); // 税额

//            BigDecimal invoiceApplyAmountWithOutTax = invoiceApplyAmountIncludeTax.divide(taxRateOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
//            BigDecimal invoiceApplyTaxAmount        = invoiceApplyAmountIncludeTax.subtract(invoiceApplyAmountWithOutTax); // 税额

//            ROUND_UP //对非舍去的部分始终在保留的最低位加1
//            ROUND_DOWN //与ROUND_UP相反，始终不对非舍去的部分加1
//
//            ROUND_CEILING //如果为正，则舍入原则与ROUND_UP一致，如果为负，则舍入原则与ROUND_DOWN一致
//            ROUND_FLOOR //与CEILING刚好相反 可以将这两种模式理解为坐标轴的方向
//
//            ROUND_HALF_UP //四舍五入
//            ROUND_HALF_DOWN //五舍六入
//            ROUND_HALF_EVEN //银行家舍入法，主要在美国使用。四舍六入是肯定的，五分为两种情况，前一位为奇数，则入位，前一位为偶数，则舍去。
//            ROUND_UNNECESSARY //断言使用

            BigDecimal invoiceApplyAmountWithOutTax = invoiceApplyAmountIncludeTax.divide(taxRateOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
            BigDecimal invoiceApplyTaxAmount        = invoiceApplyAmountIncludeTax.subtract(invoiceApplyAmountWithOutTax); // 税额

            BigDecimal invoiceApplyTaxAmountCount        = BigDecimal.ZERO;
            BigDecimal invoiceApplyAmountWithOutTaxCount = BigDecimal.ZERO;

            report.append(StrUtil.format("\n负数项::金额(含税):{} 负数项::金额(不含税):{} 负数项::税额:{} 累计税额:{} 税率: {}",
                    invoiceApplyAmountIncludeTax.doubleValue(),
                    invoiceApplyAmountWithOutTax.doubleValue(),
                    invoiceApplyTaxAmount.doubleValue(),
                    invoiceApplyTaxAmountCount,
                    taxRateOneSurplus)
            );


            List<BigDecimal> invoiceApplyTaxAmountList        = new ArrayList<>();
            List<BigDecimal> invoiceApplyAmountWithOutTaxList = new ArrayList<>();

            /****************************************** 项目信息 明细 start ******************************************/

            xmxx = new ArrayList<>();

//			_entry.getValue().getTransferListBySheetMerge().forEach(item -> {
            int i    = 0;
            int size = _entry.getValue().getTransferListBySheetMerge().size();
            for (ExportExcelInvoiceApplyCustomerForceScoreDimCreditVo item : _entry.getValue().getTransferListBySheetMerge()) {

                i++;
//				BigDecimal taxRateItemHundred    = item.getOperationTaxRate();
//				BigDecimal taxRateItemOne        = taxRateItemHundred.divide(new BigDecimal(100L), 2, BigDecimal.ROUND_HALF_UP);

                BigDecimal taxRateItemHundred    = item.getOperationTaxRate();
                BigDecimal taxRateItemOne        = item.getOperationTaxRate();
                BigDecimal taxRateItemOneSurplus = new BigDecimal(1L).add(taxRateItemOne);


                /******************************************** 差异点 start ********************************************/


//				// 正常项::金额(含税) = 单价(含税) * 数量
//				BigDecimal goodsItemAmount = item.getPrice().multiply(new BigDecimal(item.getQuantity()));
//				// 正常项::金额(不含税) = 金额(含税) 除以 (1+税率(0-1))
//				BigDecimal goodsItemAmountWithOutTax = goodsItemAmount.divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
//				// 正常项::税额 = 金额(含税) - 金额(不含税)
//				BigDecimal goodsItemTaxAmount = goodsItemAmount.subtract(goodsItemAmountWithOutTax);
//
//				// 扣减项::扣除额(含税) = 正常项::金额(含税) - 实际金额(含税)
//				BigDecimal goodsItemDiffAmount = goodsItemAmount.subtract(item.getBalance());
//				// 扣减项::扣除额(不含税) = 扣除额(含税) 除以 (1+税率(0-1))
//				BigDecimal goodsItemDiffAmountWithOutTax = goodsItemDiffAmount.divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
//				// 扣减项::扣除额(税额) = 扣除额(含税) - 扣除额(不含税)
//				BigDecimal goodsItemDiffTaxAmount = goodsItemDiffAmount.subtract(goodsItemDiffAmountWithOutTax);// 扣除额 税额
//
//				BigDecimal goodsItemDiffAmountMinus = goodsItemDiffAmount.multiply(new BigDecimal(-1L)); // 扣除额 转换成 -扣除额
//				BigDecimal goodsItemDiffAmountWithOutTaxMinus = goodsItemDiffAmountWithOutTax.multiply(new BigDecimal(-1L));// -扣除额 不包含 税
//				BigDecimal goodsItemDiffTaxAmountMinus = goodsItemDiffTaxAmount.multiply(new BigDecimal(-1L));// -扣除额 税额

                /******************************************** 差异点   end ********************************************/


                // 红冲 正数项::金额(含税) = 单价(含税) * 数量
                BigDecimal goodsItemAmount = item.getBalance();

                // 从数据上存在流水金额为0的情况 钝角!
                // SELECT * FROM `unified_order_credit` WHERE order_sn = 'ME20210804005105461704' 证明
//                正在显示第 0 - 24 行 (共 98 行, 查询花费 0.1460 秒。)
//                SELECT * FROM `unified_order_credit` WHERE balance = 0
                if (goodsItemAmount.compareTo(BigDecimal.ZERO) == 0) {
                    continue;
                }

                // 红冲 正数项::金额(不含税) = 金额(含税) 除以 (1+税率(0-1))
                BigDecimal goodsItemAmountWithOutTax = goodsItemAmount.divide(taxRateItemOneSurplus, 2, BigDecimal.ROUND_HALF_UP);
                // 红冲 正数项::税额 = 金额(含税) - 金额(不含税)
                BigDecimal goodsItemTaxAmount = goodsItemAmount.subtract(goodsItemAmountWithOutTax);

//                // 红冲 正数项::金额(含税) 转换成 红冲 负数项::金额(含税)
//                BigDecimal goodsItemAmountMinus = goodsItemAmount.multiply(new BigDecimal(-1L));
//                // 红冲 正数项::金额(不含税) 转换成 红冲 负数项::金额(不含税)
//                BigDecimal goodsItemAmountWithOutTaxMinus = goodsItemAmountWithOutTax.multiply(new BigDecimal(-1L));
//                // 红冲 正数项::税额 转换成 红冲 负数项::税额
//                BigDecimal goodsItemTaxAmountMinus = goodsItemTaxAmount.multiply(new BigDecimal(-1L));


                // 最后一项
                if (size == i) {


                    goodsItemTaxAmount = invoiceApplyTaxAmount
                            .subtract(invoiceApplyTaxAmountCount);

//                    goodsItemAmountWithOutTax = invoiceApplyAmountWithOutTax
//                            .subtract(invoiceApplyAmountWithOutTaxCount);

                    invoiceApplyTaxAmountCount = invoiceApplyTaxAmountCount
                            /* 正常项-税额(加正数) */.add(goodsItemTaxAmount)
                            /* 扣除项-税额(加负数) */.add(BigDecimal.ZERO);

                    invoiceApplyAmountWithOutTaxCount = invoiceApplyAmountWithOutTaxCount
                            .add(goodsItemAmountWithOutTax)
                            .add(BigDecimal.ZERO);

                    report.append(StrUtil.format("\n负数项::金额(含税):{} 负数项::金额(不含税):{} 负数项::税额:{} 累计税额:{} 税率:{} - 最后一项 前",
                            goodsItemAmount,
                            goodsItemAmountWithOutTax,
                            goodsItemTaxAmount,
                            invoiceApplyTaxAmountCount,
                            taxRateItemOneSurplus
                    ));

                } else {

                    invoiceApplyTaxAmountCount = invoiceApplyTaxAmountCount
                            /* 正常项-税额(加正数) */.add(goodsItemTaxAmount)
                            /* 扣除项-税额(加负数) */.add(BigDecimal.ZERO);

                    invoiceApplyAmountWithOutTaxCount = invoiceApplyAmountWithOutTaxCount
                            .add(goodsItemAmountWithOutTax)
                            .add(BigDecimal.ZERO);

                    report.append(StrUtil.format("\n负数项::金额(含税):{} 负数项::金额(不含税):{} 负数项::税额:{} 累计税额:{} 税率:{} ",
                            goodsItemAmount,
                            goodsItemAmountWithOutTax,
                            goodsItemTaxAmount,
                            invoiceApplyTaxAmountCount,
                            taxRateItemOneSurplus
                    ));
                }


//				plus-minus

                SingleBillingRequestBodyKpContentXmxxItemVo plus = null;
//				SingleBillingRequestBodyKpContentXmxxItemVo minus = null;

//

                // 优化20220926 新增红字申请不含税金额判断 ReqMemo == "0000000100" 销方申请
//                String xmje = "";
//                        if("0000000100".equals(form.getReqMemo()) || (!"".equals(form.getReason()) && 0==fptxxListItemVo.getInvoiceType())){
//                            xmje = (NumberUtil.roundStr(goodsItemAmountWithOutTax.doubleValue() * -1, 2)) ;// 项目金额;小数点后 2 位,以 元为单位 精确到分。 等于=单价*数量, 根据含税标志,确定此金额是否为含税金额。
//                            //xmje(NumberUtil.roundStr(goodsItemAmountWithOutTax.doubleValue() * -1, 2)) // 项目金额;小数点后 2 位,以 元为单位 精确到分。 等于=单价*数量, 根据含税标志,确定此金额是否为含税金额。
//
//                        }else{
//                            xmje = (NumberUtil.roundStr(goodsItemAmount.doubleValue() * -1, 2)) ;// 项目金额;小数点后 2 位,以 元为单位 精确到分。 等于=单价*数量, 根据含税标志,确定此金额是否为含税金额。
//                            //xmje(NumberUtil.roundStr(goodsItemAmount.doubleValue() * -1, 2)) // 项目金额;小数点后 2 位,以 元为单位 精确到分。 等于=单价*数量, 根据含税标志,确定此金额是否为含税金额。
//
//                        }

                plus = SingleBillingRequestBodyKpContentXmxxItemVo.builder()
                        .xmmc(AnStringUtils.szhtxx(item.getGoodsName(), AnStringUtils.GOODS_NAME_LENGHT)) // 商品名称; 如 FPHXZ=1,则此商 品行为 折扣行,此 版本折扣行不允 许 多行折扣,折扣行 必须紧 邻被折扣 行,项目名称必须 与被折扣行一致。
//						.xmmc(item.getGoodsName()) // 商品名称; 如 FPHXZ=1,则此商 品行为 折扣行,此版本折扣行不允许多行折扣,折扣行必须紧邻被折扣行,项目名称必须与被折扣行一致。
                        .xmdw(AnStringUtils.szhtxx(item.getUnit(), AnStringUtils.UNIT_LENGHT)) // 单位; 项目单位
                        .ggxh(AnStringUtils.szhtxx(item.getSelfOption(), AnStringUtils.SELF_OPTION_LENGHT)) // 型号; 规格型号 // 第13行:GGXH  非通行费发票 字符串长度小于等于40" // 单价 数量 规格型号 计量单位这些可以不传

                        .hsbz("1") // 含税标志;表示项目单价和项 目金额是 否含税。0-表示都不含税,1-表示都含税。 item.getFlagTaxIncluded()
                        .yhzcbs("0") // 优惠政策标识;0:不使用,1:使 用 item.getFlagTaxIncentives()

                        .fphxz("0") // 发票行性质;0正常行、1折扣行、 2被折扣行

                        .lslbs("") // 零税率标识;空:非零税率, 1: 免税, 2:不征税, 3 普通零税率
                        .zzstsgl("") // 增值税特殊管理;当 YHZCBS(优惠政策标识) 为 1 时必 填

//                        .xmsl(item.getQuantity() * -1) // 数量; 小数点后 8 位, 小 数点后都是 0 时, PDF 上只显示整数 (特殊情况:如果不传单价, 数量。则含税标志选 0,项目 金额传不含税)
                        .xmsl(-1) // 数量; 小数点后 8 位, 小 数点后都是 0 时, PDF 上只显示整数 (特殊情况:如果不传单价, 数量。则含税标志选 0,项目 金额传不含税)
                        // 注意，红冲的不是拿真的数量
//                        .xmdj(item.getPrice()) // 单价; 小数点后 8 位小数 点后都是 0 时,PDF 上只显 示2 位小数; 否则只显示至 最后 一位不为0 的数字 (特殊情况:如果不传,需同时 不传单价和数量)
                        // 注意，红冲的不是拿真的单价
                        .xmdj(item.getBalance()) // 单价; 小数点后 8 位小数 点后都是 0 时,PDF 上只显 示2 位小数; 否则只显示至 最后 一位不为0 的数字 (特殊情况:如果不传,需同时 不传单价和数量)

//						.kce(NumberUtil.roundStr(goodsItemDiffAmount.doubleValue(), 2)) // 扣除额;小数点 2 位小数,不能大于不含税金额 说明如下:
//						1、差额征税的发票如果没有折扣的话,只能允许一条商 品行。
//						2、具体差额征税发票的计算 方法如下: 不含税差额 = 不含税金额 - 扣除额; 税额 = 不含税差额 * 税率;
                        // 优化20220926 新增红字申请不含税金额判断
                        //.xmje(xmje)
                        .xmje(NumberUtil.roundStr(goodsItemAmount.doubleValue() * -1, 2)) // 项目金额;小数点后 2 位,以 元为单位 精确到分。 等于=单价*数量, 根据含税标志,确定此金额是否为含税金额。
                        .se(NumberUtil.roundStr(goodsItemTaxAmount.doubleValue() * -1, 2)) // 税额;小数点后 2 位,以 元为单位 精确到分

                        .spbm(item.getOperationTaxCode()) // 税收分类编码;商品编码?
                        .sl(item.getOperationTaxRate().toString()) // 税率;如果税率为 0,表示免税
                        .build();


                invoiceApplyTaxAmountList.add(goodsItemTaxAmount);

                xmxx.add(plus);
            }

            /****************************************** 项目信息 明细   end ******************************************/

            /**************************************** 发票体信息 start ****************************************/

            // 精度差
//            invoiceApplyAmountIncludeTax
//            invoiceApplyAmountWithOutTax
//            invoiceApplyTaxAmount

//            BigDecimal invoiceApplyAmountWithOutTaxRevise = invoiceApplyAmountIncludeTax.subtract(invoiceApplyTaxAmountCount);
//            invoiceApplyTaxAmountCount

            fptxx = SingleBillingRequestBodyKpContentFptxxVo.builder()
                    .fpqqlsh(StrUtil.format("{}{}", invoiceApplyCustomer.getInvoiceApplySn(), "R")) // 发票请求唯一流水号;

                    .kpxm("办公用品") // 开票项目名称; 主要开票商品,或者第一条 商品,取项目信息中第一条 数据的项目名称(或传递大 类例 如:办公用品)
                    .dkbz("0") // 代开标志; 0:自开 1:代开 默认:0
                    .fjh("") // 分机号; 为空时随机获取分机盘开 票,不为空是指定分机盘开 具(A9 暂时不支持,C48 支 持)

                    .xhfmc(invoiceBankInfoVo.getCompanyName()) // 销货方名称;
                    .xhfNsrsbh(invoiceBankInfoVo.getTaxpayersIDCode()) // 销货方纳税人识别号;
                    .xhfYhzh(StrUtil.format("{}{}", invoiceBankInfoVo.getBankName(), invoiceBankInfoVo.getBankAccount())) // 销货方银行账号;
                    .xhfDzdh(StrUtil.format("{}{}", invoiceBankInfoVo.getAddress(), invoiceBankInfoVo.getTel())) // 销货方地址电话;

                    .ghfmc(sysEnterpriseExtInvoiceParam.getCompanyName()) // 购货方名称;
                    .ghfNsrsbh(sysEnterpriseExtInvoiceParam.getTaxpayersIDCode()) // 购货方纳税人识别号;
                    .ghfYhzh(StrUtil.format("{}{}", sysEnterpriseExtInvoiceParam.getInvoiceBank(), sysEnterpriseExtInvoiceParam.getInvoiceAccount())) // 购货方银行账号;
                    .ghfDzdh(StrUtil.format("{}{}", sysEnterpriseExtInvoiceParam.getInvoiceAddress(), sysEnterpriseExtInvoiceParam.getInvoicePhone())) // 购货方地址电话;
                    .ghfqylx(PurchaserCompanyTypeEnum.getPurchaserCompanyType(sysEnterpriseExtInvoiceParam.getSelectedInvoiceTitle())) // 购货方企业类型; 购货方企业类型 01 02 代表企业,两者没区别,03 代表个人

                    // .ghfSj(sysEnterpriseExtInvoiceParam.getInvoicePhone()) // 购货方手机(发票交付手机号);
                    .ghfSj("") // 购货方手机(发票交付手机号); mark by linjinyu at 20220614 按道理是设置为不交付的，这个设置没作用？按这样说，这个手机就是不能填充罗 【【结算中心-测试环境】积分结算-发票申请管理-【深圳航信电子发票】不用交付手机号】https://www.tapd.cn/33003868/bugtrace/bugs/view?bug_id=1133003868001005367
                    .ghfEmail(sysEnterpriseExtInvoiceParam.getInvoiceEmail()) // 购货方邮箱(发票交付邮箱); // sysEnterpriseExtInvoiceParam.getInvoiceEmail()

                    .kpy(invoiceBankInfoVo.getPersonNameInvoice()) // 开票人;
                    .sky(invoiceBankInfoVo.getPersonNameCashier()) // 收款人;
                    .fhr(invoiceBankInfoVo.getPersonNameReview()) // 复核人;

                    .billSentSign(StrUtil.isBlank(sysEnterpriseExtInvoiceParam.getInvoiceEmail()) ?
                            SzhtxxElectronicInvoiceFptxxBillSentSignEnum.不交付.getKey().toString() : SzhtxxElectronicInvoiceFptxxBillSentSignEnum.交付邮箱.getKey().toString()) // 交付标志; 0:不交付, 1:交付邮箱, 2 交付手机, 3:手机邮箱都交付 默认:0
                    // 产品说 默认交付为 1:交付邮箱
                    // 产品说 如果没有邮箱 就不交付 修改 by linjinyu at 20220418 1755
                    // SzhtxxElectronicInvoiceFptxxBillSentSignEnum

                    .qdBz("0") // 清单标志;
                    // 0-普通电子发票
                    // 1-普通电子发票(清单)
                    // 2-收购电子发票 3-收购电子发票(清单)
                    // 4-成品油电子发票(成品油无 清单)
                    // 备注:默认为 0
                    // 1、3 清单是指强制清单
                    // 0、2 如果超出 8 行自动打印 清单。
                    // 4 成品油,则商品明细行不能 有其他商品,一个商品是成 品油,另一个不是成品油, 开不出来票。并且成品油的 发票的商品行中,单价、数 量、单位必填,其中单位为 升或者吨
                    .qdxmmc("") // 清单项目名称; 清单标志为 1 时必填

                    .kplx(AnStringUtils.getString(SzhtxxElectronicInvoiceFptxxKplxEnum.红冲发票.getKey())) // 开票类型; 开票类型(1 是蓝票,2 是红票,10 特殊冲红)

                    .yfpDm(fptxxListItemVo.getFpdm()) // 原发票代码; 开红票时必填
                    .yfpHm(fptxxListItemVo.getFphm()) // 原发票号码; 开红票时必填
                    .redReason(form.getRedReason()) // 需求变动-接口变动-冲红原因,普通发票、电子普通发票、卷票冲红需给出冲红原因。^_^1-销货退回;2-开票有误;3-服务中止;4-销售折让;对于冲红原因为 1、2、3 的，会执行本地校验和联网校验两项检查，只要其中一项检查不通过，不允许开具红票，其它:为空; 引用 enum SzhtxxElectronicInvoiceFptxxRedReasonEnum


                    /**************************************** 妈的，改错地方 - 问题处理 - 蓝字发票时间长度不合法 start ****************************************/
//                    .blueInvDate(fptxxListItemVo.getKprq()) // 需求变动-接口变动-原蓝字发票的开具日期^_^redReason不为空时，则要求填写原蓝字发票的开具日期，格式:yyyyMMdd, 如:20211230注:如果原开票软件中原蓝票的开票时间不一致时，则以开票软件的为准，不作不一致的提示。

//                【【结算中心-测试环境】积分结算-金税盘发票-【深圳航信电子发票】红冲发票-【确认红冲】，提示“蓝字发票时间长度不合法”】
//                https://www.tapd.cn/33003868/bugtrace/bugs/view?bug_id=1133003868001005077
                    // mark by linjinyu at 20220509

                    // 他喵的不统一 这里要处理一下 数据库的为 yyyyMMddHHmmss
                    .blueInvDate(DateUtil.parse(fptxxListItemVo.getKprq(), "yyyyMMddHHmmss").toString("yyyyMMdd"))

                    /**************************************** 妈的，改错地方 - 问题处理 - 蓝字发票时间长度不合法 start ****************************************/


                    .blueInvKind(AnStringUtils.getString(SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum.电子票.getKey())) // 需求变动-接口变动-原蓝字发票的发票类型^_^redReason不为空时，则要求填写原蓝字发票的发票类型，对应蓝字发票种类主要包括以下4类:2-普通发票;41-卷票;51-电子普通发票;999-其他普通发票(如:通行费发票)

                    .invoiceType(SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum.电子票.getKey()) // 需求变动-接口变动-发票类型^_^0-专票;2-普票;41-卷票;42-二手车;51-电子票;52-电子专票;12-机动车发票;不传默认电子发票; 引用 enum SzhtxxElectronicInvoiceFptxxInvoiceTypeEnum
//                    .invReason() // 需求变动-接口变动-开具理由^_^规模纳税人(含转登记)、代开、委托代开企业，自 2022-01-01 至 2022-12-31 起 3%、1%、0.5%税率降为免税，此期间依旧开具 3% 或 1% 税率的，需要传入开具理由。; 引用 enum SzhtxxElectronicInvoiceFptxxInvReasonEnum

                    .kphjje(NumberUtil.roundStr(invoiceApplyAmountIncludeTax.doubleValue() * -1, 2)) // 价税合计; 小数点后 2 位,以 元为单位 精确到分
                    .hjbhsje(NumberUtil.roundStr(invoiceApplyAmountWithOutTax.doubleValue() * -1, 2)) // 合计不含税金额; 小数点后 2 位,以 元为单位 精确到分 (单行商品金额之 和)。
//                    .hjbhsje(NumberUtil.roundStr(invoiceApplyAmountWithOutTaxRevise.doubleValue() * -1, 2)) // 合计不含税金额; 小数点后 2 位,以 元为单位 精确到分 (单行商品金额之 和)。
                    .hjse(NumberUtil.roundStr(invoiceApplyTaxAmount.doubleValue() * -1, 2)) // 合计税额; 小数点后 2 位,以 元为单位 精确到分 (单行商品税额之和)
//                    .hjse(NumberUtil.roundStr(invoiceApplyTaxAmountCount.doubleValue() * -1, 2)) // 合计税额; 小数点后 2 位,以 元为单位 精确到分 (单行商品税额之和)

                    //【后端已处理-前端已处理-20220322-【结算中心-测试环境】【前端处理】积分结算-金税盘发票-“红冲”页面，发票备注初始内容错误。】https://www.tapd.cn/33003868/bugtrace/bugs/view?bug_id=1133003868001004696
                    // linjinyu 在状态 [处理中 - 开发] 添加 2022-03-22 13:17
//                    .bz(StrUtil.format("对应正数发票代码:{},发票号:{}", fptxxListItemVo.getFpdm(), fptxxListItemVo.getFphm())) // 备注" 增值税发票红字发票开具时,备注要求: 开具负数发票,必须在备注中注明"对应正数发票代码:XXXXXXXXX 号码:YYYYYYYY"字 样,其中"X"为 发票代码,"Y"为发票号码。
                    // linjinyu 在状态 [处理中 - 开发] 添加 2022-03-31 10:30
//                    .bz(StrUtil.format("对应正数发票代码:{} 号码:{}", fptxxListItemVo.getFpdm(), fptxxListItemVo.getFphm())) // 备注" 增值税发票红字发票开具时,备注要求: 开具负数发票,必须在备注中注明"对应正数发票代码:XXXXXXXXX 号码:YYYYYYYY"字 样,其中"X"为 发票代码,"Y"为发票号码。
                    // linjinyu 在状态 [处理中 - 开发] 添加 2022-03-31 19:09
                    .bz(StrUtil.format("对应正数发票代码:{}号码:{}", fptxxListItemVo.getFpdm(), fptxxListItemVo.getFphm())) // 备注" 增值税发票红字发票开具时,备注要求: 开具负数发票,必须在备注中注明"对应正数发票代码:XXXXXXXXX 号码:YYYYYYYY"字 样,其中"X"为 发票代码,"Y"为发票号码。

                    .callBackUrl("") // pdf 生成回调标志;

                    .build();


            if (form.getFormState() == CommonFormStateEnum.提交) {
                fptxx.setBz(form.getBz());
            }

            /**************************************** 发票体信息   end ****************************************/
//            invoiceApplyAmountIncludeTax
//            invoiceApplyAmountWithOutTax

//            000000100_CIA2022032418321664R数量*单价与项目金额误差大于0.01
            log.info("\n{}" +
                            "\n价税合计额(#.##) {}" +
                            "\n合计不含税(#.##) {}" +

                            "\n税额1 - 价税合计额 ÷ (1+税率(0-1)) - 合计不含税 = {}" +
                            "\n税额2 - 每一项税额累加                          {}" +
                            "\n合计不含税1 - 计算 {}" +
                            "\n合计不含税2 - 累加 {}" +
                            "\n价税合计额 - 税额1 = {}" +
                            "\n价税合计额 - 税额2 = {}" +
                            "\n税额1(#.##) {}" +
                            "\n税额2(#.##) {} " +
                            "\n{}",
                    report.toString(),
                    invoiceApplyAmountIncludeTax,
                    invoiceApplyAmountWithOutTax,

                    invoiceApplyTaxAmount,
                    invoiceApplyTaxAmountCount,// invoiceApplyTaxAmountList.stream().reduce(BigDecimal.ZERO, BigDecimal::add),

                    invoiceApplyAmountWithOutTax,
                    invoiceApplyAmountWithOutTaxCount,

                    invoiceApplyAmountIncludeTax.subtract(invoiceApplyTaxAmount),
                    invoiceApplyAmountIncludeTax.subtract(invoiceApplyTaxAmountCount),
                    NumberUtil.roundStr(invoiceApplyTaxAmount.doubleValue(), 2),
                    NumberUtil.roundStr(invoiceApplyTaxAmountCount.doubleValue(), 2),
                    JSONUtil.toJsonStr(invoiceApplyTaxAmountList)
            );
        }

        return SingleBillingRequestBodyKpContent
                .builder()
                .fptxx(fptxx)
                .xmxx(xmxx)
                .build();
    }

}
