# CbCaseApproveLevelServiceImpl.java

    package cloud.wconnect.finance.service.chargeback.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.pojo.dto.UserDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.service.UsergroupInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.finance.cons.chargeback.CaseActionEnum;
    import cloud.wconnect.finance.cons.chargeback.CaseErrorCons;
    import cloud.wconnect.finance.cons.chargeback.CaseResultEnum;
    import cloud.wconnect.finance.cons.chargeback.StageCons;
    import cloud.wconnect.finance.entity.chargeback.*;
    import cloud.wconnect.finance.mapper.chargeback.CbCaseApproveLevelMapper;
    import cloud.wconnect.finance.pojo.dto.chargeback.CbCaseApproveLevelDTO;
    import cloud.wconnect.finance.pojo.dto.chargeback.CbCaseApproveLevelUserDTO;
    import cloud.wconnect.finance.pojo.dto.chargeback.CbCaseCCDTO;
    import cloud.wconnect.finance.pojo.dto.chargeback.SaveApproveLevelDTO;
    import cloud.wconnect.finance.pojo.query.chargeback.CbCaseApproveLevelQuery;
    import cloud.wconnect.finance.service.chargeback.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Nullable;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.*;
    import java.util.concurrent.Executor;
    import java.util.stream.Collectors;

    import static java.util.stream.Collectors.toList;
    import static java.util.stream.Collectors.toSet;

    /**
     * <p>
     * case 审批层级表 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-08-22
     */
    @Service
    @DS("finance")
    public class CbCaseApproveLevelServiceImpl extends ServiceImpl<CbCaseApproveLevelMapper, CbCaseApproveLevel> implements ICbCaseApproveLevelService {

        @Autowired
        private ICbCaseApproveLevelUserService cbCaseApproveLevelUserService;
        @Autowired
        private ICbCaseBankOverTimeDayService cbCaseBankOverTimeDayService;
        @Autowired
        private ChargebackBlacklistSendEventService chargebackBlacklistSendEventService;
        @Autowired
        private ICbCaseApproveLogService cbCaseApproveLogService;
        @Autowired
        private ICbCaseResultService cbCaseResultService;
        @Autowired
        private ICbCaseActionService cbCaseActionService;
        @Lazy
        @Autowired
        private ICbCaseNotifyService cbCaseNotifyService;
        @Autowired
        private ICbAdjService cbAdjService;
        @Lazy
        @Autowired
        private ICbCaseService cbCaseService;
        @Autowired
        private ICbCaseStageEvolutionService cbCaseStageEvolutionService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private UsergroupInternalService usergroupInternalService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private Executor executor;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 校验存在
         *
         * @param id 数据 id
         * @return 审批层级
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        public CbCaseApproveLevel checkExist(long id) {
            CbCaseApproveLevel approveLevel = getById(id);
            if (Objects.isNull(approveLevel)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the cbCaseApproveLevel is not found;id:" + id);
            }
            return approveLevel;
        }

        /**
         * 保存审批层级
         *
         * @param propertyId      资产 id
         * @param approveLevelDTO 审批层级信息
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(Integer propertyId, SaveApproveLevelDTO approveLevelDTO) {
            //查询最大的index
            Integer maxIndex = baseMapper.getMaxIndexForUpdate(propertyId, approveLevelDTO.getStage());
            if (Objects.isNull(maxIndex)) {
                // 从 0 级开始
                approveLevelDTO.setApproveIndex(0);
            } else {
                //设置 index = maxIndex + 1
                approveLevelDTO.setApproveIndex(++maxIndex);

                if (CollectionUtils.isNotEmpty(approveLevelDTO.getUsers())) {
                    checkUserRepeat(propertyId, approveLevelDTO.getStage(), approveLevelDTO.getUsers(), null);
                }
            }

            CbCaseApproveLevel approveLevel = new CbCaseApproveLevel()
                    .setPropertyId(propertyId).setStage(approveLevelDTO.getStage())
                    .setName(approveLevelDTO.getName()).setApproveIndex(approveLevelDTO.getApproveIndex())
                    .setRule(approveLevelDTO.getRule()).setCc(JsonUtils.convertObjListToString(objectMapper, approveLevelDTO.getCc()));
            save(approveLevel);

            cbCaseApproveLevelUserService.saveBatch(approveLevel.getId(), propertyId, approveLevelDTO);
        }

        /**
         * 检查用户是否存在 其他 审批层级
         *
         * @param propertyId 资产 id
         * @param stage      阶段
         * @param userIds    操作人
         * @param id         审批层级 id
         * @author yyt
         * @date 2023/12/29
         */
        private void checkUserRepeat(Integer propertyId, Integer stage, List<Integer> userIds, @Nullable Long id){
            if (CollectionUtils.isEmpty(userIds)) {
                return;
            }

            // 选择用户在整个审批流程（不包含0级）中只能配置一次
            long count = cbCaseApproveLevelUserService.count(new QueryWrapper<CbCaseApproveLevelUser>().lambda()
                    .eq(CbCaseApproveLevelUser::getPropertyId, propertyId)
                    .eq(CbCaseApproveLevelUser::getStage, stage)
                    .ne(Objects.nonNull(id), CbCaseApproveLevelUser::getLevelId, id)
                    .gt(CbCaseApproveLevelUser::getApproveIndex, BaseConstant.ZERO)
                    .in(CbCaseApproveLevelUser::getUserId, userIds));
            if (count > BaseConstant.LONG_ZERO) {
                throw new BadRequestException(CommonErrorI18nConstant.APPROVE_USER_ALREADY_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "User has been configured in the audit hierarchy.");
            }
        }

        /**
         * 修改审批层级
         *
         * @param updateBeforeApproveLevel 修改之前的审批层级
         * @param approveLevelDTO          审批层级信息
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(CbCaseApproveLevel updateBeforeApproveLevel, SaveApproveLevelDTO approveLevelDTO) {
            if (updateBeforeApproveLevel.getApproveIndex() > BaseConstant.ZERO) {
                checkUserRepeat(updateBeforeApproveLevel.getPropertyId(), approveLevelDTO.getStage(),
                        approveLevelDTO.getUsers(), updateBeforeApproveLevel.getId());
            }

            if (!updateBeforeApproveLevel.getRule().equals(approveLevelDTO.getRule())
                    && approveLevelDTO.getRule() == ApproveCons.RULE_ANY) {
                //审批规则由全部 改为 任一 时：当前节点已经有任一人审核过时，推送数据至下一个节点审核
                handlePushNextApproveLevel(updateBeforeApproveLevel, ApproveCons.PUSH_TP_UPDATE_RULE, Collections.emptyList());
            }
            //审批规则是全部时，变更了审批人数量，需要判断是否推送至下一个节点审核
            if (updateBeforeApproveLevel.getRule().equals(approveLevelDTO.getRule())
                    && approveLevelDTO.getRule() == ApproveCons.RULE_ALL) {
                //更新后的审批配置
                handlePushNextApproveLevel(updateBeforeApproveLevel, ApproveCons.PUSH_TP_UPDATE_USER, approveLevelDTO.getUsers());
            }

            //修改 approve level
            Long levelId = updateBeforeApproveLevel.getId();
            CbCaseApproveLevel updateContent = new CbCaseApproveLevel();
            updateContent.setName(approveLevelDTO.getName());
            updateContent.setRule(approveLevelDTO.getRule());
            updateContent.setId(updateBeforeApproveLevel.getId());
            updateContent.setCc(JsonUtils.convertObjListToString(objectMapper, approveLevelDTO.getCc()));
            updateById(updateContent);

            if (!approveLevelDTO.getName().equals(updateBeforeApproveLevel.getName())) {
                // 重置 case 审批日志中的 审批层级名称
                cbCaseApproveLogService.update(new UpdateWrapper<CbCaseApproveLog>().lambda()
                        .set(CbCaseApproveLog::getName, approveLevelDTO.getName())
                        .eq(CbCaseApproveLog::getLevelId, updateBeforeApproveLevel.getId()));
            }

            // 删除当前节点下的审核人
            Integer propertyId = updateBeforeApproveLevel.getPropertyId();
            cbCaseApproveLevelUserService.clearLeveUser(propertyId, levelId);
            // 新增修改的 审批人
            approveLevelDTO.setApproveIndex(updateBeforeApproveLevel.getApproveIndex());
            cbCaseApproveLevelUserService.saveBatch(levelId, propertyId, approveLevelDTO);
        }

        /**
         * 推送至下一审批层级
         *
         * @param approveLevel     审批节点
         * @param pushType         推送类型（1: 删除某一节点时推送 2.修改某一节点规则，由全部改为任一时推送 3.节点规则是全部时，删除某一节点上的人员时推送）
         * @param unApproveUserIds 修改后的审批人
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        public void handlePushNextApproveLevel(CbCaseApproveLevel approveLevel, int pushType, @Nullable List<Integer> unApproveUserIds) {
            // 如果是 0 级处理人，则不推送下一阶段
            Integer approveIndex = approveLevel.getApproveIndex();
            if (approveIndex == BaseConstant.ZERO) {
                return;
            }

            //查询当前节点下待审核的数据
            List<CbCase> approvingCases = cbCaseService.list(new QueryWrapper<CbCase>().lambda()
                    .eq(CbCase::getPropertyId, approveLevel.getPropertyId()).eq(CbCase::getStage, approveLevel.getStage())
                    .eq(CbCase::getLevelId, approveLevel.getId())
                    .eq(CbCase::getApproveStatus, ApproveStatusEnum.APPROVEING.getStatus()));
            if(CollectionUtils.isEmpty(approvingCases)){
                return;
            }
            approvingCases = filter(pushType, unApproveUserIds, approvingCases);
            if (CollectionUtils.isEmpty(approvingCases)) {
                return;
            }

            pushNextApproveLevel(approveLevel, approvingCases, false);
        }

        /**
         * 过滤出待推送至下一节点的数据
         *
         * @param pushType          推送类型（1: 删除某一节点时推送 2.修改某一节点规则，由全部改为任一时推送 3.节点规则是全部时，删除某一节点上的人员时推送）
         * @param updApproveUserIds 修改后的审批人
         * @param approvingCases    case
         * @return 待推送至下一节点的数据
         * @author yyt
         * @date 2023/8/25
         */
        private List<CbCase> filter(int pushType, List<Integer> updApproveUserIds, List<CbCase> approvingCases) {
            if (pushType == ApproveCons.PUSH_TP_DELETE) {
                return approvingCases;
            } else if (pushType == ApproveCons.PUSH_TP_UPDATE_RULE) {
                //查询待审核数据 在当前节点下对应的审核日志
                Map<Long, Long> approveLogCountByCaseId = cbCaseApproveLogService.listByCases(approvingCases).stream()
                        .collect(Collectors.groupingBy(CbCaseApproveLog::getCaseId, Collectors.counting()));
                // 过滤出当前节点已经有人审批的数据
                return approvingCases.stream().filter(item -> Objects.nonNull(approveLogCountByCaseId.get(item.getId())))
                        .collect(toList());
            } else if (pushType == ApproveCons.PUSH_TP_UPDATE_USER) {
                //查询待审核数据 在当前节点下对应的审核日志
                Map<Long, List<CbCaseApproveLog>> approveLogsByCaseId = cbCaseApproveLogService.listByCases(approvingCases)
                        .stream().collect(Collectors.groupingBy(CbCaseApproveLog::getCaseId));
                //当已审批的集合 包含 修改的审批人集合 时推送至下一个节点
                return approvingCases.stream().filter(item -> {
                    //获取已审批人集合
                    Set<Integer> approveUserIds = approveLogsByCaseId.getOrDefault(item.getId(), Collections.emptyList())
                            .stream().map(CbCaseApproveLog::getApproveBy).collect(toSet());
                    //过滤出《已审批人》包含《更新后的审批配置》的数据
                    return approveUserIds.containsAll(updApproveUserIds);
                }).collect(toList());
            } else {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_INVALID,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("pushType"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "not match push type");
            }
        }

        /**
         * case 推送下一审批节点
         *
         * @param currentApproveLevel 审批层级
         * @param approvingCases      审批中 case
         * @param singleCasePush      单个 case 推送
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        public void pushNextApproveLevel(CbCaseApproveLevel currentApproveLevel, List<CbCase> approvingCases, boolean singleCasePush) {
            //查询下一个审批节点
            Integer propertyId = currentApproveLevel.getPropertyId();
            Integer stage = currentApproveLevel.getStage();
            Integer approveIndex = currentApproveLevel.getApproveIndex();
            CbCaseApproveLevel nextApproveLevel = getOne(new QueryWrapper<CbCaseApproveLevel>().lambda()
                    .eq(CbCaseApproveLevel::getPropertyId, propertyId).eq(CbCaseApproveLevel::getStage, stage)
                    .gt(CbCaseApproveLevel::getApproveIndex, approveIndex)
                    .orderByAsc(CbCaseApproveLevel::getApproveIndex).last("limit 1"));
            if (Objects.isNull(nextApproveLevel)) {
                // 如果 当前阶段 final 且 没有配置审批层级，则直接返回
                if (currentApproveLevel.getStage() == StageCons.FINAL && currentApproveLevel.getApproveIndex() == BaseConstant.ZERO) {
                    // 调整审批状态为：无需审批
                    cbCaseService.updateBatchById(approvingCases.stream().map(item -> {
                        CbCase updateContent = new CbCase();
                        updateContent.setId(item.getId());
                        updateContent.setApproveStatus(ApproveStatusEnum.WITHOUT_APPROVAL.getStatus());
                        return updateContent;
                    }).collect(toList()));
                    return;
                }

                // 推送下一个 阶段
                pushNextStage(propertyId, stage, approvingCases, singleCasePush);
            } else {
                Map<Long, CbCaseAction> caseActionByCaseId = cbCaseActionService.list(approvingCases.stream().map(CbCase::getId)
                                .collect(toList()), Lists.newArrayList(currentApproveLevel.getStage()))
                        .stream().collect(Collectors.toMap(CbCaseAction::getCaseId, v -> v));

                List<CbCaseAction> updateCaseActions = Lists.newArrayListWithExpectedSize(approvingCases.size());

                // 推送下一审批节点
                cbCaseService.updateBatchById(approvingCases.stream().map(item -> {
                    CbCase updateContent = new CbCase();
                    updateContent.setId(item.getId());
                    updateContent.setLevelId(nextApproveLevel.getId());
                    updateContent.setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus());
                    item.setLevelId(nextApproveLevel.getId());

                    CbCaseAction caseAction = caseActionByCaseId.get(item.getId());
                    if (Objects.nonNull(caseAction)) {
                        updateCaseActions.add(new CbCaseAction().setId(caseAction.getId()).setLevelId(item.getLevelId())
                                .setBatchId(item.getBatchId()).setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus()));
                    }

                    return updateContent;
                }).collect(toList()));

                if (CollectionUtils.isNotEmpty(updateCaseActions)) {
                    cbCaseActionService.updateBatchById(updateCaseActions);
                }

                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    // 发送邮件通知
                    cbCaseNotifyService.notifyUser(approvingCases, null, singleCasePush, null);
                });
            }
        }

        /**
         * 推送下一阶段
         *
         * @param propertyId     资产 id
         * @param stage          阶段
         * @param cases          case
         * @param singleCasePush 单个 case 推送
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        public void pushNextStage(Integer propertyId, Integer stage, List<CbCase> cases, boolean singleCasePush) {
            // case 当前阶段完成，判断是否需要进入下一阶段
            switch (stage) {
                case StageCons.INITIAL_CASE:
                    pushNextStageForInitCase(propertyId, cases, singleCasePush);
                    break;
                case StageCons.HOTEL_OPERATION:
                    pushNextStageForHotelOperation(propertyId, cases, singleCasePush);
                    break;
                case StageCons.OVERDUE:
                    pushNextStageForOverdue(propertyId, cases, singleCasePush);
                    break;
                case StageCons.PORTAL:
                    pushNextStageForPortal(propertyId, cases, singleCasePush);
                    break;
                case StageCons.PATCH:
                    pushNextStageForPatch(propertyId, cases, singleCasePush);
                    break;
                case StageCons.FINAL:
                    pushNextStageForFinal(cases);
                    break;
                default:
            }
        }

        /**
         * init case 阶段 推送
         *
         * @param propertyId    资产 id
         * @param cases         case
         * @author yyt
         * @date 2023/9/6
         */
        private void pushNextStageForInitCase(Integer propertyId, List<CbCase> cases, boolean singleCasePush) {
            CbCaseApproveLevel nextApproveLevel = checkZeroLevelExist(propertyId, StageCons.HOTEL_OPERATION);
            Long levelId = nextApproveLevel.getId();
            String timeZone = propertyI18nRedisService.get(propertyId).getTimezone();
            Map<Integer, Integer> overtTimeDayByBankId = cbCaseBankOverTimeDayService.listByPropertyId(propertyId).stream()
                    .collect(Collectors.toMap(CbCaseBankOverTimeDay::getBankId, CbCaseBankOverTimeDay::getDay));

            long now = DateTimeUtils.nowTimestamp();

            // 推送至 hotel operation 阶段
            cbCaseService.updateBatchById(cases.stream().map(item -> {
                CbCase updateContent = new CbCase();
                updateContent.setId(item.getId());
                updateContent.setStatus(BaseConstant.ZERO);
                updateContent.setStatusSetBy(sessionContext.getLoginUserId());
                updateContent.setStatusSetTime(now);
                updateContent.setStage(StageCons.HOTEL_OPERATION);
                item.setStage(StageCons.HOTEL_OPERATION);
                updateContent.setLevelId(levelId);
                item.setLevelId(levelId);
                updateContent.setBatchId(BaseConstant.LONG_ZERO);
                updateContent.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());

                Integer day = overtTimeDayByBankId.get(item.getVendor());
                if (Objects.nonNull(day)) {
                    long replayBy = DateTimeUtils.dateNow(timeZone).plusDays(day).toEpochSecond();
                    updateContent.setReplyBy(replayBy);
                    item.setReplyBy(replayBy);
                }
                return updateContent;
            }).collect(toList()));

            // 发送邮件通知
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                cbCaseNotifyService.notifyUser(cases, null, singleCasePush, StageCons.INITIAL_CASE);
            });
        }

        /**
         * hotel operation 阶段 推送
         *
         * @param propertyId    资产 id
         * @param cases         case
         * @author yyt
         * @date 2023/9/6
         */
        private void pushNextStageForHotelOperation(Integer propertyId, List<CbCase> cases, boolean singleCasePush) {
            List<Long> caseIds = cases.stream().map(CbCase::getId).collect(toList());
            Map<Long, Map<Integer, CbCaseAction>> actionsByCaseId = cbCaseActionService.list(caseIds,
                            Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.OVERDUE)).stream()
                    .collect(Collectors.groupingBy(CbCaseAction::getCaseId, Collectors.toMap(CbCaseAction::getStage, v -> v)));

            List<Integer> stages = Lists.newArrayList(StageCons.FINAL, StageCons.PORTAL, StageCons.PATCH);
            Map<Integer, Long> levelIdByStage = checkZeroLevelExist(propertyId, stages).stream()
                    .collect(Collectors.toMap(CbCaseApproveLevel::getStage, CbCaseApproveLevel::getId));

            String timeZone = propertyI18nRedisService.get(propertyId).getTimezone();
            Map<Integer, Integer> overtTimeDayByBankId = cbCaseBankOverTimeDayService.listByPropertyId(propertyId).stream()
                    .collect(Collectors.toMap(CbCaseBankOverTimeDay::getBankId, CbCaseBankOverTimeDay::getDay));

            long now = DateTimeUtils.nowTimestamp();
            List<CbCase> saveBlankCases = Lists.newArrayListWithExpectedSize(cases.size());

            // case 状态演变记录
            Map<Long, CbCaseStageEvolution> statusEvolutionByCaseId =
                    cbCaseStageEvolutionService.list(new QueryWrapper<CbCaseStageEvolution>().lambda()
                                    .in(CbCaseStageEvolution::getCaseId, caseIds))
                            .stream().collect(Collectors.toMap(CbCaseStageEvolution::getCaseId, v -> v));
            // 已存在的审批层级
            List<Long> existLevelIds;
            if (MapUtils.isNotEmpty(statusEvolutionByCaseId)) {
                // 已存在的审批层级
                existLevelIds = listByIds(statusEvolutionByCaseId.values().stream()
                        .map(CbCaseStageEvolution::getLastLevelId).toList()).stream().map(CbCaseApproveLevel::getId).toList();
            } else {
                existLevelIds = Collections.emptyList();
            }

            List<Long> deleteStatusEvolutionIds = Lists.newArrayListWithExpectedSize(cases.size());

            cbCaseService.updateBatchById(cases.stream().map(item -> {
                CbCase updateContent = new CbCase();
                updateContent.setId(item.getId());
                updateContent.setStatus(BaseConstant.ZERO);
                updateContent.setStatusSetBy(sessionContext.getLoginUserId());
                updateContent.setStatusSetTime(now);
                updateContent.setBatchId(BaseConstant.LONG_ZERO);
                updateContent.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());

                int nextStage;
                long levelId;
                Map<Integer, CbCaseAction> actionByStage = actionsByCaseId.get(item.getId());
                CbCaseAction hotelOperationAction = actionByStage.get(StageCons.HOTEL_OPERATION);
                // 如果有状态演变记录，则根据状态演变记录中的历史阶段及状态，跳转置对应阶段；如果没有演变记录，则根据 action 选择结果跳转置对应阶段
                CbCaseStageEvolution cbCaseStageEvolution = statusEvolutionByCaseId.get(item.getId());
                if (Objects.nonNull(cbCaseStageEvolution)) {
                    nextStage = cbCaseStageEvolution.getLastStage();
                    levelId = cbCaseStageEvolution.getLastLevelId();
                    updateContent.setBatchId(cbCaseStageEvolution.getBatchId());
                    updateContent.setApproveStatus(cbCaseStageEvolution.getApproveStatus());
                    updateContent.setStatus(cbCaseStageEvolution.getLastStatus());
                    // 检查下对应阶段的 level 是否存在，如果不存在（进入酒店表态阶段后，把进入酒店表态阶段前 case 所在阶段的 level 删除，就会出现这种情况），则置待送审状态，重新发起审批。
                    if (!existLevelIds.contains(levelId)) {
                        levelId = levelIdByStage.get(nextStage);
                        updateContent.setBatchId(BaseConstant.LONG_ZERO);
                        updateContent.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
                    }
                    // case 进入对应阶段后，要清除掉状态演变记录
                    deleteStatusEvolutionIds.add(cbCaseStageEvolution.getId());
                } else {
                    CbCaseAction overdueAction = actionByStage.get(StageCons.OVERDUE);
                    if (Objects.isNull(overdueAction)) {
                        // accept -> final 阶段
                        if (hotelOperationAction.getAction() == CaseActionEnum.ACCEPT.getCode()) {
                            nextStage = StageCons.FINAL;
                            levelId = levelIdByStage.get(StageCons.FINAL);
                        } else {
                            // dispute -> portal
                            nextStage = StageCons.PORTAL;
                            levelId = levelIdByStage.get(StageCons.PORTAL);
                        }
                    } else {
                        if (hotelOperationAction.getAction() == CaseActionEnum.ACCEPT.getCode() && overdueAction.getAction() == CaseActionEnum.ACCEPT.getCode()) {
                            // 财务、酒店 都是 accept -> final 阶段
                            nextStage = StageCons.FINAL;
                            levelId = levelIdByStage.get(StageCons.FINAL);

                            // 处理下 patch by
                            updateContent.setPatchBy(0);
                            updateContent.setPatchTime(0L);
                        } else if (hotelOperationAction.getAction() == CaseActionEnum.DISPUTE.getCode() && overdueAction.getAction() == CaseActionEnum.DISPUTE.getCode()) {
                            // 财务、酒店 都是 dispute -> portal
                            nextStage = StageCons.PORTAL;
                            levelId = levelIdByStage.get(StageCons.PORTAL);
                        } else {
                            if (overdueAction.getAction() == CaseActionEnum.ACCEPT.getCode() && hotelOperationAction.getAction() == CaseActionEnum.DISPUTE.getCode()) {
                                // 财务 accept，酒店 dispute -> patch
                                nextStage = StageCons.PATCH;
                                levelId = levelIdByStage.get(StageCons.PATCH);

                                Integer day = overtTimeDayByBankId.get(item.getVendor());
                                if (Objects.nonNull(day)) {
                                    long adjReplayBy = DateTimeUtils.dateNow(timeZone).plusDays(day).toEpochSecond();
                                    updateContent.setAdjReplyBy(adjReplayBy);
                                    item.setAdjReplyBy(adjReplayBy);
                                }
                            } else {
                                // 财务 dispute，酒店 accept -> portal
                                nextStage = StageCons.PORTAL;
                                levelId = levelIdByStage.get(StageCons.PORTAL);
                            }
                        }
                    }
                }

                Boolean generateBlackList = hotelOperationAction.getGenerateBlackList();
                if (Objects.nonNull(generateBlackList) && generateBlackList) {
                    saveBlankCases.add(item);
                }

                updateContent.setStage(nextStage);
                updateContent.setLevelId(levelId);
                updateContent.setHasHotelOperations(true);
                item.setLevelId(levelId);
                item.setStage(nextStage);
                return updateContent;
            }).collect(toList()));

            Map<Long, CbCaseAction> caseActionByCaseId = cbCaseActionService.list(caseIds, Lists.newArrayList(StageCons.HOTEL_OPERATION))
                    .stream().collect(Collectors.toMap(CbCaseAction::getCaseId, v -> v));
            if (MapUtils.isNotEmpty(caseActionByCaseId)) {
                cbCaseActionService.updateBatchById(caseActionByCaseId.values().stream().map(item ->
                                new CbCaseAction().setId(item.getId()).setApproveStatus(ApproveStatusEnum.APPROVED.getStatus()))
                        .collect(toList()));
            }

            if (CollectionUtils.isNotEmpty(deleteStatusEvolutionIds)) {
                cbCaseStageEvolutionService.removeBatchByIds(deleteStatusEvolutionIds);
            }

            // 发送审批 adj 事件
            cbAdjService.handleApproveAdjBatch(cases);

            if (!saveBlankCases.isEmpty()) {
                // 发送生成黑名单事件
                chargebackBlacklistSendEventService.sendAddBlackEvent(saveBlankCases);
            }

            // 发送邮件通知
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                cbCaseNotifyService.notifyUser(cases, null, singleCasePush, StageCons.HOTEL_OPERATION);
            });
        }

        /**
         * overdue 阶段 推送
         *
         * @param propertyId    资产 id
         * @param cases         case
         * @author yyt
         * @date 2023/9/6
         */
        private void pushNextStageForOverdue(Integer propertyId, List<CbCase> cases, boolean singleCasePush) {
            List<Integer> stages = Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.PORTAL, StageCons.FINAL, StageCons.PATCH);
            Map<Integer, Long> levelIdByStage = checkZeroLevelExist(propertyId, stages).stream()
                    .collect(Collectors.toMap(CbCaseApproveLevel::getStage, CbCaseApproveLevel::getId));

            List<Long> caseIds = cases.stream().map(CbCase::getId).collect(toList());
            Map<Long, CbCaseAction> hotelOperationCaseActionByCaseId = cbCaseActionService.list(caseIds,
                            Lists.newArrayList(StageCons.HOTEL_OPERATION)).stream()
                    .collect(Collectors.toMap(CbCaseAction::getCaseId, v -> v));

            String timeZone = propertyI18nRedisService.get(propertyId).getTimezone();
            Map<Integer, Integer> overtTimeDayByBankId = cbCaseBankOverTimeDayService.listByPropertyId(propertyId).stream()
                    .collect(Collectors.toMap(CbCaseBankOverTimeDay::getBankId, CbCaseBankOverTimeDay::getDay));

            long now = DateTimeUtils.nowTimestamp();

            cbCaseService.updateBatchById(cases.stream().map(item -> {
                CbCase updateContent = new CbCase();
                updateContent.setId(item.getId());
                updateContent.setStatus(BaseConstant.ZERO);
                updateContent.setStatusSetBy(sessionContext.getLoginUserId());
                updateContent.setStatusSetTime(now);
                updateContent.setBatchId(BaseConstant.LONG_ZERO);
                updateContent.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
                int nextStage;
                long levelId;
                // accept
                if (item.getAction() == CaseActionEnum.ACCEPT.getCode()) {
                    // 如果没有 酒店表态，则进入 酒店表态阶段
                    // 如果有 酒店表态， 酒店 选择 accept 则进入 final，酒店 选择 dispute 则进入 patch
                    CbCaseAction hotelOperationCaseAction = hotelOperationCaseActionByCaseId.get(item.getId());
                    if (Objects.isNull(hotelOperationCaseAction)) {
                        nextStage = StageCons.HOTEL_OPERATION;
                        levelId = levelIdByStage.get(StageCons.HOTEL_OPERATION);
                    } else {
                        // 如果 酒店表态阶段 未审批完成，则继续走 酒店表态阶段，如果审批完成，则后续阶段
                        if(hotelOperationCaseAction.getApproveStatus() != ApproveStatusEnum.APPROVED.getStatus()){
                            nextStage = StageCons.HOTEL_OPERATION;
                            levelId = hotelOperationCaseAction.getLevelId();
                            updateContent.setBatchId(hotelOperationCaseAction.getBatchId());
                            updateContent.setApproveStatus(hotelOperationCaseAction.getApproveStatus());
                        } else {
                            if (hotelOperationCaseAction.getAction() == CaseActionEnum.ACCEPT.getCode()) {
                                // accept -> final
                                nextStage = StageCons.FINAL;
                                levelId = levelIdByStage.get(StageCons.FINAL);

                                // 处理下 patch by
                                updateContent.setPatchBy(0);
                                updateContent.setPatchTime(0L);
                            } else {
                                // dispute -> patch
                                nextStage = StageCons.PATCH;
                                levelId = levelIdByStage.get(StageCons.PATCH);

                                Integer day = overtTimeDayByBankId.get(item.getVendor());
                                if (Objects.nonNull(day)) {
                                    long adjReplayBy = DateTimeUtils.dateNow(timeZone).plusDays(day).toEpochSecond();
                                    updateContent.setAdjReplyBy(adjReplayBy);
                                    item.setAdjReplyBy(adjReplayBy);
                                }
                            }
                        }
                    }
                } else {
                    // dispute -> portal
                    nextStage = StageCons.PORTAL;
                    levelId = levelIdByStage.get(StageCons.PORTAL);
                    updateContent.setPatchBy(BaseConstant.ZERO);
                    updateContent.setPatchTime(BaseConstant.LONG_ZERO);
                }
                updateContent.setStage(nextStage);
                updateContent.setLevelId(levelId);
                item.setStage(nextStage);
                item.setLevelId(levelId);
                return updateContent;
            }).collect(toList()));

            // 发送邮件通知
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                cbCaseNotifyService.notifyUser(cases, null, singleCasePush, StageCons.OVERDUE);
            });
        }

        /**
         * portal 阶段 推送
         *
         * @param propertyId    资产 id
         * @param cases         case
         * @author yyt
         * @date 2023/9/6
         */
        private void pushNextStageForPortal(Integer propertyId, List<CbCase> cases, boolean singleCasePush) {
            List<Long> caseIds = cases.stream().map(CbCase::getId).collect(toList());
            Map<Long, Integer> resultByCaseId = cbCaseResultService.listByCaseIds(caseIds, Lists.newArrayList(CaseResultEnum.WIN.getCode(),
                            CaseResultEnum.LOSS.getCode()))
                    .stream().collect(Collectors.toMap(CbCaseResult::getCaseId, CbCaseResult::getResult));

            Map<Long, Map<Integer, CbCaseAction>> actionsByCaseId = cbCaseActionService.list(caseIds,
                            Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.OVERDUE))
                    .stream().collect(Collectors.groupingBy(CbCaseAction::getCaseId, Collectors.toMap(CbCaseAction::getStage, v -> v)));

            List<Integer> stages = Lists.newArrayList(StageCons.FINAL, StageCons.PATCH);
            Map<Integer, Long> levelIdByStage = checkZeroLevelExist(propertyId, stages).stream()
                    .collect(Collectors.toMap(CbCaseApproveLevel::getStage, CbCaseApproveLevel::getId));

            Map<Integer, Integer> overtTimeDayByBankId = cbCaseBankOverTimeDayService.listByPropertyId(propertyId).stream()
                    .collect(Collectors.toMap(CbCaseBankOverTimeDay::getBankId, CbCaseBankOverTimeDay::getDay));

            long now = DateTimeUtils.nowTimestamp();
            String timeZone = propertyI18nRedisService.get(propertyId).getTimezone();
            List<CbCase> deleteAdjCases = Lists.newArrayListWithExpectedSize(cases.size());
            List<CbCase> updateCases = cases.stream()
                    .filter(item -> Objects.nonNull(resultByCaseId.get(item.getId())))
                    .map(item -> {
                        CbCase updateContent = new CbCase();
                        updateContent.setId(item.getId());
                        updateContent.setStatus(BaseConstant.ZERO);
                        updateContent.setStatusSetBy(sessionContext.getLoginUserId());
                        updateContent.setStatusSetTime(now);

                        // overdue 选择 dispute 且 hotel operation 选择 accept
                        Map<Integer, CbCaseAction> actionByStage = actionsByCaseId.get(item.getId());
                        CbCaseAction hotelOperationAction = actionByStage.get(StageCons.HOTEL_OPERATION);
                        CbCaseAction overdueAction = actionByStage.get(StageCons.OVERDUE);
                        boolean hasAdj = Objects.nonNull(overdueAction) && overdueAction.getAction() == CaseActionEnum.DISPUTE.getCode()
                                && Objects.nonNull(hotelOperationAction) && hotelOperationAction.getAction() == CaseActionEnum.ACCEPT.getCode();

                        int nextStage;
                        long levelId;

                        Integer result = resultByCaseId.get(item.getId());
                        if (result == CaseResultEnum.WIN.getCode()) {
                            // 如果 财务选择的 dispute，酒店选择的 accept，那么此 adj 无效，需要删除 adj
                            if (hasAdj) {
                                // 删除 adj
                                deleteAdjCases.add(item);
                            }
                            //  win -> final 阶段
                            nextStage = StageCons.FINAL;
                            levelId = levelIdByStage.get(StageCons.FINAL);
                        } else {
                            //如果 财务选择的 dispute，酒店选择的 accept，那么此 adj 有效，直接进入 final 环节就行，不需要走 patch 环节
                            if (hasAdj) {
                                // lose -> final 阶段
                                nextStage = StageCons.FINAL;
                                levelId = levelIdByStage.get(StageCons.FINAL);
                            } else {
                                // lose -> patch 阶段
                                nextStage = StageCons.PATCH;
                                levelId = levelIdByStage.get(StageCons.PATCH);

                                Integer day = overtTimeDayByBankId.get(item.getVendor());
                                if (Objects.nonNull(day)) {
                                    long adjReplayBy = DateTimeUtils.dateNow(timeZone).plusDays(day).toEpochSecond();
                                    updateContent.setAdjReplyBy(adjReplayBy);
                                    item.setAdjReplyBy(adjReplayBy);
                                }
                            }
                        }
                        updateContent.setBatchId(BaseConstant.LONG_ZERO);
                        updateContent.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
                        updateContent.setStage(nextStage);
                        updateContent.setLevelId(levelId);
                        item.setStage(nextStage);
                        item.setLevelId(levelId);
                        return updateContent;
                    }).collect(toList());
            if (CollectionUtils.isNotEmpty(updateCases)) {
                cbCaseService.updateBatchById(updateCases);
            }

            if (!deleteAdjCases.isEmpty()) {
                // 删除 adj
                cbAdjService.handleDeleteAdjBatch(propertyId, deleteAdjCases);
            }

            // 发送邮件通知
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                cbCaseNotifyService.notifyUser(cases, null, singleCasePush, StageCons.PORTAL);
            });
        }

        /**
         * patch 阶段 推送
         *
         * @param propertyId    资产 id
         * @param cases         case
         * @author yyt
         * @date 2023/9/6
         */
        private void pushNextStageForPatch(Integer propertyId, List<CbCase> cases, boolean singleCasePush) {
            // 推送至 final 阶段
            long now = DateTimeUtils.nowTimestamp();
            Long levelId = checkZeroLevelExist(propertyId, StageCons.FINAL).getId();
            cbCaseService.updateBatchById(cases.stream().map(item -> {
                CbCase updateContent = new CbCase();
                updateContent.setId(item.getId());
                updateContent.setStatus(BaseConstant.ZERO);
                updateContent.setStatusSetBy(sessionContext.getLoginUserId());
                updateContent.setStatusSetTime(now);
                updateContent.setStage(StageCons.FINAL);
                item.setStage(StageCons.FINAL);
                updateContent.setLevelId(levelId);
                item.setLevelId(levelId);
                updateContent.setBatchId(BaseConstant.LONG_ZERO);
                updateContent.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
                return updateContent;
            }).collect(toList()));

            // 发送审批 adj 事件
            cbAdjService.handleApproveAdjBatch(cases);

            // 发送邮件通知
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                cbCaseNotifyService.notifyUser(cases, null, singleCasePush, StageCons.PATCH);
            });
        }

        /**
         * final 阶段 推送
         *
         * @param cases      case
         * @author yyt
         * @date 2023/9/6
         */
        private void pushNextStageForFinal(List<CbCase> cases) {
            // 标记审批完成
            cbCaseService.updateBatchById(cases.stream().map(item -> {
                CbCase updateContent = new CbCase();
                updateContent.setId(item.getId());
                updateContent.setApproveStatus(ApproveStatusEnum.APPROVED.getStatus());
                return updateContent;
            }).collect(toList()));
        }

        /**
         * 删除审批层级
         *
         * @param approveLevel 审批层级
         * @author yyt
         * @date 2023/8/26
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void delete(CbCaseApproveLevel approveLevel) {
            // 0 级配置不可以删除
            if (approveLevel.getApproveIndex() == BaseConstant.ZERO) {
                throw new BadRequestException(CaseErrorCons.OPERATION_FAIL_CONFIG_HANDLER_CANNOT_DELETE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "The handler configuration cannot be deleted.");
            }

            handlePushNextApproveLevel(approveLevel, ApproveCons.PUSH_TP_DELETE, Collections.emptyList());

            Long levelId = approveLevel.getId();
            removeById(levelId);
            cbCaseApproveLevelUserService.clearLeveUser(approveLevel.getPropertyId(), levelId);
        }

        /**
         * 检查 0 级配置存在
         *
         * @param propertyId 资产 id
         * @param stage      阶段
         * @return 0 级配置
         * @author yyt
         * @date 2023/8/31
         */
        @Override
        public CbCaseApproveLevel checkZeroLevelExist(Integer propertyId, int stage) {
            CbCaseApproveLevel zeroLevel = getOne(new QueryWrapper<CbCaseApproveLevel>().lambda()
                    .eq(CbCaseApproveLevel::getPropertyId, propertyId).eq(CbCaseApproveLevel::getStage, stage)
                    .eq(CbCaseApproveLevel::getApproveIndex, BaseConstant.ZERO));
            if (Objects.isNull(zeroLevel)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the cbCaseApproveLevel is not found");
            }
            return zeroLevel;
        }

        /**
         * 检查 0 级配置存在
         *
         * @param propertyId 资产 id
         * @param stages     阶段
         * @return 0 级配置
         * @author yyt
         * @date 2023/8/31
         */
        @Override
        public List<CbCaseApproveLevel> checkZeroLevelExist(Integer propertyId, List<Integer> stages) {
            List<CbCaseApproveLevel> zeroLevels = list(new QueryWrapper<CbCaseApproveLevel>().lambda()
                    .eq(CbCaseApproveLevel::getPropertyId, propertyId).in(CbCaseApproveLevel::getStage, stages)
                    .eq(CbCaseApproveLevel::getApproveIndex, BaseConstant.ZERO));
            if (CollectionUtils.isEmpty(zeroLevels) || zeroLevels.size() != stages.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the cbCaseApproveLevel is not found");
            }
            return zeroLevels;
        }

        /**
         * 获取某个用户所属审批层级
         *
         * @param propertyId 资产 id
         * @param stage      阶段
         * @param userId     用户
         * @return 审批层级
         * @author yyt
         * @date 2023/8/31
         */
        @Override
        public CbCaseApproveLevel getUserApproveLevel(Integer propertyId, Integer stage, int userId) {
            CbCaseApproveLevelUser approveLevelUser = cbCaseApproveLevelUserService.getOne(new QueryWrapper<CbCaseApproveLevelUser>()
                    .lambda().eq(CbCaseApproveLevelUser::getPropertyId, propertyId)
                    .eq(CbCaseApproveLevelUser::getStage, stage).eq(CbCaseApproveLevelUser::getUserId, userId)
                    .gt(CbCaseApproveLevelUser::getApproveIndex, BaseConstant.ZERO).select(CbCaseApproveLevelUser::getLevelId));
            if (Objects.isNull(approveLevelUser)) {
                return null;
            }
            return getById(approveLevelUser.getLevelId());
        }

        /**
         * 查询某阶段审批层级（不包含 0 级）
         *
         * @param propertyId 资产 id
         * @param stage      阶段
         * @return 审批层级
         * @author yyt
         * @date 2023/9/9
         */
        @Override
        public List<CbCaseApproveLevel> listApproveLevel(Integer propertyId, Integer stage) {
            return list(new LambdaQueryWrapper<CbCaseApproveLevel>()
                    .eq(CbCaseApproveLevel::getPropertyId, propertyId)
                    .eq(CbCaseApproveLevel::getStage, stage)
                    .gt(CbCaseApproveLevel::getApproveIndex, BaseConstant.ZERO)
                    .orderByAsc(CbCaseApproveLevel::getApproveIndex));
        }

        /**
         * 查询某阶段下的审批层级
         *
         * @param propertyId 资产 id
         * @param stage      阶段
         * @return 审批层级
         * @author yyt
         * @date 2024/12/4
         **/
        @Override
        public List<CbCaseApproveLevel> listAllApproveLevel(Integer propertyId, Integer stage) {
            return list(new QueryWrapper<CbCaseApproveLevel>().lambda()
                    .eq(CbCaseApproveLevel::getPropertyId, propertyId)
                    .eq(CbCaseApproveLevel::getStage, stage)
                    .orderByAsc(CbCaseApproveLevel::getApproveIndex));
        }

        /**
         * 查询某资产下的审批层级
         *
         * @param propertyId 资产 id
         * @return 审批层级
         * @author yyt
         * @date 2024/1/17
         */
        @Override
        public List<CbCaseApproveLevel> listApproveLevel(Integer propertyId) {
            return list(new QueryWrapper<CbCaseApproveLevel>().lambda().eq(CbCaseApproveLevel::getPropertyId, propertyId));
        }

        /**
         * 获取 case 当前拒绝的审批层级
         *
         * @param cs case
         * @return 审批层级
         * @author yyt
         * @date 2024/2/19
         **/
        @Override
        public CbCaseApproveLevel getCaseRejectLevel(CbCase cs) {
            CbCaseApproveLog approveLog = cbCaseApproveLogService.getOne(new QueryWrapper<CbCaseApproveLog>().lambda()
                    .eq(CbCaseApproveLog::getCaseId, cs.getId()).eq(CbCaseApproveLog::getStage, cs.getStage())
                    .eq(CbCaseApproveLog::getBatchId, cs.getBatchId()).eq(CbCaseApproveLog::getIsApproved, false)
                    .orderByDesc(CbCaseApproveLog::getId).last(" limit 1"));

            return getById(approveLog.getLevelId());
        }

        /**
         * 查询 0 级层级
         *
         * @param propertyId 资产 id
         * @return 0 级层级
         * @author yyt
         * @date 2024/2/21
         **/
        @Override
        public List<CbCaseApproveLevel> listZeroLevel(Integer propertyId) {
            return list(new QueryWrapper<CbCaseApproveLevel>().lambda().eq(CbCaseApproveLevel::getPropertyId, propertyId)
                    .eq(CbCaseApproveLevel::getApproveIndex, BaseConstant.ZERO));
        }

        /**
         * 查询审批层级
         *
         * @param tenantId   租户 id
         * @param propertyId 资产 id
         * @param query      查询条件
         * @return 审批层级
         * @author yyt
         * @date 2026/1/19
         **/
        @Override
        public List<CbCaseApproveLevelDTO> listApproveLevelDTO(int tenantId, int propertyId, CbCaseApproveLevelQuery query) {
            List<CbCaseApproveLevel> approveLevels = listAllApproveLevel(propertyId, query.getStage());
            if (CollectionUtils.isEmpty(approveLevels)) {
                return Collections.emptyList();
            }

            List<Long> leveIds = approveLevels.stream().map(CbCaseApproveLevel::getId).collect(Collectors.toList());
            Map<Long, List<CbCaseApproveLevelUser>> approveUserByLevelId = cbCaseApproveLevelUserService.listApproveUser(propertyId, leveIds)
                    .stream().collect(Collectors.groupingBy(CbCaseApproveLevelUser::getLevelId));
            List<Integer> userIds = approveUserByLevelId.values().stream().flatMap(Collection::stream)
                    .map(CbCaseApproveLevelUser::getUserId).collect(Collectors.toList());

            Map<Integer, List<Integer>> ccBySource = approveLevels.stream()
                    .filter(item -> StringUtils.isNotBlank(item.getCc()))
                    .flatMap(item ->
                            JsonUtils.convertStringToList(objectMapper, item.getCc(), CbCaseCCDTO.class).stream()
                    ).collect(Collectors.groupingBy(CbCaseCCDTO::getSource,
                            Collectors.mapping(CbCaseCCDTO::getIdentityId, Collectors.toList())));

            userIds.addAll(ccBySource.getOrDefault(EmailConfigCons.SOURCE_USER, Collections.emptyList()));
            Map<Integer, UserDTO> userById = userInternalService.getUsers(tenantId, userIds)
                    .stream().collect(Collectors.toMap(UserDTO::getId, v -> v));

            Map<Integer, String> userGroupNameById = usergroupInternalService.getUserGroupNameByIds(tenantId, propertyId,
                    ccBySource.getOrDefault(EmailConfigCons.SOURCE_USERGROUP, Collections.emptyList()));

            return beanMapper.mapList(approveLevels, CbCaseApproveLevelDTO.class,
                    (entity, item) -> {
                        item.setUsers(approveUserByLevelId.getOrDefault(item.getId(), Collections.emptyList())
                                .stream().map(user -> {
                                    UserDTO userDTO = userById.getOrDefault(user.getUserId(), new UserDTO());
                                    return new CbCaseApproveLevelUserDTO(userDTO.getId(), userDTO.getName(), userDTO.getAccount(),
                                            userDTO.getEmail(), userDTO.getPhone());
                                }).collect(Collectors.toList()));

                        String cc = entity.getCc();
                        if (StringUtils.isNotBlank(cc)) {
                            item.setCcList(setCcInfo(cc, userById, userGroupNameById));
                        } else {
                            item.setCcList(Collections.emptyList());
                        }
                    });
        }

        /**
         * 设置抄送人信息
         *
         * @param cc                抄送人
         * @param userById          用户信息
         * @param userGroupNameById 用户组信息
         * @return 抄送人信息
         * @author yyt
         * @date 2026/1/19
         **/
        private List<CbCaseCCDTO> setCcInfo(String cc, Map<Integer, UserDTO> userById, Map<Integer, String> userGroupNameById) {
            List<CbCaseCCDTO> approveccs = JsonUtils.convertStringToList(objectMapper, cc, CbCaseCCDTO.class);
            for (CbCaseCCDTO approvecc : approveccs) {
                Integer source = approvecc.getSource();
                Integer identityId = approvecc.getIdentityId();
                switch (source) {
                    case EmailConfigCons.SOURCE_USER:
                        UserDTO userDTO = userById.get(identityId);
                        if (Objects.nonNull(userDTO)) {
                            approvecc.setIdentityName(userDTO.getName());
                            approvecc.setEmail(userDTO.getEmail());
                        }
                        break;
                    case EmailConfigCons.SOURCE_USERGROUP:
                        approvecc.setIdentityName(userGroupNameById.get(identityId));
                        break;
                    default:
                        break;
                }
            }
            return approveccs;
        }

    }