# GuestLedgerCheckTask.java

    package cloud.wconnect.frontoffice.businesstask;

    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.pojo.dto.line.PublicFlexMsgParm;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.MailInternalService;
    import cloud.wconnect.commonlib.service.NotifyInternalService;
    import cloud.wconnect.commonlib.util.EmailUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.LineMessageUtil;
    import cloud.wconnect.frontoffice.cons.FunctionPointEnum;
    import cloud.wconnect.frontoffice.cons.I18nConstant;
    import cloud.wconnect.frontoffice.cons.guestledgercheck.GuestLedgerCheckFieldI18nConstant;
    import cloud.wconnect.frontoffice.cons.guestledgercheck.GuestLedgerCheckNotifyConfigEnum;
    import cloud.wconnect.frontoffice.entity.guestledgercheck.*;
    import cloud.wconnect.frontoffice.pojo.vo.guestledgercheck.OpBalanceRvNotifyVO;
    import cloud.wconnect.frontoffice.pojo.vo.guestledgercheck.OvBalanceRvNotifyVO;
    import cloud.wconnect.frontoffice.service.guestledgercheck.*;
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    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.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;

    import java.math.BigDecimal;
    import java.text.MessageFormat;
    import java.util.*;

    import static java.util.stream.Collectors.*;

    /**
     * @author lichen
     * @description 定时发送需 Resolve 的 Guest Ledger Check 邮件
     * @date 2023/3/18 14:32
     */
    @Slf4j
    @Component
    public class GuestLedgerCheckTask {

        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private IGstLedgOpBalanceReservationService opBalanceReservationService;
        @Autowired
        private IGlcEmailConfigService glcEmailConfigService;
        @Autowired
        private IGlcEmailTargetService emailTargetService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IGstLedgOvBalanceReservationService ovBalanceReservationService;
        @Autowired
        private IGstLedgTotalAmountService totalAmountService;
        @Autowired
        private IGstLedgOpBalRvNoteService opBalRvNoteService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private LineMessageUtil lineMessageUtil;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Value("${here.link.guest-ledger-check}")
        private String hereLink;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private IGstLedgOverBalanceConfigService balanceConfigService;

        /**
         * 通知未解决的数据
         *
         * @param scheduler 调度任务
         * @return void
         * @author lichen
         * @date 2023/11/25
         */
        @Async
        public void notifyUnResolveData(GlcScheduler scheduler) {
            log.info("GuestLedgerCheckNotify  Start send email about guest ledger check resolved");
            String subject = "[Warning] - Exceptions about send email about guest ledger check resolved";
            StringBuilder warningEmail = new StringBuilder("<p>There is something wrong when send email guest ledger check resolved.</p>");
            warningEmail.append(MessageFormat.format("<p>propertyId:{0}</p>", scheduler.getPropertyId()));
            try {
                PropertyDTO propertyDTO = propertyI18nRedisService.get(scheduler.getPropertyId());
                if (!propertyDTO.getStatus()) return;

                GlcEmailConfig emailConfig = glcEmailConfigService.getOne(new LambdaQueryWrapper<GlcEmailConfig>()
                        .eq(GlcEmailConfig::getPropertyId, propertyDTO.getId())
                        .eq(GlcEmailConfig::getType, scheduler.getCategory()));
                if (Objects.isNull(emailConfig)) return;
                if (!emailConfig.getStatus()) {
                    log.info("GuestLedgerCheckTask.end, email config status is false, propertyId:{},schedulerJobId:{},type:{}",
                            propertyDTO.getId(), scheduler.getId(), scheduler.getCategory());
                    return;
                }
                List<GlcEmailTarget> emailTargets = emailTargetService.listByConfigId(Lists.newArrayList(emailConfig.getId()));
                if (CollectionUtils.isEmpty(emailTargets)) return;

                //检索 open balance 未 resolve 的列表信息
                List<GstLedgOpBalanceReservation> opBalanceReservations = opBalanceReservationService.listByResolved(propertyDTO.getId(),
                        false);
                //检索 over balance 列表信息
                List<GstLedgOvBalanceReservation> ovBalanceReservations = ovBalanceReservationService.listByPropertyId(propertyDTO.getId());
                if (CollectionUtils.isEmpty(opBalanceReservations) && CollectionUtils.isEmpty(ovBalanceReservations)) {
                    log.info("GuestLedgerCheckNotify.end have not data");
                    return;
                }
                //检索资产对应的 total amount 值
                BigDecimal totalAmount = totalAmountService.getTotalAmountByPropertyId(propertyDTO.getId());

                //按资产id分组要显示的 open balance 列表信息
                List<OpBalanceRvNotifyVO> openBalanceVOList = handleOpenBalance(opBalanceReservations, propertyDTO);
                //按资产id分组要显示的 over balance 列表信息
                List<OvBalanceRvNotifyVO> overBalanceVOList = handleOverBalance(ovBalanceReservations, propertyDTO);

                //查询资产的Balance setting
                BigDecimal balanceSetting = balanceConfigService.get(propertyDTO.getId());

                InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
                String calculationRule = number.getCalculationRule();
                String currency = number.getCurrency();
                //格式国际化金额
                String totalWriteOffAmount = InternationalizationUtils.formatAmountViewValue(totalAmount, calculationRule, currency);
                //资产配置的余额下限
                String balanceFloorStr = InternationalizationUtils.formatAmountViewValue(balanceSetting, calculationRule, currency);
                //发送通知
                doSendNotify(propertyDTO, emailConfig, emailTargets, openBalanceVOList, overBalanceVOList, totalWriteOffAmount, balanceFloorStr);
            } catch (Exception e) {
                log.error("GuestLedgerCheckNotify.end error: " + e.getMessage(), e);
                warningEmail.append("<p>Exception:").append(e).append("</p>");
                mailInternalService.sendAlertEmail(subject, warningEmail.toString());
            }
        }

        /**
         * 发送通知
         *
         * @param propertyDTO          资产信息
         * @param emailConfig          邮件配置
         * @param emailTargets         邮件对象集合
         * @param opBalanceRvNotifyVos opBalance 信息
         * @param ovBalanceRvNotifyVos ovBalance 信息
         * @param totalWriteOffAmount  总金额
         * @param balanceFloorStr      资产配置的余额下限
         * @return void
         * @author lichen
         * @date 2023/11/25
         */
        private void doSendNotify(PropertyDTO propertyDTO, GlcEmailConfig emailConfig, List<GlcEmailTarget> emailTargets,
                                  List<OpBalanceRvNotifyVO> opBalanceRvNotifyVos, List<OvBalanceRvNotifyVO> ovBalanceRvNotifyVos,
                                  String totalWriteOffAmount, String balanceFloorStr) {
            String language = propertyDTO.getLanguage();
            String functionName = i18nMessageUtils.message(FunctionCons.GUEST_LEDGER_CHECK, language);

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            String calculationRule = number.getCalculationRule();
            String currency = number.getCurrency();

            //累计total open balance amount
            BigDecimal totalOpenBalanceAmount = BigDecimal.ZERO;
            if (CollectionUtils.isNotEmpty(opBalanceRvNotifyVos)) {
                totalOpenBalanceAmount = opBalanceRvNotifyVos.stream().map(OpBalanceRvNotifyVO::getBalanceAmount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
            }
            String totalOpenBalanceAmountStr = InternationalizationUtils.formatAmountViewValue(totalOpenBalanceAmount, calculationRule, currency);

            //拼装hereLink参数
            String hereLinkWithParm = MessageFormat.format(hereLink + "?property={0}&tenant={1}", propertyDTO.getAbbr(), propertyDTO.getTenantSymbol());

            //默认主题的国际化code
            String defaultSubjectI18nCode = GuestLedgerCheckNotifyConfigEnum.getDefaultSubjectI18nCode(emailConfig.getType());
            String defaultSubject = i18nMessageUtils.message(defaultSubjectI18nCode, language, functionName, propertyDTO.getName());
            //自定义主体优先，其次默认主题
            String subject = StringUtils.isNotBlank(emailConfig.getSubject()) ? emailConfig.getSubject() : defaultSubject;

            NotifyDTO notifyDTO = new NotifyDTO();
            notifyDTO.setPropertyId(propertyDTO.getId());
            notifyDTO.setFunctionPoint(FunctionPointEnum.GUEST_LEDGER_CHECK_NO_RESOLVED.getFunctionPoint());
            notifyDTO.setSubject(subject);
            notifyDTO.setIdentityList(beanMapper.mapList(emailTargets, NotifyIdentityDTO.class));

            //生成邮件内容
            Context context = new Context();
            context.setVariable("propertyName", propertyDTO.getName());
            context.setVariable("functionName", functionName);
            context.setVariable("openBalanceVos", opBalanceRvNotifyVos);
            context.setVariable("overBalanceVos", ovBalanceRvNotifyVos);
            context.setVariable("totalBalanceAmount", totalOpenBalanceAmountStr);
            context.setVariable("totalWriteOffAmount", totalWriteOffAmount);
            context.setVariable("hereLink", hereLinkWithParm);
            context.setVariable("balanceSetting", balanceFloorStr);
            String mailBody = templateEngine.process("guestledgercheck/guestLedgerCheck-waitResolve_" + language, context);
            notifyDTO.setEmailContent(EmailUtil.getFullEmailContent(defaultSubject, mailBody, language));

            //生成line通知内容
            LinkedHashMap<String, String> businessMap = new LinkedHashMap<>();
            businessMap.put(i18nMessageUtils.message(GuestLedgerCheckFieldI18nConstant.I18N_TOTAL_BALANCE_AMOUNT, language),
                    totalOpenBalanceAmountStr);
            businessMap.put(i18nMessageUtils.message(GuestLedgerCheckFieldI18nConstant.I18N_TOTAL_LOSS, language),
                    totalWriteOffAmount);

            PublicFlexMsgParm publicFlexMsgParm = PublicFlexMsgParm.builder()
                    .propertyId(propertyDTO.getId())
                    .propertyName(propertyDTO.getName())
                    .functionName(functionName)
                    .title("Waiting for resolution")
                    .link(hereLinkWithParm)
                    .businessMap(businessMap)
                    .build();
            JSONObject textMsgJsonObject = lineMessageUtil.generatePublicFlexMsg(publicFlexMsgParm);
            notifyDTO.setLineContent(Lists.newArrayList(textMsgJsonObject));

            notifyInternalService.sendNotify(notifyDTO);
        }

        /**
         * 转换成VO
         *
         * @param opBalanceReservations opBalance 数据
         * @param propertyDTO           资产DTO
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.guestledger.OpBalanceRvNotifyVO>
         * @author lichen
         * @date 2024/3/16
         */
        private List<OpBalanceRvNotifyVO> handleOpenBalance(List<GstLedgOpBalanceReservation> opBalanceReservations, PropertyDTO propertyDTO) {
            if (CollectionUtils.isEmpty(opBalanceReservations)) {
                return Collections.emptyList();
            }

            String language = propertyDTO.getLanguage();
            String timezone = propertyDTO.getTimezone();
            //获取资产国际化配置
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            String longDate = dateTime.getLongDate();
            String hourClock = dateTime.getHourClock();
            String nameOrder = propertyDTO.getInternationalization().getNameOrder();

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            String calculationRule = number.getCalculationRule();
            String currency = number.getCurrency();

            String i18nYes = i18nMessageUtils.message(I18nConstant.YES, language);
            String i18nNo = i18nMessageUtils.message(I18nConstant.NO, language);

            // 获取每个Reservation下的Note总数
            List<Long> rvIds = opBalanceReservations.stream().map(GstLedgOpBalanceReservation::getId).collect(toList());
            List<GstLedgOpBalRvNote> opBalRvNoteList = opBalRvNoteService.listByRvIds(rvIds);
            Map<Long, Long> opBalRvNoteMap = opBalRvNoteList.stream().collect(groupingBy(GstLedgOpBalRvNote::getOpBalRvId, counting()));

            List<OpBalanceRvNotifyVO> voList = Lists.newArrayListWithCapacity(opBalanceReservations.size());
            //循环处理
            for (GstLedgOpBalanceReservation opBalanceReservation : opBalanceReservations) {
                OpBalanceRvNotifyVO vo = beanMapper.map(opBalanceReservation, OpBalanceRvNotifyVO.class);
                // 设置Reservation下的Note总数
                Long rvId = opBalanceReservation.getId();
                vo.setNoteTotal(opBalRvNoteMap.getOrDefault(rvId, BaseConstant.LONG_ZERO));
                vo.setCheckInDateStr(InternationalizationUtils.formatDateTime(opBalanceReservation.getCheckInDate(),
                        longDate, StringUtils.EMPTY, timezone, language));
                vo.setCheckOutDateStr(InternationalizationUtils.formatDateTime(opBalanceReservation.getCheckOutDate(),
                        longDate, StringUtils.EMPTY, timezone, language));
                vo.setGuestName(InternationalizationUtils.formatName(opBalanceReservation.getFirstName(), opBalanceReservation.getLastName(), nameOrder));
                Long balAmntUpdatedTime = opBalanceReservation.getBalAmntUpdatedTime();
                if (balAmntUpdatedTime != 0) {
                    vo.setBalAmntUpdatedTimeStr(InternationalizationUtils.formatDateTime(balAmntUpdatedTime,
                            longDate, hourClock, timezone, language));
                }
                Long wrOffAmntUpdatedTime = opBalanceReservation.getWrOffAmntUpdatedTime();
                if (wrOffAmntUpdatedTime != 0) {
                    vo.setWrOffAmountUpdatedTimeStr(InternationalizationUtils.formatDateTime(wrOffAmntUpdatedTime,
                            longDate, hourClock, timezone, language));
                }

                //设置GM是否评论
                vo.setGmInformedDesc(opBalanceReservation.getGmInformed() ? i18nYes : i18nNo);

                //格式国际化金额
                if (Objects.nonNull(opBalanceReservation.getBalanceAmount())) {
                    vo.setBalanceAmountStr(InternationalizationUtils.formatAmountViewValue(opBalanceReservation.getBalanceAmount(), calculationRule, currency));
                }
                if (Objects.nonNull(opBalanceReservation.getOriBalAmount())) {
                    vo.setOriBalAmountStr(InternationalizationUtils.formatAmountViewValue(opBalanceReservation.getOriBalAmount(), calculationRule, currency));
                }
                if (Objects.nonNull(opBalanceReservation.getAmountPaid())) {
                    vo.setAmountPaidStr(InternationalizationUtils.formatAmountViewValue(opBalanceReservation.getAmountPaid(), calculationRule, currency));
                }
                if (Objects.nonNull(opBalanceReservation.getWriteOffAmount())) {
                    vo.setWriteOffAmountStr(InternationalizationUtils.formatAmountViewValue(opBalanceReservation.getWriteOffAmount(), calculationRule, currency));
                }

                vo.setGmComment(EmailUtil.changeLine(opBalanceReservation.getGmComment()));
                voList.add(vo);
            }
            return voList;
        }

        /**
         * 查询Over Balance
         *
         * @param ovBalanceReservations over Balance
         * @param propertyDTO           资产DTO
         * @return java.util.List<cloud.wconnect.frontoffice.pojo.vo.guestledger.OvBalanceRvNotifyVO>
         * @author lichen
         * @date 2024/3/16
         */
        private List<OvBalanceRvNotifyVO> handleOverBalance(List<GstLedgOvBalanceReservation> ovBalanceReservations, PropertyDTO propertyDTO) {
            if (CollectionUtils.isEmpty(ovBalanceReservations)) {
                return Collections.emptyList();
            }

            String language = propertyDTO.getLanguage();
            String timezone = propertyDTO.getTimezone();
            //获取资产国际化配置
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            String longDate = dateTime.getLongDate();
            String hourClock = dateTime.getHourClock();
            String nameOrder = propertyDTO.getInternationalization().getNameOrder();

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            String calculationRule = number.getCalculationRule();
            String currency = number.getCurrency();

            List<OvBalanceRvNotifyVO> voList = Lists.newArrayListWithCapacity(ovBalanceReservations.size());
            for (GstLedgOvBalanceReservation ovBalanceReservation : ovBalanceReservations) {
                OvBalanceRvNotifyVO vo = beanMapper.map(ovBalanceReservation, OvBalanceRvNotifyVO.class);

                vo.setCheckInDateStr(InternationalizationUtils.formatDateTime(ovBalanceReservation.getCheckInDate(),
                        longDate, StringUtils.EMPTY, timezone, language));
                vo.setCheckOutDateStr(InternationalizationUtils.formatDateTime(ovBalanceReservation.getCheckOutDate(),
                        longDate, StringUtils.EMPTY, timezone, language));
                vo.setGuestName(InternationalizationUtils.formatName(ovBalanceReservation.getFirstName(), ovBalanceReservation.getLastName(), nameOrder));
                Long currBalUpdatedTime = ovBalanceReservation.getCurrBalUpdatedTime();
                if (currBalUpdatedTime != 0) {
                    vo.setCurrBalUpdatedTimeStr(InternationalizationUtils.formatDateTime(currBalUpdatedTime,
                            longDate, hourClock, timezone, language));
                }
                Long authAmntUpdatedTime = ovBalanceReservation.getAuthAmntUpdatedTime();
                if (authAmntUpdatedTime != 0) {
                    vo.setAuthAmntUpdatedTimeStr(InternationalizationUtils.formatDateTime(authAmntUpdatedTime,
                            longDate, hourClock, timezone, language));
                }

                //格式国际化金额
                if (Objects.nonNull(ovBalanceReservation.getCurrentBalance())) {
                    vo.setCurrentBalanceStr(InternationalizationUtils.formatAmountViewValue(ovBalanceReservation.getCurrentBalance(), calculationRule, currency));
                }
                if (Objects.nonNull(ovBalanceReservation.getAuthAmount())) {
                    vo.setAuthAmountStr(InternationalizationUtils.formatAmountViewValue(ovBalanceReservation.getAuthAmount(), calculationRule, currency));
                }
                vo.setHotelResponse(EmailUtil.changeLine(ovBalanceReservation.getHotelResponse()));
                voList.add(vo);
            }
            return voList;
        }
    }