# CbCaseActionServiceImpl.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.ApproveStatusEnum;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.finance.cons.S3BucketFunctionCons;
    import cloud.wconnect.finance.cons.chargeback.*;
    import cloud.wconnect.finance.entity.chargeback.CbCase;
    import cloud.wconnect.finance.entity.chargeback.CbCaseAction;
    import cloud.wconnect.finance.entity.chargeback.CbCaseApproveLog;
    import cloud.wconnect.finance.entity.chargeback.CbCaseResult;
    import cloud.wconnect.finance.mapper.chargeback.CbCaseActionMapper;
    import cloud.wconnect.finance.pojo.dto.chargeback.SaveCaseActionDTO;
    import cloud.wconnect.finance.service.chargeback.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    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.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    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;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-08-22
     */
    @Service
    @DS("finance")
    public class CbCaseActionServiceImpl extends ServiceImpl<CbCaseActionMapper, CbCaseAction> implements ICbCaseActionService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private SessionContext sessionContext;
        @Lazy
        @Autowired
        private ICbCaseService cbCaseService;
        @Lazy
        @Autowired
        private ICbCaseApproveLevelService cbCaseApproveLevelService;
        @Autowired
        private ICbCasePublishLogService cbCasePublishLogService;
        @Autowired
        private ICbCaseApproveLogService cbCaseApproveLogService;
        @Lazy
        @Autowired
        private ICbCaseResultService cbCaseResultService;
        @Autowired
        private ICbCaseStageEvolutionService cbCaseStageEvolutionService;
        @Lazy
        @Autowired
        private ICbAdjService cbAdjService;
        @Autowired
        private Executor executor;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        @Value("${aws-s3.region}")
        private String region;
        @Value("#{${aws-s3.function-bucket}}")
        private Map<String, Map<String, Object>> bucketMap;

        /**
         * 获取 case action
         *
         * @param caseIds case id
         * @param stage   阶段
         * @return case action
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        public List<CbCaseAction> list(List<Long> caseIds, @Nullable List<Integer> stage) {
            return list(new QueryWrapper<CbCaseAction>().lambda().in(CbCaseAction::getCaseId, caseIds)
                    .in(Objects.nonNull(stage), CbCaseAction::getStage, stage));
        }

        /**
         * 获取 case action
         *
         * @param caseId case id
         * @param stage  阶段
         * @return case action
         * @author yyt
         * @date 2023/8/25
         */
        @Override
        public CbCaseAction getCaseAction(Long caseId, Integer stage) {
            return getOne(new QueryWrapper<CbCaseAction>().lambda().eq(CbCaseAction::getCaseId, caseId)
                    .eq(CbCaseAction::getStage, stage));
        }

        /**
         * 保存 action
         *
         * @param cs                case
         * @param saveCaseActionDTO case action
         * @author yyt
         * @date 2023/9/1
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(CbCase cs, SaveCaseActionDTO saveCaseActionDTO) {
            Long caseId = cs.getId();
            Integer stage = cs.getStage();
            Integer approveStatus = cs.getApproveStatus();
            Integer status = cs.getStatus();

            // 生成黑名单（false：否，true：是）
            Boolean generateBlackList = saveCaseActionDTO.getGenerateBlackList();

            CbCase updateCase = new CbCase();

            CbCaseAction targetStageAction = getCaseAction(caseId, saveCaseActionDTO.getStage());
            switch (saveCaseActionDTO.getStage()) {
                case StageCons.HOTEL_OPERATION:
                    if (Objects.isNull(targetStageAction)) {
                        if (cs.getStage() == StageCons.OVERDUE) {
                            // 如果当前阶段是 overdue 阶段，则抹去 overdue 阶段填写内容、推送日志、审批日志，HasOverdue 置 false
                            remove(new QueryWrapper<CbCaseAction>().lambda().eq(CbCaseAction::getCaseId, caseId)
                                    .eq(CbCaseAction::getStage, StageCons.OVERDUE));
                            cbCaseApproveLogService.remove(new QueryWrapper<CbCaseApproveLog>().lambda().eq(CbCaseApproveLog::getCaseId, caseId)
                                    .eq(CbCaseApproveLog::getStage, StageCons.OVERDUE));
                            cbCasePublishLogService.clearLog(caseId, Lists.newArrayList(StageCons.OVERDUE));
                            updateCase.setHasOverdue(false);
                        } else {
                            // 如果有 overdue 阶段， 则校验必须跟 overdue 阶段选择一致
                            CbCaseAction caseAction = getCaseAction(cs.getId(), StageCons.OVERDUE);
                            if (Objects.nonNull(caseAction) && !caseAction.getAction().equals(saveCaseActionDTO.getAction())) {
                                throw new BadRequestException(CaseErrorCons.CB_SAVE_ACTION_FAIL,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                        "The hotel operations stage selection action must be the same as the overdue stage selection");
                            }
                        }
                    } else {

                        // 如果操作的 dispute，则检查 adj 不能存在
                        if (saveCaseActionDTO.getAction() == CaseActionEnum.DISPUTE.getCode()) {
                            if (cbAdjService.countByCaseId(caseId) > 0) {
                                throw new BadRequestException(CaseErrorCons.DELETE_ADJ_FIRST,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Please delete adj first");
                            }
                        }
                    }

                    // 当前阶段不是 酒店表态阶段且不是 overdue 阶段，记录下 状态演变记录，为后续推送置下个阶段做准备
                    if (!Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.OVERDUE).contains(stage)) {
                        cbCaseStageEvolutionService.saveOrUpdateRewrite(cs);
                    }

                    // 当 reason 符合生成黑名单的规则时，generateBlackList 参数不能为 null
                    if (Arrays.stream(BlackReasonCons.values()).map(BlackReasonCons::getReason)
                            .anyMatch(reason -> reason.equals(cs.getActualReason()))) {
                        if (Objects.isNull(generateBlackList)) {
                            throw new BadRequestException(CommonErrorI18nConstant.CANNOT_EMPTY,
                                    i18nMessageUtils.getErrorParamMapNotNeedTranslate("generateBlackList"),
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the generateBlackList must not be null");
                        }
                    } else {
                        if (Objects.nonNull(generateBlackList)) {
                            throw new BadRequestException(CommonErrorI18nConstant.MUST_EMPTY,
                                    i18nMessageUtils.getErrorParamMapNotNeedTranslate("generateBlackList"),
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the generateBlackList must be null");
                        }
                    }

                    break;
                case StageCons.OVERDUE:
                    if (Objects.isNull(targetStageAction)) {
                        // 阶段必须是 hotel operation / overdue，状态是 初始化
                        if (!Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.OVERDUE).contains(stage)
                                || status != BaseConstant.ZERO) {
                            throw new BadRequestException(CaseErrorCons.OPERATION_ACTION_FAIL_STAGE_STATUS,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                    "The stage must be hotel operation or overdue, and the status must be initialized before operation can be carried out.");
                        }
                    } else {
                        // 如果是 hotel operation，则 任何状态都可以变更
                        if (stage == StageCons.HOTEL_OPERATION) {

                        } else if (stage == StageCons.PORTAL) {
                            CbCaseResult caseResult = cbCaseResultService.getCaseResult(caseId);
                            if (caseResult != null) {
                                // result 不是 win / lose ，就可以修改
                                if (Lists.newArrayList(CaseResultEnum.WIN.getCode(), CaseResultEnum.LOSS.getCode()).contains(caseResult.getResult())) {
                                    throw new BadRequestException(CaseErrorCons.OPERATION_ACTION_FAIL_RESULT,
                                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                            "the result is win or lose, can not change overdue action.");
                                }
                            }
                        } else {
                            // 校验阶段是 overdue / patch，审批状态是：待审批 / 审批拒绝，才可以操作 action
                            if (!Lists.newArrayList(StageCons.OVERDUE, StageCons.PATCH).contains(stage)
                                    || !Lists.newArrayList(ApproveStatusEnum.PENDING.getStatus(),
                                    ApproveStatusEnum.REJECTED.getStatus()).contains(approveStatus)) {
                                throw new BadRequestException(CaseErrorCons.OPERATION_ACTION_FAIL_STAGE_APPROVE,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                        "The stage must be overdue or patched, and the approval status must be pending or rejected before operation can be carried out.");
                            }
                        }

                        CbCaseAction hotelOperationAction = getCaseAction(caseId, StageCons.HOTEL_OPERATION);

                        // 若选择 dispute， hotel operation 已经填写过 adj 时，则必须先删除 adj 后，再选择 dispute
                        if (saveCaseActionDTO.getAction() == CaseActionEnum.DISPUTE.getCode()) {
                            if (Objects.nonNull(hotelOperationAction)) {
                                cbAdjService.handleDeleteAdj(cs);
                            }
                        }

                        // 若变更 action，且 hotel operation 阶段有保存记录，则同步更新 action
                        if (Objects.nonNull(hotelOperationAction) && !targetStageAction.getAction().equals(saveCaseActionDTO.getAction())) {
                            update(new UpdateWrapper<CbCaseAction>().lambda().set(CbCaseAction::getAction, saveCaseActionDTO.getAction())
                                    .eq(CbCaseAction::getCaseId, caseId).eq(CbCaseAction::getStage, StageCons.HOTEL_OPERATION));
                        }

                        // 如果选择 accept，则检查 是否有 result 记录，如果有则删除
                        CbCaseResult caseResult = cbCaseResultService.getCaseResult(caseId);
                        if (Objects.nonNull(caseResult) && saveCaseActionDTO.getAction() == CaseActionEnum.ACCEPT.getCode()) {
                            cbCaseResultService.clearResult(caseId);
                        }
                    }

                    updateCase.setHasOverdue(true);
                    break;
                default:
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("stage", Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.OVERDUE)),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the stage must in [2:hotel operation，3:overdue]");
            }

            Long levelId = cbCaseApproveLevelService.checkZeroLevelExist(cs.getPropertyId(), saveCaseActionDTO.getStage()).getId();

            int userId = sessionContext.getLoginUserId();
            long now = DateTimeUtils.nowTimestamp();
            if (Objects.isNull(targetStageAction)) {
                targetStageAction = beanMapper.map(saveCaseActionDTO, CbCaseAction.class);
                targetStageAction.setPropertyId(cs.getPropertyId());
                targetStageAction.setCaseId(caseId);
                targetStageAction.setStage(saveCaseActionDTO.getStage());
                targetStageAction.setCreatedBy(userId);
                targetStageAction.setCreatedTime(now);
                targetStageAction.setLevelId(levelId);
                targetStageAction.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
                save(targetStageAction);
            } else {
                CbCaseAction updateCaseAction = beanMapper.map(saveCaseActionDTO, CbCaseAction.class);
                updateCaseAction.setUpdatedBy(userId);
                updateCaseAction.setUpdatedTime(now);
                updateCaseAction.setId(targetStageAction.getId());
                updateById(updateCaseAction);

                CbCaseAction finalTargetStageAction = targetStageAction;
                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    //修改前的s3Key集合
                    List<String> updateBeforeS3Keys = AttachmentUtil.getKeysByAbbrAttachmentArrayJson(finalTargetStageAction.getAttachments(), objectMapper);
                    //修改后的s3Key集合
                    List<String> updateAfterS3Keys = AttachmentUtil.getKeysByAbbrAttachmentArrayJson(saveCaseActionDTO.getAttachments(), objectMapper);
                    //删除的附件 = 修改前的附件集合 - 修改后的附件集合
                    List<String> delS3Keys = Lists.newArrayList(updateBeforeS3Keys);
                    delS3Keys.removeAll(updateAfterS3Keys);
                    S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.CHARGEBACK), delS3Keys);
                });

            }

            updateCase.setId(caseId);
            updateCase.setStatus(saveCaseActionDTO.getAction() == CaseActionEnum.ACCEPT.getCode() ?
                    CaseStatusEnum.ACCEPT.getCode() : CaseStatusEnum.DISPUTE.getCode());
            updateCase.setStatusSetBy(userId);
            updateCase.setStatusSetTime(now);
            // 有 overdue 就以 overdue 结果为准，没有以 hotel operation 为准
            if (saveCaseActionDTO.getStage() == StageCons.HOTEL_OPERATION) {
                CbCaseAction caseAction = getCaseAction(caseId, StageCons.OVERDUE);
                if (Objects.isNull(caseAction)) {
                    updateCase.setAction(saveCaseActionDTO.getAction());
                }
            } else {
                updateCase.setAction(saveCaseActionDTO.getAction());
            }
            updateCase.setStage(saveCaseActionDTO.getStage());
            updateCase.setLevelId(levelId);
            updateCase.setBatchId(BaseConstant.LONG_ZERO);
            updateCase.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
            cbCaseService.updateById(updateCase);
        }

        /**
         * 清空 action
         *
         * @param cs     case
         * @param stages 阶段
         * @author yyt
         * @date 2023/9/7
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void clearAction(CbCase cs, @Nullable List<Integer> stages) {
            Long caseId = cs.getId();
            List<CbCaseAction> cbCaseActionList = list(new QueryWrapper<CbCaseAction>().lambda().eq(CbCaseAction::getCaseId, caseId)
                    .in(CollectionUtils.isNotEmpty(stages), CbCaseAction::getStage, stages));
            if (CollectionUtils.isNotEmpty(cbCaseActionList)) {
                List<Long> actionIds = cbCaseActionList.stream().map(CbCaseAction::getId).toList();
                removeByIds(actionIds);

                TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                    //删除附件
                    List<String> delS3Keys = cbCaseActionList.stream().map(action ->
                            AttachmentUtil.getKeysByAbbrAttachmentArrayJson(action.getAttachments(), objectMapper)
                    ).flatMap(Collection::stream).toList();
                    S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.CHARGEBACK), delS3Keys);
                });

            }

            if (Objects.isNull(stages)) {
                CbCase updateCase = new CbCase();
                updateCase.setId(caseId);
                updateCase.setAction(0);
                updateCase.setHasOverdue(false);
                updateCase.setHasHotelOperations(false);
                cbCaseService.updateById(updateCase);
            } else {
                if (stages.contains(StageCons.OVERDUE)) {
                    CbCase updateCase = new CbCase();
                    updateCase.setId(caseId);
                    updateCase.setHasOverdue(false);
                    cbCaseService.updateById(updateCase);
                }
                if (stages.contains(StageCons.HOTEL_OPERATION)) {
                    CbCase updateCase = new CbCase();
                    updateCase.setId(caseId);
                    updateCase.setHasHotelOperations(false);
                    cbCaseService.updateById(updateCase);
                }
            }
        }

    }