# CbCaseApproveServiceImpl.java

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

    import cloud.wconnect.commonlib.component.SessionContext;
    import cloud.wconnect.commonlib.cons.*;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.finance.cons.chargeback.*;
    import cloud.wconnect.finance.entity.chargeback.*;
    import cloud.wconnect.finance.mapper.chargeback.CbCaseMapper;
    import cloud.wconnect.finance.pojo.dto.chargeback.ApproveCaseDTO;
    import cloud.wconnect.finance.pojo.dto.chargeback.SubmissionDTO;
    import cloud.wconnect.finance.service.chargeback.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    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.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.Set;
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadLocalRandom;
    import java.util.stream.Collectors;

    /**
     * @author yyt
     * 2023/8/31
     */
    @Service
    @DS("finance")
    public class CbCaseApproveServiceImpl extends ServiceImpl<CbCaseMapper, CbCase> implements ICbCaseApproveService {

        @Lazy
        @Autowired
        private ICbCaseApproveLevelService cbCaseApproveLevelService;
        @Autowired
        private ICbCaseApproveLevelUserService cbCaseApproveLevelUserService;
        @Autowired
        private ICbCaseApproveLogService cbCaseApproveLogService;
        @Autowired
        private ICbCasePublishLogService cbCasePublishLogService;
        @Lazy
        @Autowired
        private ICbCaseNotifyService cbCaseNotifyService;
        @Autowired
        private ICbCaseActionService cbCaseActionService;
        @Autowired
        private ICbCaseResultService cbCaseResultService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private Executor executor;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        @Lazy
        private ICbAdjService cbAdjService;

        /**
         * finance publish
         *
         * @param cs            case
         * @param operationType 1：initial 阶段操作 publish，2：overdue 阶段操作 publish，3：portal 操作 publish，4：final 阶段操作 publish
         * @param submissionDTO 邮件追加内容
         * @author yyt
         * @date 2023/8/31
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void financeSubmission(CbCase cs, int operationType, SubmissionDTO submissionDTO) {
            CbCase updateCaseContent = new CbCase();
            Long caseId = cs.getId();
            Integer stage = cs.getStage();
            Integer status = cs.getStatus();
            Integer approveStatus = cs.getApproveStatus();
            List<Integer> effectiveApproveStatuses = Lists.newArrayList(ApproveStatusEnum.PENDING.getStatus(),
                    ApproveStatusEnum.REJECTED.getStatus());

            Map<Integer, CbCaseAction> actionByStage = cbCaseActionService.list(Lists.newArrayList(caseId),
                            Lists.newArrayList(StageCons.HOTEL_OPERATION, StageCons.OVERDUE))
                    .stream().collect(Collectors.toMap(CbCaseAction::getStage, v -> v));
            CbCaseAction hotelOperationAction = actionByStage.get(StageCons.HOTEL_OPERATION);

            CbCaseResult caseResult;
            List<Integer> statuses;

            switch (operationType) {
                case FinancePublishCons.PUBLISH_INITIAL_CASE:
                    if (stage != StageCons.INITIAL_CASE || status != CaseStatusEnum.DRAFT.getCode()
                            || !effectiveApproveStatuses.contains(approveStatus)) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_INITIAL,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The stage must be initialized, the status is draft, and it has not been published for approval or rejected for approval before it can be operated.");
                    }
                    // 更新 推送人
                    updateCaseContent.setHotelOperationBy(sessionContext.getLoginUserId());
                    updateCaseContent.setHotelOperationTime(DateTimeUtils.nowTimestamp());
                    break;
                case FinancePublishCons.PUBLISH_OVERDUE:
                    statuses = Lists.newArrayList(CaseStatusEnum.ACCEPT.getCode(), CaseStatusEnum.DISPUTE.getCode());
                    if (stage != StageCons.OVERDUE || !statuses.contains(status)
                            || !effectiveApproveStatuses.contains(approveStatus)) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_OVERDUE,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The stage must be overdue, the status must be accepted or disputed, and approval has not been issued or rejected before operation can be carried out.");
                    }

                    break;
                case FinancePublishCons.PUBLISH_PORTAL:
                    statuses = Lists.newArrayList(CaseStatusEnum.WIN.getCode(), CaseStatusEnum.LOSS.getCode());
                    if (stage != StageCons.PORTAL || !statuses.contains(status)
                            || !effectiveApproveStatuses.contains(approveStatus)) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_PORTAL,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The stage must be a portal website, with a status of success or failure, not yet published for approval or rejected for approval, in order to operate.");
                    }

                    caseResult = cbCaseResultService.getCaseResult(caseId);
                    // resultDate 必须填写，才能送审
                    if (caseResult.getResultDate() == BaseConstant.ZERO) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_PORTAL_RESULT_DATE,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the resultDate must fill in");
                    }

                    if (caseResult.getResult() == CaseResultEnum.LOSS.getCode()) {
                        // 如果 case 已经有 adj 则不需要进入 patch 阶段
                        // 如果 没有，则进入 patch 阶段，设置 推送人
                        boolean hasAdj = Objects.nonNull(hotelOperationAction) && hotelOperationAction.getAction() == CaseActionEnum.ACCEPT.getCode();
                        if (!hasAdj) {
                            // 更新 推送人
                            updateCaseContent.setPatchBy(sessionContext.getLoginUserId());
                            updateCaseContent.setPatchTime(DateTimeUtils.nowTimestamp());
                        }
                    }
                    break;
                case FinancePublishCons.PUBLISH_FINAL:
                    // 酒店表态阶段未完成，则不能推送
                    if (!cs.getHasHotelOperations()) {
                        throw new BadRequestException(CaseErrorCons.CB_CLOSE_FAIL, BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The case has not gone through the hotel statement stage and cannot be completed");
                    }

                    if (stage != StageCons.FINAL || !effectiveApproveStatuses.contains(approveStatus)) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_LAST,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "Failed to update the data that maybe updated by others");
                    }
                    caseResult = cbCaseResultService.getCaseResult(cs.getId());
                    if (caseResult == null) {
                        // action accept 时， result final date 必须填写
                        if (cs.getResultFinalDate() == BaseConstant.LONG_ZERO && !cs.getNonChargeback()) {
                            throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_LAST_BANK_RECEIVING_DATE,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the Bank Receiving/Write-off Date must fill in");
                        }
                    } else {
                        // 为 0 时，表示 action 为 accept 不需要校验下面这个规则
                        if (caseResult.getResult() != BaseConstant.ZERO) {
                            // No Withdrawal 勾选时，result final date 可以不填；未勾选时，result final date 必须填写
                            if (!caseResult.getNoWithdrawal() && caseResult.getResultFinalDate() == BaseConstant.ZERO) {
                                throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_LAST_BANK_RECEIVING_DATE,
                                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the Bank Receiving/Write-off Date must fill in");
                            }
                        }
                    }

                    updateCaseContent.setStatus(CaseStatusEnum.CLOSE.getCode());
                    updateCaseContent.setStatusSetBy(sessionContext.getLoginUserId());
                    updateCaseContent.setStatusSetTime(DateTimeUtils.nowTimestamp());
                    break;
                default:
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("operationType",
                                    Lists.newArrayList(FinancePublishCons.PUBLISH_INITIAL_CASE, FinancePublishCons.PUBLISH_OVERDUE, FinancePublishCons.PUBLISH_PORTAL, FinancePublishCons.PUBLISH_FINAL)),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "the operationType mus int [1：initial stage publish，2：overdue stage publish，3：portal stage publish，4：final stage publish]");
            }

            // batch id
            long batchId = System.currentTimeMillis();
            cs.setBatchId(batchId);

            // 重置 0 级配置 id
            CbCaseApproveLevel zeroLevel = cbCaseApproveLevelService.checkZeroLevelExist(cs.getPropertyId(), cs.getStage());
            updateCaseContent.setLevelId(zeroLevel.getId());
            cs.setLevelId(zeroLevel.getId());

            updateCaseContent.setId(caseId);
            updateCaseContent.setBatchId(batchId);
            updateById(updateCaseContent);

            handleApproveForCurrentUser(cs);

            // 新增通知日志
            cbCasePublishLogService.save(new CbCasePublishLog().setPropertyId(cs.getPropertyId()).setCaseId(caseId)
                    .setStage(stage).setBatchId(batchId).setOperationType(OperationCons.PUBLISH)
                    .setMoreContent(StringUtils.isEmpty(submissionDTO.getMoreContent()) ? StringUtils.EMPTY : submissionDTO.getMoreContent())
                    .setNotifiedBy(sessionContext.getLoginUserId())
                    .setNotifiedTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 处理 当前登录人 审批 case
         *
         * @param cs case
         * @author yyt
         * @date 2023/9/6
         */
        private void handleApproveForCurrentUser(CbCase cs) {
            // 检查当前用户是否是审批人员
            Integer propertyId = cs.getPropertyId();
            int userId = sessionContext.getLoginUserId();
            Long caseId = cs.getId();
            Integer stage = cs.getStage();
            CbCaseApproveLevel approveLevelForCurrentLoginUser =
                    cbCaseApproveLevelService.getUserApproveLevel(propertyId, stage, userId);
            if (Objects.isNull(approveLevelForCurrentLoginUser)) {
                CbCaseApproveLevel currentApproveLevel = cbCaseApproveLevelService.getById(cs.getLevelId());
                cbCaseApproveLevelService.pushNextApproveLevel(currentApproveLevel, Lists.newArrayList(cs), true);
            } else {
                // 如果当前用户是审批人，则从当前用户所属审批层开始审批
                Long levelId = approveLevelForCurrentLoginUser.getId();
                // 如果是 任一 审批规则，则进入下个审批层级
                if (approveLevelForCurrentLoginUser.getRule() == ApproveCons.RULE_ANY) {
                    cbCaseApproveLevelService.pushNextApproveLevel(approveLevelForCurrentLoginUser, Lists.newArrayList(cs),true);
                } else {
                    // 如果是 全部，检查当前审批层级人员 是否只有当前用户一人，如果是，则推送下一个审批层级
                    List<Integer> approveUserIds = cbCaseApproveLevelUserService.listApproveUser(propertyId, stage, levelId).stream()
                            .map(CbCaseApproveLevelUser::getUserId).toList();
                    if (approveUserIds.size() == 1 && approveUserIds.contains(userId)) {
                        cbCaseApproveLevelService.pushNextApproveLevel(approveLevelForCurrentLoginUser, Lists.newArrayList(cs), true);
                    } else {
                        // 更新 case 审批层级
                        CbCase updateCaseContent = new CbCase();
                        updateCaseContent.setId(caseId);
                        updateCaseContent.setLevelId(levelId);
                        updateCaseContent.setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus());
                        cs.setLevelId(levelId);
                        updateById(updateCaseContent);

                        CbCaseAction caseAction = cbCaseActionService.getCaseAction(caseId, cs.getStage());
                        if (Objects.nonNull(caseAction)) {
                            cbCaseActionService.updateById(new CbCaseAction().setId(caseAction.getId()).setLevelId(cs.getLevelId())
                                    .setBatchId(cs.getBatchId()).setApproveStatus(ApproveStatusEnum.APPROVEING.getStatus()));
                        }

                        // 通知当前层级下的其他审批人员
                        TransactionUtils.afterCommitAsyncExecute(executor, () ->
                                cbCaseNotifyService.notifyUser(Lists.newArrayList(cs), userId, true, null));
                    }
                }
                // 新增审批日志
                cbCaseApproveLogService.save(new CbCaseApproveLog().setPropertyId(propertyId).setCaseId(caseId)
                        .setStage(stage).setBatchId(cs.getBatchId()).setLevelId(levelId)
                        .setName(approveLevelForCurrentLoginUser.getName())
                        .setApproveIndex(approveLevelForCurrentLoginUser.getApproveIndex())
                        .setApproveBy(userId).setIsApproved(true).setApproveTime(DateTimeUtils.nowTimestamp()));
            }
        }

        /**
         * 撤销审批
         *
         * @param cs     case
         * @param userId 操作人
         * @author yyt
         * @date 2023/8/31
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void revokeApproval(CbCase cs, int userId) {
            // 审批中可以撤销
            if (cs.getApproveStatus() != ApproveStatusEnum.APPROVEING.getStatus()) {
                throw new BadRequestException(CaseErrorCons.OPERATION_FAIL_STATUS_MUST_APPROVING,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "It must be under approval before it can be operated.");
            }

            Long id = cs.getId();
            Integer propertyId = cs.getPropertyId();
            Integer stage = cs.getStage();
            CbCaseApproveLevel zeroLevel = cbCaseApproveLevelService.checkZeroLevelExist(propertyId, stage);

            CbCase updateCase = new CbCase();
            updateCase.setId(id);
            updateCase.setLevelId(zeroLevel.getId());
            updateCase.setBatchId(BaseConstant.LONG_ZERO);
            updateCase.setApproveStatus(ApproveStatusEnum.PENDING.getStatus());
            updateById(updateCase);

            // 新增撤回日志
            // 随机数
            Long batchId = Long.valueOf(System.currentTimeMillis() + "" + ThreadLocalRandom.current().nextInt(100));
            cbCasePublishLogService.save(new CbCasePublishLog().setPropertyId(propertyId).setCaseId(id)
                    .setStage(stage).setBatchId(batchId).setOperationType(OperationCons.REVOCATION)
                    .setMoreContent(StringUtils.EMPTY).setNotifiedBy(userId).setNotifiedTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 审批
         *
         * @param cs             case
         * @param approveCaseDTO 审批信息
         * @author yyt
         * @date 2023/8/31
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void approve(CbCase cs, ApproveCaseDTO approveCaseDTO) {
            // 校验审批状态
            if (cs.getApproveStatus() != ApproveStatusEnum.APPROVEING.getStatus()) {
                throw new BadRequestException(CaseErrorCons.OPERATION_FAIL_STATUS_MUST_APPROVING,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "It must be under approval before it can be operated.");
            }

            Integer propertyId = cs.getPropertyId();
            Integer stage = cs.getStage();
            Long levelId = cs.getLevelId();
            Set<Integer> allApproveUserIds = cbCaseApproveLevelUserService.listApproveUser(propertyId, stage, levelId)
                    .stream().map(CbCaseApproveLevelUser::getUserId).collect(Collectors.toSet());
            int userId = sessionContext.getLoginUserId();
            if (!allApproveUserIds.contains(userId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "The loginUser is not approver.");
            }

            CbCaseApproveLevel currentApproveLevel = cbCaseApproveLevelService.getById(cs.getLevelId());
            if (approveCaseDTO.getIsApproved()) {
                if (currentApproveLevel.getRule() == ApproveCons.RULE_ANY) {
                    cbCaseApproveLevelService.pushNextApproveLevel(currentApproveLevel, Lists.newArrayList(cs),true);
                } else {
                    Set<Integer> hasApprovedUserIds = cbCaseApproveLogService.listByCases(Lists.newArrayList(cs))
                            .stream().map(CbCaseApproveLog::getApproveBy).collect(Collectors.toSet());
                    hasApprovedUserIds.add(userId);
                    if (hasApprovedUserIds.containsAll(allApproveUserIds)) {
                        cbCaseApproveLevelService.pushNextApproveLevel(currentApproveLevel, Lists.newArrayList(cs),true);
                    }
                }
            } else {
                CbCaseApproveLevel zeroLevel = cbCaseApproveLevelService.checkZeroLevelExist(propertyId, stage);

                CbCase updateCase = new CbCase();
                updateCase.setId(cs.getId());
                updateCase.setApproveStatus(ApproveStatusEnum.REJECTED.getStatus());
                cs.setApproveStatus(ApproveStatusEnum.REJECTED.getStatus());
                updateCase.setLevelId(zeroLevel.getId());
                updateById(updateCase);
                // 拒绝后，通知送审人
                TransactionUtils.afterCommitAsyncExecute(executor, () -> cbCaseNotifyService.rejectNotifyUser(cs));
            }

            // 新增审批日志
            cbCaseApproveLogService.save(new CbCaseApproveLog().setPropertyId(propertyId).setCaseId(cs.getId())
                    .setStage(stage).setBatchId(cs.getBatchId()).setLevelId(levelId)
                    .setName(currentApproveLevel.getName())
                    .setApproveIndex(currentApproveLevel.getApproveIndex()).setApproveBy(userId)
                    .setIsApproved(approveCaseDTO.getIsApproved()).setApproveOpinion(approveCaseDTO.getApproveOpinion())
                    .setApproveTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 酒店送审
         *
         * @param cs            case
         * @param operationType 1：Hotel Operation 阶段操作 publish，2：patch 阶段操作 publish
         * @param submissionDTO 邮件追加内容
         * @author yyt
         * @date 2023/9/6
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void hotelSubmission(CbCase cs, int operationType, SubmissionDTO submissionDTO) {
            Long caseId = cs.getId();
            Integer stage = cs.getStage();
            Integer status = cs.getStatus();
            Integer approveStatus = cs.getApproveStatus();
            List<Integer> effectiveApproveStatuses = Lists.newArrayList(ApproveStatusEnum.PENDING.getStatus(),
                    ApproveStatusEnum.REJECTED.getStatus());
            CbCaseAction caseAction = cbCaseActionService.getCaseAction(caseId, stage);
            switch (operationType) {
                case HotelPublishCons.PUBLISH_HOTEL_OPERATION:
                    if (stage != StageCons.HOTEL_OPERATION || !Lists.newArrayList(CaseStatusEnum.ACCEPT.getCode(),
                            CaseStatusEnum.DISPUTE.getCode()).contains(status) || !effectiveApproveStatuses.contains(approveStatus)) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_HOTEL_OPERATION,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The stage must be hotel operation, with a status of acceptance or dispute, and no approval or rejection has been issued before it can be operated.");
                    }
                    // 选择 accept，adj 必须填写
                    if (caseAction.getAction() == CaseActionEnum.ACCEPT.getCode()) {
                        if(cbAdjService.countByCaseId(caseId) == 0){
                            throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_HOTEL_OPERATION_ADJ,
                                    BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Please fill in the adj first");
                        }
                    }
                    break;
                case HotelPublishCons.PUBLISH_PATCH:
                    if (stage != StageCons.PATCH || status != CaseStatusEnum.FILL_OUT_ADJ.getCode()
                            || !effectiveApproveStatuses.contains(approveStatus)) {
                        throw new BadRequestException(CaseErrorCons.OPERATION_PUBLISH_FAIL_STAGE_PATCH,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                                "The stage must be a patch, the status must be filled in the adjustment form, and it must not have been released for approval or rejected for approval before it can be operated.");
                    }
                    break;
                default:
                    throw new BadRequestException(CommonErrorI18nConstant.MUST_IN_LIST,
                            i18nMessageUtils.getErrorParamMapNotNeedTranslate("operationType", Lists.newArrayList(HotelPublishCons.PUBLISH_HOTEL_OPERATION, HotelPublishCons.PUBLISH_PATCH)),
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "the operationType must in [1：Hotel Operation publish，2：patch publish]");
            }

            // batch id
            long batchId = System.currentTimeMillis();
            cs.setBatchId(batchId);
            CbCase updateCaseContent = new CbCase();
            updateCaseContent.setId(caseId);

            // 重置 0 级配置 id
            CbCaseApproveLevel zeroLevel = cbCaseApproveLevelService.checkZeroLevelExist(cs.getPropertyId(), cs.getStage());
            updateCaseContent.setLevelId(zeroLevel.getId());
            cs.setLevelId(zeroLevel.getId());

            updateCaseContent.setBatchId(batchId);
            updateById(updateCaseContent);

            handleApproveForCurrentUser(cs);

            // 新增通知日志
            cbCasePublishLogService.save(new CbCasePublishLog().setPropertyId(cs.getPropertyId()).setCaseId(caseId)
                    .setStage(stage).setBatchId(batchId).setOperationType(OperationCons.PUBLISH)
                    .setMoreContent(StringUtils.isEmpty(submissionDTO.getMoreContent()) ? StringUtils.EMPTY : submissionDTO.getMoreContent())
                    .setNotifiedBy(sessionContext.getLoginUserId())
                    .setNotifiedTime(DateTimeUtils.nowTimestamp()));
        }

    }