package com.engine.salary.service.impl;

import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.api.formmode.mybatis.util.SqlProxyHandle;
import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.salary.biz.SalarySendBiz;
import com.engine.salary.biz.SalarySendInfoBiz;
import com.engine.salary.cache.SalaryCacheKey;
import com.engine.salary.config.SalaryElogConfig;
import com.engine.salary.constant.HrmSalaryPayrollConf;
import com.engine.hrmelog.entity.dto.LoggerContext;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.progress.ProgressDTO;
import com.engine.salary.entity.salaryBill.bo.SalaryBillBO;
import com.engine.salary.entity.salaryBill.bo.SalaryTemplateBO;
import com.engine.salary.entity.salaryBill.dto.*;
import com.engine.salary.entity.salaryBill.param.SalaryExportPdfParam;
import com.engine.salary.entity.salaryBill.param.SalarySendGrantParam;
import com.engine.salary.entity.salaryBill.param.SalarySendInfoQueryParam;
import com.engine.salary.entity.salaryBill.po.SalarySendInfoPO;
import com.engine.salary.entity.salaryBill.po.SalarySendPO;
import com.engine.salary.entity.salaryBill.po.SalaryTemplatePO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctEmployeePO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctResultPO;
import com.engine.salary.entity.salarysob.po.SalarySobPO;
import com.engine.salary.entity.taxagent.po.TaxAgentPO;
import com.engine.salary.enums.OperateTypeEnum;
import com.engine.salary.enums.salarybill.*;
import com.engine.salary.enums.salarysend.SalarySendGrantTypeEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.mapper.salarybill.SalarySendInfoMapper;
import com.engine.salary.mapper.salarybill.SalarySendMapper;
import com.engine.salary.service.*;
import com.engine.salary.util.JsonUtil;
import com.engine.salary.util.SalaryDateUtil;
import com.engine.salary.util.SalaryEntityUtil;
import com.engine.salary.util.SalaryI18nUtil;
import com.engine.salary.util.pdf.HtmlToPdf;
import com.google.common.collect.Lists;
import com.weaver.util.threadPool.ThreadPoolUtil;
import com.weaver.util.threadPool.constant.ModulePoolEnum;
import com.weaver.util.threadPool.entity.LocalRunnable;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import weaver.hrm.User;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.engine.salary.constant.HrmSalaryPayrollConf.GEN_PDF;

/**
 * 工资单发放
 * <p>Copyright: Copyright (c) 2022</p>
 * <p>Company: 泛微软件</p>
 *
 * @author qiantao
 * @version 1.0
 **/
@Slf4j
public class SalaryBillServiceImpl extends Service implements SalaryBillService {


    private SalarySendMapper getSalarySendMapper() {
        return SqlProxyHandle.getProxy(SalarySendMapper.class);
    }

    private SalarySendInfoMapper getSalarySendInfoMapper() {
        return SqlProxyHandle.getProxy(SalarySendInfoMapper.class);
    }


    private SalaryTemplateService getSalaryTemplateService(User user) {
        return ServiceUtil.getService(SalaryTemplateServiceImpl.class, user);
    }

    private SalarySendService getSalarySendService(User user) {
        return ServiceUtil.getService(SalarySendServiceImpl.class, user);
    }

    private SalarySobService getSalarySobService(User user) {
        return ServiceUtil.getService(SalarySobServiceImpl.class, user);
    }

    private SalaryAcctResultService getSalaryAcctResultService(User user) {
        return ServiceUtil.getService(SalaryAcctResultServiceImpl.class, user);
    }

    private SalaryAcctEmployeeService getSalaryAcctEmployeeService(User user) {
        return ServiceUtil.getService(SalaryAcctEmployeeServiceImpl.class, user);
    }

    private TaxAgentService getTaxAgentService(User user) {
        return ServiceUtil.getService(TaxAgentServiceImpl.class, user);
    }

    private SalarySendRangeService getSalarySendRangeService(User user) {
        return ServiceUtil.getService(SalarySendRangeServiceImpl.class, user);
    }

    private SalaryAcctRecordService getSalaryAcctRecordService(User user) {
        return ServiceUtil.getService(SalaryAcctRecordServiceImpl.class, user);
    }

    private SalaryEmployeeService getSalaryEmployeeService(User user) {
        return ServiceUtil.getService(SalaryEmployeeServiceImpl.class, user);
    }

    private SalaryBillBaseSetService getSalaryBillBaseSetService(User user) {
        return ServiceUtil.getService(SalaryBillBaseSetServiceImpl.class, user);
    }


    private SalarySendInfoBiz salarySendInfoMapper = new SalarySendInfoBiz();
    private SalarySendBiz mapper = new SalarySendBiz();

    private SalaryEmployeeService salaryEmployeeService;

    private SalarySobItemService salarySobItemService;


    private ProgressService getProgressService(User user) {
        return ServiceUtil.getService(ProgressServiceImpl.class, user);
    }

    /**
     * 工资单发放 start
     **********************************************************************/
    @Override
    public Map<String, Object> grant(SalarySendGrantParam param) {
        // 1.检查和获取工资单发放
        SalarySendPO salarySend = checkAndGetSalarySend(param.getSalarySendId());
//        // 已经冻结不能操作
//        if (salarySend.getSendStatus().equals(NumberUtils.INTEGER_ONE)) {
//            return;
//        }
        // 获取默认模板
        SalarySobPO salarySob = getSalarySobService(user).getById(salarySend.getSalarySobId());
        List<SalaryTemplatePO> salaryTemplates = salarySob == null ? Lists.newArrayList() : getSalaryTemplateService(user).getDefaultTemplates(Arrays.asList(salarySend.getSalarySobId(), salarySob.getId()));
        if (CollectionUtils.isEmpty(salaryTemplates)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100518, "没有默认模板，无法发送"));
        }
        SalaryTemplatePO salaryTemplate = salaryTemplates.get(0);

        // 发送通道
        Set<MessageChannelEnum> sendChannels = SalaryBillBO.buildSendChannels(salaryTemplate);
        if (CollectionUtils.isEmpty(sendChannels)) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(149767, "工资单模板至少开启一个发送通道"));
        }
        boolean isReplenish = NumberUtils.INTEGER_ONE.equals(salarySend.getSalaryAcctType());
        if (isReplenish && StringUtils.isEmpty(salaryTemplate.getReplenishName())) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(204428, "请完善补发工资单模板设置"));
        }
        // 2.检查是否正在发送中
        ProgressDTO salaryBillProgress = getProgressService(user).getProgress(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + param.getSalarySendId());
        if (Objects.nonNull(salaryBillProgress) && salaryBillProgress.isStatus()
                && Optional.ofNullable(salaryBillProgress.getProgress()).orElse(BigDecimal.ZERO).compareTo(BigDecimal.ONE) < 0) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(136104, "有其他人员正在发送中，请等待其他人员发送结束后再操作"));
        }
        // 初始化进度
        ProgressDTO initProgress = ProgressDTO.builder()
                .title(SalaryI18nUtil.getI18nLabel(136097, "发送中"))
                .totalQuantity(NumberUtils.INTEGER_ZERO)
                .calculatedQuantity(NumberUtils.INTEGER_ZERO)
                .progress(BigDecimal.ZERO)
                .status(true)
                .message("")
                .build();
        getProgressService(user).initProgress(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), initProgress);

        List<Long> ids = param.getIds();

        if (param.getSalarySendRangeIds() != null) {
            //如果传了范围id，则使用范围id发放
            ids = getSalarySendRangeService(user)
                    .getSendInfoIdsBySendId(salarySend.getId(), param.getSalarySendRangeIds(), SalarySendGrantTypeEnum.GRANT);
            if (ids.isEmpty()) {// 由于查出来是空的，会导致全部发放，在此进行拦截
                throw new SalaryRunTimeException("工资发放范围内没有匹配员工");
            }
        }
        // 异步执行
        List<Long> finalIds = ids;
        LocalRunnable localRunnable = new LocalRunnable() {
            @Override
            public void execute() {
                handleGrant(salarySend, finalIds, salarySob, salaryTemplate);
            }
        };
        ThreadPoolUtil.fixedPoolExecute(ModulePoolEnum.OTHER, "salaryBillGrant", localRunnable);

        return new HashMap<>();
    }

    /**
     * 对于已经发送的工资单，重新生成pdf
     * @param param
     * @return
     */
    @Override
    public Map<String, Object> reGenPdf(SalarySendGrantParam param) {
        // 1.检查和获取工资单发放
        SalarySendPO salarySend = checkAndGetSalarySend(param.getSalarySendId());
        // 获取默认模板
        SalarySobPO salarySob = getSalarySobService(user).getById(salarySend.getSalarySobId());
        List<SalaryTemplatePO> salaryTemplates = salarySob == null ? Lists.newArrayList() : getSalaryTemplateService(user).getDefaultTemplates(Arrays.asList(salarySend.getSalarySobId(), salarySob.getId()));
        SalaryTemplatePO salaryTemplate = salaryTemplates.get(0);
        List<Long> ids = param.getIds();
        if (param.getSalarySendRangeIds() != null) {
            //如果传了范围id，则使用范围id发放
            ids = getSalarySendRangeService(user)
                    .getSendInfoIdsBySendId(salarySend.getId(), param.getSalarySendRangeIds(), SalarySendGrantTypeEnum.GRANT);
            if (ids.isEmpty()) {// 由于查出来是空的，会导致全部发放，在此进行拦截
                throw new SalaryRunTimeException("工资发放范围内没有匹配员工");
            }
        }
        try {
            TaxAgentPO taxAgentPO = getTaxAgentService(user).getById(salarySob.getTaxAgentId());
            // 1.构建发送参数
            SalaryBillSendDTO salaryBillSendParam = buildSendParams(salarySend, taxAgentPO.getName(), salaryTemplate);
            // 2.获取可发送的列表，此步最耗时，需要解密核算数据
            List<Integer> sendStatusList = Arrays.asList(SalarySendStatusEnum.UNSEND.getValue(), SalarySendStatusEnum.WITHDRAW.getValue(), SalarySendStatusEnum.ALREADYSEND.getValue());
            List<Map<String, Object>> enableSendList = getEnableSendList(salarySend, ids, salaryBillSendParam, sendStatusList);
            //生成pdf
            genPdf(salaryBillSendParam, enableSendList, true);
            getProgressService(user).finish(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), true);
        } catch (Exception e) {
            log.info("发送出错：{}", e.getMessage(), e);
        }
        return new HashMap<>();
    }


    /**
     * 处理发送
     * 注意：
     * 1.IM和云桥通道是发送完成后，只需要在预览时获取核算数据，所以发送时可以忽略获取核算数据；
     * 2.邮件通道需要在发送时获取，拼凑成html代码，有图片会转化为base64编码变慢，且图片越大越慢
     *
     * @param salarySend
     * @param ids
     * @param salarySob
     * @param salaryTemplate
     */
    private void handleGrant(SalarySendPO salarySend, List<Long> ids, SalarySobPO salarySob, SalaryTemplatePO salaryTemplate) {
        try {

            TaxAgentPO taxAgentPO = getTaxAgentService(user).getById(salarySob.getTaxAgentId());

            // 1.构建发送参数
            SalaryBillSendDTO salaryBillSendParam = buildSendParams(salarySend, taxAgentPO.getName(), salaryTemplate);

            // 2.获取可发送的列表，此步最耗时，需要解密核算数据
            List<Integer> sendStatusList = Arrays.asList(SalarySendStatusEnum.UNSEND.getValue(), SalarySendStatusEnum.WITHDRAW.getValue());
            List<Map<String, Object>> enableSendList = getEnableSendList(salarySend, ids, salaryBillSendParam, sendStatusList);

            // 3.发送消息：先修改数据再发消息，避免出错后无法撤回
            List<Long> successIds = sendMessage(enableSendList, salaryBillSendParam);

            //生成pdf
            genPdf(salaryBillSendParam, enableSendList, false);

            // 4.发放
            grantSendInfo(successIds, salarySend, salaryTemplate, salaryBillSendParam);

            // 5.更新数量
            updateSendNum(salarySend, salarySob);

            int total = enableSendList.size();
            int successCount = successIds.size();
            String messsage = SalaryI18nUtil.getI18nLabel(139208, "发送完成") + ": "
                    + SalaryI18nUtil.getI18nLabel(134807, "成功条数") + "[" + (successCount) + "], "
                    + SalaryI18nUtil.getI18nLabel(134808, "失败条数") + "[" + (total - successCount) + "]";
            // 发送进度完成
            getProgressService(user).finish(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), true, messsage);
        } catch (Exception e) {
            log.info("发送出错：{}", e.getMessage(), e);
            // 发送进度失败
            getProgressService(user).fail(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), SalaryI18nUtil.getI18nLabel(136118, "工资单发送出错") + ": " + e.getMessage());
        } finally {
        }
    }

    private void genPdf(SalaryBillSendDTO salaryBillSendParam, List<Map<String, Object>> enableSendList, boolean isSync) {
        if (GEN_PDF) {
            if (isSync) {
                genPdf(salaryBillSendParam, enableSendList);
            } else {
                LocalRunnable localRunnable = new LocalRunnable() {
                    @Override
                    public void execute() {
                      genPdf(salaryBillSendParam, enableSendList);
                    }
                };
                ThreadPoolUtil.fixedPoolExecute(ModulePoolEnum.OTHER, "salaryBillGenPdf", localRunnable);
            }

        }
    }

    private void genPdf(SalaryBillSendDTO salaryBillSendParam, List<Map<String, Object>> enableSendList) {
        // 获取人员完整信息
        Map<String, DataCollectionEmployee> allEmployeeMap = getEmployeeWholeInfo(enableSendList);
        //生成工资单pdf
        enableSendList.forEach(e -> {
            // 构建人员信息
            SalaryBillBO.buildEmployeeInfo(salaryBillSendParam, allEmployeeMap.get(e.get("employeeId").toString()));
            SalaryBillBO.genPdf(e, salaryBillSendParam);
        });

        //合并工资单pdf
        //1、先获取所有工资单
        Long id = salaryBillSendParam.getSalarySend().getId();
        List<SalarySendInfoPO> salarySendInfos = getSalarySendInfoMapper().listSome(SalarySendInfoPO.builder().salarySendId(id).sendStatus(1).build());
        //2、工资单pdf转为路径
        String yyyyMM = SalaryDateUtil.getFormatYearMonth(salaryBillSendParam.getSalaryDate());
        List<String> filesToMerge = salarySendInfos.stream().map(po -> HrmSalaryPayrollConf.GEN_PATH + yyyyMM + File.separator + id + File.separator + po.getId() + ".pdf").collect(Collectors.toList());
        String pdfPath = HrmSalaryPayrollConf.GEN_PATH + yyyyMM + File.separator + id + File.separator + id + ".pdf";
        SalaryBillBO.mergePdf(pdfPath, filesToMerge);
    }

    /**
     * 构建发送参数
     *
     * @param salarySend
     * @param taxAgentName
     * @param salaryTemplate
     * @return
     */
    private SalaryBillSendDTO buildSendParams(SalarySendPO salarySend, String taxAgentName, SalaryTemplatePO salaryTemplate) {
        // 发送通道
        Set<MessageChannelEnum> sendChannels = SalaryBillBO.buildSendChannels(salaryTemplate);
        if (CollectionUtils.isEmpty(sendChannels)) {
            return null;
        }
        Date salaryMonth = salarySend.getSalaryMonth();

        boolean isReplenish = NumberUtils.INTEGER_ONE.equals(salarySend.getSalaryAcctType());
//        SalaryTemplateBO.copyShowSet(isReplenish, salaryTemplate);

        // 消息标题
//        String title = SalaryBillBO.getBillTitle(salaryTemplate.getTheme(), salaryMonth, 0L);

        // 云桥企业微信消息图
        String domain = "";
        // todo 访问本项目中的图片需要登陆才能访问，且变成空白，所以还是得改成公网，或者后续鉴权忽略
        // StringUtils.isEmpty(server) || StringUtils.isEmpty(staticPathPattern) ? "https://wx.weaver.com.cn/main/salary/cover.png" : (server+staticPathPattern).replace("**", "salary_bill.png");
        String picUrl = domain + String.format("/papi/file/preview?type=imgs&fileId=%s&random=123456", salaryTemplate.getBackground()); // "https://wx.weaver.com.cn/main/salary/cover.png";
        String backgroundBase64 = "";
//        if (StringUtils.isNotBlank(salaryTemplate.getBackground()) && isEnableEmail) {
        // 2.base64码的方式，这种方式可以不管图片地址是否是内外网，缺点是内容太长
//            FileData fileByte = fileDownloadClient.getFileByte(Long.valueOf(salaryTemplate.getBackground()));
//            backgroundBase64 = Base64Encoder.encode(fileByte.getData());
        // 千万不要这样做，否则存入salaryTemplate字段会变的很大： salaryTemplate.setBackground(backgroundBase64);
//        }

        // 工资单预览实体名
        String entityName = SalaryI18nUtil.getI18nLabel(94626, "工资单");

        // 获取人员信息项目分类
        List<SalaryTemplateSalaryItemSetListDTO> salaryItemSetList = JSONArray.parseArray(isReplenish ? salaryTemplate.getReplenishSalaryItemSetting() : salaryTemplate.getSalaryItemSetting(), SalaryTemplateSalaryItemSetListDTO.class);
        // 国际化处理
//        salaryItemSetList = SalaryTemplateBO.handleI18n(salaryItemSetList);
        SalaryTemplateSalaryItemSetListDTO employeeInformation = SalaryTemplateBO.getEmployeeInformation(salaryItemSetList);

        Map<String, String> employeeField = SalaryBillBO.buildEmployeeFieldName();

        String remarkI18n = SalaryI18nUtil.getI18nLabel(140036, "发放说明");
        if (StringUtils.isNotEmpty(salaryTemplate.getTextContent())) {
            String yearI18n = SalaryI18nUtil.getI18nLabel(100325, "年");
            String monthI18n = SalaryI18nUtil.getI18nLabel(100326, "月");
            salaryTemplate.setTextContent(salaryTemplate.getTextContent().replace("${salaryMonth}", salaryMonth.getYear() + yearI18n + salaryMonth.getMonth() + monthI18n));
        }

        // 工资单水印设置
        SalaryBillWatermarkDTO salaryBillWatermark = getSalaryBillBaseSetService(user).getWatermarkSetting();
        // 工资单水印文本动态变量
        List<String> wmTextFieldIds = SalaryBillBO.getWmTextFieldIds(domain, salaryBillWatermark);
        // 邮件水印模板
        boolean isEnableEmail = salaryTemplate.getEmailStatus().equals(SalaryTemplateWhetherEnum.TRUE.getValue());
        String emailWmContentTemplate = SalaryBillBO.buildEmailWmContentTemplate(isEnableEmail, salaryBillWatermark);

        return SalaryBillSendDTO.builder()
                .salaryDate(salaryMonth)
                // 消息标题
//                .title(title)
                // 消息内容：目前跟title一样
//                .text(title)
                // 工资单预览实体名
                .entityName(entityName)
                // 发送通道
                .sendChannels(sendChannels)
                // 发送者
                .sendUser(user)
                // 工资单模板
                .salaryTemplate(salaryTemplate)
                // 工资单背景图base64编码
                .backgroundBase64(backgroundBase64)
                // 云桥工资单消息图
                .picUrl(picUrl)
                // 扣缴义务人名称
                .taxAgentName(taxAgentName)
                // 工资单模板-薪资项目设置
                .salaryItemSetList(salaryItemSetList)
                // 工资单模板-员工基本信息
                .employeeInformation(employeeInformation)
                // 工资单模板-对应人员字段
                .employeeField(employeeField)
                //短信模板
                .smsSetting(JSON.parseObject(salaryTemplate.getSmsSetting(), SalaryTemplateSMSSetDTO.class))
                // 工资单模板-发放说明标签国际化
                .remarkI18n(remarkI18n)
                // 邮件是否可以发送邮件
//                .isEnableEmail(isEnableEmail)
                // 工资单发放
                .salarySend(salarySend)
                // 收入所得项目类型
//                .incomeCategorys(incomeCategorys)
                //薪资项目收入所得类型
//                .salaryItemIncomeCategoryMap(salaryItemIncomeCategoryMap)
                // 工资单水印设置
                .watermarkSetting(salaryBillWatermark)
                // 工资单水印文本动态变量
                .wmTextFieldIds(wmTextFieldIds)
                // 邮件水印模板
                .emailWmContentTemplate(emailWmContentTemplate)
                .build();
    }

    /**
     * 获取可发送数据
     *
     * @param salarySend
     * @param ids
     * @return
     */
    private List<Map<String, Object>> getEnableSendList(SalarySendPO salarySend, List<Long> ids, SalaryBillSendDTO salaryBillSendParam, List<Integer> sendStatus) {
        SalarySendInfoQueryParam queryParam = new SalarySendInfoQueryParam();
        queryParam.setSalarySendId(salarySend.getId());
        // 空就是所有
        queryParam.setIds(ids);
        queryParam.setSendStatuss(sendStatus);
        // 2.获取基本数据
        List<SalarySendInfoListDTO> list = Lists.newArrayList();
        if (CollectionUtils.isEmpty(ids)) {
            list = getSalarySendInfoMapper().list(queryParam);
        } else {
            List<List<Long>> partition = Lists.partition(ids, 500);
            for (List<Long> idsPart : partition) {
                queryParam.setIds(idsPart);
                list.addAll(getSalarySendInfoMapper().list(queryParam));
            }
        }

//        // 是否是合并计税
//        boolean isMerge = this.getSalarySendService(user).isMergeBySalarySend(salarySend);
        List<SalaryAcctEmployeePO> salaryAcctEmployeeList = this.getSalaryAcctEmployeeService(user).listBySalaryAcctRecordId(salarySend.getSalaryAccountingId());
        List<Long> acctEmployeeIds = salaryAcctEmployeeList.stream().map(SalaryAcctEmployeePO::getEmployeeId).distinct().collect(Collectors.toList());
        list = list.stream().filter(f -> acctEmployeeIds.contains(f.getEmployeeId())).collect(Collectors.toList());

        List<Long> employeeIds = list.stream().map(SalarySendInfoListDTO::getEmployeeId).collect(Collectors.toList());

        // 初始化进度
        ProgressDTO initProgress = ProgressDTO.builder()
                .title("发送中")
                .totalQuantity(employeeIds.size() * 2 + 1)
                .status(true)
                .calculatedQuantity(NumberUtils.INTEGER_ZERO)
                .progress(BigDecimal.ZERO)
                .message("")
                .build();
        getProgressService(user).initProgress(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), initProgress);
        // 3.组装详情数据
        // 获取薪资项目数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = Lists.newArrayList();
        List<SalaryAcctResultPO> salaryAcctResultValues = Lists.newArrayList();

        //是否需要发送邮件
        boolean isEnableEmail = salaryBillSendParam.getSendChannels().contains(MessageChannelEnum.EMAIL);
        boolean isEnableSMS = salaryBillSendParam.getSendChannels().contains(MessageChannelEnum.SMS);
        if (CollectionUtils.isNotEmpty(ids)) {
            int partSize = 100;// 分片大小,越小进度条跑得越短平快，但是总体速度更慢
            List<List<Long>> partition = Lists.partition(employeeIds, partSize);
            for (List<Long> part : partition) {
                // 更新进度
                getProgressService(user).getAndAddCalculatedQty(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salarySend.getId(), part.size());
                /** 注意只有邮件或生成pdf才需要加密的核算数据 */
                if (isEnableEmail || isEnableSMS || GEN_PDF) {
                    List<SalaryAcctEmployeePO> acctEmployees = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordIdsAndEmployeeIds(Collections.singletonList(salarySend.getSalaryAccountingId()), part);
                    salaryAcctEmployees.addAll(acctEmployees);
                    salaryAcctResultValues.addAll(getSalaryAcctResultService(user).listBySalaryAcctEmployeeIds(SalaryEntityUtil.properties(acctEmployees, SalaryAcctEmployeePO::getId, Collectors.toList())));
                }
            }
        } else {
            /** 注意只有邮件才需要加密的核算数据 */
            if (isEnableEmail || isEnableSMS || GEN_PDF) {
                salaryAcctEmployees = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordId(salarySend.getSalaryAccountingId());
                salaryAcctResultValues = getSalaryAcctResultService(user).listBySalaryAcctRecordIds(Collections.singletonList(salarySend.getSalaryAccountingId()));
            }
        }
        // 外部人员
//        List<ExtEmployeePO> extEmployeeList = extEmployeeService.listAllWithDeleted(currentTenantKey);
        return getSalarySendService(user)
                .buildDetailList(list, salaryAcctEmployees, salaryAcctResultValues);
    }

    @Override
    public void confirmSalaryBill(Long salaryInfoId) {
        SalarySendInfoPO sendInfoPO = getSalarySendInfoMapper().getById(salaryInfoId);
        if (sendInfoPO == null) {
            throw new SalaryRunTimeException("工资单不存在或已被删除！");
        }
        sendInfoPO.setBillConfirmStatus(BillConfimStatusEnum.CONFIRMED.getValue());
        sendInfoPO.setUpdateTime(new Date());
        getSalarySendInfoMapper().updateIgnoreNull(sendInfoPO);
    }

    @Override
    public void feedBackSalaryBill(Long salaryInfoId) {
        SalarySendInfoPO sendInfoPO = getSalarySendInfoMapper().getById(salaryInfoId);
        if (sendInfoPO == null) {
            throw new SalaryRunTimeException("工资单不存在或已被删除！");
        }
        sendInfoPO.setBillConfirmStatus(BillConfimStatusEnum.FEEDBACK.getValue());
        sendInfoPO.setUpdateTime(new Date());
        getSalarySendInfoMapper().updateIgnoreNull(sendInfoPO);
    }

    @Override
    public String exportPdf(SalaryExportPdfParam param) {
        SalarySendPO salarySend = checkAndGetSalarySend(param.getSalarySendId());
        String yearMonth = SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth());
        String path = HrmSalaryPayrollConf.GEN_PATH + File.separator + yearMonth + File.separator + salarySend.getId() + File.separator + "%s" + ".pdf";
        Long id = param.getId();
        if (id == null) {
            path = String.format(path, param.getSalarySendId());
        } else {
            path = String.format(path, id);
        }
        return path;
    }

    @Override
    public String genPdfBeforeExport(SalaryExportPdfParam param) {
        // 先判断对应路径下是否存在pdf
        SalarySendPO salarySend = checkAndGetSalarySend(param.getSalarySendId());
        String yearMonth = SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth());
        String pdfPath = HrmSalaryPayrollConf.GEN_PATH + File.separator + yearMonth + File.separator + salarySend.getId() + File.separator + "%s" + ".pdf";
        Long id = param.getId();
        if (id == null) {
            pdfPath = String.format(pdfPath, param.getSalarySendId());
        } else {
            pdfPath = String.format(pdfPath, id);
        }
        File file = new File(pdfPath);
        if (file.exists()) {
            return "success";
        }

        // 判断对应路径下是否存在html
        String htmlPath = HrmSalaryPayrollConf.GEN_PATH + File.separator + yearMonth + File.separator + salarySend.getId() + File.separator + "%s" + ".html";
        if (id == null) {
            htmlPath = String.format(htmlPath, param.getSalarySendId());
        } else {
            htmlPath = String.format(htmlPath, id);
        }
        file = new File(htmlPath);
        if (file.exists()) {
           // 将html转成pdf
            FileUtil.del(pdfPath);
            HtmlToPdf.convert(HrmSalaryPayrollConf.TO_PDF_TOOL_PATH, htmlPath, pdfPath);
            mergePdf(param, yearMonth);
            return "success";
        }

        // 先生成html再生成pdf
        SalarySendGrantParam grantParam = SalarySendGrantParam.builder().salarySendId(param.getSalarySendId()).ids(Collections.singletonList(param.getId())).build();
        reGenPdf(grantParam);
        return "success";

    }

    private void mergePdf(SalaryExportPdfParam param, String yearMonth) {
        //合并工资单pdf
        //1、先获取所有工资单
        Long salarySendId = param.getSalarySendId();
        List<SalarySendInfoPO> salarySendInfos = getSalarySendInfoMapper().listSome(SalarySendInfoPO.builder().salarySendId(salarySendId).sendStatus(1).build());
        //2、工资单pdf转为路径
        List<String> filesToMerge = salarySendInfos.stream().map(po -> HrmSalaryPayrollConf.GEN_PATH + yearMonth + File.separator + salarySendId + File.separator + po.getId() + ".pdf").collect(Collectors.toList());
        String pdfPath = HrmSalaryPayrollConf.GEN_PATH + yearMonth + File.separator + salarySendId + File.separator + salarySendId + ".pdf";
        SalaryBillBO.mergePdf(pdfPath, filesToMerge);
    }

    public List<Map<String, Object>> getSendInfoList(Long sendId, List<Long> ids) {

        SalarySendPO salarySend = getSalarySendMapper().getById(sendId);
        SalarySendInfoQueryParam queryParam = new SalarySendInfoQueryParam();
        queryParam.setSalarySendId(salarySend.getId());
//        // 空就是所有
//        queryParam.setIds(ids);
        queryParam.setSendStatuss(Arrays.asList(SalarySendStatusEnum.UNSEND.getValue(), SalarySendStatusEnum.WITHDRAW.getValue()));
        // 2.获取基本数据
        List<SalarySendInfoListDTO> list = Lists.newArrayList();
        if (CollectionUtils.isEmpty(ids)) {
            list = getSalarySendInfoMapper().list(queryParam);
        } else {
            List<List<Long>> partition = Lists.partition(ids, 500);
            for (List<Long> idsPart : partition) {
                queryParam.setIds(idsPart);
                list.addAll(getSalarySendInfoMapper().list(queryParam));
            }
        }
        List<SalaryAcctEmployeePO> salaryAcctEmployeeList = this.getSalaryAcctEmployeeService(user).listBySalaryAcctRecordId(salarySend.getSalaryAccountingId());
        List<Long> acctEmployeeIds = salaryAcctEmployeeList.stream().map(SalaryAcctEmployeePO::getEmployeeId).distinct().collect(Collectors.toList());
        list = list.stream().filter(f -> acctEmployeeIds.contains(f.getEmployeeId())).collect(Collectors.toList());

        List<Long> employeeIds = list.stream().map(SalarySendInfoListDTO::getEmployeeId).collect(Collectors.toList());

        // 3.组装详情数据
        // 获取薪资项目数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = Lists.newArrayList();
        List<SalaryAcctResultPO> salaryAcctResultValues = Lists.newArrayList();

        //是否需要发送邮件
        if (CollectionUtils.isNotEmpty(ids)) {
            int partSize = 100;// 分片大小,越小进度条跑得越短平快，但是总体速度更慢
            List<List<Long>> partition = Lists.partition(employeeIds, partSize);
            for (List<Long> part : partition) {
                // 更新进度
                List<SalaryAcctEmployeePO> acctEmployees = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordIdsAndEmployeeIds(Collections.singletonList(salarySend.getSalaryAccountingId()), part);
                salaryAcctEmployees.addAll(acctEmployees);
                salaryAcctResultValues.addAll(getSalaryAcctResultService(user).listBySalaryAcctEmployeeIds(SalaryEntityUtil.properties(acctEmployees, SalaryAcctEmployeePO::getId, Collectors.toList())));
            }
        } else {
            salaryAcctEmployees = getSalaryAcctEmployeeService(user).listBySalaryAcctRecordId(salarySend.getSalaryAccountingId());
            salaryAcctResultValues = getSalaryAcctResultService(user).listBySalaryAcctRecordIds(Collections.singletonList(salarySend.getSalaryAccountingId()));
        }
        return getSalarySendService(user)
                .buildDetailList(list, salaryAcctEmployees, salaryAcctResultValues);
    }

    /**
     * 发送消息
     *
     * @param enableSendList
     * @param salaryBillSendParam
     */
    private List<Long> sendMessage(List<Map<String, Object>> enableSendList, SalaryBillSendDTO salaryBillSendParam) {

        if (CollectionUtils.isEmpty(enableSendList)) {
            return Collections.emptyList();
        }
        if (salaryBillSendParam == null) {
            return enableSendList.stream().map(e -> Long.valueOf(e.get("id").toString())).collect(Collectors.toList());
        }

        Set<MessageChannelEnum> sendChannels = salaryBillSendParam.getSendChannels();

        // 获取人员完整信息
        Map<String, DataCollectionEmployee> allEmployeeMap = this.getEmployeeWholeInfo(enableSendList);

        List<SalarySendInfoPO> sendInfoUpdateList = Lists.newArrayList();
        AtomicInteger index = new AtomicInteger(0);
        AtomicInteger part = new AtomicInteger(0);
        int total = enableSendList.size();
        enableSendList.forEach(e -> {

            if (sendChannels.contains(MessageChannelEnum.MSG)) {
                SalaryBillBO.sendMsg(salaryBillSendParam, Long.valueOf(e.get("id").toString()), Long.valueOf(e.get("employeeId").toString()));
            }

            if (sendChannels.contains(MessageChannelEnum.EMAIL) || sendChannels.contains(MessageChannelEnum.SMS)) {
                // 构建人员信息
                SalaryBillBO.buildEmployeeInfo(salaryBillSendParam, allEmployeeMap.get(e.get("employeeId").toString()));

                //发送邮件
                if (sendChannels.contains(MessageChannelEnum.EMAIL)) {
                    SalaryBillBO.sendEmail(e, salaryBillSendParam);
                }

                //发送短信
                if (sendChannels.contains(MessageChannelEnum.SMS)) {
                    SalaryBillBO.sendSMS(e, salaryBillSendParam);
                }
            }

            /** 发送工资单 **********************************************************/
            sendInfoUpdateList.add(SalarySendInfoPO.builder()
                    .id(Long.valueOf(e.get("id").toString()))
                    .build());
            // 更新发送进度
            index.addAndGet(1);
            part.addAndGet(1);
            if (index.get() % 100 == 0 || index.get() >= total) {
                getProgressService(user).getAndAddCalculatedQty(SalaryCacheKey.SALARY_GRANT_PROGRESS + "_" + salaryBillSendParam.getSalarySend().getId(), part.get());
                part.set(0);
            }
        });
        // 批量修改发送消息id
        if (CollectionUtils.isNotEmpty(sendInfoUpdateList)) {
            // sqlserver数据库处理，每个分片数量约=2100/插入字段数
            List<List<SalarySendInfoPO>> partition = Lists.partition(sendInfoUpdateList, 500);
//            partition.forEach(updatePart -> getSalarySendInfoMapper().batchUpdateMsgId(updatePart));
        }
        return sendInfoUpdateList.stream().map(SalarySendInfoPO::getId).collect(Collectors.toList());
    }

    /**
     * 获取人员完整信息
     *
     * @param enableSendList
     * @return
     */
    private Map<String, DataCollectionEmployee> getEmployeeWholeInfo(List<Map<String, Object>> enableSendList) {

        List<Long> ids = enableSendList.stream().map(e -> Long.valueOf(e.get("employeeId").toString())).collect(Collectors.toList());

        // 获取所有人员信息
        List<DataCollectionEmployee> simpleEmployees = getSalaryEmployeeService(user).getEmployeeByIdsAll(ids);
        return SalaryEntityUtil.convert2Map(simpleEmployees, e -> e.getEmployeeId() + "");
    }
//    /** 工资单发放 end **********************************************************************/

    /**
     * 检查和获取工资单发放信息
     *
     * @param salarySendId
     * @return
     */
    private SalarySendPO checkAndGetSalarySend(Long salarySendId) {
        if (salarySendId == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100327, "工资单发放Id必传"));
        }

        SalarySendPO salarySend = this.getSalarySendService(user).getById(salarySendId);
        if (salarySend == null) {
            throw new SalaryRunTimeException(String.format(SalaryI18nUtil.getI18nLabel(100512, "工资单发放不存在") + "[id:%s]", salarySendId));
        }
        return salarySend;
    }

//    /**
//     * 分权处理
//     *
//     * @param salarySend
//     * @param ids
//     * @param currentEmployeeId
//     * @param currentTenantKey
//     * @return
//     */
//    private List<Long> getIdsForDevolution(SalarySendPO salarySend, List<Long> ids) {
//        // 开启分权或且是非薪酬模块总管理员且发送全部
//        if (getTaxAgentService(user).isOpenDevolution(currentTenantKey) && !getTaxAgentService(user).isChief(currentEmployeeId) && CollectionUtils.isEmpty(ids)) {
//            List<SalaryAcctEmployeePO> salaryAcctEmployees = getSalaryAcctEmployeeService(user)
//                    .listByRecordIds(Collections.singletonList(salarySend.getSalaryAccountingId()));
//
//            List<Long> taxAgentIdsAsAdmin = getTaxAgentService(user).listAllTaxAgentsAsAdmin(currentEmployeeId).stream().map(TaxAgentPO::getId)
//                    .collect(Collectors.toList());
//            List<TaxAgentManageRangeEmployeeDTO> taxAgentEmployees = getTaxAgentService(user).listTaxAgentAndEmployeeTree(currentEmployeeId).stream()
//                    .filter(t -> !taxAgentIdsAsAdmin.contains(t.getTaxAgentId())).collect(Collectors.toList());
//            Map<Long, List<Long>> taxAgentEmployeesMap = SalaryEntityUtil.convert2Map(taxAgentEmployees, TaxAgentManageRangeEmployeeDTO::getTaxAgentId,
//                    v -> v.getEmployeeList().stream().map(TaxAgentManageRangeEmployeeDTO.TaxAgentEmployee::getEmployeeId).collect(Collectors.toList()));
//
//            List<ExtEmployeePO> extEmployeeList = extEmployeeService.listCanUse(currentEmployeeId);
//            // 工资单发放信息过滤
//            List<SalarySendInfoPO> salarySendInfos = new LambdaQueryChainWrapper<>(getSalarySendInfoMapper())
//                    /** 注意：需要啥就加载啥，避免加载大字段salaryTemplate */
//                    .select(SalarySendInfoPO::getId, SalarySendInfoPO::getEmployeeId, SalarySendInfoPO::getCreator)
//                    .eq(SalarySendInfoPO::getDeleteType, 0)
//                    .eq(SalarySendInfoPO::getTenantKey)
//                    .eq(SalarySendInfoPO::getSalarySendId, salarySend.getId())
//                    .list();
//
//            List<Long> acctEmployeeIds = salaryAcctEmployees.stream().filter(f ->
//                    // 作为管理员
//                    taxAgentIdsAsAdmin.contains(f.getTaxAgentId())
//                            // 作为分管理员
//                            || TaxAgentBO.checkTaxAgentAndEmployee(taxAgentEmployeesMap, f.getTaxAgentId(), f.getEmployeeId())
//                            // 外部人员
//                            || extEmployeeList.stream().anyMatch(t -> t.getTaxAgentId().equals(f.getTaxAgentId()) && t.getId().equals(f.getEmployeeId()))
//            ).map(SalaryAcctEmployeePO::getEmployeeId).collect(Collectors.toList());
//
//            ids = salarySendInfos.stream().filter(f -> acctEmployeeIds.contains(f.getEmployeeId()) || currentEmployeeId.equals(f.getCreator())).map(SalarySendInfoPO::getId)
//                    .distinct().collect(Collectors.toList());
//        }
//        return ids;
//    }

    /**
     * 发放工资单更新信息
     *
     * @param ids
     * @param salarySend
     * @param salaryTemplate
     */
    private void grantSendInfo(List<Long> ids, SalarySendPO salarySend, SalaryTemplatePO salaryTemplate, SalaryBillSendDTO salaryBillSendDTO) {
        String waterMarkJson = salaryBillSendDTO.getWatermarkSetting() == null ? "" : JsonUtil.toJsonString(salaryBillSendDTO.getWatermarkSetting());
        // 水印设置
        salaryTemplate.setSalaryWatermark(waterMarkJson);

        List<List<Long>> partition = Lists.partition(ids, 500);
        Date sendTime = new Date();
        SalarySendInfoPO po = new SalarySendInfoPO();
        po.setSendStatus(SalarySendStatusEnum.ALREADYSEND.getValue());
        po.setSendTime(sendTime);
        po.setSalaryTemplate(JsonUtil.toJsonString(salaryTemplate));
        po.setBillReadStatus(BillReadStatusEnum.UNREAD.getValue());
        po.setBillConfirmStatus(BillConfimStatusEnum.UNCONFIRMED.getValue());
        po.setSendEmployeeId(Long.valueOf(user.getUID()));
        partition.forEach(idsp -> {
            salarySendInfoMapper.updateGrantWithdraw(po, salarySend.getId(), Arrays.asList(SalarySendStatusEnum.UNSEND.getValue(), SalarySendStatusEnum.WITHDRAW.getValue()), idsp);
        });
    }

//    /**
//     * 撤回工资单更新信息
//     *
//     * @param ids
//     * @param salarySend
//     * @param currentTenantKey
//     */
//    private void withdrawSendInfo(List<Long> ids, SalarySendPO salarySend) {
//        List<List<Long>> partition = Lists.partition(ids, DbCrudConstant.DEF_PART_SIZE);
//        partition.forEach(part -> new LambdaUpdateChainWrapper<>(getSalarySendInfoMapper())
//                .eq(SalarySendInfoPO::getDeleteType, 0)
//                .eq(SalarySendInfoPO::getTenantKey)
//                .eq(SalarySendInfoPO::getSalarySendId, salarySend.getId())
//                .eq(SalarySendInfoPO::getSendStatus, SalarySendStatusEnum.ALREADYSEND.getValue())
//                .in(SalarySendInfoPO::getId, part)
//                .set(SalarySendInfoPO::getSendStatus, SalarySendStatusEnum.WITHDRAW.getValue())
//                .update());
//    }
//

    /**
     * 更新发送数量
     *
     * @param salarySend
     * @param salarySob
     */
    private void updateSendNum(SalarySendPO salarySend, SalarySobPO salarySob) {
        List<SalarySendInfoPO> list = salarySendInfoMapper.listSome(SalarySendInfoPO.builder().salarySendId(salarySend.getId()).build());
        List<SalarySendInfoPO> sendList = list.stream().filter(e -> e.getSendStatus().equals(SalarySendStatusEnum.ALREADYSEND.getValue())).collect(Collectors.toList());
        int sendNum = sendList.size();
        int sendTotal = list.size();
        SalarySendPO salarySendNew = new SalarySendPO();
        try {
            BeanUtils.copyProperties(salarySendNew, salarySend);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        salarySendNew.setSendNum(sendNum);
        salarySendNew.setSendTotal(sendTotal);
        salarySendNew.setLastSendTime(new Date());

        mapper.updateById(salarySendNew);

        // 记录日志
        LoggerContext loggerContext = new LoggerContext();
        loggerContext.setTargetId(String.valueOf(salarySend.getId()));
        loggerContext.setTargetName(SalaryDateUtil.getFormatYearMonth(salarySend.getSalaryMonth()) + "-" + (salarySob == null ? "" : salarySob.getName()));
        loggerContext.setOperateType(OperateTypeEnum.UPDATE.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "工资单发放"));
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "工资单发放"));
        loggerContext.setOldValues(salarySend);
        loggerContext.setNewValues(salarySendNew);
        loggerContext.setOperator(user.getUID() + StringUtils.EMPTY);
        loggerContext.setOperatorName(Objects.isNull(user) ? StringUtils.EMPTY : user.getUsername());
        SalaryElogConfig.salarySendLoggerTemplate.write(loggerContext);
    }

//    /**
//     * 工资单撤回 start
//     **********************************************************************/
//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public void withdraw(SalarySendWithdrawParam param, boolean isRange, List<Long> rangeIds) {
//        // 1.检查和获取工资单发放
//        SalarySendPO salarySend = checkAngGetSalarySend(param.getSalarySendId());
////        // 已经冻结不能操作
////        if (salarySend.getSendStatus().equals(NumberUtils.INTEGER_ONE)) {
////            return;
////        }
//
//        // 检查是否正在撤回中
//        SalaryBillProgressDTO salaryBillProgress = getProgressService(user)
//                .getProgress(SalaryCacheKey.SALARY_WITHDRAW_PROGRESS, "" + salarySend.getId());
//        if (Objects.nonNull(salaryBillProgress) && salaryBillProgress.isStatus()
//                && Optional.ofNullable(salaryBillProgress.getProgress()).orElse(BigDecimal.ZERO).compareTo(BigDecimal.ONE) < 0) {
//            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(136105, "有其他人员正在撤回中，请等待其他人员撤回结束后再操作"));
//        }
//
//        // 初始化进度
//        SalaryBillProgressDTO initProgress = getProgressService(user)
//                .buildInitProgress("撤回中", 136106, NumberUtils.INTEGER_ONE, NumberUtils.INTEGER_ZERO, BigDecimal.ZERO);
//        getProgressService(user).initProgress(SalaryCacheKey.SALARY_WITHDRAW_PROGRESS, "" + salarySend.getId(), initProgress);
//
//        List<Long> ids = isRange ? getSalarySendRangeService(user).getSendInfoIdsBySalarySendIdAndOperateType(salarySend.getId(), rangeIds, SalarySendOperateTypeEnum.WITHDRAW) : param.getIds();
//        // 异步执行撤回
//        LocalRunnable localRunnable = new LocalRunnable() {
//            @Override
//            public void execute() {
//                handleWithdraw(salarySend, ids);
//            }
//        };
//        ThreadPoolUtil.fixedPoolExecute(ModulePoolEnum.OTHER, "salaryBillWithdraw", localRunnable);
//    }
//
//    /**
//     * 处理撤回
//     *
//     * @param salarySend
//     * @param ids
//     * @param currentEmployeeId
//     * @param currentTenantKey
//     */
//    private void handleWithdraw(SalarySendPO salarySend, List<Long> ids) {
//        try {
//            DSTenantKeyThreadVar.tenantKey.set(currentTenantKey);
//
//            // 开启分权或且是非薪酬模块总管理员且发送全部,谁能进来就是能看全部
//            // ids = getIdsForDevolution(salarySend, ids);
//
//            // 获取可撤回的工资单
//            List<SalarySendInfoPO> enableWithdrawList = Lists.newArrayList();
//            if (CollectionUtils.isEmpty(ids)) {
//                List<SalaryAcctEmployeePO> salaryAcctEmployees = this.getSalarySendService(user).getSalaryAcctEmployees(salarySend.getSalaryAccountingId(), null);
//                List<Long> acctEmployeeIds = salaryAcctEmployees.stream().map(SalaryAcctEmployeePO::getEmployeeId).distinct().collect(Collectors.toList());
//                List<List<Long>> partition = Lists.partition(acctEmployeeIds, DbCrudConstant.DEF_PART_SIZE);
//                for (List<Long> part : partition) {
//                    enableWithdrawList.addAll(new LambdaQueryChainWrapper<>(getSalarySendInfoMapper())
//                            /** 注意：需要啥就加载啥，避免加载大字段salaryTemplate */
//                            .select(SalarySendInfoPO::getId, SalarySendInfoPO::getSendMsgId)
//                            .eq(SalarySendInfoPO::getDeleteType, 0)
//                            .eq(SalarySendInfoPO::getTenantKey)
//                            .eq(SalarySendInfoPO::getSalarySendId, salarySend.getId())
//                            .eq(SalarySendInfoPO::getSendStatus, SalarySendStatusEnum.ALREADYSEND.getValue())
//                            .in(SalarySendInfoPO::getEmployeeId, part)
//                            .list());
//                }
//            } else {
//                List<List<Long>> partition = Lists.partition(ids, DbCrudConstant.DEF_PART_SIZE);
//                for (List<Long> part : partition) {
//                    enableWithdrawList.addAll(new LambdaQueryChainWrapper<>(getSalarySendInfoMapper())
//                            /** 注意：需要啥就加载啥，避免加载大字段salaryTemplate */
//                            .select(SalarySendInfoPO::getId, SalarySendInfoPO::getSendMsgId)
//                            .eq(SalarySendInfoPO::getDeleteType, 0)
//                            .eq(SalarySendInfoPO::getTenantKey)
//                            .eq(SalarySendInfoPO::getSalarySendId, salarySend.getId())
//                            .eq(SalarySendInfoPO::getSendStatus, SalarySendStatusEnum.ALREADYSEND.getValue())
//                            .in(SalarySendInfoPO::getId, part)
//                            .list());
//                }
//            }
//
//            // 初始化进度
//            SalaryBillProgressDTO initProgress = getProgressService(user)
//                    .buildInitProgress("撤回中", 136106, enableWithdrawList.size() * 2 + 1, enableWithdrawList.size(), new BigDecimal("0.5"));
//            getProgressService(user).initProgress(SalaryCacheKey.SALARY_WITHDRAW_PROGRESS, "" + salarySend.getId(), initProgress);
//
//            // 1.撤回消息：先修改数据再发消息，避免出错后无法撤回
//            String text = SalaryI18nUtil.getI18nLabel(184533, "此工资单已被撤回，如有问题，请联系管理员");
//            String title = SalaryI18nUtil.getI18nLabel(94626, "工资单");
//            List<Long> sendInfoUpdateIds = Lists.newArrayList();
//
//            AtomicInteger index = new AtomicInteger(0);
//            AtomicInteger part = new AtomicInteger(0);
//            int total = enableWithdrawList.size();
//            enableWithdrawList.forEach(e -> {
//                if (!e.getSendMsgId().equals(0L)) {
//                    CancleMessageEntity cme = SalaryBillBO.buildCancleMessageEntity(e.getSendMsgId(), text, title);
//                    /** 撤回工资单 **********************************************************/
//                    WeaResult<Object> cancelResult = asyncSystemMessageRest.cancleMsg(cme);
//                    if (cancelResult.isStatus()) {
//                        sendInfoUpdateIds.add(e.getId());
//                    }
//                } else {
//                    sendInfoUpdateIds.add(e.getId());
//                }
//                // 更新撤回进度
//                index.addAndGet(1);
//                part.addAndGet(1);
//                if (index.get() % 100 == 0 || index.get() >= total) {
//                    getProgressService(user).getAndAddCalculatedQty(SalaryCacheKey.SALARY_WITHDRAW_PROGRESS, "" + salarySend.getId(), part.get());
//                    part.set(0);
//                }
//            });
//            if (CollectionUtils.isNotEmpty(sendInfoUpdateIds)) {
//                List<List<Long>> partition = Lists.partition(sendInfoUpdateIds, DbCrudConstant.DEF_PART_SIZE);
//                partition.forEach(idsPart -> new LambdaUpdateChainWrapper<>(getSalarySendInfoMapper())
//                        .eq(SalarySendInfoPO::getDeleteType, 0)
//                        .eq(SalarySendInfoPO::getTenantKey)
//                        .in(SalarySendInfoPO::getId, idsPart)
//                        .set(SalarySendInfoPO::getSendMsgId, 0)
//                        .update());
//            }
//            // 2.撤回
//            withdrawSendInfo(sendInfoUpdateIds, salarySend);
//            // 3.更新数量
//            updateSendNum(salarySend, getSalarySobService(user).getById(salarySend.getSalarySobId()), null,
//                    SalaryI18nUtil.getI18nLabel(100521, "撤回工资单发放"));
//
//            int successCount = sendInfoUpdateIds.size();
//            String messsage = SalaryI18nUtil.getI18nLabel(139211, "撤回完成") + ": "
//                    + SalaryI18nUtil.getI18nLabel(134807, "成功条数") + "[" + (successCount) + "], "
//                    + SalaryI18nUtil.getI18nLabel(134808, "失败条数") + "[" + (total - successCount) + "]";
//            // 撤回进度完成
//            getProgressService(user).finish(SalaryCacheKey.SALARY_WITHDRAW_PROGRESS, "" + salarySend.getId(), messsage, true);
//        } catch (Exception e) {
//            log.info("撤回出错：{}", e.getMessage(), e);
//            // 发送进度失败
//            getProgressService(user)
//                    .fail(SalaryCacheKey.SALARY_WITHDRAW_PROGRESS, "" + salarySend.getId(), SalaryI18nUtil.getI18nLabel(136142, "工资单撤回出错") + ": " + e.getMessage());
//        } finally {
//            DSTenantKeyThreadVar.tenantKey.remove();
//        }
//    }
//    /** 工资单撤回 end **********************************************************************/
//
//    /**
//     * 我的工资单 start
//     **********************************************************************/
//    @Override
//    public Map<String, Object> mySalaryBill(Long salaryInfoId) {
//        // 企业微信端示例数据
//        if (salaryInfoId.equals(1234567890L)) {
//            return handle4WeChartDemo(salaryInfoId);
//        }
//
//        SalarySendInfoPO salarySendInfo = new LambdaQueryChainWrapper<>(getSalarySendInfoMapper())
//                .eq(SalarySendInfoPO::getDeleteType, 0)
//                .eq(SalarySendInfoPO::getTenantKey)
//                .eq(SalarySendInfoPO::getId, salaryInfoId).one();
//        if (salarySendInfo == null) {
//            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100511, "工资单信息不存在"));
//        }
//        // 获取模板
//        String salaryTemplateContent = salarySendInfo.getSalaryTemplate();
//        if (StringUtils.isBlank(salaryTemplateContent)) {
//            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100513, "没有默认模板，无法查看"));
//        }
//        SalaryTemplatePO salaryTemplate = SalaryBillBO.buildSalaryTemplateContent(salaryTemplateContent);
//
//        // 判断是否是补发
//        boolean isReplenish = NumberUtils.INTEGER_ONE.equals(salarySendInfo.getSalaryAcctType());
//        SalaryTemplateBO.copyShowSet(isReplenish, salaryTemplate);
//
//        // 获取人员信息项目分类
//        List<SalaryTemplateSalaryItemSetListDTO> listDTOS = JSONArray.parseArray(isReplenish ? salaryTemplate.getReplenishSalaryItemSetting() : salaryTemplate.getSalaryItemSetting(), SalaryTemplateSalaryItemSetListDTO.class);
//        // 国际化处理
//        listDTOS = SalaryTemplateBO.handleI18n(listDTOS);
//        // 获取人员信息项目分类
//        SalaryTemplateSalaryItemSetListDTO employeeInformation = SalaryTemplateBO.getEmployeeInformation(listDTOS);
//        // 1.薪资项目赋值
//        List<SalaryTemplateSalaryItemSetListDTO> itemSetListDTOS = listDTOS.stream()
//                .filter(e -> !SalaryTemplateSalaryItemSetGroupConstant.EMPLOYEE_INFO_GROUP_ID.equals(e.getGroupId())).collect(Collectors.toList());
//        // 获取薪资核算数据
//        List<SalaryAcctEmployeePO> salaryAcctEmployees = getSalaryAcctEmployeeService(user)
//                .listByRecordIdAndEmployeeIds(salarySendInfo.getSalaryAcctRecordId(), Collections.singletonList(salarySendInfo.getEmployeeId()));
//        Set<Long> salaryAcctEmployeeIds = SalaryEntityUtil.properties(salaryAcctEmployees, SalaryAcctEmployeePO::getId);
//        List<SalaryAcctResultValuePO> salaryAcctResultValues = getSalaryAcctResultService(user).listBySalaryAcctEmployeeIds(salaryAcctEmployeeIds);
////        List<SalaryAcctResultPO> salaryAcctResultList = getSalaryAcctResultService(user).listBySalaryAcctRecordIdsAndEmployeeIds(Collections.singletonList(salarySendInfo.getSalaryAcctRecordId()), Collections.singletonList(currentEmployeeId));
//        if (CollectionUtils.isNotEmpty(salaryAcctResultValues)) {
//            SalaryAcctRecordPO salaryAcctRecord = getSalaryAcctRecordService(user).getById(salarySendInfo.getSalaryAcctRecordId());
//            // 当前查询核算记录是回算，但是发放不是回算的，那么就取源头核算结果
//            boolean isOrigin = salaryAcctRecord != null && !isReplenish && salaryAcctRecord.getBackCalcStatus().equals(NumberUtils.INTEGER_ONE);
//            Map<String, String> map = Maps.newHashMap();
//            if (salarySendInfo.getTaxAgentId().equals(0L)) {
//                for (SalaryAcctResultValuePO salaryAcctResultValue : salaryAcctResultValues) {
//                    if (isOrigin) {
//                        if (salaryAcctResultValue.getOriginResultValue() != null && !salaryAcctResultValue.getOriginResultValue().isEmpty()) {
//                            map.putAll(salaryAcctResultValue.getOriginResultValue());
//                        }
//                    } else {
//                        map.putAll(salaryAcctResultValue.getResultValue());
//                    }
//                }
//            } else {
//                Map<String, SalaryAcctEmployeePO> salaryAcctEmployeeMap = SalaryEntityUtil
//                        .convert2Map(salaryAcctEmployees, salaryAcctEmployee -> salaryAcctEmployee.getEmployeeId() + "-" + salaryAcctEmployee.getTaxAgentId());
//                Map<Long, SalaryAcctResultValuePO> singleEmpAcctMap = SalaryEntityUtil.convert2Map(salaryAcctResultValues, SalaryAcctResultValuePO::getSalaryAcctEmployeeId);
//                SalaryAcctEmployeePO salaryAcctEmployee = salaryAcctEmployeeMap.get(salarySendInfo.getEmployeeId() + "-" + salarySendInfo.getTaxAgentId());
//                if (salaryAcctEmployee != null && singleEmpAcctMap.containsKey(salaryAcctEmployee.getId())) {
//                    SalaryAcctResultValuePO salaryAcctResultValue = singleEmpAcctMap.get(salaryAcctEmployee.getId());
//                    if (Objects.nonNull(salaryAcctResultValue)) {
//                        if (isOrigin) {
//                            if (salaryAcctResultValue.getOriginResultValue() != null && !salaryAcctResultValue.getOriginResultValue().isEmpty()) {
//                                map.putAll(salaryAcctResultValue.getOriginResultValue());
//                            }
//                        } else {
//                            map.putAll(salaryAcctResultValue.getResultValue());
//                        }
//                    }
//                }
//            }
//            org.apache.commons.collections4.CollectionUtils.emptyIfNull(itemSetListDTOS)
//                    .forEach(item -> item.getItems().forEach(e -> e.setSalaryItemValue(Optional.ofNullable(map.get("" + e.getId())).orElse(""))));
//        } else {
//            org.apache.commons.collections4.CollectionUtils.emptyIfNull(itemSetListDTOS)
//                    .forEach(item -> item.getItems().forEach(e -> e.setSalaryItemValue("")));
//        }
//        // 2.人员信息赋值
//        if (employeeInformation != null && CollectionUtils.isNotEmpty(employeeInformation.getItems())) {
//            // 如果是外部人员
//            if (salarySendInfo.getEmployeeType().equals(SalarySendEmployeeTypeEnum.EXT_EMPLOYEE.getValue())) {
//                ExtEmployeePO extEmployeePO = extEmployeeService.getByIdWithDeleted(salarySendInfo.getEmployeeId());
//                if (extEmployeePO != null) {
//                    SimpleEmployee simpleEmployee = new SimpleEmployee();
//                    simpleEmployee.setId(extEmployeePO.getId());
//                    simpleEmployee.setUsername(extEmployeePO.getUsername());
//                    simpleEmployee.setMobile(extEmployeePO.getUsername());
//                    SalaryBillBO.buildEmployeeInfo(employeeInformation, simpleEmployee, SalaryBillBO.buildEmployeeFieldName());
//                }
//            } else {
//                SimpleEmployee simpleEmployee = hrmCommonEmployeeService.getEmployeeById(salarySendInfo.getEmployeeId());
//                if (simpleEmployee != null) {
//                    // 查询人员状态
//                    HrmStatus hrmStatusDb = hrmCommonHrmStatusService.getHrmStatusByCodeId(simpleEmployee.getPersonnelStatus());
//                    simpleEmployee.setPersonnelStatus(hrmStatusDb == null ? "" : hrmStatusDb.getName());
//                }
//                SalaryBillBO.buildEmployeeInfo(employeeInformation, simpleEmployee, SalaryBillBO.buildEmployeeFieldName());
//            }
//        }
//        // 主题设置
//        salaryTemplate.setTheme(SalaryBillBO
//                .getBillTitle(salaryTemplate.getTheme(), salarySendInfo.getSalaryMonth(), TenantContext.getCurrentTenant().getTenantName()));
//        // 背景图
//        if (StringUtils.isNotBlank(salaryTemplate.getBackground())) {
//            salaryTemplate.setBackground(String.format("/papi/file/preview?type=imgs&fileId=%s&random=123456", salaryTemplate.getBackground()));
//        }
//
//        Map<String, Object> resultMap = new LinkedHashMap<>();
//        resultMap.put("tenantName", TenantContext.getCurrentTenant().getTenantName());
//        resultMap.put("sendTime", SalaryDateUtil.getFormatLocalDateTime(salarySendInfo.getSendTime()));
//        resultMap.put("employeeInformation", employeeInformation);
//        resultMap.put("salaryGroups", itemSetListDTOS);
//        resultMap.put("salaryTemplate", salaryTemplate);
//        return resultMap;
//    }
//
//    /**
//     * 企业微信端示例数据
//     *
//     * @param salaryInfoId
//     * @return
//     */
//    private Map<String, Object> handle4WeChartDemo(Long salaryInfoId) {
//        Map<String, Object> resultMap = new LinkedHashMap<>();
//        if (salaryInfoId.equals(1234567890L)) {
//            List<SalaryTemplateSalaryItemSetListDTO> itemSetListDTOS = Lists.newArrayList();
//            List<SalaryTemplateSalaryItemListDTO> items = Lists.newArrayList();
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338782").salaryItemValue("5000.00").name(SalaryI18nUtil.getI18nLabel(184508, "基本工资")).sortedIndex(0).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338783").salaryItemValue("2000.00").name(SalaryI18nUtil.getI18nLabel(184537, "岗位工资")).sortedIndex(1).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739465941241856001").salaryItemValue("100.00").name(SalaryI18nUtil.getI18nLabel(184538, "其他补贴")).sortedIndex(2).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739466138709688320").salaryItemValue("3.00").name(SalaryI18nUtil.getI18nLabel(184539, "绩效分值")).sortedIndex(3).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739505579468095489").salaryItemValue("600.00").name(SalaryI18nUtil.getI18nLabel(184540, "工时奖金")).sortedIndex(4).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739505652314767360").salaryItemValue("500.00").name(SalaryI18nUtil.getI18nLabel(184544, "成果奖金")).sortedIndex(5).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739506094797062145").salaryItemValue("200.00").name(SalaryI18nUtil.getI18nLabel(184545, "考勤扣款")).sortedIndex(6).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739506691648290816").salaryItemValue("800.00").name(SalaryI18nUtil.getI18nLabel(184546, "餐补")).sortedIndex(7).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739516333975699456").salaryItemValue("1").name(SalaryI18nUtil.getI18nLabel(184547, "当月缺勤")).sortedIndex(8).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("739516913670455296").salaryItemValue("200.00").name(SalaryI18nUtil.getI18nLabel(184549, "缺勤扣款")).sortedIndex(9).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338798").salaryItemValue("200.00").name(SalaryI18nUtil.getI18nLabel(184550, "社保代扣")).sortedIndex(10).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338799").salaryItemValue("200.00").name(SalaryI18nUtil.getI18nLabel(184551, "公积金代扣")).sortedIndex(11).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("741652500368097281").salaryItemValue("8800.00").name(SalaryI18nUtil.getI18nLabel(184553, "当月计税收入")).sortedIndex(12).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338770").salaryItemValue("0").name(SalaryI18nUtil.getI18nLabel(184555, "当前累计专项附加扣除合计")).sortedIndex(13).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338780").salaryItemValue("1000.00").name(SalaryI18nUtil.getI18nLabel(184556, "当月预扣个税")).sortedIndex(14).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338800").salaryItemValue("8700.00").name(SalaryI18nUtil.getI18nLabel(184557, "当月实发现金")).sortedIndex(15).build());
//            items.add(SalaryTemplateSalaryItemListDTO.builder().id("735706397435338802").salaryItemValue("").name(SalaryI18nUtil.getI18nLabel(84961, "备注")).sortedIndex(16).build());
//            itemSetListDTOS.add(SalaryTemplateSalaryItemSetListDTO.builder()
//                    .groupId("custom_144")
//                    .groupName(SalaryI18nUtil.getI18nLabel(184561, "工资薪金项目"))
//                    .sortedIndex(0)
//                    .items(items)
//                    .build());
//            SalaryTemplatePO salaryTemplate = SalaryTemplatePO.builder()
//                    .id(759519856313417728L)
//                    .name(SalaryI18nUtil.getI18nLabel(184563, "测试模板"))
//                    .description("")
//                    .theme(SalaryI18nUtil.getI18nLabel(184565, "(示例)工资单明细"))
//                    .background("")
//                    .textContent(SalaryI18nUtil.getI18nLabel(184590, "(示例)本数据仅为您收入预发金额，考勤、绩效及各类专项奖励的差异值将根据实际数据后续在下次发薪时统一予以调整，如有异议，请联系****。"))
//                    .textContentPosition(2)
//                    .salaryItemNullStatus(0)
//                    .salaryItemZeroStatus(0)
//                    .salaryItemSetting(JSON.toJSONString(itemSetListDTOS))
//                    .build();
//            resultMap.put("tenantName", TenantContext.getCurrentTenant().getTenantName());
//            resultMap.put("sendTime", SalaryDateUtil.getFormatLocalDateTime(LocalDateTime.now()));
//            resultMap.put("employeeInformation", null);
//            resultMap.put("salaryGroups", itemSetListDTOS);
//            resultMap.put("salaryTemplate", salaryTemplate);
//        }
//        return resultMap;
//    }
//
//    @Override
//    public Page<SalaryMySalaryBillListDTO> mySalaryBillListPage(Page<SalaryMySalaryBillListDTO> page, SalaryBillQueryParam queryParam, String tenantKey) {
//        getSalarySendInfoMapper().mySalaryBillList(page, queryParam, tenantKey);
//        return page;
//    }
    /** 我的工资单 end **********************************************************************/
}
