package com.friday.custody.service.trustService.htsc.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.friday.base.common.response.DubboResult;
import com.friday.custody.entity.*;
import com.friday.custody.service.basicService.impl.*;
import com.friday.custody.service.trustService.citicZhongXin.service.OutsourceZhongXinService;
import com.friday.custody.service.trustService.guosen.utils.StrUtil;
import com.friday.custody.service.trustService.htsc.common.HtscConstants;
import com.friday.custody.common.CustodyContants;
import com.friday.custody.dto.HtscEmailDTO;
import com.friday.custody.dto.TenantEscrowAuthorizationInfoDto;
import com.friday.custody.service.trustService.htsc.dto.*;
import com.friday.custody.utils.*;
import com.friday.custody.enums.DataDictionaryTypeEnum;
import com.friday.custody.enums.EmailConfigPurposeEnum;
import com.friday.custody.enums.RetCodeEnum;
import com.friday.custody.service.basicService.DataDictionaryTypeCertificateCsdcService;
import com.friday.custody.service.basicService.PullDataFailureLogService;
import com.friday.custody.service.logicService.DataDictionaryBasicService;
import com.friday.custody.service.trustService.OutsourceService;
import com.friday.custody.service.trustService.gtja.dto.HTSCCustShareDetailsDto;
import com.friday.custody.service.trustService.gtja.dto.HTSCFundProfitDto;
import com.friday.custody.service.trustService.gtja.dto.HTSCValuationFeeDto;
import com.friday.custody.service.trustService.htsc.utils.HTSCGetDataUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.mail.*;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.friday.custody.common.CustodyContants.YEAR_MONTH_DAY_REGEX;

@Service
@Slf4j
public class OutsourceHtscServiceImpl implements OutsourceService {
    @Value("${custody.remote.url.htsc.htscUrlPrefix}")
    private String htscUrlPrefix;

    @Autowired
    private PullDataFailureLogService pullDataFailureLogService;

    @Autowired
    private DataDictionaryBasicService dataDictionaryBasicService;

    @Autowired
    private FundInfoServiceImpl fundInfoService;//a
    @Autowired
    private InvestorInfoServiceImpl investorInfoService;//b
    @Autowired
    private FundNetValueInfoServiceImpl fundNetValueInfoService;//c
    @Autowired
    private CustShareInfoServiceImpl custShareInfoService;//d
    @Autowired
    private TgAccountBalanceInfoServiceImpl tgAccountBalanceInfoService;//e
    @Autowired
    private TgAccountSeqInfoServiceImpl tgAccountSeqInfoService;//f
    @Autowired
    private MjAccountBalanceInfoServiceImpl mjAccountBalanceInfoService;//g
    @Autowired
    private MjAccountSeqInfoServiceImpl mjAccountSeqInfoService;//h
    @Autowired
    private FundTradeInfoServiceImpl fundTradeInfoService;//i
    @Autowired
    private FundValueTableDataInfoServiceImpl fundValueTableDataInfoService;//j
    @Autowired
    private InvestorBankInfoServiceImpl investorBankInfoService;
    @Autowired
    private FundProfitInfoServiceImpl fundProfitInfoService;
    @Autowired
    private CustShareDetailInfoServiceImpl custShareDetailInfoService;
    @Autowired
    private FundTempFareInfoServiceImpl fundTempFareInfoService;
    @Autowired
    private AccountInfoServiceImpl accountInfoService;
    @Autowired
    private FundFeeDataInfoServiceImpl fundFeeDataInfoService;
    @Autowired
    private ChargeFeeDetailInfoServiceImpl chargeFeeDetailInfoService;
    @Autowired
    private DataDictionaryTypesOfFeeDetailsServiceImpl dataDictionaryTypesOfFeeDetailsService;
    @Autowired
    private PayFeeDetailInfoServiceImpl payFeeDetailInfoService;
    @Autowired
    private DataDictionaryTrustServiceImpl trustService;

    @Resource(name = "pullDataThreadPool")
    private ExecutorService pullDataThreadPool;

    @Autowired
    private FundOpenInfoServiceImpl fundOpenInfoService;

    @Autowired
    private DataDictionaryOpenStateServiceImpl openStateService;

    @Autowired
    private DataDictionaryTypeOfFundValuationServiceImpl typeOfFundValuationService;

    @Autowired
    private FundValuationServiceImpl fundValuationService;
    @Autowired
    private OutsourceZhongXinService outsourceZhongXinService;

    private final static LinkedBlockingDeque<TenantEscrowAuthorizationInfoDto> taskQueue = new LinkedBlockingDeque<>();

    @Override
    public R initTenantData(List<TenantEscrowAuthorizationInfoDto> tenants) {
        return null;
    }

    @Override
    public R initTenantDataSingle(TenantEscrowAuthorizationInfoDto tenant) {
        log.info("华泰托管任务进入队列...");
        try {
            taskQueue.put(tenant);
            return R.ok("华泰托管任务进入队列成功!");
        } catch (InterruptedException e) {
            log.error("华泰托管任务进入队列失败...", e);
            Thread.currentThread().interrupt();
            return R.error(RetCodeEnum.SYS_ERROR.getCode(), "华泰托管任务进入队列失败!");
        }
    }

    /**
     * 华泰托管专门用来初始化数据的
     *
     * @return
     */
    public R initDataByScheduled() {
        if (!taskQueue.isEmpty()) {
            taskQueue.forEach(item -> {
                getDataFromHTSC(item, CustodyContants.INIT_BY_MONTH_LOOP_TIME);
            });
        }
        return R.ok("华泰托管数据入库成功！");
    }

    public void getDataFromHTSC(TenantEscrowAuthorizationInfoDto tenant, Integer loopNum) {
        log.info("getDataFromHTSC初始化托管_授权信息:{}",JSON.toJSONString(tenant));
        try {
            //按照标准表的维度去拿数据 然后冗余的数据 就放入threadlocal中吧
            //拿到数据之后 直接调用 basicservice 的保存数据接口
            //每个接口处理自己的异常 这样每个标准表的入库不需要单独处理
            String trustNo = "34";
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
            //1 基金产品信息表（fund_info）
            processFundInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(1/15)");
            //2 基金出资人信息表（investor_info）
            processInvestorInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(2/15)");

            //3 基金净值信息表（fund_net_value_info）
            processFundNetValueInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(3/15)");

            //4 基金持有信息表（cust_share_info）
            processCustInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(4/15)");

            //15 资金账户基本信息表 暂时未录入标准表
            processAccountInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(5/15),getDataFromHTSC设置的缓存accountInfoTL为{}",JSON.toJSONString(accountInfoTL.get()));

            //16 托管⼾银⾏账⼾余额信息表（TGAccount_balance_info）
            processTGAccountBalanceInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(6/15)");

            //17 托管⼾流⽔表（TGAccount_seq_info）
            processTGAccountSeqInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(7/15)");

            //16 募集⼾银⾏账⼾余额信息表（MJAccount_balance_info）
            processMJAccountBalanceInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(8/15)");

            //19 募集⼾流⽔表（MJAccount_seq_info）
            processMJAccountSeqInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(9/15)");

            //8 基金交易确认信息表（fund_trade_info）
            processFundTradeInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(10/15)");

            //20 基金估值报表数据表 fund_value_table_data_info
            processFundValueTableDataInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(11/15)");

            // 0330 新加
            //6 基金出资人银行账户信息表（investor_bank_info
            processInvestorBankInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(12/15)");

            //10 业绩报酬信息表（fund_profit_info
            processFundProfitInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(13/15)");

            //11 基金持有份额明细表（cust_share_detail_info
            processCustShareDetailInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSC初始化进度(14/15)");

            //14 基金估值表费用信息表（fund_temp_fare_info
            processFundTempFareInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSC初始化进度(15/15) 初始化完成,授权信息为{}",JSON.toJSONString(tenant));
            // 费用计提明细表（charge_fee_detail_info）
            processChargeFeeDetailInfoData(tenant, trustNo, loopNum);
            //费用支付明细表 (pay_fee_detail_info)
            processPayFeeDetailInfoData(tenant, trustNo, loopNum);

            contentsOfValuationSheetByEmail(tenant,trustNo,loopNum);
            //基金开放日信息表
            processFundOpenInfoData(tenant, trustNo, loopNum);
        } finally {
            fundNameAndCodeTL.remove();
            accountInfoTL.remove();
            fundInnerCodeAndCodeTL.remove();
        }
    }

    private void getDataFromHTSCAt7AM(TenantEscrowAuthorizationInfoDto tenant, Integer loopNum) {
        log.info("getDataFromHTSCAt7AM 授权信息：{}",tenant);
        try {
            //按照标准表的维度去拿数据 然后冗余的数据 就放入threadlocal中吧
            //拿到数据之后 直接调用 basicservice 的保存数据接口
            //每个接口处理自己的异常 这样每个标准表的入库不需要单独处理
            String trustNo = "34";
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
            //1 基金产品信息表（fund_info）
            processFundInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSCAt7AM 进度：（1/13）");
            //2 基金出资人信息表（investor_info）
            processInvestorInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSCAt7AM 进度：（2/13）");

            //4 基金持有信息表（cust_share_info）
            processCustInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSCAt7AM 进度：（4/13）");

            //15 (account_info)
            processAccountInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSCAt7AM 进度：（5/13）,getDataFromHTSCAt7AM设置的缓存accountInfoTL为{}",JSON.toJSONString(accountInfoTL.get()));

            //17 托管⼾流⽔表（TGAccount_seq_info） （2.1.12 历史查询）
            processTGAccountSeqInfoHistoryData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSCAt7AM 进度：（6/13）");

            //19 募集⼾流⽔表（MJAccount_seq_info） (2.1.15 募集户历史流水查询)
            processMJAccountSeqInfoHistoryData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSCAt7AM 进度：（7/13）");

            //8 基金交易确认信息表（fund_trade_info）
            processFundTradeInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSCAt7AM 进度：（8/13）");

            //20 基金估值报表数据表 fund_value_table_data_info
            processFundValueTableDataInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSCAt7AM 进度：（9/13）");

            // 0330 新加
            //6 基金出资人银行账户信息表（investor_bank_info
            processInvestorBankInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSCAt7AM 进度：（10/13）");

            //10 业绩报酬信息表（fund_profit_info
            processFundProfitInfoData(tenant, trustNo, today, loopNum);
            log.info("getDataFromHTSCAt7AM 进度：（11/13）");

            //11 基金持有份额明细表（cust_share_detail_info
            processCustShareDetailInfoData(tenant, trustNo, today);
            log.info("getDataFromHTSCAt7AM 进度：（12/13）");

            //14 基金估值表费用信息表（fund_temp_fare_info
            processFundTempFareInfoData(tenant, trustNo, today,loopNum);
            log.info("getDataFromHTSCAt7AM 进度：（13/13）");
            // 费用计提明细表（charge_fee_detail_info）
            processChargeFeeDetailInfoData(tenant, trustNo, loopNum);
            //费用支付明细表 (pay_fee_detail_info)
            processPayFeeDetailInfoData(tenant, trustNo, loopNum);
            //基金开放日信息表
            processFundOpenInfoData(tenant, trustNo, loopNum);
        } finally {
            fundNameAndCodeTL.remove();
            accountInfoTL.remove();
            fundInnerCodeAndCodeTL.remove();
        }
    }
    public void processFundOpenInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Integer loopNum) {
        try {
            CompletableFuture.supplyAsync(() -> {
                //1 先拿到需要的数据 这里拿到已经处理好了的数据
                //2.1.9
                String startDay;
                String endDay;
                LocalDate localDate = LocalDate.now();
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                endDay = dateTimeFormatter.format(localDate);
                for (int i = 0; i < loopNum; i++) {
                    localDate = localDate.minusDays(30);
                    startDay = dateTimeFormatter.format(localDate);
                    try {
                        List<JSONObject> remoteData = htscGetDataUtil.processFundOpenInfoData(tenant, trustNo, htscUrlPrefix, startDay, endDay);
                        if (!ObjectUtils.isEmpty(remoteData)) {
                            log.info("华泰查询funOpenInfo数据{}条", remoteData.size());
                            //2 封装数据
                            List<FundOpenInfoEntity> entities = getFundOpenInfo(tenant, trustNo, remoteData);
                            //3 保存数据
                            fundOpenInfoService.saveData(null, entities);
                            log.info("华泰封装fundOpenInfo数据{}条", entities.size());
                        }
                    } catch (Exception e) {
                        log.error("批量保存fundOpenInfo失败！", e);
                    }
                    endDay = startDay;
                }
                return null;
            }, pullDataThreadPool);
        } catch (Exception e) {
            log.error("htsc:获取fundOpenInfo数据失败！", e);
        }
    }

    private List<FundOpenInfoEntity> getFundOpenInfo(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<JSONObject> remoteData) {
        //获取数据字典
        Map<String, String> typeOfOpenDayDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.TYPE_OF_OPEN_DAY_SERVICE, trustNo);
        Map<String, Map<String, String>> openStateDic = openStateService.queryOpenStateWithDetail(trustNo);
        //封装数据
        List<FundOpenInfoEntity> entityList = new ArrayList<>();
        Map<String, String> dataMap;
        for (JSONObject data : remoteData){
            dataMap = JSONObject.toJavaObject(data, Map.class);
            String fundCode = null;
            String innerFundCode = null;
            String remoteFundCode = dataMap.get("fundCode");
            List<FundInfoEntity> fundInfo = fundInfoService.getDataByFundCode(dataMap.get("fundCode"), trustNo, tenant.getTenantName());
            FundInfoEntity fundInfoEntity = fundInfoService.queryByInnerFundCode(trustNo, tenant.getTenantName(), remoteFundCode);
            //此数据能在基金产品信息表（fund_info）中找到和它相等的基金备案编码，此数据落fund_code+inner_fund_code字段
            if (!CollectionUtils.isEmpty(fundInfo)){
                fundCode = remoteFundCode;
                innerFundCode = remoteFundCode;
            } else if (Objects.nonNull(fundInfoEntity) && remoteFundCode.equals(fundInfoEntity.getInnerFundCode())){
                //此数据不能在基金产品信息表（fund_info）中找到和它相等的基金备案编码但能找到和它相等的基金内部代码，
                // 此数据落inner_fund_code字段，取与之对应的基金备案代码落fund_code字段
                fundCode = fundInfoEntity.getFundCode();
                innerFundCode = remoteFundCode;
            }
            //开放日类型转义
            String openType = MatchDataDictionaryKeyUtil.getKey(typeOfOpenDayDic, dataMap.get("isTemp"));
            entityList.add(FundOpenInfoEntity.builder()
                    .fundCode(fundCode)
                    .innerFundCode(innerFundCode)
                    .fundOpenDate(dataMap.get("openDate"))
                    .openType(openType)
                    .openStatus(MatchDataDictionaryKeyUtil.getKey(openType, openStateDic, dataMap.get("openType")))
                    .trustNo(trustNo)
                    .admin(tenant.getTenantName())
                    .createTime(new Date())
                    .updateTime(new Date())
                    .build());
        }
        return entityList;
    }

    public void processPayFeeDetailInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Integer loopNum) {
        try {
            CompletableFuture.supplyAsync(() -> {
                //1 先拿到需要的数据 这里拿到已经处理好了的数据
                //2.1.9
                String startDay;
                String endDay;
                LocalDate localDate = LocalDate.now();
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                endDay = dateTimeFormatter.format(localDate);
                for (int i = 0; i < loopNum; i++) {
                    localDate = localDate.minusDays(30);
                    startDay = dateTimeFormatter.format(localDate);
                    try {
                        List<JSONObject> remoteData = htscGetDataUtil.processPayFeeDetailInfoData(tenant, trustNo, htscUrlPrefix, startDay, endDay);
                        if (!ObjectUtils.isEmpty(remoteData)) {
                            log.info("华泰查询payFeeDetailInfo数据{}条", remoteData.size());
                            //2 封装数据
                            List<PayFeeDetailInfoEntity> entities = getPayFeeDetailInfo(tenant, trustNo, remoteData);
                            //3 保存数据
                            payFeeDetailInfoService.saveData(null, entities);
                            log.info("华泰封装payFeeDetailInfo数据{}条", entities.size());
                        }
                    } catch (Exception e) {
                        log.error("批量保存payFeeDetailInfo失败！", e);
                    }
                    endDay = startDay;
                }
                return null;
            }, pullDataThreadPool);
        } catch (Exception e) {
            log.error("批量保存payFeeDetailInfo失败！", e);
        }
    }

    private List<PayFeeDetailInfoEntity> getPayFeeDetailInfo(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<JSONObject> remoteDataList) {
        //获取字典
        Map<String, String> typesOfFeeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.TYPES_OF_FEE_SERVICE, trustNo);
        //封装数据
        List<PayFeeDetailInfoEntity> entityList = new ArrayList<>();
        Map<String, String> remoteDataMap;
        for (JSONObject remoteData : remoteDataList){
            remoteDataMap = JSONObject.toJavaObject(remoteData, Map.class);
            entityList.add(PayFeeDetailInfoEntity.builder()
                    .fundCode(remoteDataMap.get("fundCode"))
                    .fundName(remoteDataMap.get("fundName"))
                    .feeType(MatchDataDictionaryKeyUtil.getKey(typesOfFeeDic, remoteDataMap.get("feeType")))
                    .feeDate(remoteDataMap.get("feeDate"))
                    .chargeFee(BigDecimalUtil.obj2BigDecimalTrunToZero(remoteDataMap.get("fee")))
                    .trustNo(trustNo)
                    .admin(tenant.getTenantName())
                    .createTime(new Date())
                    .updateTime(new Date())
                    .build());
        }
        return entityList;
    }

    public void processChargeFeeDetailInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Integer loopNum) {
        try {
            CompletableFuture.supplyAsync(() -> {
                //1 先拿到需要的数据 这里拿到已经处理好了的数据
                //2.1.9
                String startDay;
                String endDay;
                LocalDate localDate = LocalDate.now();
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                endDay = dateTimeFormatter.format(localDate);
                for (int i = 0; i < loopNum; i++) {
                    localDate = localDate.minusDays(30);
                    startDay = dateTimeFormatter.format(localDate);
                    try {
                        List<JSONObject> remoteData = htscGetDataUtil.processChargeFeeDetailInfoData(tenant, trustNo, htscUrlPrefix, startDay, endDay);
                        if (!ObjectUtils.isEmpty(remoteData)) {
                            log.info("华泰查询chargeFeeDetailInfo数据{}条", remoteData.size());
                            //2 封装数据
                            List<ChargeFeeDetailInfoEntity> entities = getChargeFeeDataDetailInfo(tenant, trustNo, remoteData);
                            //3 保存数据
                            chargeFeeDetailInfoService.saveData(null, entities);
                            log.info("华泰封装chargeFeeDetailInfo数据{}条", entities.size());
                        }
                    } catch (Exception e) {
                        log.error("批量保存ChargeFeeDetailInfo失败！", e);
                    }
                    endDay = startDay;
                }
                return null;
            }, pullDataThreadPool);
        } catch (Exception e) {
            log.error("批量保存ChargeFeeDetailInfo失败！", e);
        }
    }

    public void processChargeFeeDetailInfoDataV2(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Integer loopNum) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.9
            String startDay;
            String endDay;
            LocalDate localDate = LocalDate.now();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            endDay = dateTimeFormatter.format(localDate);
            for (int i = 0; i < loopNum; i++) {
                localDate = localDate.minusDays(30);
                startDay = dateTimeFormatter.format(localDate);
                try {
                    List<JSONObject> remoteData = htscGetDataUtil.processChargeFeeDetailInfoData(tenant, trustNo, htscUrlPrefix, startDay, endDay);
                    if (!ObjectUtils.isEmpty(remoteData)) {
                        log.info("华泰查询chargeFeeDetailInfo数据{}条", remoteData.size());
                        //2 封装数据
                        List<ChargeFeeDetailInfoEntity> entities = getChargeFeeDataDetailInfo(tenant, trustNo, remoteData);
                        //3 保存数据
                        chargeFeeDetailInfoService.saveData(null, entities);
                        log.info("华泰封装chargeFeeDetailInfo数据{}条", entities.size());
                    }
                } catch (Exception e) {
                    log.error("批量保存ChargeFeeDetailInfo失败！", e);
                }
                endDay = startDay;
            }
        } catch (Exception e) {
            log.error("批量保存ChargeFeeDetailInfo失败！", e);
        }
    }

    private List<ChargeFeeDetailInfoEntity> getChargeFeeDataDetailInfo(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<JSONObject> remoteDataList) {
       //获取字典
        Map<String, String> typesOfFeeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.TYPES_OF_FEE_SERVICE, trustNo);
        List<ChargeFeeDetailInfoEntity> entityList = new ArrayList<>();
        Map<String,String> remoteDataMap;
        for (JSONObject remoteData : remoteDataList){
            remoteDataMap = JSONObject.toJavaObject(remoteData, Map.class);
            entityList.add(ChargeFeeDetailInfoEntity.builder()
                    .fundCode(remoteDataMap.get("fundCode"))
                    .fundName(remoteDataMap.get("fundName"))
                    .feeType(MatchDataDictionaryKeyUtil.getKey(typesOfFeeDic, remoteDataMap.get("feeType")))
                    .feeDate(remoteDataMap.get("feeDate"))
                    .chargeFee(BigDecimalUtil.obj2BigDecimalTrunToZero(remoteDataMap.get("fee")))
                    .trustNo(trustNo)
                    .admin(tenant.getTenantName())
                    .createTime(new Date())
                    .updateTime(new Date())
                    .build());
        }
        return entityList;
    }

    public void processFundTempFareInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            CompletableFuture.supplyAsync(() -> {
                //1 先拿到需要的数据 这里拿到已经处理好了的数据
                //2.1.9
                String startDay;
                String endDay;
                LocalDate localDate = LocalDate.now();
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                endDay = dateTimeFormatter.format(localDate);
                for (int i = 0; i < loopNum; i++) {
                    localDate = localDate.minusDays(29);
                    startDay = dateTimeFormatter.format(localDate);
                    try {
                        Map<String, Object> remoteData = htscGetDataUtil.processFundTempFareInfoData(tenant, trustNo, htscUrlPrefix , startDay,endDay);
                        log.info("华泰获取fundTempFareInfo/fundFeeDataInfo数据{}条",remoteData.size());
                        if (PullDataFailureUtil.isFailed(remoteData)) {
                            pullDataFailureLogService.saveErrorData(tenant.getTenantName(), remoteData);
                            return null;
                        }
                        //2 封装数据
                        List<FundTempFareInfoEntity> entities = getFundTempFareInfoData(tenant, trustNo, remoteData);
                        log.info("华泰封装fundTempFareInfo数据{}条",remoteData.size());
                        //fund_fee_data_info
                        List<FundFeeDataInfoEntity> fundFeeDataInfoEntities = getFundFeeDataInfo(tenant, trustNo, remoteData);
                        log.info("华泰封装fundFeeDataInfo数据{}条",remoteData.size());
                        //3 保存数据
                        fundTempFareInfoService.saveData(null, entities);
                        //fund_fee_data_info
                        fundFeeDataInfoService.saveData(null,fundFeeDataInfoEntities);
                    } catch (Exception e) {
                        log.error("批量保存FundProfitInfo失败！", e);
                    }
                    endDay = startDay;
                }
                // TODO: 2022/1/13 加一个处理失败的日志统计表
                return null;
            }, pullDataThreadPool);
        } catch (Exception e) {
            log.error("批量保存FundValueTableDataInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }

    }

    private List<FundFeeDataInfoEntity> getFundFeeDataInfo(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> remoteData) {
        List<FundFeeDataInfoEntity> entityList = new ArrayList<>();
        Date now = new Date();
        if (!remoteData.isEmpty()) {
            remoteData.entrySet().forEach(entry -> {
                ArrayList<Object> dtos = (ArrayList<Object>) entry.getValue();
                dtos.forEach(item -> {
                    HTSCValuationFeeDto dto = (HTSCValuationFeeDto) item;
                    //增值税
                    entityList.add(FundFeeDataInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(StringUtils.isEmpty(dto.getD_confirmDate()) ? "" : dto.getD_confirmDate())
                            .fareDebit(BigDecimalUtil.obj2BigDecimalTrunToZero(dto.getF_vat()))
                            .subjectCode("ZZS")
                            .subjectName("增值税")
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                    //附加税
                    entityList.add(FundFeeDataInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(StringUtils.isEmpty(dto.getD_confirmDate()) ? "" : dto.getD_confirmDate())
                            .fareDebit(BigDecimalUtil.obj2BigDecimalTrunToZero(dto.getF_surtax()))
                            .subjectCode("FJS")
                            .subjectName("附加税")
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                    //管理费
                    entityList.add(FundFeeDataInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(StringUtils.isEmpty(dto.getD_confirmDate()) ? "" : dto.getD_confirmDate())
                            .fareDebit(BigDecimalUtil.obj2BigDecimalTrunToZero(dto.getF_management()))
                            .subjectCode("GLF")
                            .subjectName("管理费")
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                    //托管费TGF
                    entityList.add(FundFeeDataInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(StringUtils.isEmpty(dto.getD_confirmDate()) ? "" : dto.getD_confirmDate())
                            .fareDebit(BigDecimalUtil.obj2BigDecimalTrunToZero(dto.getF_hosting()))
                            .subjectCode("TGF")
                            .subjectName("托管费")
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                    //业绩报酬YJBC
                    entityList.add(FundFeeDataInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(StringUtils.isEmpty(dto.getD_confirmDate()) ? "" : dto.getD_confirmDate())
                            .fareDebit(BigDecimalUtil.obj2BigDecimalTrunToZero(dto.getF_businessCompensation()))
                            .subjectCode("YJBC")
                            .subjectName("业绩报酬")
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                    //运营服务费YYFWF
                    entityList.add(FundFeeDataInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(StringUtils.isEmpty(dto.getD_confirmDate()) ? "" : dto.getD_confirmDate())
                            .fareDebit(BigDecimalUtil.obj2BigDecimalTrunToZero(dto.getF_operationService()))
                            .subjectCode("YYFWF")
                            .subjectName("运营服务费")
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                });
            });
        }
        return entityList;
    }

    private List<FundTempFareInfoEntity> getFundTempFareInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> remoteData) {
        List<FundTempFareInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!remoteData.isEmpty()) {
            remoteData.entrySet().forEach(entry -> {
                ArrayList<Object> dtos = (ArrayList<Object>) entry.getValue();
                dtos.forEach(item -> {
                    HTSCValuationFeeDto dto = (HTSCValuationFeeDto) item;
                    result.add(FundTempFareInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .confirmDate(dto.getD_confirmDate())
                            .fareVat(BigDecimalUtil.obj2BigDecimal(dto.getF_vat()))
                            .fareSurtax(BigDecimalUtil.obj2BigDecimal(dto.getF_surtax()))
                            .fareManagement(BigDecimalUtil.obj2BigDecimal(dto.getF_management()))
                            .fareHosting(BigDecimalUtil.obj2BigDecimal(dto.getF_hosting()))
                            .fareBusiness(BigDecimalUtil.obj2BigDecimal(dto.getF_businessCompensation()))
                            .fareOperationService(BigDecimalUtil.obj2BigDecimal(dto.getF_operationService()))
                            .fareVatTemp(BigDecimalUtil.obj2BigDecimal(dto.getF_zg_vat()))
                            .fareSurtaxTemp(BigDecimalUtil.obj2BigDecimal(dto.getF_zg_surtax()))
                            .fundCode(dto.getC_prdtConvertCode())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                });
            });
        }
        return result;
    }

    public void processCustShareDetailInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            Map<String, String> fundInnerCodeAndCodeMap = fundInnerCodeAndCodeTL.get();
            CompletableFuture.supplyAsync(() -> {
                //1 先拿到需要的数据 这里拿到已经处理好了的数据
                //2.1.25
                try {
                    //1 获取数据
                    List<HTSCCustShareDetailsDto> custShareDetailInfoData = htscGetDataUtil.processCustShareDetailInfoData(tenant, trustNo, htscUrlPrefix);
                    //2 封装成标准表的对象
                    if (!CollectionUtils.isEmpty(custShareDetailInfoData)) {
                        List<CustShareDetailInfoEntity> entities = getCustShareDetailInfoData(tenant, trustNo, custShareDetailInfoData, fundInnerCodeAndCodeMap);
                        //3 调用 basicservice 的保存数据接口
                        custShareDetailInfoService.saveData(null, entities);
                    }
                } catch (Exception e) {
                    log.error("批量保存CustShareDetailInfo失败！", e);
                }
                // TODO: 2022/1/13 加一个处理失败的日志统计表
                return null;
            }, pullDataThreadPool);
        } catch (Exception e) {
            log.error("批量保存CustShareDetailInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<CustShareDetailInfoEntity> getCustShareDetailInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<HTSCCustShareDetailsDto> custShareDetailInfoData, Map<String, String> fundInnerCodeAndCodeMap) {
        Map<String, String> accountStatusDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.ACCOUNT_STATUS_SERVICE, trustNo);
        Map<String, String> shareSourceDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SHARE_SOURCE_SERVICE, trustNo);
        List<CustShareDetailInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!custShareDetailInfoData.isEmpty()) {
            custShareDetailInfoData.forEach(item -> {
                String fundCode = fundInnerCodeAndCodeMap.get(item.getFundCode());
                result.add(CustShareDetailInfoEntity.builder()
                        .innerFundCode(item.getFundCode())
                        .fundName(item.getFundName())
                        .registDate(item.getRegistDate())
                        .confirmDate(item.getConfDate())
                        .fundAcco(item.getFundAcco())
                        .tradeAcco(item.getTradeAcco())
                        .holdDay(item.getHoldDays())
                        .acctStatus(MatchDataDictionaryKeyUtil.getKey(accountStatusDic, item.getAcctStatus()))
                        .remainShares(BigDecimalUtil.obj2BigDecimal(item.getRemainShares()))
                        .cserialNo(item.getCserialNo())
                        .sourceType(MatchDataDictionaryKeyUtil.getKey(shareSourceDic, item.getSourceType()))
                        .oriSource(MatchDataDictionaryKeyUtil.getKey(shareSourceDic, item.getOriSourceType()))
                        .oriBalance(BigDecimalUtil.obj2BigDecimal(item.getOriCfmBalance()))
                        .oriShares(BigDecimalUtil.obj2BigDecimal(item.getOriCfmShares()))
                        .oriNetValue(BigDecimalUtil.obj2BigDecimal(item.getOriNav(), 4))
                        .deductedBalance(BigDecimalUtil.obj2BigDecimal(item.getDeductedBalance()))
                        .lastDeductDate(item.getLastDeductDate())
                        .lastDeductNav(BigDecimalUtil.obj2BigDecimal(item.getLastDeductNav(), 4))
                        .seqNo(com.friday.custody.utils.StrUtil.getSeqNo(null))
                        .fundCode(fundCode)
                        .admin(tenant.getTenantName())
                        .trustNo(trustNo)
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;



    }

    public void processFundProfitInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            Map<String, String> fundInnerCodeAndCodeMap = fundInnerCodeAndCodeTL.get();
            CompletableFuture.supplyAsync(() -> {
                //1 先拿到需要的数据 这里拿到已经处理好了的数据
                //2.1.23
                String startDay;
                String endDay;
                LocalDate localDate = LocalDate.now();
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                endDay = dateTimeFormatter.format(localDate);
                for (int i = 0; i < loopNum; i++) {
                    localDate = localDate.minusDays(30);
                    startDay = dateTimeFormatter.format(localDate);
                    try {
                        //1 获取数据
                        List<HTSCFundProfitDto> valuationDetailDtos = htscGetDataUtil.processFundProfitInfoData(tenant, trustNo, htscUrlPrefix, startDay, endDay);
                        //2 封装成标准表的对象
                        if (!CollectionUtils.isEmpty(valuationDetailDtos)) {
                            List<FundProfitInfoEntity> entities = getFundProfitInfoData(tenant, trustNo, valuationDetailDtos, fundInnerCodeAndCodeMap);
                            //3 调用 basicservice 的保存数据接口
                            fundProfitInfoService.saveData(null, entities);
                        }
                    } catch (Exception e) {
                        log.error("批量保存FundProfitInfo失败！", e);
                    }
                    endDay = startDay;
                }
                // TODO: 2022/1/13 加一个处理失败的日志统计表
                return null;
            }, pullDataThreadPool);
        } catch (Exception e) {
            log.error("批量保存FundValueTableDataInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }

    }

    private List<FundProfitInfoEntity> getFundProfitInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<HTSCFundProfitDto> valuationDetailDtos, Map<String, String> fundInnerCodeAndCodeMap) {
        Map<String, String> businessNameDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.BUSINESS_NAME_SERVICE, trustNo);
        Map<String, String> sellerDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SELLER_SERVICE, "华泰证券");
        List<FundProfitInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!valuationDetailDtos.isEmpty()) {
            valuationDetailDtos.forEach(item -> {
                String fundCode = fundInnerCodeAndCodeMap.get(item.getFundCode());
                result.add(FundProfitInfoEntity.builder()
                        .fundCode(fundCode)
                        .innerFundCode(item.getFundCode())
                        .fundName(item.getFundName())
                        .confirmDate(item.getConfDate())//todo 格式未定
                        .businessName(MatchDataDictionaryKeyUtil.getKey(businessNameDic, item.getBusType()))
                        .fundAcco(item.getFundAcco())
                        .tradeAcco(item.getTradeAcco())
                        .agencyNo(MatchDataDictionaryKeyUtil.getKey(sellerDic, item.getAgencyNo()))
                        .agencyName(item.getAgencyName())
                        .registDate(item.getRegistDate())
                        .shares(BigDecimalUtil.obj2BigDecimalTrunToZero(item.getShares()))
                        .oriDate(item.getOriBeginDate())
                        .oriNav(BigDecimalUtil.obj2BigDecimal(item.getOriNav(), 4))
                        .oriTotalNav(BigDecimalUtil.obj2BigDecimal(item.getOriTotalNav(), 4))
                        .nav(BigDecimalUtil.obj2BigDecimal(item.getNav(), 4))
                        .totalNav(BigDecimalUtil.obj2BigDecimal(item.getTotalNav(), 4))
                        .factBalance(BigDecimalUtil.obj2BigDecimal(item.getFactBalance()))
                        .factShares(BigDecimalUtil.obj2BigDecimal(item.getFactShare()))
                        .currRatio(BigDecimalUtil.obj2BigDecimal(item.getCurrRatio(), 4))
                        .yearRatio(BigDecimalUtil.obj2BigDecimal(item.getYearRatio(), 4))
                        .holdDay(item.getHold())
                        .cserialNo(item.getCserialNo())
                        .oriCserialNo(item.getOriCserialNo())
                        .admin(tenant.getTenantName())
                        .trustNo(trustNo)
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;
    }

    public void processInvestorBankInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.4投资主体银行账户信息查询
            Map<String, Object> investmentSubjects = htscGetDataUtil.processInvestorBankInfo(tenant, trustNo, htscUrlPrefix, today);
            if (PullDataFailureUtil.isFailed(investmentSubjects)) {
                pullDataFailureLogService.saveErrorData(tenant.getTenantName(), investmentSubjects);
                return;
            }
            //2 封装成标准表的对象
            List<InvestorBankInfoEntity> entities = getInvestorBankInfoData(tenant, trustNo, investmentSubjects);
            //3 调用 basicservice 的保存数据接口
            investorBankInfoService.saveData(null, entities);

        } catch (Exception e) {
            log.error("批量保存InvestorBankInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }


    }

    private List<InvestorBankInfoEntity> getInvestorBankInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> investmentSubjects) {
        List<InvestorBankInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!investmentSubjects.isEmpty()) {
            investmentSubjects.forEach((key, value) -> {
                InvestorBankInfoDto dto = (InvestorBankInfoDto) value;
                result.add(InvestorBankInfoEntity.builder()
                        .fundAcco(dto.getC_fundacco())
                        .tradeAcco(dto.getC_tradeacco())
                        .branchBankName(dto.getC_branchbankname())
                        .bankNo(dto.getC_bankacco())
                        .nameInBank(dto.getC_nameinbank())
                        .bankNoType("银行卡号")
                        .admin(tenant.getTenantName())
                        .uniqueKey(tenant.getTenantName() + dto.getC_bankacco())
                        .trustNo(trustNo)
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;
    }

    public void processFundValueTableDataInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {

        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.21
            String curDay;
            LocalDate localDate = LocalDate.now();
            //将loopNum的循环次数转变为日期
            loopNum = loopNum == 1 ? 31 : 365 * 3;
//            loopNum = loopNum == 12 ? 365 : 365 * 3;
            ReentrantLock lock = new ReentrantLock();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            for (int i = 0; i < loopNum; i++) {
                try {
                    Thread.sleep(6000L);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.error("睡眠的线程被打断了...InterruptedException:", e);
                }
                //todo 取出交易日
                curDay = dateTimeFormatter.format(localDate);
                String finalCurDay = curDay;
                CompletableFuture.supplyAsync(() -> {
                    try {
                        //1 获取数据
                        List<ValuationDetailDto> valuationDetailDtos = htscGetDataUtil.processFundValueTableDataInfoData(tenant, trustNo, htscUrlPrefix, today, finalCurDay, lock);
                        //2 封装成标准表的对象
                        if (!CollectionUtils.isEmpty(valuationDetailDtos)) {
                            List<FundValueTableDataInfoEntity> entities = getFundValueTableDataInfoData(tenant, trustNo, valuationDetailDtos);
                            List<FundValuationEntity> fundValuationEntityList = getFundValautionTableDataInfoData(tenant, trustNo, valuationDetailDtos);
                            //3 调用 basicservice 的保存数据接口
                            fundValueTableDataInfoService.saveData(null, entities);
                            fundValuationService.saveData(null, fundValuationEntityList);
                        }
                    } catch (Exception e) {
                        log.error("批量保存FundValueTableDataInfo失败！", e);
                    }
                    // TODO: 2022/1/13 加一个处理失败的日志统计表
                    return null;
                }, pullDataThreadPool);
                localDate = localDate.minusDays(1);
            }
        } catch (Exception e) {
            log.error("批量保存FundValueTableDataInfo失败！", e);
            Thread.currentThread().interrupt();
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }

    }

    private List<FundValueTableDataInfoEntity> getFundValueTableDataInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<ValuationDetailDto> valuationDetailDtos) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        List<FundValueTableDataInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!valuationDetailDtos.isEmpty()) {
            valuationDetailDtos.forEach(item -> {
                result.add(FundValueTableDataInfoEntity.builder()
                        .fundCode(item.getFundCode())
                        .fundName(item.getFundName())
                        .valueDate(item.getValueDate())
                        .kmCode(item.getKmCode())
                        .kmName(item.getKmName())
                        .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, item.getDcCode()))
                        .vaRate(BigDecimalUtil.obj2BigDecimal(item.getVaRate(), 4))
                        .amount(BigDecimalUtil.obj2BigDecimal(item.getAmount()))
                        .perCost(BigDecimalUtil.obj2BigDecimal(item.getPerCost()))
                        .origCost(BigDecimalUtil.obj2BigDecimal(item.getOrigCost()))
                        .portCost(BigDecimalUtil.obj2BigDecimal(item.getPortCost()))
                        .costBi(BigDecimalUtil.obj2BigDecimal(item.getCostBl(), 4))
                        .vapPrice(BigDecimalUtil.obj2BigDecimal(item.getVapRice()))
                        .origMv(BigDecimalUtil.obj2BigDecimal(item.getOrigMv()))
                        .portMv(BigDecimalUtil.obj2BigDecimal(item.getPortMv()))
                        .mvBl(BigDecimalUtil.obj2BigDecimal(item.getMvBl(), 4))
                        .origIv(BigDecimalUtil.obj2BigDecimal(item.getOrigIv()))
                        .portIv(BigDecimalUtil.obj2BigDecimal(item.getPortIv()))
                        .suspension(item.getSuspension())
                        .admin(tenant.getTenantName())
                        .trustNo(trustNo)
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;
    }


    private List<FundValuationEntity> getFundValautionTableDataInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<ValuationDetailDto> valuationDetailDtos) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        Map<String, String> typeOfFundValuationDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.TYPE_OF_FUND_VALUATION, trustNo);
        Map<String, String> kmCodeNameMap = typeOfFundValuationService.queryKeyValue();
        List<FundValuationEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!valuationDetailDtos.isEmpty()) {
            valuationDetailDtos.forEach(item -> {
                String kmCode = MatchDataDictionaryKeyUtil.getKey(typeOfFundValuationDic, item.getKmCode());
                String kmName = kmCodeNameMap.get(kmCode);
                if (StringUtils.isNotBlank(kmCode)) {
                    result.add(FundValuationEntity.builder()
                            .fundCode(item.getFundCode())
                            .fundName(item.getFundName())
                            .valueDate(item.getValueDate())
                            .kmCode(kmCode)
                            .kmName(kmName)
                            .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, item.getDcCode()))
                            .vaRate(BigDecimalUtil.obj2BigDecimal(item.getVaRate(), 4))
                            .amount(BigDecimalUtil.obj2BigDecimal(item.getAmount()))
                            .perCost(BigDecimalUtil.obj2BigDecimal(item.getPerCost()))
                            .origCost(BigDecimalUtil.obj2BigDecimal(item.getOrigCost()))
                            .portCost(BigDecimalUtil.obj2BigDecimal(item.getPortCost()))
                            .costBi(BigDecimalUtil.obj2BigDecimal(item.getCostBl(), 4))
                            .vapPrice(BigDecimalUtil.obj2BigDecimal(item.getVapRice()))
                            .origMv(BigDecimalUtil.obj2BigDecimal(item.getOrigMv()))
                            .portMv(BigDecimalUtil.obj2BigDecimal(item.getPortMv()))
                            .mvBl(BigDecimalUtil.obj2BigDecimal(item.getMvBl(), 4))
                            .origIv(BigDecimalUtil.obj2BigDecimal(item.getOrigIv()))
                            .portIv(BigDecimalUtil.obj2BigDecimal(item.getPortIv()))
                            .suspension(item.getSuspension())
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                }
            });
        }
        return result;
    }


    private void processFundTradeInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.6
            ReentrantLock lock = new ReentrantLock();
            String startDate;
            String endDate;
            LocalDate localDate = LocalDate.now();
            LocalDate lastMonth;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(CustodyContants.DATE_FORMAT);
            endDate = dateTimeFormatter.format(localDate);
            Map<String, String> fundInnerCodeAndCodeMap = fundInnerCodeAndCodeTL.get();
            for (int i = 0; i < loopNum; i++) {
                lastMonth = localDate.minusDays(30);
                startDate = dateTimeFormatter.format(lastMonth);
                String finalStartDate = startDate;
                String finalEndDate = endDate;
                CompletableFuture.supplyAsync(() -> {
                    try {
                        //1 获取数据
                        Map<String, Object> remoteData = htscGetDataUtil.processInvestTradeCfmDetailData(tenant, trustNo, htscUrlPrefix, today, finalStartDate, finalEndDate, lock);
                        if (PullDataFailureUtil.isFailed(remoteData)) {
                            pullDataFailureLogService.saveErrorData(tenant.getTenantName(), remoteData);
                            return null;
                        }
                        //2 封装数据
                        List<FundTradeInfoEntity> entities = getFundInvestTradeCfmDetailData(tenant, trustNo, remoteData, fundInnerCodeAndCodeMap);
                        //3 保存数据
                        fundTradeInfoService.saveData(null, entities);
                    } catch (Exception e) {
                        log.error("批量保存FundTradeInfo失败！", e);
                    }
                    // TODO: 2022/1/13 加一个处理失败的日志统计表
                    return null;
                }, pullDataThreadPool);
                localDate = lastMonth;
                endDate = startDate;
            }
        } catch (Exception e) {
            log.error("批量保存FundTradeInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    public void processFundTradeInfoDataV2(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.6
            ReentrantLock lock = new ReentrantLock();
            String startDate;
            String endDate;
            LocalDate localDate = LocalDate.now();
            LocalDate lastMonth;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(CustodyContants.DATE_FORMAT);
            endDate = dateTimeFormatter.format(localDate);
            Map<String, String> fundInnerCodeAndCodeMap = fundInnerCodeAndCodeTL.get();
            for (int i = 0; i < loopNum; i++) {
                lastMonth = localDate.minusDays(30);
                startDate = dateTimeFormatter.format(lastMonth);
                String finalStartDate = startDate;
                String finalEndDate = endDate;
                try {
                    //1 获取数据
                    Map<String, Object> remoteData = htscGetDataUtil.processInvestTradeCfmDetailData(tenant, trustNo, htscUrlPrefix, today, finalStartDate, finalEndDate, lock);
                    if (PullDataFailureUtil.isFailed(remoteData)) {
                        pullDataFailureLogService.saveErrorData(tenant.getTenantName(), remoteData);
                        return;
                    }
                    //2 封装数据
                    List<FundTradeInfoEntity> entities = getFundInvestTradeCfmDetailData(tenant, trustNo, remoteData, fundInnerCodeAndCodeMap);
                    //3 保存数据
                    fundTradeInfoService.saveData(null, entities);
                } catch (Exception e) {
                    log.error("批量保存FundTradeInfo失败！", e);
                }
                // TODO: 2022/1/13 加一个处理失败的日志统计表
                localDate = lastMonth;
                endDate = startDate;
            }
        } catch (Exception e) {
            log.error("批量保存FundTradeInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<FundTradeInfoEntity> getFundInvestTradeCfmDetailData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> remoteData, Map<String, String> fundInnerCodeAndCodeMap) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        Map<String, String> sellerDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SELLER_SERVICE, "华泰证券");
        Map<String, String> businessTypeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.BUSINESS_NAME_SERVICE, trustNo);
        Map<String, String> confirmStatusDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CONFIRM_STATUS_SERVICE, trustNo);
        Map<String, String> shareAdjustDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SHARE_ADJUST_SERVICE, trustNo);
        List<FundTradeInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!remoteData.isEmpty()) {
            remoteData.entrySet().forEach(entry -> {
                ArrayList<Object> dtos = (ArrayList<Object>) entry.getValue();
                dtos.forEach(item -> {
                    InvestTradeCfmDetailDto dto = (InvestTradeCfmDetailDto) item;
                    result.add(FundTradeInfoEntity.builder()
                            .fundAcco(dto.getC_fundacco())
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .requestDate(dto.getD_requestdate())
                            .confirmDate(dto.getD_cdate())
                            .agencyName(dto.getC_agencyname())
                            .agencyNo(MatchDataDictionaryKeyUtil.getKey(sellerDic, dto.getC_agencyno()))
                            .tradeAcco(dto.getC_tradeacco())
                            .businessName(MatchDataDictionaryKeyUtil.getKey(businessTypeDic, dto.getC_businname().getCode()))
                            .netValue(BigDecimalUtil.obj2BigDecimal(dto.getF_netvalue(), 4))
                            .confirmBalance(BigDecimalUtil.obj2BigDecimal(dto.getF_confirmbalance()))
                            .confirmShares(BigDecimalUtil.obj2BigDecimal(dto.getF_confirmshares()))
                            .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, dto.getF_currency().getCode()))
                            .confirmStatus(MatchDataDictionaryKeyUtil.getKey(confirmStatusDic, dto.getC_status()))
                            .applyShares(BigDecimalUtil.obj2BigDecimal(dto.getF_shares()))
                            .applyBalance(BigDecimalUtil.obj2BigDecimal(dto.getF_balance()))
                            .profitBalance(BigDecimalUtil.obj2BigDecimal(dto.getF_profitbalance()))
                            .tradeFare(BigDecimalUtil.obj2BigDecimal(dto.getF_tradefare()))
                            .requestNo(dto.getC_requestno())
                            .agio(BigDecimalUtil.obj2BigDecimal(dto.getF_agio(), 4))
                            .summary(dto.getC_memo())
                            .adjustCause(MatchDataDictionaryKeyUtil.getKey(shareAdjustDic, dto.getC_adjustcause()))
                            .cserialNo(dto.getC_cserialno())
                            .fundCode(StrUtil.getFundCode(fundInnerCodeAndCodeMap, dto.getC_fundcode()))
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(now)
                            .updateTime(now)
                            .build());
                });
            });

        }
        return result;

    }

    private void processMJAccountSeqInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {

        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            //2.1.14
            List<AccountCurrentDetailDto2> mjAccountBalanceInfos = htscGetDataUtil.processMJAccountCurrentDetailData(tenant, accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today);
            saveData2MJAccountSeqInfo(tenant, trustNo, mjAccountBalanceInfos);
            //2.1.15
            ReentrantLock lock = new ReentrantLock();
            String startDate;
            String endDate;
            // 单个募集户历史流水查询 不能以当前日期结尾
            LocalDate localDate = LocalDate.now().minusDays(1L);
            LocalDate lastMonth;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(CustodyContants.DATE_FORMAT);
            endDate = dateTimeFormatter.format(localDate);
            for (int i = 0; i < loopNum; i++) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    log.error("睡眠的线程被打断了...InterruptedException:", e);
                    Thread.currentThread().interrupt();
                }
                lastMonth = localDate.minusDays(30);
                startDate = dateTimeFormatter.format(lastMonth);
                String finalStartDate = startDate;
                String finalEndDate = endDate;
                CompletableFuture.supplyAsync(() -> {
                    try {
                        //1 获取数据
                        List<AccountCurrentDetailDto2> historyMJAccountBalanceInfos = htscGetDataUtil.processHistoryMJAccountBalanceInfoData(tenant,
                                accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today, loopNum, finalStartDate, finalEndDate, lock);
                        saveData2MJAccountSeqInfo(tenant, trustNo, historyMJAccountBalanceInfos);
                    } catch (Exception e) {
                        log.error("批量保存MJAccountSeqInfo失败！", e);
                    }
                    // TODO: 2022/1/13 加一个处理失败的日志统计表
                    return null;
                }, pullDataThreadPool);
                localDate = lastMonth;
                endDate = startDate;
            }
        } catch (Exception e) {
            log.error("批量保存MJAccountSeqInfo失败！", e);
            Thread.currentThread().interrupt();
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }
    private void processMJAccountSeqCurrentInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {

        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            //2.1.14
            List<AccountCurrentDetailDto2> mjAccountBalanceInfos = htscGetDataUtil.processMJAccountCurrentDetailData(tenant, accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today);
            saveData2MJAccountSeqInfo(tenant, trustNo, mjAccountBalanceInfos);

        } catch (Exception e) {
            log.error("批量保存MJAccountSeqInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private void processMJAccountSeqInfoHistoryData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {

        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            //2.1.15
            ReentrantLock lock = new ReentrantLock();
            String startDate;
            String endDate;
            // 单个募集户历史流水查询 不能以当前日期结尾
            LocalDate localDate = LocalDate.now().minusDays(1L);
            LocalDate lastMonth;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(CustodyContants.DATE_FORMAT);
            endDate = dateTimeFormatter.format(localDate);
            for (int i = 0; i < loopNum; i++) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    log.error("睡眠的线程被打断了...InterruptedException:", e);
                    Thread.currentThread().interrupt();
                }
                lastMonth = localDate.minusDays(30);
                startDate = dateTimeFormatter.format(lastMonth);
                String finalStartDate = startDate;
                String finalEndDate = endDate;
                CompletableFuture.supplyAsync(() -> {
                    try {
                        //1 获取数据
                        List<AccountCurrentDetailDto2> historyMJAccountBalanceInfos = htscGetDataUtil.processHistoryMJAccountBalanceInfoData(tenant,
                                accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today, loopNum, finalStartDate, finalEndDate, lock);
                        saveData2MJAccountSeqInfo(tenant, trustNo, historyMJAccountBalanceInfos);
                    } catch (Exception e) {
                        log.error("批量保存MJAccountSeqInfo失败！", e);
                    }
                    // TODO: 2022/1/13 加一个处理失败的日志统计表
                    return null;
                }, pullDataThreadPool);
                localDate = lastMonth;
                endDate = startDate;
            }
        } catch (Exception e) {
            log.error("批量保存MJAccountSeqInfo失败！", e);
            Thread.currentThread().interrupt();
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }
    private void saveData2MJAccountSeqInfo(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<AccountCurrentDetailDto2> mjAccountBalanceInfos) {
        try {
            //2 封装成标准表的对象
            List<MjAccountSeqInfoEntity> entities = getMJAccountSeqInfoData(tenant, trustNo, mjAccountBalanceInfos);
            //3 调用 basicservice 的保存数据接口
            mjAccountSeqInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存MJAccountSeqInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }



    private List<MjAccountSeqInfoEntity> getMJAccountSeqInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<AccountCurrentDetailDto2> mjAccountBalanceInfos) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        // 数据字典:收付方向
        Map<String, String> paymentDirectionDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.PAYMENT_DIRECTION_SERVICE, trustNo);
        List<MjAccountSeqInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!mjAccountBalanceInfos.isEmpty()) {
            mjAccountBalanceInfos.forEach(item -> {
                result.add(MjAccountSeqInfoEntity.builder()
                        .fundCode(item.getFundCode())
                        .fundName(item.getFundName())
                        .transferInOut(MatchDataDictionaryKeyUtil.getKey(paymentDirectionDic, item.getWay()))//收付方向
                        .seqNo(com.friday.custody.utils.StrUtil.getSeqNoWithoutNoRandom(item.getSeqNo()))//(存在且长度超过32位字符）截取前32位字符  若不存在，则在数据录入时生成32位随机（大/小写字母+数字）字符串
                        .account(item.getAccount())
                        .accountName(item.getAccountName())
                        .opAccount(item.getOpAccount())
                        .opAccountName(item.getOpAccountName())
                        .amount(BigDecimalUtil.obj2BigDecimal(item.getAmount()))
                        .balance(BigDecimalUtil.obj2BigDecimal(item.getBalance()))
                        .transferDate(DateFormaterUtil.removeTimeAndConnectionSymbol(item.getTransferDate(), "-"))
                        .transferTime(StringUtils.isNotBlank(item.getTransferDate()) ? item.getTransferDate().split(" ")[1] : null)
                        .summary(item.getSummary())
                        .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, item.getCurrency()))
                        .updateDate(item.getUpdateTime())
                        .uniqueKey(tenant.getTenantName() + trustNo + item.getFundCode() + item.getAccount() + item.getOpAccount() + item.getTransferDate())
                        .admin(tenant.getTenantName())
                        .trustNo(trustNo)
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;

    }

    public void processMJAccountBalanceInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.10	托管户余额查询
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            List<BalanceAccountDto2> mjAccountBalanceInfoData = htscGetDataUtil.processMJAccountBalanceInfoData(tenant, accountInfoMap.get(HtscConstants.MJ_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today);
            //2 封装成标准表的对象
            List<MjAccountBalanceInfoEntity> entities = getMJAccountBalanceInfoData(tenant, trustNo, mjAccountBalanceInfoData);
            //3 调用 basicservice 的保存数据接口
            mjAccountBalanceInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存MJAccountBalanceInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<MjAccountBalanceInfoEntity> getMJAccountBalanceInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<BalanceAccountDto2> mjAccountBalanceInfoData) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        List<MjAccountBalanceInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!mjAccountBalanceInfoData.isEmpty()) {
            mjAccountBalanceInfoData.forEach(item -> {
                result.add(MjAccountBalanceInfoEntity.builder()
                        .bankAcco(item.getAccount())
                        .accoBalance(BigDecimalUtil.obj2BigDecimal(item.getAcctBalance()))
                        .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, item.getCurrency()))
                        .updateDate(item.getUpdateTime())
                        .trustNo(trustNo)
                        .admin(tenant.getTenantName())
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;
    }

    private static final ThreadLocal<Map<String, List<BalanceAccountDto2>>> accountInfoTL = new ThreadLocal<>();

    /**
     *
     * @param tenant
     * @param trustNo
     * @param today
     */
    public void processAccountInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            ReentrantLock lock = new ReentrantLock();
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.16	资金账户基本信息查询
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> tgAccountBalanceInfoData = htscGetDataUtil.processAccountInfoData(tenant, trustNo, htscUrlPrefix, today, lock);
            //2 封装成标准表的对象
            Map<String, List<BalanceAccountDto2>> accountInfos = new HashMap<>();
            List<BalanceAccountDto2> tgAccountInfos = new ArrayList<>();
            List<BalanceAccountDto2> mjAccountInfos = new ArrayList<>();
            //2.2 根据accountTypeCode将其分为托管户和募集户
            tgAccountBalanceInfoData.forEach((key, value) -> {
                if (!CollectionUtils.isEmpty(value)) {
                    value.forEach(item -> {
                        switch (item.getAccountTypeCode()) {
                            case HtscConstants.TG_ACCOUNT_TYPE_CODE:
                                tgAccountInfos.add(item);
                                break;
                            case HtscConstants.MJ_ACCOUNT_TYPE_CODE:
                                mjAccountInfos.add(item);
                                break;
                        }
                    });
                }
            });
            accountInfos.put(HtscConstants.TG_ACCOUNT_TYPE_CODE, tgAccountInfos);
            accountInfos.put(HtscConstants.MJ_ACCOUNT_TYPE_CODE, mjAccountInfos);
            accountInfoTL.set(accountInfos);
            List<AccountInfoEntity> entities = getAccountInfoData(tenant, trustNo, tgAccountBalanceInfoData);
            //3 调用 basicservice 的保存数据接口
            accountInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存AccountInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<AccountInfoEntity> getAccountInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, List<BalanceAccountDto2>> tgAccountBalanceInfoData) {
        Map<String, String> accountTypeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.ACCOUNT_TYPE_SERVICE, trustNo);
        Map<String, String> bankCodeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.BANK_CODE_SERVICE, "资金清算系统银行编码");
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        List<AccountInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!tgAccountBalanceInfoData.isEmpty()) {
            tgAccountBalanceInfoData.forEach((key, value) -> {
                if (!CollectionUtils.isEmpty(value)) {
                    for (BalanceAccountDto2 dto : value) {
                        result.add(AccountInfoEntity.builder()
                                .fundCode(dto.getFundCode())
                                .fundName(dto.getFundName())
                                .accoTypeCode(MatchDataDictionaryKeyUtil.getKey(accountTypeDic,dto.getAccountTypeCode()))
                                .bankAcco(dto.getAccount())
                                .nameInBank(dto.getAccountName())
                                .bankCode(MatchDataDictionaryKeyUtil.getKey(bankCodeDic,dto.getBankCode().getCode()))
                                .branchBankName(dto.getOpenBank())
                                .largePayAcco(dto.getLargePayAccount())
                                .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic,dto.getCurrency()))
                                .trustNo(trustNo)
                                .admin(tenant.getTenantName())
                                .createTime(now)
                                .updateTime(now)
                                .build());
                    }
                }
            });
        }
        return result;
    }


    /**
     * @param tenant
     * @param trustNo
     * @param today
     * @param loopNum
     */
    private void processTGAccountSeqInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            //2.1.11
            List<AccountCurrentDetailDto2> tgAccountBalanceInfos = htscGetDataUtil.processTGAccountCurrentDetailData(tenant, accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today);
            saveData2TgAccountSeqInfo(tenant, trustNo, tgAccountBalanceInfos);
            //2.1.12
            ReentrantLock lock = new ReentrantLock();
            String startDate;
            String endDate;
            LocalDate localDate = LocalDate.now();
            localDate = localDate.minusDays(1);
            LocalDate lastMonth;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            endDate = dateTimeFormatter.format(localDate);
            for (int i = 0; i < loopNum; i++) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    log.error("睡眠的线程被打断了...InterruptedException:", e);
                    Thread.currentThread().interrupt();
                }
                lastMonth = localDate.minusDays(30);
                startDate = dateTimeFormatter.format(lastMonth);
                String finalStartDate = startDate;
                String finalEndDate = endDate;
                CompletableFuture.supplyAsync(() -> {
                    try {
                        //1 获取数据
                        List<AccountCurrentDetailDto2> historyTgAccountBalanceInfos = htscGetDataUtil.processHistoryTgAccountBalanceInfoData(tenant,
                                accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today, loopNum, finalStartDate, finalEndDate, lock);
                        saveData2TgAccountSeqInfo(tenant, trustNo, historyTgAccountBalanceInfos);
                    } catch (Exception e) {
                        log.error("批量保存TGAccountSeqInfo失败！", e);
                    }
                    // TODO: 2022/1/13 加一个处理失败的日志统计表
                    return null;
                }, pullDataThreadPool);
                localDate = lastMonth;
                endDate = startDate;
            }
        } catch (Exception e) {
            log.error("批量保存TGAccountSeqInfo失败！", e);
            Thread.currentThread().interrupt();
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }


    }

    private void processTGAccountSeqInfoCurrentData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            //2.1.11
            List<AccountCurrentDetailDto2> tgAccountBalanceInfos = htscGetDataUtil.processTGAccountCurrentDetailData(tenant, accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today);
            saveData2TgAccountSeqInfo(tenant, trustNo, tgAccountBalanceInfos);
        } catch (Exception e) {
            log.error("批量保存TGAccountSeqInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private void processTGAccountSeqInfoHistoryData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            //2.1.12
            ReentrantLock lock = new ReentrantLock();
            String startDate;
            String endDate;
            LocalDate localDate = LocalDate.now();
            localDate = localDate.minusDays(1);
            LocalDate lastMonth;
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            endDate = dateTimeFormatter.format(localDate);
            for (int i = 0; i < loopNum; i++) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    log.error("睡眠的线程被打断了...InterruptedException:", e);
                    Thread.currentThread().interrupt();
                }
                lastMonth = localDate.minusDays(30);
                startDate = dateTimeFormatter.format(lastMonth);
                String finalStartDate = startDate;
                String finalEndDate = endDate;
                CompletableFuture.supplyAsync(() -> {
                    try {
                        //1 获取数据
                        List<AccountCurrentDetailDto2> historyTgAccountBalanceInfos = htscGetDataUtil.processHistoryTgAccountBalanceInfoData(tenant,
                                accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE), trustNo, htscUrlPrefix, today, loopNum, finalStartDate, finalEndDate, lock);
                        saveData2TgAccountSeqInfo(tenant, trustNo, historyTgAccountBalanceInfos);
                    } catch (Exception e) {
                        log.error("批量保存TGAccountSeqInfo失败！", e);
                    }
                    // TODO: 2022/1/13 加一个处理失败的日志统计表
                    return null;
                }, pullDataThreadPool);
                localDate = lastMonth;
                endDate = startDate;
            }
        } catch (Exception e) {
            log.error("批量保存TGAccountSeqInfo失败！", e);
            Thread.currentThread().interrupt();
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }


    }

    private void saveData2TgAccountSeqInfo(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<AccountCurrentDetailDto2> tgAccountBalanceInfos) {
        try {
            //2 封装成标准表的对象
            List<TgAccountSeqInfoEntity> entities = getTgAccountSeqInfoData(tenant, trustNo, tgAccountBalanceInfos);
            //3 调用 basicservice 的保存数据接口
            tgAccountSeqInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存TgAccountSeqInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<TgAccountSeqInfoEntity> getTgAccountSeqInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<AccountCurrentDetailDto2> tgAccountBalanceInfos) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        Map<String, String> paymentDirectionDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.PAYMENT_DIRECTION_SERVICE, trustNo);
        List<TgAccountSeqInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!tgAccountBalanceInfos.isEmpty()) {
            tgAccountBalanceInfos.forEach(item -> {
                result.add(TgAccountSeqInfoEntity.builder()
                        .fundCode(item.getFundCode())
                        .fundName(item.getFundName())
                        .transferInOut(MatchDataDictionaryKeyUtil.getKey(paymentDirectionDic, item.getWay()))//收付方向
                        .seqNo(com.friday.custody.utils.StrUtil.getSeqNoWithoutNoRandom(item.getSeqNo()))
                        .account(item.getAccount())
                        .accountName(item.getAccountName())
                        .opAccount(item.getOpAccount())
                        .opAccountName(item.getOpAccountName())
                        .amount(BigDecimalUtil.obj2BigDecimal(item.getAmount()))
                        .transferDate(item.getTransferDate())
                        .transferTime(StringUtils.isNotBlank(item.getTransferDate()) ? item.getTransferDate().split(" ")[1] : null)
                        .summary(item.getSummary())
                        .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, item.getCurrency()))
                        .updateDate(item.getUpdateTime())
                        .uniqueKey(tenant.getTenantName() + trustNo + item.getFundCode() + item.getAccount() + item.getOpAccount() + item.getTransferDate())
                        .admin(tenant.getTenantName())
                        .trustNo(trustNo)
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;
    }

    public void processTGAccountBalanceInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            ReentrantLock lock = new ReentrantLock();
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.10	托管户余额查询
            //这里需要传 银行账户 然后  有分页  需要按分页查
            Map<String, List<BalanceAccountDto2>> accountInfoMap = accountInfoTL.get();
            if (accountInfoMap == null || accountInfoMap.size() == 0 || CollectionUtils.isEmpty(accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE))) {
                log.error("银行账号查询为空：tenant:{},trustNo:{},today:{}", JSON.toJSONString(tenant), trustNo, today);
                return;
            }
            List<BalanceAccountDto2> tgAccountBalanceInfoData = htscGetDataUtil.processTGAccountBalanceInfoData(tenant, accountInfoMap.get(HtscConstants.TG_ACCOUNT_TYPE_CODE),
                    trustNo, htscUrlPrefix, today, lock);
            //2 封装成标准表的对象
            List<TgAccountBalanceInfoEntity> entities = getTGAccountBalanceInfoData(tenant, trustNo, tgAccountBalanceInfoData);
            //3 调用 basicservice 的保存数据接口
            tgAccountBalanceInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存TGAccountBalanceInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<TgAccountBalanceInfoEntity> getTGAccountBalanceInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, List<BalanceAccountDto2> tgAccountBalanceInfoData) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        List<TgAccountBalanceInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!tgAccountBalanceInfoData.isEmpty()) {
            tgAccountBalanceInfoData.forEach(item -> {
                result.add(TgAccountBalanceInfoEntity.builder()
                        .bankAcco(item.getAccount())
                        .accoBalance(BigDecimalUtil.obj2BigDecimal(item.getAcctBalance()))
                        .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, item.getCurrency()))
                        .updateDate(item.getUpdateTime())
                        .trustNo(trustNo)
                        .admin(tenant.getTenantName())
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }
        return result;
    }

    public void processCustInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.8
            Map<String, Object> custSharesData = htscGetDataUtil.processCustSharesData(tenant, trustNo, htscUrlPrefix, today);
            if (PullDataFailureUtil.isFailed(custSharesData)) {
                pullDataFailureLogService.saveErrorData(tenant.getTenantName(), custSharesData);
                return;
            }
            //2 封装成标准表的对象
            List<CustShareInfoEntity> entities = getCustInfoData(tenant, trustNo, custSharesData);
            //3 调用 basicservice 的保存数据接口
            custShareInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存CustInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    private List<CustShareInfoEntity> getCustInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> custSharesData) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        Map<String, String> dividendWayDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.DIVIDEND_WAY_SERVICE, trustNo);
        Map<String, String> sellerDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SELLER_SERVICE, "华泰证券");
        //查询出当前管理人产品信息
        List<FundInfoEntity> fundInfoEntities = fundInfoService.getFundCodeAndFundNameAndInnerFundCodeFromFundInfo(tenant.getTenantName(), trustNo);
        Map<String, String> fundCodeMap = fundInfoEntities.stream().collect(Collectors.toMap(FundInfoEntity::getInnerFundCode, FundInfoEntity::getFundCode));
        List<CustShareInfoEntity> result = new ArrayList<>();
        Date now = new Date();
        if (!custSharesData.isEmpty()) {
            custSharesData.entrySet().forEach(entry -> {
                ArrayList<Object> dtos = (ArrayList<Object>) entry.getValue();
                dtos.forEach(item -> {
                    CustSharesDto dto = (CustSharesDto) item;
                    String fundCode = fundCodeMap.get(dto.getC_fundcode());
                    result.add(CustShareInfoEntity.builder()
                            .fundAcco(dto.getC_fundacco())
                            .agencyNo(MatchDataDictionaryKeyUtil.getKey(sellerDic, dto.getC_agencyno()))
                            .innerFundCode(dto.getC_fundcode())
                            .fundName(dto.getC_fundname())
                            .lastHoldShares(BigDecimalUtil.obj2BigDecimal(dto.getF_lastholdshares()))
                            .netValueDate(ObjectUtils.isEmpty(dto.getD_cdate()) ? null : dto.getD_cdate())
                            .totalProfit(BigDecimalUtil.obj2BigDecimal(dto.getF_totalprofit()))
                            .realShares(BigDecimalUtil.obj2BigDecimal(dto.getF_realshares()))
                            .bonusMethod(MatchDataDictionaryKeyUtil.getKey(dividendWayDic, dto.getC_bonustype()))
                            .agencyName(dto.getC_agencyname())
                            .tradeAcco(dto.getC_tradeacco())
                            .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, dto.getF_currency().getCode()))
                            .frozenShares(BigDecimalUtil.obj2BigDecimal(dto.getF_frozenshares()))
                            .fundCode(fundCode)
                            //（根据此条响应数据的最新持有份额数last_hold_shares减去冻结份额frozen_shares得到可用份额作为此字段数据）需将String类型数据转换为Number(20,2)
                            .enableShares(BigDecimalUtil.obj2BigDecimal(dto.getF_lastholdshares()).subtract(BigDecimalUtil.obj2BigDecimal(dto.getF_frozenshares())).setScale(2, RoundingMode.HALF_DOWN))
                            //（1.根据此数据的基金备案代码fund_code、基金名称fund_name、净值日期net_value_date，在fund_net_value_info中找到匹配的记录，获取记录中的资产份额shares；
                            //2.用此数据的最新持有份额数last_hold_shares除以shares乘以100
                            //得到此字段的数据）需将String类型数据转换为Decimal(8,4)
                            // entity.setUniqueKey(entity.getAdmin() + entity.getFundCode() + entity.getFundName()  entity.getNetValueDate());
                            .shareRatio(BigDecimalUtil.getShareRatio(fundNetValueInfoService.getSharesByUniqueKey(FundNetValueInfoEntity.builder().uniqueKey(tenant.getTenantName() + fundCode + dto.getC_fundname() + dto.getD_cdate() ).build()),BigDecimalUtil.obj2BigDecimal(dto.getF_lastholdshares())))
                            .trustNo(trustNo)
                            .admin(tenant.getTenantName())
                            .createTime(now)
                            .updateTime(now)
                            .build());

                });
            });
        }
        return result;
    }

    private String getFundCode(CustSharesDto dto) {
        String s = fundNameAndCodeTL.get().get(dto.getC_fundname());
        if (StringUtils.isNotBlank(s)) {
            return s;
        }
        return dto.getC_fundcode();
    }


    private void processFundNetValueInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today, Integer loopNum) {
        //2.1.5 1分钟内同一个appId最多20次调用
        //这个接口是需要传日期的 那么拿三年内所有的数据
        //净值日期开始，固定格式yyyyMMdd（开始时间和结束时间跨度建议一个月，否则可能会超时）
        String startDate;
        String endDate;
        LocalDate localDate = LocalDate.now();
        LocalDate lastMonth;
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        endDate = dateTimeFormatter.format(localDate);
        ReentrantLock lock = new ReentrantLock();
        Map<String, String> stringStringMap = fundNameAndCodeTL.get();
        loopNum = (loopNum == 12 ? 24 : 1);
        for (int i = 0; i < loopNum; i++) {
            lastMonth = localDate.minusDays(15);
            startDate = dateTimeFormatter.format(lastMonth);
            String finalStartDate = startDate;
            String finalEndDate = endDate;
            CompletableFuture.runAsync(() -> {
                try {
                    //1 获取数据
                    Map<String, Object> fundValueData = htscGetDataUtil.processFundValueData(tenant, trustNo, htscUrlPrefix, today, finalStartDate, finalEndDate, lock);
                    if (PullDataFailureUtil.isFailed(fundValueData)) {
                        pullDataFailureLogService.saveErrorData(tenant.getTenantName(), fundValueData);
                    }
                    //2 封装数据
                    List<FundNetValueInfoEntity> entities = getFundNetValueInfoData(tenant, trustNo, fundValueData, stringStringMap);
                    //3 保存数据
                    fundNetValueInfoService.saveData(null, entities);
                    //4 更改基金持有人信息的基金持有份额比例
                    List<CustShareInfoEntity> updateSharesDto = new ArrayList<>();
                    for (FundNetValueInfoEntity entity : entities) {
                        List<CustShareInfoEntity> custShareInfoEntities = custShareInfoService.queryByFundInfo(CustShareInfoEntity.builder().innerFundCode(entity.getInnerFundCode()).fundName(entity.getFundName()).netValueDate(entity.getNetValueDate()).build());
                        if (!CollectionUtils.isEmpty(custShareInfoEntities)) {
                            custShareInfoEntities.forEach(custShareInfoEntity -> {
                                BigDecimal shareRatio = BigDecimalUtil.getShareRatio(entity.getShares(), custShareInfoEntity.getLastHoldShares());
                                if (shareRatio != null) {
                                    custShareInfoEntity.setShareRatio(shareRatio);
                                    updateSharesDto.add(custShareInfoEntity);
                                }
                            });
                        }
                        if (!CollectionUtils.isEmpty(updateSharesDto)) {
                            custShareInfoService.updateShares(updateSharesDto);
                        }
                    }
                } catch (Exception e) {
                    log.error("批量保存fundNetValueInfo失败！", e);
                }
                // TODO: 2022/1/13 加一个处理失败的日志统计表
            }, pullDataThreadPool);
            localDate = lastMonth;
            endDate = startDate;
        }
    }


    private List<FundNetValueInfoEntity> getFundNetValueInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> fundValueData, Map<String, String> stringStringMap) {
        Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, trustNo);
        Map<String, String> fundClassificationLogoDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.FUND_CLASSIFICATION_LOGO_SERVICE, trustNo);

        List<FundNetValueInfoEntity> result = new ArrayList<>();
        if (!fundValueData.isEmpty()) {
            fundValueData.entrySet().forEach(entry -> {
                ArrayList<Object> dtos = (ArrayList<Object>) entry.getValue();
                dtos.forEach(item -> {
                    FundValueDto dto = (FundValueDto) item;
                    String fundCode = null;
                    if (!Objects.isNull(dto.getC_fundcode())) {
                        fundCode = dto.getC_fundcode();
                        //当fundLevel=2时，fundCode取fundInfo表中的innerFundCode=dto.getC_fundcode()的值
                        if ("2".equals(dto.getFund_level())) {
                            fundCode = fundInfoService.getFundCodeByInnerFundCode(fundCode, trustNo);
                        }
                    }
                    result.add(FundNetValueInfoEntity.builder()
                            .innerFundCode(dto.getC_fundcode())
                            .fundCode(fundCode)
                            .fundName(dto.getC_fundname())
                            .netValueDate(dto.getD_date())
                            .netValue(BigDecimalUtil.obj2BigDecimal(dto.getF_netvalue(), 4))
                            .totalNetValue(BigDecimalUtil.obj2BigDecimal(dto.getF_totalnetvalue(), 4))
                            .shares(BigDecimalUtil.obj2BigDecimal(dto.getF_todayshares()))
                            .dividendYield(BigDecimalUtil.obj2BigDecimal(dto.getF_dividendyield()))
                            .assetValue(BigDecimalUtil.obj2BigDecimal(dto.getF_todayassetnetvalue()))
                            .totalAsset(BigDecimalUtil.obj2BigDecimal(dto.getF_todaytotalasset()))
                            .currency(MatchDataDictionaryKeyUtil.getKey(currencyDic, dto.getF_currency().getCode()))
                            .accumulated(BigDecimalUtil.obj2BigDecimal(dto.getF_accumulated()))
                            .fundLevel(MatchDataDictionaryKeyUtil.getKey(fundClassificationLogoDic, dto.getFund_level()))
                            .admin(tenant.getTenantName())
                            .trustNo(trustNo)
                            .createTime(new Date())
                            .updateTime(new Date())
                            .build());
                });

            });
        }
        return result;
    }

    private String getFundCode(FundValueDto dto, Map<String, String> stringStringMap) {
        if (!Objects.isNull(dto.getC_fundcode())) {
            if ("2".equals(dto.getFund_level())) {
                String fundCode = stringStringMap.get(dto.getC_fundname());
                if (StringUtils.isNotBlank(fundCode)) {
                    return fundCode;
                }
            }
        }
        return dto.getF_convertcode();
    }

    @Autowired
    HTSCGetDataUtil htscGetDataUtil;
    private static final ThreadLocal<Map<String, String>> fundNameAndCodeTL = new ThreadLocal<>();
    private static final ThreadLocal<Map<String, String>> fundInnerCodeAndCodeTL = new ThreadLocal<>();

    public void processFundInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.1
            Map<String, Object> prdtInfoMap = htscGetDataUtil.processPrdtInfo(tenant, trustNo, htscUrlPrefix, today);
            if (PullDataFailureUtil.isFailed(prdtInfoMap)) {
                pullDataFailureLogService.saveErrorData(tenant.getTenantName(), prdtInfoMap);
                return;
            }
            //将母基金封装成标准表的对象
            List<FundInfoEntity> entities = getPrdtFundInfoData(tenant, trustNo, prdtInfoMap);
            //保存数据
            fundInfoService.saveData(null, entities);
            //获取子基金
            //2.1.2
            Map<String, Object> childPrdtInfo = htscGetDataUtil.processChildPrdtInfo(tenant, trustNo, htscUrlPrefix, today);
            if (PullDataFailureUtil.isFailed(childPrdtInfo)) {
                pullDataFailureLogService.saveErrorData(tenant.getTenantName(), childPrdtInfo);
                return;
            }
            //将子基金封装成标准表的对象
            List<FundInfoEntity> childEntities = getChildFundInfoData(tenant, trustNo, prdtInfoMap, childPrdtInfo);
            //保存子基金
            fundInfoService.saveData(null, childEntities);

            //2.2 将基金名称与code放入tl中
            Map<String, String> collect = entities.stream().collect(Collectors.toMap(FundInfoEntity::getFundName, FundInfoEntity::getFundCode));
            fundNameAndCodeTL.set(collect);
            //2.3 将基金备案代码与内部代码放入tl中
            Map<String, String> innerFundCodeMap = new HashMap<>();
            entities.forEach(fundInfoEntity -> {
                innerFundCodeMap.put(fundInfoEntity.getInnerFundCode(), fundInfoEntity.getFundCode());
            });
            fundInnerCodeAndCodeTL.set(innerFundCodeMap);
            //3 调用 basicservice 的保存数据接口
            fundInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存fundIno失败！", e);
        }
        // TODO: 2022/1/13 加一个处理失败的日志统计表
    }


    private void processInvestorInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, String today) {
        try {
            //1 先拿到需要的数据 这里拿到已经处理好了的数据
            //2.1.3
            Map<String, Object> investmentSubjects = htscGetDataUtil.processInvestmentSubjects(tenant, trustNo, htscUrlPrefix, today);
            if (PullDataFailureUtil.isFailed(investmentSubjects)) {
                pullDataFailureLogService.saveErrorData(tenant.getTenantName(), investmentSubjects);
                return;
            }
            //2 封装成标准表的对象
            List<InvestorInfoEntity> entities = getInvestorInfoData(tenant, trustNo, investmentSubjects);
            //3 调用 basicservice 的保存数据接口
            investorInfoService.saveData(null, entities);
        } catch (Exception e) {
            log.error("批量保存InvestorInfo失败！", e);
            // TODO: 2022/1/13 加一个处理失败的日志统计表
        }
    }

    @Autowired
    DataDictionaryTypeCertificateCsdcService certificateCsdcService;

    /**
     * 封装基金产品开放信息表数据
     *
     * @param tenant
     * @param trustNo
     * @param fundValueData
     * @return
     */
    private List<InvestorInfoEntity> getInvestorInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> fundValueData) {
        Map<String, String> custTypeCsdcDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CUST_TYPE_CSDC_SERVICE, trustNo);
        Map<String, Map<String, String>> identityTypeCsdcDic = certificateCsdcService.queryCertificateWithDetail(trustNo);
        List<InvestorInfoEntity> result = new ArrayList<>();
        if (!fundValueData.isEmpty()) {
            Date now = new Date();
            fundValueData.entrySet().forEach(entry -> {
                InvestmentSubjectsDto dto = (InvestmentSubjectsDto) entry.getValue();
                String key = MatchDataDictionaryKeyUtil.getKey(custTypeCsdcDic, dto.getC_custtype_zd().getCode());
                result.add(InvestorInfoEntity.builder()
                        .fundAcco(dto.getC_fundacco())
                        .custType(key)
                        .custName(dto.getC_custname())
                        .identityType(MatchDataDictionaryKeyUtil.getKey(key, identityTypeCsdcDic, dto.getC_identitytype_zd().getCode()))
                        .identityNo(dto.getC_identityno())
                        .trustNo(trustNo)
                        .admin(tenant.getTenantName())
                        .createTime(now)
                        .updateTime(now)
                        .build());
            });
        }

        return result;
    }

    /**
     * @param tenant
     * @param trustNo
     * @param prdtInfoMap
     * @return
     */
    private List<FundInfoEntity> getPrdtFundInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> prdtInfoMap) {
        //1 查询字典表 ：之后可优化 暂时先把它做成这种不灵活的吧
        // Map<String, String>  key为字典的key value 为detail所对应的具体的值
        Map<String, String> fundOrganizationDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.FUND_ORGANIZATION_SERVICE, trustNo);
        Map<String, String> fundTypeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.FUND_TYPE_SERVICE, trustNo);
        Map<String, String> productStatusDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.PRODUCT_STATUS_SERVICE, trustNo);
        Map<String, String> fundOperationDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.FUND_OPERATION_FORM_SERVICE, trustNo);
//        Map<String, String> durationTypeDic = durationTypeService.queryWithDetail(trustNo);
        Map<String, String> settlementModeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SETTLEMENT_MODE_SERVICE, trustNo);
        Map<String, String> serviceModelDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.SERVICE_MODEL_SERVICE, trustNo);
        Map<String, String> durationTypeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.DURATION_TYPE_SERVICE, trustNo);
        Map<String, String> privateFundTypeDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.PRIVATE_FUND_TYPE_SERVICE, trustNo);

        //2 封装数据
        List<FundInfoEntity> result = new ArrayList<>();
        Date date = new Date();
        prdtInfoMap.forEach((key, value) -> {
            //2.1 先是生成一条记录
            PrdtInfoDto prdtInfoPO = (PrdtInfoDto) value;
            //获取托管外包编码、托管编码
            String trust = trustService.selectKey(prdtInfoPO.getOutSourceOrg());
            String trustPersonNo = trustService.selectKey(prdtInfoPO.getTrusteeName());
            FundInfoEntity entity = FundInfoEntity.builder()
                    .fundCode(prdtInfoPO.getFund_record_code())
                    .innerFundCode(prdtInfoPO.getFundcode())
                    .fundName(prdtInfoPO.getFundname())
                    .orgForm(MatchDataDictionaryKeyUtil.getKey(fundOrganizationDic, prdtInfoPO.getOrg_form().getCode()))
                    .fundType(MatchDataDictionaryKeyUtil.getKey(fundTypeDic, prdtInfoPO.getFundtype().getCode()))
                    .peType(MatchDataDictionaryKeyUtil.getKey(privateFundTypeDic, prdtInfoPO.getFundtype().getCode()))
                    .operationMethod(MatchDataDictionaryKeyUtil.getKey(fundOperationDic, prdtInfoPO.getOperation_method().getCode()))
                    .durationType(MatchDataDictionaryKeyUtil.getKey(durationTypeDic, prdtInfoPO.getDuration_type().getCode()))
                    .durationPeriod(prdtInfoPO.getDuration_period())
                    .settlementType(MatchDataDictionaryKeyUtil.getKey(settlementModeDic, prdtInfoPO.getSettlement_type().getCode()))
                    .serviceMode(MatchDataDictionaryKeyUtil.getKey(serviceModelDic, prdtInfoPO.getBusType()))
                    .createDate(prdtInfoPO.getCreate_date())//固定格式：yyyyMMdd
                    .fundLevel(prdtInfoPO.getHave_child_flag())
                    .fundStatus(MatchDataDictionaryKeyUtil.getKey(productStatusDic, prdtInfoPO.getPrdtStatus()))
                    .createTime(date)
                    .updateTime(date)
                    .admin(tenant.getTenantName())
                    .trustNo(StringUtils.isBlank(trust) ? trustNo: trust)
                    .trustPersonNo(trustPersonNo)
                    .build();
            log.info("转换前：{}", JSON.toJSONString(prdtInfoPO));
            log.info("转换后：{}", JSON.toJSONString(entity));
            result.add(entity);
        });
        return result;
    }

    /**
     * @param tenant
     * @param trustNo
     * @param prdtInfoMap
     * @return
     */
    private List<FundInfoEntity> getChildFundInfoData(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Map<String, Object> prdtInfoMap, Map<String, Object> childPrdtInfo) {
        //1.获取字典
        //2 封装数据
        List<FundInfoEntity> result = new ArrayList<>();
        Date date = new Date();
        prdtInfoMap.forEach((key, value) -> {
            //2.1 先是生成一条记录
            PrdtInfoDto prdtInfoPO = (PrdtInfoDto) value;
            //2.2 然后查询childPrdtInfo 生成子基金数据
            if (childPrdtInfo.containsKey(prdtInfoPO.getFund_record_code())) {
                List<ChildPrdtInfoDto> childPrdtInfos = (List<ChildPrdtInfoDto>) childPrdtInfo.get(prdtInfoPO.getFund_record_code());
                if (!CollectionUtils.isEmpty(childPrdtInfos)) {
                    childPrdtInfos.forEach(childPrdtInfoPO -> {
                        log.info("子基金：{}", JSON.toJSONString(childPrdtInfoPO));
                        //若响应数据母基金代码parentprdtcode和基金代码fundcode不同，则为分级子基金
                        if (!childPrdtInfoPO.getFundcode().equals(childPrdtInfoPO.getParentprdtcode())) {
                            //在fund_info中找到inner_fund_code等于此响应数据母基金代码parentprdtcode对应的记录，获取外包机构、托管人名称字段
                            FundInfoEntity fundInfoEntity = fundInfoService.queryByInnerFundCode(childPrdtInfoPO.getParentprdtcode(), trustNo, tenant.getTenantName());
                            String childTrustNo = null;
                            String childTrustPerNo = null;
                            if (!Objects.isNull(fundInfoEntity)){
                                childTrustNo = fundInfoEntity.getTrustNo();
                                childTrustPerNo = fundInfoEntity.getTrustPersonNo();
                            }
                            result.add(FundInfoEntity.builder()
                                    .innerFundCode(childPrdtInfoPO.getFundcode())
                                    .fundName(childPrdtInfoPO.getFundname())
                                    .fundCode(childPrdtInfoPO.getParentPrdtConvertCode())
                                    .fundLevel("2")
                                    .trustNo(StringUtils.isBlank(childTrustNo) ? trustNo : childTrustNo)//托管人外包编码
                                    .trustPersonNo(childTrustPerNo)//托管人编码
                                    .admin(tenant.getTenantName())
                                    .createTime(date)
                                    .updateTime(date)
                                    .build());
                        }
                    });
                }
            }
        });
        return result;
    }

    @Override
    public R updateDataDaily(List<TenantEscrowAuthorizationInfoDto> tenants) {
        return null;
    }

    @Override
    public R updateSingleDataDaily(TenantEscrowAuthorizationInfoDto tenant) {
        if (!Objects.isNull(tenant)) {
            getDataFromHTSCAt7AM(tenant, CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME);
        }
        return null;
    }

    @Override
    public R PullRemoteDataFirstQueueJob() {
        return null;
    }

    @Override
    public R initLaterTenantData(String versionStr, List<TenantEscrowAuthorizationInfoDto> tenants) {
        return null;
    }

    @Override
    public R initLaterSingleTenantData(String versionStr, TenantEscrowAuthorizationInfoDto tenant) {
        log.info("initLaterSingleTenantData is begin...the versionStr is {} ,the tenant is {}", versionStr, JSON.toJSONString(tenant));
        String trustNo = tenant.getTrustNo();
        Integer loopNum = CustodyContants.INIT_BY_MONTH_LOOP_TIME;
//        Integer loopNum = CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME;
        String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
        try {
            //获取账户信息
            getAccountInfoByTrustNoAndAdmin(trustNo, tenant.getTenantName());
            Map<String, List<BalanceAccountDto2>> stringListMap = accountInfoTL.get();
            log.info("updateSingleData1h缓存中的信息accountInfoTL为{}", JSON.toJSONString(stringListMap));
            //数据库查不到从上游查
            if (CollectionUtils.isEmpty(stringListMap)) {
                processAccountInfoData(tenant, trustNo, today);
            }
            switch (versionStr) {
                case CustodyContants.R_PT_S_CUSTODY_3_2_6:
                    processMJAccountSeqInfoHistoryData(tenant, tenant.getTrustNo(), today, loopNum);
//                log.info("更新华泰募集户历史流水完成!");
//                processMJAccountSeqCurrentInfoData(tenant, tenant.getTrustNo(), today,loopNum);
                    log.info("更新华泰募集户当天流水完成!");
                    processTGAccountSeqInfoHistoryData(tenant, tenant.getTrustNo(), today, loopNum);
                    log.info("更新华泰托管户历史流水完成!");
//                processTGAccountSeqInfoCurrentData(tenant, tenant.getTrustNo(), today, loopNum);
//                log.info("更新华泰托管户当天流水完成!");
                    break;
            }
        } catch (Exception e) {
            log.error("华泰更新流水表失败!", e);
        }
        return null;

    }

    private void initTenantDataBy304(TenantEscrowAuthorizationInfoDto tenant, String trustNo, Integer loopNum) {
        String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
        try {
            log.info("318patch01：htsc更新fundOpenInfo数据开始");
            processFundOpenInfoData(tenant, trustNo, loopNum);
            log.info("318patch01：htsc更新fundOpenInfo数据结束！");
        } catch (Exception e) {
            log.error("initTenantDataBy304 is error:", e);
        } finally {
            fundInnerCodeAndCodeTL.remove();
        }
    }

    @Override
    public R initByAppointedTrustNoAndMouth(TenantEscrowAuthorizationInfoDto tenant, int mouth) {
        log.info("initByAppointedTrustNoAndMouth_tenant={},mouth={}",JSON.toJSONString(tenant),mouth);
        getDataFromHTSC(tenant,mouth);
        return R.ok();
    }

    @Override
    public R updateData20min(List<TenantEscrowAuthorizationInfoDto> data) {
        return null;
    }

    @Override
    public R updateSingleData20min(TenantEscrowAuthorizationInfoDto tenant) {
        return null;
    }

    @Override
    public R updateData1h(List<TenantEscrowAuthorizationInfoDto> data) {
        return null;
    }

    @Override
    public R updateSingleData1h(TenantEscrowAuthorizationInfoDto tenant) {
        log.info("updateSingleData1h_华泰托管数据每1小时更新开始.....授权信息为{}",JSON.toJSONString(tenant));
        try {
            String trustNo = tenant.getTrustNo();
            String tenantName = tenant.getTenantName();
            Integer loopNum = CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME;
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
            //获取账户信息
            getAccountInfoByTrustNoAndAdmin(trustNo,tenantName);
            Map<String, List<BalanceAccountDto2>> stringListMap = accountInfoTL.get();
            log.info("updateSingleData1h缓存中的信息accountInfoTL为{}",JSON.toJSONString(stringListMap));
            //数据库查不到从上游查
            if(CollectionUtils.isEmpty(stringListMap)) {
                processAccountInfoData(tenant,trustNo,today);
            }

            //19 募集⼾流⽔表（MJAccount_seq_info） (2.1.14 募集户当日流水查询)
            processMJAccountSeqCurrentInfoData(tenant, trustNo, today, loopNum);
            log.info("updateSingleData1h_进度：(1/4)");
            //16 募集⼾银⾏账⼾余额信息表（MJAccount_balance_info）
            processMJAccountBalanceInfoData(tenant, trustNo, today);
            log.info("updateSingleData1h_进度：(2/4)");
            //17 托管⼾流⽔表（TGAccount_seq_info） （2.1.11 当日查询）
            processTGAccountSeqInfoCurrentData(tenant, trustNo, today, loopNum);
            log.info("updateSingleData1h_进度：(3/4)");
            //16 托管⼾银⾏账⼾余额信息表（TGAccount_balance_info）
            processTGAccountBalanceInfoData(tenant, trustNo, today);
            log.info("updateSingleData1h_进度：(4/4) 华泰托管数据每1小时更新结束....");

            log.info("华泰托管数据每1小时更新结束....");
            return R.ok("华泰托管数据每1小时更新完成");
        }finally {
            accountInfoTL.remove();
        }
    }

    @Override
    public R updateDataCJ(String date, String flag, TenantEscrowAuthorizationInfoDto tenant) {
        return null;
    }

    @Override
    public R updateNetValueData(List<TenantEscrowAuthorizationInfoDto> tenants) {
        return null;
    }

    @Override
    public R updateSingleNetValueData(TenantEscrowAuthorizationInfoDto tenant) {
        log.info("华泰每8点、12点、20点更新数据开始！");
        try{
            String trustNo = tenant.getTrustNo();
            Integer loopNum = CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME;
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
            processFundNetValueInfoData(tenant, trustNo, today, loopNum);
            log.info("华泰每8点、12点、20点更新数据结束！");
        }catch(Exception e){
            log.error("华泰每8点、12点、20点更新数据异常！", e);
        }
        return R.ok("华泰每8点、12点、20点更新数据完成！");
    }

    @Override
    public DubboResult updateMJBalanceData(TenantEscrowAuthorizationInfoDto tenant) {
        try {
            String trustNo = tenant.getTrustNo();
            int loopNum = CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME;
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";

            // 募集户银行账户余额信息表（MJAccount_balance_info）
            processMJAccountBalanceInfoData(tenant, trustNo, today);
            return DubboResult.newSuccess();
        } catch (Exception e) {
            log.error("手动更新募集户余额失败", e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "手动更新募集户余额失败" + e.getMessage());
        }
    }

    @Override
    public DubboResult updateMJSeqData(TenantEscrowAuthorizationInfoDto tenant) {
        try {
            String trustNo = tenant.getTrustNo();
            int loopNum = CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME;
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";

            // 募集户流⽔表（MJAccount_seq_info） (2.1.14 募集户当日流水查询)
            processMJAccountSeqCurrentInfoData(tenant, trustNo, today, loopNum);
            return DubboResult.newSuccess();
        } catch (Exception e) {
            log.error("手动更新募集户流水失败", e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "手动更新募集户流水失败" + e.getMessage());
        }
    }

    @Override
    public DubboResult updateTGBalanceData(TenantEscrowAuthorizationInfoDto tenant) {
        try {
            String trustNo = tenant.getTrustNo();
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
            // 托管户银行账户余额信息表（TGAccount_balance_info）
            processTGAccountBalanceInfoData(tenant, trustNo, today);
            return DubboResult.newSuccess();
        } catch (Exception e) {
            log.error("手动更新托管户余额失败", e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "手动更新托管户余额失败" + e.getMessage());
        }
    }

    @Override
    public DubboResult updateTGSeqData(TenantEscrowAuthorizationInfoDto tenant) {
        try {
            String trustNo = tenant.getTrustNo();
            int loopNum = CustodyContants.UPDATE_BY_MONTH_DAILY_LOOP_TIME;
            String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "Z";
            // 托管户流水表（TGAccount_seq_info） （2.1.11 当日查询）
            processTGAccountSeqInfoCurrentData(tenant, trustNo, today, loopNum);
            return DubboResult.newSuccess();
        } catch (Exception e) {
            log.error("手动更新托管户流水失败", e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "手动更新托管户流水失败" + e.getMessage());
        }
    }

    public ThreadLocal<Map<String, List<BalanceAccountDto2>>> getAccountInfoByTrustNoAndAdmin(String trustNo,String tenantName){
        Map<String, List<BalanceAccountDto2>> accountInfos = new HashMap<>();
        List<AccountInfoEntity> mjAccountInfoList = accountInfoService.getListByAccTypeCodeAndAdmin(trustNo, HtscConstants.MJ_ACCOUNT_TYPE_CODE,tenantName);
        List<AccountInfoEntity> tgAccountInfoList = accountInfoService.getListByAccTypeCodeAndAdmin(trustNo, HtscConstants.TG_ACCOUNT_TYPE_CODE,tenantName);
        List<BalanceAccountDto2> mjBalanceAccountDtoList = new ArrayList<>();
        List<BalanceAccountDto2> tgBalanceAccountDtoList = new ArrayList<>();
        if (mjAccountInfoList != null){
            for (AccountInfoEntity mjAccInfo : mjAccountInfoList) {
                BalanceAccountDto2 balanceAccountDto2 = new BalanceAccountDto2();
                balanceAccountDto2.setFundCode(mjAccInfo.getFundCode());
                balanceAccountDto2.setFundName(mjAccInfo.getFundName());
                balanceAccountDto2.setAccount(mjAccInfo.getBankAcco());
                mjBalanceAccountDtoList.add(balanceAccountDto2);
            }
        }
        if (tgAccountInfoList != null) {
            for (AccountInfoEntity tgAccInfo : tgAccountInfoList) {
                BalanceAccountDto2 balanceAccountDto2 = new BalanceAccountDto2();
                balanceAccountDto2.setFundCode(tgAccInfo.getFundCode());
                balanceAccountDto2.setFundName(tgAccInfo.getFundName());
                balanceAccountDto2.setAccount(tgAccInfo.getBankAcco());
                tgBalanceAccountDtoList.add(balanceAccountDto2);
            }
        }
        accountInfos.put(HtscConstants.MJ_ACCOUNT_TYPE_CODE,mjBalanceAccountDtoList);
        accountInfos.put(HtscConstants.TG_ACCOUNT_TYPE_CODE,tgBalanceAccountDtoList);
        accountInfoTL.set(accountInfos);
        return accountInfoTL;
    }

    public ThreadLocal<Map<String, List<BalanceAccountDto2>>> getAccountInfo(String trustNo){
        Map<String, List<BalanceAccountDto2>> accountInfos = new HashMap<>();
        List<AccountInfoEntity> mjAccountInfoList = accountInfoService.getListByAccTypeCode(trustNo, HtscConstants.MJ_ACCOUNT_TYPE_CODE);
        List<AccountInfoEntity> tgAccountInfoList = accountInfoService.getListByAccTypeCode(trustNo, HtscConstants.TG_ACCOUNT_TYPE_CODE);
        List<BalanceAccountDto2> mjBalanceAccountDtoList = new ArrayList<>();
        List<BalanceAccountDto2> tgBalanceAccountDtoList = new ArrayList<>();
        if (mjAccountInfoList != null){
            for (AccountInfoEntity mjAccInfo : mjAccountInfoList) {
                BalanceAccountDto2 balanceAccountDto2 = new BalanceAccountDto2();
                balanceAccountDto2.setFundCode(mjAccInfo.getFundCode());
                balanceAccountDto2.setFundName(mjAccInfo.getFundName());
                balanceAccountDto2.setAccount(mjAccInfo.getBankAcco());
                mjBalanceAccountDtoList.add(balanceAccountDto2);
            }
        }
        if (tgAccountInfoList != null) {
            for (AccountInfoEntity tgAccInfo : tgAccountInfoList) {
                BalanceAccountDto2 balanceAccountDto2 = new BalanceAccountDto2();
                balanceAccountDto2.setFundCode(tgAccInfo.getFundCode());
                balanceAccountDto2.setFundName(tgAccInfo.getFundName());
                balanceAccountDto2.setAccount(tgAccInfo.getBankAcco());
                tgBalanceAccountDtoList.add(balanceAccountDto2);
            }
        }
        accountInfos.put(HtscConstants.MJ_ACCOUNT_TYPE_CODE,mjBalanceAccountDtoList);
        accountInfos.put(HtscConstants.TG_ACCOUNT_TYPE_CODE,tgBalanceAccountDtoList);
        accountInfoTL.set(accountInfos);
        return accountInfoTL;
    }
    @Autowired
    private TenantEmailAuthorizationInfoServiceImpl tenantEmailInfoService;

    @Autowired
    MailNewUtils mailNewUtils;

    @Autowired
    public Environment env;

    public static final String[]  OFFICIAL_PROFILES= {"prod","ty-prod"};

    //华泰-邮件获取估值表内容
    private void contentsOfValuationSheetByEmail(TenantEscrowAuthorizationInfoDto tenant, String trustNo, int loopNum){
        log.info("contentsOfValuationSheetByEmail入参tenant={},trustNo={},loopNum={}",tenant,trustNo,loopNum);
        List<TenantEmailAuthorizationInfoEntity> tenantEmailAuthorizationInfoEntities = tenantEmailInfoService.queryByTenantName(tenant.getTenantName(), trustNo);
        for (TenantEmailAuthorizationInfoEntity tenantEmail : tenantEmailAuthorizationInfoEntities) {
            String purpose = EmailConfigPurposeEnum.ALL.getPurpose();
            log.info("contentsOfValuationSheetByEmail:ALL.getPurpose={},tenantEmail.getPurpose={}",purpose,tenantEmail.getPurpose());
            if (purpose.equals(tenantEmail.getPurpose())) {
                log.info("contentsOfValuationSheetByEmail 配置的邮件为all，跳过,{}",tenantEmail.getPurpose());
                continue;
            }
            if (!ObjectUtils.isEmpty(tenantEmail)) {
                try {
                    //获取邮件
                    LocalDate localDate = LocalDate.now();
                    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                    String startTime = dateTimeFormatter.format(localDate.minusMonths(loopNum));
                    //拿到所有邮件
                    //Message[] weMessage = MailUtil.getWEMessage(tenantEmail.getEmailHost(), tenantEmail.getEmailAddress(), tenantEmail.getEmailSecret(), ZhongXinConstants.PROTOCOL);
                    Message[] weMessage = MailUtil.getMessageByTenantEmail(tenantEmail);
                    //mailNewUtils.printMessageInfo(weMessage,500);
//                    //快速筛选 方便测试
//                    log.info("updateTotalAsset_tenantEmail={}", JSON.toJSONString(tenantEmail));
//                    Message[] filterMessage = new Message[800];
//                    System.arraycopy(weMessage, weMessage.length - 2000, filterMessage, 0, 800);

                    //过滤符合条件的邮件
                    List<Message> messages = MailUtil.filterMessage(weMessage,
                            tenantEmail.getFromEmail(),
                            startTime,
                            tenantEmail.getEmailSubject(),
                            Boolean.TRUE);
                    //打印邮件相关信息 并且更新（ todo ）
                    if (!CollectionUtils.isEmpty(messages)) {
                        log.info("contentsOfValuationSheetByEmail_拿到的messages数量为{}", messages.size());

                        //生产且没有配置附件路径 就不保存
                        Profiles profiles = Profiles.of(OFFICIAL_PROFILES);
                        boolean isOfficial = env.acceptsProfiles(profiles);
                        String dirFilePath = tenantEmail.getDirFilePath();
                        if(StringUtils.isBlank(dirFilePath) && isOfficial){
                            log.info("生产环境且没有配置路径，不保存附件！");
                        }else{
                            if(StringUtils.isBlank(dirFilePath)){
                                dirFilePath = mailNewUtils.getDefaultAttachmentParentPath(tenantEmail);
                            }
                        }
                        for (Message message : messages) {
                            if(StringUtils.isNotBlank(dirFilePath)){
                                mailNewUtils.saveAttachment(message,dirFilePath);
                            }
                            MailUtil re = new MailUtil((MimeMessage) message);
                            handleHtscEmailMessage(message, tenant, re.getSubject());
                        }
                    } else {
                        log.error("华泰contentsOfValuationSheetByEmail 过滤后messages信息为空");
                        MailNewUtils.saveMailFailureLog(tenant.getTenantName(),trustNo,"华泰contentsOfValuationSheetByEmail 过滤后messages信息为空");
                    }


                } catch (ParseException pe) {
                    pe.printStackTrace();
                    log.error("时间转化异常！", pe);
                    MailNewUtils.saveMailFailureLog(tenant.getTenantName(),trustNo,"时间转化异常！"+pe);
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                    log.error("分析邮件异常！", ioe);
                    MailNewUtils.saveMailFailureLog(tenant.getTenantName(),trustNo,"分析邮件异常！"+ioe);
                } catch (MessagingException me) {
                    me.printStackTrace();
                    log.error("获取邮件异常！", me);
                    MailNewUtils.saveMailFailureLog(tenant.getTenantName(),trustNo,"获取邮件异常！"+me);
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("！", e);
                    MailNewUtils.saveMailFailureLog(tenant.getTenantName(),trustNo,"！"+e);
                }
            }
        }
    }
    public static final String NET_VALUE_TABLE = "产品估值表";

    public static final String START_ROW_FLAG = "科目代码";

    //不含中文
    public static final Pattern pattern = Pattern.compile("[^\\u4e00-\\u9fa5]+");

    public static final String DEFAULT_PATTERN = "yyyyMMdd";
    /**
     * 更新totalAsset
     *
     * @param part
     * @throws MessagingException
     * @throws IOException
     */
    public void handleEmailMessage(Part part, TenantEscrowAuthorizationInfoDto tenant, String subject) throws MessagingException, IOException, ParseException {
        String fileName;
        if (part.isMimeType("multipart/*")) {
            Multipart mp = (Multipart) part.getContent();
            BodyPart mPart;
            for (int i = 0; i < mp.getCount(); i++) {
                try{
                    mPart = mp.getBodyPart(i);
                    fileName = mPart.getFileName();
                    //
                    if ((fileName != null)) {
                        //SSR674_一村投资天天向上2号私募证券投资基金_产品估值表_日报_20221014
                        fileName = MailUtil.decodeText(fileName);
                        log.info("temp:subject={}",subject);
                        //取得时间
                        String date = com.friday.custody.utils.StrUtil.getDateStrFromStringByPattern(subject, MailUtil.DATE_PATTERN);
                        log.info("fileName:{}", fileName);
                        //fundCode基金代码
                        String fundCode= StringUtils.substringBefore(fileName,"_");
                        //一村投资天天向上2号私募证券投资基金_产品估值表_日报_20221014
                        String suffixName = StringUtils.substringAfter(fileName, "_");
                        //产品估值表_日报_20221014
                        String after = StringUtils.substringAfter(suffixName, "_");
                        //产品估值表
                        String valuationData=StringUtils.substringBefore(after, "_");
                        //valueDate估值日期
                        String valueDateBefore = StringUtils.substringAfterLast(after, "_");
                        String valueDate=StringUtils.substringBefore(valueDateBefore,".");
                        //fundName基金名称:一村投资天天向上2号私募证券投资基金
                        String fundName =StringUtils.substringBefore(suffixName, "_");

                        Workbook workbook = ExcelWriteUtil.getWorkbook(mPart.getInputStream());
                        Sheet sheetAt = workbook.getSheetAt(0);
                        boolean flag = false;
                        int rowNum = 0;
                        List<FundValueTableDataInfoEntity> entities = new ArrayList<>();
                        for (Row row : sheetAt) {
                            rowNum ++;
                            try{
                                if(Objects.isNull(row) || Objects.isNull(row.getCell(0))){
                                    flag = false;
                                    continue;
                                }
                                if(row.getCell(0).getCellTypeEnum().equals(CellType.STRING) && START_ROW_FLAG.equals(ExcelUtils.getStringValue(row.getCell(0)))){
                                    flag = true;
                                    continue;
                                }

                                //更新数据 fundValueDataInfo
                                if(flag){
                                    String cellx0 = ExcelUtils.getStringValue(row.getCell(0));
                                    boolean matches = pattern.matcher(cellx0).matches();
                                    if(!matches){
                                        log.info("该行第一个单元格内容为{},含有中文，跳过查询下一行",cellx0);
                                        flag = false;
                                        continue;
                                    }

                                    if(StringUtils.isBlank(cellx0)){
                                        log.info("该行第一个单元格内容为空,含有中文，跳过查询下一行",cellx0);
                                        flag = false;
                                        continue;
                                    }

                                    char keyChar = 'a';
                                    HashMap<Character,String> dataMap = new HashMap<>();
                                    for (int j = 0; j < 16; j++) {
                                        Cell cell = row.getCell(j);
                                        String stringValue = ExcelUtils.getStringValue(cell);
                                        dataMap.put(keyChar,stringValue);
                                        keyChar ++;
                                    }
                                    log.info("华泰估值表邮件解析第{}行，拿到的内容{}",rowNum,JSON.toJSONString(dataMap));
                                    Map<String, String> currencyDic = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, tenant.getTrustNo());//币种
                                    String currency = MatchDataDictionaryKeyUtil.getKey(currencyDic, dataMap.get('c'));

                                    FundValueTableDataInfoEntity build = FundValueTableDataInfoEntity.builder()
                                            .fundCode(fundCode)
                                            .fundName(fundName)
                                            .valueDate(valueDate)
                                            .kmCode(StringUtils.isBlank(dataMap.get('a'))?null:dataMap.get('a'))
                                            .kmName(StringUtils.isBlank(dataMap.get('b'))?null:dataMap.get('b'))
                                            .currency(currency)  //  c列
                                            .vaRate(StringUtils.isBlank(dataMap.get('d'))?null:new BigDecimal(dataMap.get('d')))  // d列
                                            .amount(StringUtils.isBlank(dataMap.get('e'))?null:new BigDecimal(dataMap.get('e')))  // e列
                                            .perCost(StringUtils.isBlank(dataMap.get('f'))?null:new BigDecimal(dataMap.get('f')))  // f列
                                            .origCost(StringUtils.isBlank(dataMap.get('g'))?null:new BigDecimal(dataMap.get('g')))  // g列
                                            .portCost(StringUtils.isBlank(dataMap.get('h'))?null:new BigDecimal(dataMap.get('h')))  // h列
                                            .costBi(StringUtils.isBlank(dataMap.get('i'))?null:new BigDecimal(dataMap.get('i')))  // i列
                                            .vapPrice(StringUtils.isBlank(dataMap.get('j'))?null:new BigDecimal(dataMap.get('j')))  // j列
                                            .origMv(StringUtils.isBlank(dataMap.get('k'))?null:new BigDecimal(dataMap.get('k')))  // k列
                                            .portMv(StringUtils.isBlank(dataMap.get('l'))?null:new BigDecimal(dataMap.get('l')))  //l列
                                            .mvBl(StringUtils.isBlank(dataMap.get('m'))?null:new BigDecimal(dataMap.get('m')))  //m列
                                            .origIv(StringUtils.isBlank(dataMap.get('n'))?null:new BigDecimal(dataMap.get('n')))  //n列
                                            .portIv(StringUtils.isBlank(dataMap.get('o'))?null:new BigDecimal(dataMap.get('o')))  //o列
                                            .suspension(StringUtils.isBlank(dataMap.get('p'))?null:dataMap.get('p'))  //p列
                                            .trustNo("34")
                                            .admin(tenant.getTenantName())
                                            .updateTime(new Date())
                                            .build();

                                    log.info("拿到的fundValueTableDataInfoEntity为{}",JSON.toJSONString(build));

                                    entities.add(build);
                                }
                            }catch (Exception e){
                                log.error("华泰估值表邮件解析第{}行异常,尝试解析下一行",rowNum,e);
                                MailNewUtils.saveMailFailureLog(tenant.getTenantName(),tenant.getTrustNo(),"邮件解析第"+rowNum+"行异常,尝试解析下一行");
                                continue;
                            }
                        }
                        if(!CollectionUtils.isEmpty(entities)){
                            log.info("从subject:{}中获取fundValueTableData 共计{}条",subject,entities.size());
                            fundValueTableDataInfoService.saveData(null,entities);
                        }else{
                            log.error("从subject:{}中未获得fundValueTableData",subject);
                            MailNewUtils.saveMailFailureLog(tenant.getTenantName(),tenant.getTrustNo(),"未获得fundValueTableData");
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }
    }

    public void handleHtscEmailMessage(Part part, TenantEscrowAuthorizationInfoDto tenant, String subject) throws MessagingException, IOException {
        log.info("华泰处理邮件 getTotalAsset tenant={} subject={}",JSON.toJSONString(tenant),subject);
        String fileName;
        Map<String, String> currencyDict = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.CURRENCY_SERVICE, tenant.getTrustNo());//币种
        Map<String, String> typeOfFundValuation = dataDictionaryBasicService.queryWithDetail(DataDictionaryTypeEnum.TYPE_OF_FUND_VALUATION, tenant.getTrustNo());
        Map<String, String> fundValuationMap = typeOfFundValuationService.queryKeyValue();

        if (part.isMimeType("multipart/*")) {
            Multipart mp = (Multipart) part.getContent();
            BodyPart mPart;
            for (int i = 0; i < mp.getCount(); i++) {
                try{
                    mPart = mp.getBodyPart(i);
                    fileName = mPart.getFileName();
                    if ((fileName != null)) {
                        fileName = MailUtil.decodeText(fileName);
                        HSSFWorkbook hssfWorkbook = new HSSFWorkbook(mPart.getInputStream());
                        HSSFSheet sheet = hssfWorkbook.getSheetAt(0);
                        HtscEmailDTO htscEmailDto = parseExcelHtsc(sheet, fileName, currencyDict, typeOfFundValuation, fundValuationMap);
                        log.error("htscEmailDto:{}",htscEmailDto);
                        FundNetValueInfoEntity fundNetValueInfo = htscEmailDto.getFundNetValueInfoEntity();
                        fundNetValueInfo.setAdmin(tenant.getTenantName());
                        fundNetValueInfo.setTrustNo(tenant.getTrustNo());
                        fundNetValueInfo.setUpdateTime(new Date());
                        //更新估值表
                        List<FundValueTableDataInfoEntity> fundValueTableDataInfoList = htscEmailDto.getFundValueTableDataInfoEntity();

                        if (!CollectionUtils.isEmpty(fundValueTableDataInfoList)){
                            for (FundValueTableDataInfoEntity entity: fundValueTableDataInfoList
                            ) {
                                entity.setAdmin(tenant.getTenantName());
                                entity.setTrustNo(tenant.getTrustNo());
                                entity.setCreateTime(new Date());
                                entity.setUpdateTime(new Date());
                            }
                        }
                        fundValueTableDataInfoService.saveData(null,fundValueTableDataInfoList);

                        // fundValuation
                        List<FundValuationEntity> fundValuationEntityList = htscEmailDto.getFundValuationEntityList();
                        if (!CollectionUtils.isEmpty(fundValuationEntityList)){
                            for (FundValuationEntity entity: fundValuationEntityList
                            ) {
                                entity.setAdmin(tenant.getTenantName());
                                entity.setTrustNo(tenant.getTrustNo());
                                entity.setCreateTime(new Date());
                                entity.setUpdateTime(new Date());
                            }
                        }
                        fundValuationService.saveData(null, fundValuationEntityList);
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    log.error("解析华泰估值表失败!", e);
                }
            }
        }
    }


    public HtscEmailDTO parseExcelHtsc(HSSFSheet sheet, String fileName, Map<String, String> currencyDic, Map<String, String> typeOfFundValuationDic, Map<String, String> fundValuationMap) {
        String fundName = null;
        String fundCode = null;
        String fundValueDate = null;
        BigDecimal totalAsset = new BigDecimal(0);
        HtscEmailDTO result = new HtscEmailDTO();
        int LAST_ROW_OF_SUBJECT_CODE = outsourceZhongXinService.getLastRowOfSubjectCode(sheet);
        List<FundValueTableDataInfoEntity> fundValueTableDataInfoEntityList = new ArrayList<>();
        List<FundValuationEntity> fundValuationEntityList = new ArrayList<>();
        Pattern yyyyMMddPattern = Pattern.compile(YEAR_MONTH_DAY_REGEX);

        for (int rowNum=0; rowNum <=sheet.getLastRowNum(); rowNum++){
            HSSFRow sheetRow = sheet.getRow(rowNum);
            if (sheetRow == null){
                continue;
            }
            for (int cellNum = 0; cellNum <= sheetRow.getLastCellNum(); cellNum++) {
                HSSFCell cell = sheetRow.getCell(cellNum);
                if (cell == null){
                    continue;
                }
                //通过文件名获取基金编码,估值日期
                Matcher matcher = yyyyMMddPattern.matcher(fileName);
                if (matcher.find()) {
                    fundValueDate = matcher.group();
                } else {
                    log.error("华泰上游excel文件名格式变化fundValueDate无法解析，fundValueDate:{}", fileName);

                }
                if (fileName.contains("_")) {
                    String[] strings = fileName.trim().split("_");
                    fundCode = strings[0];
                    fundName = strings[1];
                } else {
                    log.error("华泰上游excel文件名格式变化fundCode/fundName无法解析，fundCode:{}", fileName);
                }
                //解析估值表数据 获取A列非中文字段
                if (cell.getColumnIndex() == 0
                        && !CellType.BLANK.equals(cell.getCellTypeEnum())
                        && !"".equals(OutsourceZhongXinService.getValue(cell))
                        && cell.getRowIndex() > LAST_ROW_OF_SUBJECT_CODE) {
                    String kmCode = OutsourceZhongXinService.getValue(cell);
                    String kmName = sheetRow.getCell(1) != null ? OutsourceZhongXinService.getValue(sheetRow.getCell(1)) : null;
                    String currency = sheetRow.getCell(2) != null ? OutsourceZhongXinService.getValue(sheetRow.getCell(2)) : null;
                    BigDecimal vaRate = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(3)));
                    BigDecimal amount = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(4)));
                    BigDecimal perCost = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(5)));
                    BigDecimal origCost = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(6)));
                    BigDecimal portCost = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(7)));
                    BigDecimal costBi = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(8)));
                    BigDecimal vapPrice = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(9)));
                    BigDecimal origMv = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(10)));
                    BigDecimal portMv = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(11)));
                    BigDecimal mvBl = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(12)));
                    BigDecimal origIv = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(13)));
                    BigDecimal portIv = OutsourceZhongXinService.getBigDecimalFromCell(ExcelUtils.getStringValue(sheetRow.getCell(14)));
                    String suspension = sheetRow.getCell(15) != null ? OutsourceZhongXinService.getValue(sheetRow.getCell(15)) : null;

                    //币种
                    String targetCurrency = MatchDataDictionaryKeyUtil.getKey(currencyDic, currency);
                    FundValueTableDataInfoEntity entity = FundValueTableDataInfoEntity.builder()
                            .fundCode(fundCode)
                            .fundName(fundName)
                            .valueDate(fundValueDate)
                            .kmCode(kmCode)
                            .kmName(kmName)
                            .currency(targetCurrency)
                            .vaRate(vaRate)
                            .amount(amount)
                            .perCost(perCost)
                            .origCost(origCost)
                            .portCost(portCost)
                            .costBi(costBi)
                            .vapPrice(vapPrice)
                            .origMv(origMv)
                            .portMv(portMv)
                            .mvBl(mvBl)
                            .origIv(origIv)
                            .portIv(portIv)
                            .suspension(suspension)
                            .build();
                    fundValueTableDataInfoEntityList.add(entity);
                    // 新增估值表
                    //获取估值表kmCode估值表
                    String targetKmCode = MatchDataDictionaryKeyUtil.getKey(typeOfFundValuationDic, kmCode);
                    String targetKmName = MatchDataDictionaryKeyUtil.getKey(fundValuationMap, targetKmCode);
                    if (StringUtils.isNotBlank(targetKmCode)) {
                        FundValuationEntity fundValuationEntity = FundValuationEntity.builder()
                                .fundCode(fundCode)
                                .fundName(fundName)
                                .valueDate(fundValueDate)
                                .kmCode(targetKmCode)
                                .kmName(targetKmName)
                                .currency(targetCurrency)
                                .vaRate(vaRate)
                                .amount(amount)
                                .perCost(perCost)
                                .origCost(origCost)
                                .portCost(portCost)
                                .costBi(costBi)
                                .vapPrice(vapPrice)
                                .origMv(origMv)
                                .portMv(portMv)
                                .mvBl(mvBl)
                                .origIv(origIv)
                                .portIv(portIv)
                                .suspension(suspension)
                                .build();
                        fundValuationEntityList.add(fundValuationEntity);
                    }
                }
            }

        }
        result.setFundValueTableDataInfoEntity(fundValueTableDataInfoEntityList);
        result.setFundValuationEntityList(fundValuationEntityList);
        return result;
    }
}
