# CbCaseTask.java

    package cloud.wconnect.finance.businesstask.chargeback;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.FunctionCons;
    import cloud.wconnect.commonlib.pojo.dto.NotifyDTO;
    import cloud.wconnect.commonlib.pojo.dto.NotifyIdentityDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.MailInternalService;
    import cloud.wconnect.commonlib.service.NotifyInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.EmailUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.finance.cons.FunctionPointEnum;
    import cloud.wconnect.finance.cons.chargeback.ChargebackNotifyEnum;
    import cloud.wconnect.finance.cons.chargeback.StageCons;
    import cloud.wconnect.finance.cons.chargeback.StageEnum;
    import cloud.wconnect.finance.entity.chargeback.*;
    import cloud.wconnect.finance.pojo.vo.chargeback.ApprovalAlertListVO;
    import cloud.wconnect.finance.service.chargeback.*;
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    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.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;

    import java.text.MessageFormat;
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;

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

    /**
     * Case
     *
     * @author yyt
     * 2023/9/9
     */
    @Service
    @Slf4j
    public class CbCaseTask {

        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private UserInternalService userInternalService;
        @Value("${here.link.chargeback.case-list}")
        private String hereLink;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private ICbCaseEmailConfigService cbCaseEmailConfigService;
        @Autowired
        private ICbCaseEmailTargetService cbCaseEmailTargetService;
        @Autowired
        private ICbCaseOverTimeNotifyLogService cbCaseOverTimeNotifyLogService;
        @Autowired
        private ICbCaseBankService cbCaseBankService;
        @Lazy
        @Autowired
        private ICbCaseService cbCaseService;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private BeanMapper beanMapper;

        /**
         * 通知超期 case
         *
         * @param propertyId 资产 id
         * @author yyt
         * @date 2023/9/9
         */
        @Async
        public void notifyOverTimeCase(Integer propertyId) {
            log.info("notifyOverTimeCase... start");
            String alertTitle = "[Warning] - Exceptions About Notify Over Time Case";
            StringBuilder warningEmail = new StringBuilder("<p>There is something wrong when notify over time case.</p>");

            try {
                // 查询邮件配置
                CbCaseEmailConfig config = cbCaseEmailConfigService.get(propertyId, ChargebackNotifyEnum.NOTIFY_OVER_TIME.getCode(), true);
                if (Objects.isNull(config)) {
                    log.warn("notifyOverTimeCase... no enabled mail config was found");
                    log.info("notifyOverTimeCase... end");
                    return;
                }

                PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
                if (!propertyDTO.getStatus()) {
                    log.warn("notifyOverTimeCase... the property is closed, no processing required");
                    log.info("notifyOverTimeCase... end");
                    return;
                }

                List<CbCaseEmailTarget> emailTargets = cbCaseEmailTargetService.list(config.getId());
                if (emailTargets.isEmpty()) {
                    log.warn("notifyOverTimeCase... email target is not set");
                    log.info("notifyOverTimeCase... end");
                    return;
                }

                Map<Integer, String> vendorDescByVal = cbCaseBankService.list(propertyId)
                        .stream().collect(Collectors.toMap(CbCaseBank::getId, CbCaseBank::getName));

                // 汇总超期 case
                List<ApprovalAlertListVO> replayByCase = packageReplayByCase(propertyDTO, vendorDescByVal);
                List<ApprovalAlertListVO> adjReplay = packageAdjReplayByCase(propertyDTO, vendorDescByVal);
                if (CollectionUtils.isEmpty(replayByCase) && CollectionUtils.isEmpty(adjReplay)) {
                    log.info("notifyOverTimeCase... no case about approval alert.");
                    log.info("notifyOverTimeCase... end");
                    return;
                }

                long now = DateTimeUtils.nowTimestamp();
                List<CbCaseOverTimeNotifyLog> insertNotifyLogs = Lists.newArrayListWithExpectedSize(replayByCase.size()
                        + adjReplay.size());
                if (CollectionUtils.isNotEmpty(replayByCase)) {
                    insertNotifyLogs.addAll(replayByCase.stream().map(item -> new CbCaseOverTimeNotifyLog().setPropertyId(propertyId)
                                    .setCaseId(item.getCaseId()).setStage(StageCons.HOTEL_OPERATION).setCreatedTime(now)).toList());
                }
                if (CollectionUtils.isNotEmpty(adjReplay)) {
                    insertNotifyLogs.addAll(adjReplay.stream().map(item -> new CbCaseOverTimeNotifyLog().setPropertyId(propertyId)
                                    .setCaseId(item.getCaseId()).setStage(StageCons.PATCH).setCreatedTime(now)).toList());
                }
                if (CollectionUtils.isNotEmpty(insertNotifyLogs)) {
                    cbCaseOverTimeNotifyLogService.saveBatch(insertNotifyLogs);
                }

                String propertyName = propertyDTO.getName();
                String language = propertyDTO.getLanguage();
                String functionName = i18nMessageUtils.message(FunctionCons.CHARGE_BACK, language);
                String defaultSubject = cbCaseEmailConfigService.getDefaultSubject(propertyId, ChargebackNotifyEnum.NOTIFY_OVER_TIME.getCode());

                NotifyDTO notifyDTO = new NotifyDTO();
                notifyDTO.setPropertyId(propertyId);
                notifyDTO.setFunctionPoint(FunctionPointEnum.CASE_OVERDUE_NOTIFY.getFunctionPoint());
                notifyDTO.setSubject(StringUtils.isNotBlank(config.getSubject()) ? config.getSubject() : defaultSubject);
                notifyDTO.setIdentityList(beanMapper.mapList(emailTargets, NotifyIdentityDTO.class));

                // CPE have 10 overdue cases
                JSONObject msg = generateLineMsg(propertyName, replayByCase.size() + adjReplay.size());
                notifyDTO.setLineContent(Lists.newArrayList(msg));

                Context context = new Context();
                String finalLink = hereLink + "?property=" + propertyDTO.getAbbr() + "&tenant=" + propertyDTO.getTenantSymbol() + "&type=1";
                context.setVariable("hereLink", finalLink);
                context.setVariable("propertyName", propertyName);
                context.setVariable("functionName", functionName);
                context.setVariable("replayByCase", replayByCase.stream().limit(EmailUtil.LIMIT_COUNT).collect(toList()));
                context.setVariable("replayByCaseCount", replayByCase.size());
                context.setVariable("replayByCaseExceedLimit", replayByCase.size() > EmailUtil.LIMIT_COUNT);
                context.setVariable("adjReplayByCase", adjReplay.stream().limit(EmailUtil.LIMIT_COUNT).collect(toList()));
                context.setVariable("adjReplayByCaseCount", adjReplay.size());
                context.setVariable("adjReplayByCaseExceedLimit", adjReplay.size() > EmailUtil.LIMIT_COUNT);
                String mailContent = templateEngine.process("chargeback/case-over-time_" + language, context);
                notifyDTO.setEmailContent(EmailUtil.getFullEmailContent(defaultSubject, mailContent, language));
                notifyInternalService.sendNotify(notifyDTO);

                log.info("notifyOverTimeCase... send email,property:{}", propertyDTO.getAbbr());
            } catch (Exception e) {
                log.error("notifyOverTimeCase...  exception occurs:", e);
                warningEmail.append("<p>Exception:").append(e).append("</p>");
                mailInternalService.sendAlertEmail(alertTitle, warningEmail.toString());
            }
            log.info("notifyOverTimeCase... end");
        }

        /**
         * 创建 line 消息
         *
         * @param propertyName 资产名称
         * @param size         case 数量
         * @return line 消息
         * @author yyt
         * @date 2023/11/27
         */
        private JSONObject generateLineMsg(String propertyName, int size) {
            String lineMsg = MessageFormat.format("{0} have {1} overdue cases", propertyName, size);
            JSONObject msg = new JSONObject();
            msg.put("type", "text");
            msg.put("text", lineMsg);
            return msg;
        }

        /**
         * 封装超期 case
         *
         * @param propertyDTO     资产
         * @param vendorDescByVal 银行平台标识
         * @return 超期 case
         * @author yyt
         * @date 2023/9/9
         */
        private List<ApprovalAlertListVO> packageReplayByCase(PropertyDTO propertyDTO, Map<Integer, String> vendorDescByVal) {
            List<CbCase> replayCases = cbCaseService.listNotApprovedOverTimeCase(propertyDTO, StageCons.HOTEL_OPERATION);
            if (replayCases.isEmpty()) {
                return Collections.emptyList();
            }

            // 为了避免重复频繁地预警通知，通知过的不再通知
            replayCases = filterAlreadyNotifyCase(replayCases, StageCons.HOTEL_OPERATION);
            if (replayCases.isEmpty()) {
                return Collections.emptyList();
            }

            // 获取Case数据中所有的发布人姓名
            List<Integer> pendingByList = replayCases.stream().map(CbCase::getHotelOperationBy).distinct().collect(toList());
            Map<Integer, String> userNameById = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), pendingByList);
            String timezone = propertyDTO.getTimezone();
            String language = propertyDTO.getLanguage();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();

            return replayCases.stream().map(cbCase -> {
                ApprovalAlertListVO alertListVo = new ApprovalAlertListVO();
                alertListVo.setCaseNumber(cbCase.getCaseNumber());
                alertListVo.setVendor(vendorDescByVal.getOrDefault(cbCase.getVendor(), "--"));

                alertListVo.setDisputeAmount(InternationalizationUtils.formatAmountViewValue(cbCase.getDisputeAmount(),
                        number.getCalculationRule(), number.getCurrency()));

                alertListVo.setHotelOperationBy(userNameById.getOrDefault(cbCase.getHotelOperationBy(), "--"));
                alertListVo.setHotelOperationTime(cbCase.getHotelOperationTime() > BaseConstant.ZERO ?
                        InternationalizationUtils.formatDateTime(cbCase.getHotelOperationTime(),
                                dateTime.getLongDate(), dateTime.getHourClock(), timezone, language) : "--");

                alertListVo.setReplyBy(cbCase.getReplyBy() > BaseConstant.ZERO ?
                        InternationalizationUtils.formatDateTime(cbCase.getReplyBy(), dateTime.getLongDate(),
                                StringUtils.EMPTY, timezone, language) : "--");

                alertListVo.setStage(i18nMessageUtils.message(StageEnum.getStageDescForEmail(cbCase.getStage()), language));
                alertListVo.setStageCode(cbCase.getStage());
                alertListVo.setCaseId(cbCase.getId());
                return alertListVo;
            }).toList();
        }

        /**
         * 封装 adj 超期 case
         *
         * @param propertyDTO     资产
         * @param vendorDescByVal 银行平台标识
         * @return adj 超期 case
         * @author yyt
         * @date 2023/9/9
         */
        private List<ApprovalAlertListVO> packageAdjReplayByCase(PropertyDTO propertyDTO, Map<Integer, String> vendorDescByVal) {
            List<CbCase> replayCases = cbCaseService.listNotApprovedOverTimeCase(propertyDTO, StageCons.PATCH);
            if (replayCases.isEmpty()) {
                return Collections.emptyList();
            }

            // 为了避免重复频繁地预警通知，通知过的不再通知
            replayCases = filterAlreadyNotifyCase(replayCases, StageCons.PATCH);
            if (replayCases.isEmpty()) {
                return Collections.emptyList();
            }

            // 获取Case数据中所有的发布人姓名
            List<Integer> pendingByList = replayCases.stream().map(CbCase::getPatchBy).distinct().collect(toList());
            Map<Integer, String> userNameById = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), pendingByList);

            String timezone = propertyDTO.getTimezone();
            String language = propertyDTO.getLanguage();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();

            return replayCases.stream().map(cbCase -> {
                ApprovalAlertListVO alertListVo = new ApprovalAlertListVO();
                alertListVo.setCaseNumber(cbCase.getCaseNumber());
                alertListVo.setVendor(vendorDescByVal.getOrDefault(cbCase.getVendor(), "--"));

                alertListVo.setDisputeAmount(InternationalizationUtils.formatAmountViewValue(cbCase.getDisputeAmount(),
                        number.getCalculationRule(), number.getCurrency()));

                alertListVo.setPatchBy(userNameById.getOrDefault(cbCase.getPatchBy(), "--"));
                alertListVo.setPatchTime(cbCase.getPatchTime() > BaseConstant.ZERO ?
                        InternationalizationUtils.formatDateTime(cbCase.getPatchTime(), dateTime.getLongDate(),
                                dateTime.getHourClock(), timezone, language) : "--");

                alertListVo.setAdjReplyBy(cbCase.getAdjReplyBy() > BaseConstant.ZERO ?
                        InternationalizationUtils.formatDateTime(cbCase.getAdjReplyBy(),
                                dateTime.getLongDate(), StringUtils.EMPTY, timezone, language) : "--");

                alertListVo.setStage(i18nMessageUtils.message(StageEnum.getStageDescForEmail(cbCase.getStage()), language));
                alertListVo.setStageCode(cbCase.getStage());
                alertListVo.setCaseId(cbCase.getId());
                return alertListVo;
            }).toList();
        }

        /**
         * 过滤已经通知过的 case
         *
         * @param replayCases 超期 case
         * @param stage       阶段
         * @author yyt
         * @date 2023/9/9
         */
        private List<CbCase> filterAlreadyNotifyCase(List<CbCase> replayCases, int stage) {
            List<Long> caseIds = replayCases.stream().map(CbCase::getId).collect(toList());
            Map<Long, CbCaseOverTimeNotifyLog> notifyLogByCaseId =
                    cbCaseOverTimeNotifyLogService.list(new QueryWrapper<CbCaseOverTimeNotifyLog>()
                                    .lambda().in(CbCaseOverTimeNotifyLog::getCaseId, caseIds)
                                    .eq(CbCaseOverTimeNotifyLog::getStage, stage))
                            .stream().collect(toMap(CbCaseOverTimeNotifyLog::getCaseId, v -> v));
            return replayCases.stream()
                    // 已经通知过的，不再通知
                    .filter(item -> notifyLogByCaseId.get(item.getId()) == null)
                    .collect(toList());
        }

    }