package cn.iocoder.yudao.module.system.api.kingdee;

import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.string.StringUtil;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.system.api.kingdee.dto.*;
import cn.iocoder.yudao.module.system.api.kingdee.dto.customer.BDCustomerDTO;
import cn.iocoder.yudao.module.system.api.kingdee.dto.customer.BDCustomerModelDTO;
import cn.iocoder.yudao.module.system.api.kingdee.dto.org.BDOrgDTO;
import cn.iocoder.yudao.module.system.api.kingdee.dto.org.BDOrgModelDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.*;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.EnterpriseBankInfoDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.*;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.EnterpriseBankInfoMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
import cn.iocoder.yudao.module.system.enums.change.BusinessConstants;
import cn.iocoder.yudao.module.system.enums.common.BooleanEnum;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.kingdee.bos.webapi.entity.IdentifyInfo;
import com.kingdee.bos.webapi.entity.RepoRet;
import com.kingdee.bos.webapi.sdk.K3CloudApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exceptionCustomMsg;
import static cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO.PACKAGE_ID_SYSTEM;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_ERROR;
import static org.aspectj.bridge.MessageUtil.fail;

/**
 * @Author: jinyd
 * @CreateTime: 2024/11/27  8:53
 * @Description:
 * @Version: 4.0
 */

@Service
@Slf4j
public class KingdeeApiImpl implements KingdeeApi {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private SocialClientMapper socialClientMapper;

    @Resource
    private WechatMessageMapper wechatMessageMapper;

    @Resource
    private AdminUserMapper userMapper;

    @Resource
    private TenantMapper tenantMapper;

    @Resource
    private ExpenseOrderMapper orderMapper;

    @Resource
    private ExpenseOrderItemMapper orderItemMapper;

    @Resource
    private KingdeeRecordMapper kingdeeRecordMapper;

    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;

    @Resource
    private ExpenseItemMapper expenseItemMapper;

    @Resource
    private SysOverheadChargeMapper overheadChargeMapper;

    @Autowired
    private ExpensePayOrderMapper payOrderMapper;

    @Autowired
    private ExpensePayOrderItemMapper payOrderItemMapper;

    @Resource
    private EnterpriseBankInfoMapper enterpriseBankInfoMapper;

    @Override
    public Integer syncArReceivable(Long tenantId, String billingMonth){
        return syncArReceivable(tenantId,billingMonth,null);
    }
    @Override
    public Integer syncArReceivable(Long tenantId, String billingMonth,Long elderId) {
        List<ExpenseOrderDO> expenseOrderDOS = orderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .eqIfPresent(ExpenseOrderDO::getElderId,elderId)
                .eq(ExpenseOrderDO::getBillingMonth, billingMonth)
                .eq(ExpenseOrderDO::getTenantId, tenantId)
                .eq(ExpenseOrderDO::getIsLock,Boolean.TRUE)
                .eq(ExpenseOrderDO::getIsPushKingdee, BooleanEnum.FALSE.getValue())
                .in(ExpenseOrderDO::getType,1,2));
        if (null == expenseOrderDOS) {
            return 0;
        }
        Set<ExpenseOrderDO> successSet = new HashSet<>();
        // 根据elderId ， billingMonth ，payStatus比较相同，保留createdTime最新的数据
        List<ExpenseOrderDO> deduplicate = deduplicate(expenseOrderDOS);
        deduplicate.forEach(expenseOrderDO -> {
            ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(expenseOrderDO.getElderId());
            if (null == elderlyInfoDO) return;
            if (StringUtil.isEmptyORNull(elderlyInfoDO.getKingdeeCustomerNumber())) return;
            TenantDO tenantDO = tenantMapper.selectById(expenseOrderDO.getTenantId());
            String customerid = elderlyInfoDO.getKingdeeCustomerNumber();
            String orgId = tenantDO.getKingdeeOrgid();
            // 应收单默认货币
            String FMAINBOOKSTDCURRID = "PRE001";
            String fid = "";
            List<ReceivableFEntityDetail> FEntityDetailList = new ArrayList<>();
            List<ExpenseOrderItemDO> expenseOrderItemDOS = orderItemMapper.selectListByOrderId(expenseOrderDO.getId(),tenantId);
            if (CollectionUtils.isEmpty(expenseOrderItemDOS)) {
                return;
            }
            expenseOrderItemDOS.forEach(expenseOrderItemDO -> {
                if(expenseOrderItemDO.getTotalAmount().compareTo(BigDecimal.ZERO) == 0){
                    return;
                }
                // 设施费不推送应收单
                if(expenseOrderItemDO.getItemCategoryName() != null && expenseOrderItemDO.getItemCategoryName().contains("设施费")){
                    expenseOrderDO.setActualAmount(expenseOrderDO.getActualAmount().subtract(expenseOrderItemDO.getTotalAmount()));
                    return;
                }
                // 赠送不推送应收单
                if((expenseOrderItemDO.getIsFreeGift() != null && expenseOrderItemDO.getIsFreeGift() == 1)){
                    expenseOrderDO.setActualAmount(expenseOrderDO.getActualAmount().subtract(expenseOrderItemDO.getTotalAmount()));
                    return;
                }
                // 押金不推送应收单
                if((expenseOrderItemDO.getIsDeposit() != null && expenseOrderItemDO.getIsDeposit() == 1) ||
                        (expenseOrderItemDO.getItemCategoryName() != null &&
                                (expenseOrderItemDO.getItemCategoryName().contains("押金") ||
                                expenseOrderItemDO.getItemCategoryName().contains("预备金") ||
                                expenseOrderItemDO.getItemCategoryName().contains("保证金")))){
                    expenseOrderDO.setActualAmount(expenseOrderDO.getActualAmount().subtract(expenseOrderItemDO.getTotalAmount()));
                    return;
                }
                // 床位费 CI047
                // 膳食费 CI048
                // 护理费 CI050
                // 长护险 CI051
                // 服务费 CI049
                // 其他 CI046
                // 风险预备金 CI002
                String FCOSTID = getFCOSTID(expenseOrderItemDO);
                if(StringUtils.isBlank(FCOSTID)){
                    return;
                }
                // 补上长护险费用，还是应该要收的
                if(FCOSTID.equals("CI051")){
                    expenseOrderDO.setActualAmount(expenseOrderDO.getActualAmount().subtract(expenseOrderItemDO.getTotalAmount()));
                    return;
                }
                // 老人公寓四舍五入调整
                BigDecimal roundAmount = expenseOrderItemDO.getTotalAmount();
                String count = expenseOrderItemDO.getCount().toString();
                // 判断是负数
                if (roundAmount.compareTo(BigDecimal.ZERO) < 0) {
                    count = "-" + count;
                }
                ReceivableFEntityDetail entityDetail = ReceivableFEntityDetail.builder()
                        .FCOSTID(KingdeeNumberDTO.builder().FNUMBER(FCOSTID).build())
                        .FPriceQty(count)
                        .FNoTaxAmountFor_D(roundAmount)
                        .FALLAMOUNTFOR_D(roundAmount)
                        .build();
                FEntityDetailList.add(entityDetail);
            });
            BigDecimal totalAmount = FEntityDetailList.stream().map(ReceivableFEntityDetail::getFALLAMOUNTFOR_D).reduce(BigDecimal.ZERO, BigDecimal::add);
            YearMonth yearMonth = YearMonth.parse(expenseOrderDO.getBillingMonth());
            ReceivableFsubHeadFinc headFinc = ReceivableFsubHeadFinc.builder()
                    .FACCNTTIMEJUDGETIME(yearMonth.atEndOfMonth().toString())
                    .FMAINBOOKSTDCURRID(KingdeeNumberDTO.builder().FNUMBER(FMAINBOOKSTDCURRID).build())
                    .FEXCHANGETYPE(KingdeeNumberDTO.builder().FNUMBER("HLTX01_SYS").build())
                    .FExchangeRate(new BigDecimal("1.0"))
                    .FTaxAmountFor(new BigDecimal("0.0"))
                    .FNoTaxAmountFor(totalAmount)
                    .FISCARRIEDDATE(Boolean.FALSE.toString())
                    .build();
            ReceivableSaveModelDTO saveModelDTO = ReceivableSaveModelDTO.builder()
                    .FID(fid)
                    .FBillTypeID(KingdeeNumberDTO.builder().FNUMBER("YSD02_SYS").build())
                    .FBillNo(expenseOrderDO.getBillOrderNumber())
                    .FDATE(yearMonth.atEndOfMonth().toString())
                    .FISINIT(Boolean.FALSE.toString())
                    .FENDDATE_H(yearMonth.atEndOfMonth().toString())
                    .FSETTLEORGID(KingdeeNumberDTO.builder().FNUMBER(orgId).build())
                    .FCUSTOMERID(KingdeeNumberDTO.builder().FNUMBER(customerid).build())
                    .FCURRENCYID(KingdeeNumberDTO.builder().FNUMBER(FMAINBOOKSTDCURRID).build())
                    .FPayConditon(KingdeeNumberDTO.builder().FNUMBER(customerid).build())
                    .FISPRICEEXCLUDETAX(Boolean.TRUE.toString())
                    .FBUSINESSTYPE("FY")
                    .FPAYORGID(KingdeeNumberDTO.builder().FNUMBER(orgId).build())
                    .FSALEORGID(KingdeeNumberDTO.builder().FNUMBER(orgId).build())
                    .FISTAX(Boolean.FALSE.toString())
                    .FCancelStatus("A")
                    .FISHookMatch(Boolean.FALSE.toString())
                    .FISINVOICEARLIER(Boolean.FALSE.toString())
                    .FWBOPENQTY("false")
                    .FISGENERATEPLANBYCOSTITEM(Boolean.FALSE.toString())
                    .FAllAmountIsByTaxPrice(Boolean.FALSE.toString())
                    .FsubHeadFinc(headFinc)
                    .FEntityDetail(FEntityDetailList)
                    .build();
            ReceivableSaveDTO saveDTO = ReceivableSaveDTO.builder()
                    .IsDeleteEntry(Boolean.TRUE.toString())
                    .IsVerifyBaseDataField(Boolean.FALSE.toString())
                    .IsEntryBatchFill(Boolean.TRUE.toString())
                    .ValidateFlag(Boolean.TRUE.toString())
                    .NumberSearch(Boolean.TRUE.toString())
                    .IsAutoAdjustField(Boolean.FALSE.toString())
                    .InterationFlags("")
                    .IgnoreInterationFlag("")
                    .IsControlPrecision(Boolean.FALSE.toString())
                    .ValidateRepeatJson(Boolean.FALSE.toString())
                    .model(saveModelDTO)
                    .build();
            Gson gson = new Gson();
            // 将ReceivableSaveDTO转换成json字符串
            String json = gson.toJson(saveDTO);
            System.out.println("json：" + json);
            try {
                String billId = arReceivableSave(getK3CloudApi(tenantId), json, expenseOrderDO.getBillOrderNumber(), expenseOrderDO.getElderId(),tenantId);
                if(StringUtils.isNotBlank(billId)){
                    expenseOrderDO.setIsPushKingdee(Boolean.TRUE);
                    successSet.add(expenseOrderDO);
                }
            } catch (Exception e) {
                // 把异常前的成功记录更新为已推送
                if(successSet.size() > 0){
                    orderMapper.updateBatch(successSet);
                }
                e.printStackTrace();
            }
        });
        // 更新账单是否已推送金蝶状态
        if(successSet.size() > 0){
            orderMapper.updateBatch(successSet);
        }
        return expenseOrderDOS.size();
    }

    @Override
    @TenantIgnore
    public String syncArReceivebillNewBatch(List<Long> expenseOrderIdList) {
        return handleSyncArReceivebillNew(expenseOrderIdList);
    }

    @Override
    @TenantIgnore
    public String syncArReceivebillNew(Long expenseOrderId) {
        return handleSyncArReceivebillNew(Collections.singletonList(expenseOrderId));
    }

    public String handleSyncArReceivebillNew(List<Long> expenseOrderIdList) {
        StringBuilder msg = new StringBuilder();
        List<ExpenseOrderDO> expenseOrderList = orderMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderDO>()
                .in(ExpenseOrderDO::getId, expenseOrderIdList));
        Set<ExpenseOrderDO> successSet = new HashSet<>();
        for (ExpenseOrderDO expenseOrderDO : expenseOrderList) {
            if (expenseOrderDO == null) continue;
            String payTimeStr = DateUtils.formatDate(expenseOrderDO.getPayTime(), DateUtils.FORMAT_YEAR_MONTH_DAY);
            ElderlyInfoDO elderlyInfoDO = elderlyInfoMapper.selectById(expenseOrderDO.getElderId());
            if (null == elderlyInfoDO) continue;
            Long tenantId = expenseOrderDO.getTenantId();
            if (tenantId == null) {
                msg.append(elderlyInfoDO.getElderName()).append(",").append(expenseOrderDO.getBillingMonth()).append("账单不存在机构id<br/>");
                continue;
            }
            if (expenseOrderDO.getIsPushKingdeeReceiveBill()) {
                msg.append(elderlyInfoDO.getElderName()).append(",").append(expenseOrderDO.getBillingMonth()).append("账单已经推送过金蝶收款单<br/>");
                continue;
            }
            Long elderId = elderlyInfoDO.getId();
            if (StringUtil.isEmptyORNull(elderlyInfoDO.getKingdeeCustomerNumber())) continue;
            TenantDO tenantDO = tenantMapper.selectById(expenseOrderDO.getTenantId());
            String kingedeeCustomerId = elderlyInfoDO.getKingdeeCustomerNumber();
            String baseBillNo = "BILL" + expenseOrderDO.getBillOrderNumber();
            String kingedeeTenantId = tenantDO.getKingdeeOrgid();
            String currency = "PRE001";

            List<EnterpriseBankInfoDO> openAccountList = enterpriseBankInfoMapper.selectList(new LambdaQueryWrapperX<EnterpriseBankInfoDO>()
                    .eq(EnterpriseBankInfoDO::getTenantId, tenantId));
            String FACCOUNTID = "";
            if (null != openAccountList && !openAccountList.isEmpty()) {
                FACCOUNTID = openAccountList.get(0).getBankAccount();
            }
            if (StringUtils.isBlank(FACCOUNTID)) {
                throw exceptionCustomMsg(COMMON_ERROR, "不存在银行账户，请联系管理员处理");
            }

            BigDecimal normalAmount = BigDecimal.ZERO;
            BigDecimal facilityAmount = BigDecimal.ZERO;
            BigDecimal depositAmount = BigDecimal.ZERO;

            List<ExpenseOrderItemDO> expenseOrderItemList = orderItemMapper.selectList(new LambdaQueryWrapperX<ExpenseOrderItemDO>()
                    .eq(ExpenseOrderItemDO::getExpenseOrderId, expenseOrderDO.getId())
                    .eq(ExpenseOrderItemDO::getPayStatus, BooleanEnum.TRUE.getValue()));

            for (ExpenseOrderItemDO expenseOrderItemDO : expenseOrderItemList) {
                if (expenseOrderItemDO.getTotalAmount().compareTo(BigDecimal.ZERO) == 0) {
                    continue;
                }
                if (BusinessConstants.LONG_TERM_CARE_INSURANCE.equals(expenseOrderItemDO.getExpenseSource())) {
                    continue;
                }
                String itemName = StringUtils.defaultString(expenseOrderItemDO.getItemName());
                if (itemName.contains("设施费")) {
                    facilityAmount = facilityAmount.add(expenseOrderItemDO.getTotalAmount());
                    continue;
                }
                String FCOSTID = getFCOSTID(expenseOrderItemDO);
                if ("CI002".equals(FCOSTID)
                        || itemName.contains("风险金")
                        || itemName.contains("预备金")
                        || itemName.contains("保证金")
                        || itemName.contains("押金")) {
                    depositAmount = depositAmount.add(expenseOrderItemDO.getTotalAmount());
                    continue;
                }
                normalAmount = normalAmount.add(expenseOrderItemDO.getTotalAmount());
            }

            boolean needPushNormal = normalAmount.compareTo(BigDecimal.ZERO) != 0;
            boolean needPushFacility = facilityAmount.compareTo(BigDecimal.ZERO) != 0;
            boolean needPushDeposit = depositAmount.compareTo(BigDecimal.ZERO) != 0;
            if (!needPushNormal && !needPushFacility && !needPushDeposit) {
                msg.append(elderlyInfoDO.getElderName()).append(",").append(expenseOrderDO.getBillingMonth()).append("账单无可推送收款明细<br/>");
                continue;
            }
            String remark = "";
            boolean allSuccess = true;
            try {
                if (needPushNormal) {
                    YearMonth billingMonth = YearMonth.parse(expenseOrderDO.getBillingMonth());
                    remark = "生活费" + billingMonth.getMonthValue() + "月";
                    List<ReceiveBillSaveEntryDTO> normalEntryList = Collections.singletonList(
                            buildReceiveBillEntry(FACCOUNTID, payTimeStr, normalAmount, "CI001", "SFKYT01_SYS", remark)
                    );
                    String billId = pushReceiveBill(tenantId, elderId, expenseOrderDO.getBillOrderNumber(), baseBillNo,
                            "SKDLX01_SYS", kingedeeTenantId, kingedeeCustomerId, payTimeStr, currency, normalEntryList,remark);
                    if (StringUtils.isBlank(billId)) {
                        allSuccess = false;
                    }
                }

                if (needPushFacility) {
                    remark = "一次性设施购置费";
                    List<ReceiveBillSaveEntryDTO> facilityEntryList = Collections.singletonList(
                            buildReceiveBillEntry(FACCOUNTID, payTimeStr, facilityAmount, "CI005", "SFKYT02_SYS", remark)
                    );
                    String billId = pushReceiveBill(tenantId, elderId, expenseOrderDO.getBillOrderNumber() + "-SSF", baseBillNo + "-SSF",
                            "SKDLX01_SYS", kingedeeTenantId, kingedeeCustomerId, payTimeStr, currency, facilityEntryList,remark);
                    if (StringUtils.isBlank(billId)) {
                        allSuccess = false;
                    }
                }

                if (needPushDeposit) {
                    remark = "医疗押金";
                    List<ReceiveBillSaveEntryDTO> depositEntryList = Collections.singletonList(
                            buildReceiveBillEntry(FACCOUNTID, payTimeStr, depositAmount, "CI002", "SFKYT41_SYS", remark)
                    );
                    String billId = pushReceiveBill(tenantId, elderId, expenseOrderDO.getBillOrderNumber() + "-YJ", baseBillNo + "-YJ",
                            "SKDLX02_SYS", kingedeeTenantId, kingedeeCustomerId, payTimeStr, currency, depositEntryList,remark);
                    if (StringUtils.isBlank(billId)) {
                        allSuccess = false;
                    }
                }

                msg.append(elderlyInfoDO.getElderName()).append(",").append(expenseOrderDO.getBillingMonth()).append("账单推送金蝶收款单");
                if (!allSuccess) {
                    msg.append("失败。<br/>");
                } else {
                    msg.append("成功。<br/>");
                    expenseOrderDO.setIsPushKingdeeReceiveBill(Boolean.TRUE);
                    successSet.add(expenseOrderDO);
                }
            } catch (Exception e) {
                if (successSet.size() > 0) {
                    orderMapper.updateBatch(successSet);
                }
                e.printStackTrace();
            }
        }
        if (successSet.size() > 0) {
            orderMapper.updateBatch(successSet);
        }
        return msg.toString();
    }

    private ReceiveBillSaveEntryDTO buildReceiveBillEntry(String accountId, String payTimeStr, BigDecimal amount,
                                                          String costId, String purposeId, String comment) {
        return ReceiveBillSaveEntryDTO.builder()
                .FSETTLETYPEID(KingdeeNumberDTO.builder().FNUMBER("JSFS04_SYS").build())
                .FPURPOSEID(KingdeeNumberDTO.builder().FNUMBER(purposeId).build())
                .FRECTOTALAMOUNTFOR(amount)
                .FRECAMOUNTFOR_E(amount)
                .FRECNOTAXAMOUNTFOR(amount)
                .FSETTLEDISTAMOUNTFOR(BigDecimal.ZERO)
                .FHANDLINGCHARGEFOR(BigDecimal.ZERO)
                .FOVERUNDERAMOUNTFOR(BigDecimal.ZERO)
                .FACCOUNTID(KingdeeNumberDTO.builder().FNUMBER(accountId).build())
                .FOPPOSITEBANKACCOUNT("")
                .FOPPOSITEBANKNAME("")
                .FPrice(amount)
                .FQty(1)
                .FPOSTDATE(payTimeStr)
                .FRECNOTAXAMOUNT(amount)
                .FWRITTENOFFTAXAMOUNTFOR(amount)
                .FWRITTENOFFTAXAMOUNT(amount)
                .FCOSTID(KingdeeNumberDTO.builder().FNUMBER(costId).build())
                .F_PAEZ_Base(KingdeeNumberDTO.builder().FNUMBER(costId).build())
                .FCOMMENT(comment)
                .build();
    }

    private String pushReceiveBill(Long tenantId, Long elderId, String recordBillNo, String kdBillNo,
                                   String billTypeId, String kingedeeTenantId, String kingedeeCustomerId, String payTimeStr,
                                   String currency, List<ReceiveBillSaveEntryDTO> entryList,String remark) throws Exception {
        ReceiveBillSaveModelDTO saveModelDTO = ReceiveBillSaveModelDTO.builder()
                .FID("")
                .FBillNo(kdBillNo)
                .FBillTypeID(KingdeeNumberDTO.builder().FNUMBER(billTypeId).build())
                .FPAYORGID(KingdeeNumberDTO.builder().FNUMBER(kingedeeTenantId).build())
                .FDATE(payTimeStr)
                .FCONTACTUNITTYPE("BD_Customer")
                .FCONTACTUNIT(KingdeeNumberDTO.builder().FNUMBER(kingedeeCustomerId).build())
                .FPAYUNITTYPE("BD_Customer")
                .FPAYUNIT(KingdeeNumberDTO.builder().FNUMBER(kingedeeCustomerId).build())
                .FCURRENCYID(KingdeeNumberDTO.builder().FNUMBER(currency).build())
                .FEXCHANGERATE(new BigDecimal("1.0"))
                .FSETTLECUR(KingdeeNumberDTO.builder().FNUMBER(currency).build())
                .FSETTLERATE(new BigDecimal("1.0"))
                .FISINIT(Boolean.FALSE.toString())
                .FISCARRYRATE(Boolean.FALSE.toString())
                .FDOCUMENTSTATUS("Z")
                .FBUSINESSTYPE("FY")
                .FCancelStatus("A")
                .FREMARK(remark)
                .FSETTLEMAINBOOKID(KingdeeNumberDTO.builder().FNUMBER(currency).build())
                .FRECEIVEBILLENTRY(entryList)
                .build();
        ReceiveBillSaveDTO saveDTO = ReceiveBillSaveDTO.builder()
                .IsDeleteEntry(Boolean.TRUE.toString())
                .IsVerifyBaseDataField(Boolean.FALSE.toString())
                .IsEntryBatchFill(Boolean.TRUE.toString())
                .ValidateFlag(Boolean.TRUE.toString())
                .NumberSearch(Boolean.TRUE.toString())
                .IsAutoAdjustField(Boolean.FALSE.toString())
                .InterationFlags("")
                .IgnoreInterationFlag("")
                .IsControlPrecision(Boolean.FALSE.toString())
                .ValidateRepeatJson(Boolean.FALSE.toString())
                .model(saveModelDTO)
                .build();
        Gson gson = new Gson();
        String json = gson.toJson(saveDTO);
        System.out.println("json = " + json);
        return arReceivebillSave(getK3CloudApi(tenantId), json, recordBillNo, elderId, tenantId);
    }

    @Override
    public K3CloudApi getK3CloudApi(Long tenantId) {
        SocialTypeEnum environment = SocialTypeEnum.KINGDEE_PRODUCT;
        if (null == tenantId) {
            TenantDO tenantDO = tenantMapper.selectOne(new LambdaQueryWrapperX<TenantDO>().eq(TenantDO::getPackageId, PACKAGE_ID_SYSTEM));
            tenantId = tenantDO.getId();
        }
        SocialClientDO client = socialClientMapper.selectOne(environment.getType(), UserTypeEnum.ADMIN.getValue(), tenantId);
        if (null == client) {
            TenantDO tenantDO = tenantMapper.selectOne(new LambdaQueryWrapperX<TenantDO>().eq(TenantDO::getPackageId, PACKAGE_ID_SYSTEM));
            client = socialClientMapper.selectOne(environment.getType(), UserTypeEnum.ADMIN.getValue(), tenantDO.getId());
        }
        Assert.notNull(client, String.format("第三方(%d) 不存在", environment.getType()));

        IdentifyInfo identifyInfo = new IdentifyInfo();
        identifyInfo.setUserName(client.getUserName());
        identifyInfo.setAppId(client.getAgentId());
        identifyInfo.setAppSecret(client.getClientSecret());
        identifyInfo.setdCID(client.getClientId());
        identifyInfo.setServerUrl(client.getServerUrl());
        K3CloudApi api = new K3CloudApi(identifyInfo);
        return api;
    }

    @Override
    public String arReceivableSave(K3CloudApi api, String json, String fBillNo, Long elderId,Long tenantId) throws Exception {
//        String result = api.save("AR_RECEIVEBILL", json);
        String formId = "AR_receivable";
        String result = api.save(formId, json);
        Gson gson = new Gson();
        RepoRet sRet = gson.fromJson(result, RepoRet.class);
        KingdeeRecordDO kingdeeRecordDO = KingdeeRecordDO.builder()
                .tenantId(tenantId)
                .fBillNo(fBillNo)
                .elderlyId(elderId)
                .formId(formId)
                .operation("save")
                .params(json)
                .billType(0).build();
        if (sRet.isSuccessfully()) {
            String id = sRet.getResult().getId();
            log.info("应收单保存接口{}", gson.toJson(sRet.getResult()));
            kingdeeRecordDO.setFid(id);
            kingdeeRecordDO.setSyncState(1);
            kingdeeRecordDO.setParams(json);
            kingdeeRecordDO.setReturnMessage(gson.toJson(sRet.getResult()));
            kingdeeRecordMapper.insert(kingdeeRecordDO);
            return id;
        } else {
            log.info("应收单保存接口失败返回:{} " + gson.toJson(sRet.getResult()));
            kingdeeRecordDO.setSyncState(2);
            kingdeeRecordDO.setParams(json);
            kingdeeRecordDO.setReturnMessage(gson.toJson(sRet.getResult()));
            kingdeeRecordMapper.insert(kingdeeRecordDO);
        }
        return null;
    }

    @Override
    public String arReceivebillSave(K3CloudApi api, String json, String fBillNo, Long elderId,Long tenantId) throws Exception {
        String formId = "AR_RECEIVEBILL";
        String result = api.save(formId, json);
        Gson gson = new Gson();
        RepoRet sRet = gson.fromJson(result, RepoRet.class);
        KingdeeRecordDO kingdeeRecordDO = KingdeeRecordDO.builder()
                .tenantId(tenantId)
                .fBillNo(fBillNo)
                .elderlyId(elderId)
                .formId(formId)
                .operation("save")
                .params(json)
                .billType(1).build();
        if (sRet.isSuccessfully()) {
            String id = sRet.getResult().getId();
            log.info("收款单保存接口{}", gson.toJson(sRet.getResult()));
            kingdeeRecordDO.setFid(id);
            kingdeeRecordDO.setSyncState(1);
            kingdeeRecordDO.setParams(json);
            kingdeeRecordDO.setReturnMessage(gson.toJson(sRet.getResult()));
            kingdeeRecordMapper.insert(kingdeeRecordDO);
            return id;
        } else {
            log.info("收款单保存接口失败返回:{} ", gson.toJson(sRet.getResult()));
            kingdeeRecordDO.setSyncState(2);
            kingdeeRecordDO.setParams(json);
            kingdeeRecordDO.setReturnMessage(gson.toJson(sRet.getResult()));
            kingdeeRecordMapper.insert(kingdeeRecordDO);
        }
        return null;
    }

    @Override
    public String arReceivebillBatchSave(K3CloudApi api, String json) throws Exception {
        String result = api.batchSave("AR_receivable", json);
        Gson gson = new Gson();
        RepoRet sRet = gson.fromJson(result, RepoRet.class);
        if (sRet.isSuccessfully()) {
            String id = sRet.getResult().getId();
            System.out.printf("收款单保存接口%s%n", gson.toJson(sRet.getResult()));
            return id;
        } else {
            fail("收款单保存接口: " + gson.toJson(sRet.getResult()));
        }
        return null;
    }
    @Override
    public Integer syncBDCustomer(Long tenantId){
        return syncBDCustomer(tenantId,null);
    }

    @Override
    public Integer syncBDCustomer(Long tenantId,Long elderId) {
        List<ElderlyInfoDO> elderlyInfoDOS = elderlyInfoMapper.selectList(new LambdaQueryWrapperX<ElderlyInfoDO>()
                .eq(ElderlyInfoDO::getTenantId, tenantId)
                .eqIfPresent(ElderlyInfoDO::getId,elderId)
                .isNull(ElderlyInfoDO::getKingdeeCustomerNumber)
        );
        if (null == elderlyInfoDOS || elderlyInfoDOS.size() == 0) {
            return 0;
        }

        TenantDO tenantDO = tenantMapper.selectById(tenantId);
        String orgId = StringUtil.isNotEmptyORNull(tenantDO.getKingdeeOrgid()) ? tenantDO.getKingdeeOrgid() : null;
        if(orgId == null){
            return 0;
        }

        elderlyInfoDOS.forEach(elderlyInfoDO -> {
            BDCustomerModelDTO modelDTO = BDCustomerModelDTO.builder()
                    .FCreateOrgId(KingdeeNumberHumpDTO.builder().FNumber(orgId).build())
                    .FUseOrgId(KingdeeNumberHumpDTO.builder().FNumber(orgId).build())
                    .FName(elderlyInfoDO.getElderName())
                    .FTELl(elderlyInfoDO.getTelPhone())
                    .FTRADINGCURRID(KingdeeNumberHumpDTO.builder().FNumber("PRE001").build())
                    .FADDRESS("")
                    .FCustTypeId(KingdeeNumberHumpDTO.builder().FNumber("KHLB001_SYS").build())
                    .FInvoiceType("1")
                    .FTaxType(KingdeeNumberHumpDTO.builder().FNumber("SFL02_SYS").build())
                    .FPriority(1)
                    .FTaxRate(KingdeeNumberHumpDTO.builder().FNumber("SFL02_SYS").build())
                    .FISCREDITCHECK(true)
                    .FIsTrade(true)
                    .build();
            BDCustomerDTO saveDTO = BDCustomerDTO.builder()
                    .IgnoreInterationFlag("")
                    .InterationFlags("")
                    .IsAutoAdjustField("false")
                    .IsControlPrecision("false")
                    .IsDeleteEntry("true")
                    .IsEntryBatchFill("true")
                    .IsVerifyBaseDataField("false")
                    .NumberSearch("true")
                    .SubSystemId("")
                    .ValidateFlag("true")
                    .ValidateRepeatJson("false")
                    // 自动提交审核
                    .IsAutoSubmitAndAudit("true")
                    .model(modelDTO).build();
            Gson gson = new Gson();
            // 将ReceivableSaveDTO转换成json字符串
            String json = gson.toJson(saveDTO);
            try {
                String billId = bDCustomerSave(getK3CloudApi(tenantId), json, elderlyInfoDO.getId());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return null;
    }

    @Override
    public String bDCustomerSave(K3CloudApi api, String json, Long id) throws Exception {
        //业务对象标识
        String formId = "BD_Customer";

        System.out.println("json:" + json);

        //调用接口
        String resultJson = api.save(formId,json);

        //用于记录结果
        Gson gson = new Gson();
        //对返回结果进行解析和校验
        RepoRet repoRet = gson.fromJson(resultJson, RepoRet.class);
        if (repoRet.getResult().getResponseStatus().isIsSuccess()) {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
            ElderlyInfoDO update = new ElderlyInfoDO();
            update.setId(id);
            String json1 = gson.toJson(repoRet.getResult().getResponseStatus().getSuccessEntitys().get(0));
            // json的字符串转换成json
            JSONObject jsonObject = JSONObject.parseObject(json1);
            String number = jsonObject.get("Number") == null ? "" : jsonObject.get("Number").toString();
            String resultId = jsonObject.get("Id") == null ? "" : jsonObject.get("Id").toString();
            update.setKingdeeCustomerId(resultId);
            update.setKingdeeCustomerNumber(number);
            elderlyInfoMapper.updateById(update);
        } else {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
            fail("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
        }
        return null;
    }
    /**
     * 同步机构编码
     * 参考 {@link #syncBDCustomer(Long)} 的实现，将机构（租户）同步到金蝶客户主数据。
     *
     * @param tenantId 集团/当前租户 ID
     * @return 同步的机构数量
     */
    @Override
    @TenantIgnore
    public Integer syncBDOrg(Long tenantId) {
        // 1. 查询当前租户信息，获取集团 ID
        TenantDO currentTenant = tenantMapper.selectById(tenantId);
        if (currentTenant == null || currentTenant.getGroupsId() == null) {
            return 0;
        }
        BDOrgModelDTO modelDTO = BDOrgModelDTO.builder()
                .FNumber(currentTenant.getId().toString())
                .FName(currentTenant.getName())
                .FTel(currentTenant.getContactMobile())
                .FOrgFormID("ZZXTO2_SYS")
                .FIsBusinessOrg("true")
                .FOrgFunctions("101,102,103,104,107,108,109,110,111,112,113,114,115")
                .build();

        BDOrgDTO saveDTO = BDOrgDTO.builder()
                .IgnoreInterationFlag("")
                .InterationFlags("")
                .IsAutoAdjustField("false")
                .IsControlPrecision("false")
                .IsDeleteEntry("true")
                .IsEntryBatchFill("true")
                .IsVerifyBaseDataField("false")
                .NumberSearch("true")
                .SubSystemId("")
                .ValidateFlag("true")
                .ValidateRepeatJson("false")
                .IsAutoSubmitAndAudit("true")
                .model(modelDTO).build();
        Gson gson = new Gson();
        String json = gson.toJson(saveDTO);
        try {
            String billId = bDOrgSave(getK3CloudApi(tenantId), json, tenantId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private String bDOrgSave(K3CloudApi api, String json, Long id) throws Exception {
        //业务对象标识
        String formId = "ORG_Organizations";

        System.out.println("json:" + json);

        //调用接口
        String resultJson = api.save(formId,json);

        //用于记录结果
        Gson gson = new Gson();
        //对返回结果进行解析和校验
        RepoRet repoRet = gson.fromJson(resultJson, RepoRet.class);
        if (repoRet.getResult().getResponseStatus().isIsSuccess()) {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
        } else {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
            fail("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
            return "";
        }
        JSONObject submitAuditData = new JSONObject();
        JSONArray numbers = new JSONArray();
        numbers.add(id);
        submitAuditData.put("Numbers",numbers);
        resultJson = api.submit(formId,submitAuditData.toJSONString());
        //对返回结果进行解析和校验
        repoRet = gson.fromJson(resultJson, RepoRet.class);
        if (repoRet.getResult().getResponseStatus().isIsSuccess()) {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
        } else {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
            fail("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
            return "";
        }
        resultJson = api.audit(formId,submitAuditData.toJSONString());
        repoRet = gson.fromJson(resultJson, RepoRet.class);
        if (repoRet.getResult().getResponseStatus().isIsSuccess()) {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
            TenantDO update = new TenantDO();
            update.setId(id);
            update.setKingdeeOrgid(id.toString());
            tenantMapper.updateById(update);
        } else {
            System.out.printf("接口返回结果: %s%n", gson.toJson(repoRet.getResult()));
            fail("接口返回结果: " + gson.toJson(repoRet.getResult().getResponseStatus()));
            return "";
        }
        return null;
    }

    public List<ExpenseOrderDO> deduplicate(List<ExpenseOrderDO> expenseOrderDOS) {
        // 使用复合键（elderId + billingMonth + payStatus）分组，并保留每组中createdTime最新的记录
        Collection<ExpenseOrderDO> distinctValues = expenseOrderDOS.stream()
                .collect(Collectors.toMap(
                        e -> new CompositeKey(e.getElderId(), e.getBillingMonth(), e.getPayStatus()),
                        Function.identity(),
                        (a, b) -> a.getCreatedTime().compareTo(b.getCreatedTime()) >= 0 ? a : b
                ))
                .values();

        return new ArrayList<>(distinctValues);
    }

    // 定义复合键内部类，用于分组
    static class CompositeKey {
        private final Long elderId;
        private final String billingMonth;
        private final Integer payStatus; // 或 String，根据实际类型调整

        public CompositeKey(Long elderId, String billingMonth, Integer payStatus) {
            this.elderId = elderId;
            this.billingMonth = billingMonth;
            this.payStatus = payStatus;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CompositeKey that = (CompositeKey) o;
            return Objects.equals(elderId, that.elderId) &&
                    Objects.equals(billingMonth, that.billingMonth) &&
                    Objects.equals(payStatus, that.payStatus);
        }

        @Override
        public int hashCode() {
            return Objects.hash(elderId, billingMonth, payStatus);
        }
    }
    private String getFCOSTID(ExpenseOrderItemDO expenseOrderItemDO){
        // 床位费 CI047
        // 膳食费 CI048
        // 护理费 CI050
        // 长护险 CI051
        // 服务费 CI049
        // 其他 CI046
        // 风险预备金 CI002
        if (BusinessConstants.EXPENSE_ITEM.equals(expenseOrderItemDO.getExpenseSource())) {
            ExpenseItemDO expenseItemDO = expenseItemMapper.selectById(expenseOrderItemDO.getSourceExpenseItemId());
            if (null == expenseItemDO) return null;
            if(expenseItemDO.getType() != null && expenseItemDO.getType() == 1){
                return "CI047";
            }else if(expenseItemDO.getType() != null && expenseItemDO.getType() == 2){
                return "CI050";
            }else if(expenseItemDO.getType() != null && expenseItemDO.getType() == 3){
                return "CI048";
            }else if(expenseItemDO.getType() != null && expenseItemDO.getType() == 7){
                return "CI049";
            }else {
                return "CI046";
            }
//                    OverheadChargeDO overheadChargeDO = overheadChargeMapper.selectById(expenseItemDO.getItemId());
//                    if (null != overheadChargeDO && StringUtil.isNotEmptyORNull(overheadChargeDO.getKingdeeCostid())) {
//                        FCOSTID = overheadChargeDO.getKingdeeCostid();
//                    }
        } else if (BusinessConstants.LONG_TERM_CARE_INSURANCE.equals(expenseOrderItemDO.getExpenseSource())) {
            return "CI051";
        } else {
            if(expenseOrderItemDO.getItemName().contains("风险金") ||
                    expenseOrderItemDO.getItemName().contains("预备金") ||
                    expenseOrderItemDO.getItemName().contains("保证金") ||
                    expenseOrderItemDO.getItemName().contains("押金")){
                return "CI002";
            }else {
                return "CI046";
            }
        }
    }

}
