# PastReservationTask.java

    package cloud.wconnect.frontoffice.businesstask.pastreservation;

    import cloud.wconnect.commonlib.component.BeanMapper;
    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.InternationalizationDTO;
    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.*;
    import cloud.wconnect.frontoffice.cons.FunctionPointEnum;
    import cloud.wconnect.frontoffice.cons.pastreservation.PastReservationLineNotifyEnum;
    import cloud.wconnect.frontoffice.cons.pastreservation.PastReservationNotifyEnum;
    import cloud.wconnect.frontoffice.entity.pastreservation.*;
    import cloud.wconnect.frontoffice.pojo.vo.pastreservation.CheckInRecordVO;
    import cloud.wconnect.frontoffice.pojo.vo.pastreservation.PastReservationVO;
    import cloud.wconnect.frontoffice.service.pastreservation.*;
    import com.alibaba.fastjson.JSONObject;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    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.util.*;
    import java.util.stream.Collectors;

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

    /**
     * 定时任务
     *
     * @author yyt
     * 2024/10/4
     */
    @Slf4j
    @Component
    public class PastReservationTask {

        @Resource
        private IPastReservationEmailConfigService pastReservationEmailConfigService;
        @Resource
        private IPastReservationEmailTargetService pastReservationEmailTargetService;
        @Resource
        private IPastReservationCommentService pastReservationCommentService;
        @Autowired
        private IPastReservationCreditLimitService pastReservationCreditLimitService;
        @Resource
        private IPastReservationService pastReservationService;
        @Resource
        private IPastReservationOpGuestinHouseService pastReservationOpGuestinHouseService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private LineMessageUtil lineMessageUtil;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private TemplateEngine templateEngine;

        @Value("${here.link.past-reservation.list}")
        private String hereLink;

        /**
         * 连续入住通知
         *
         * @param propertyId 资产 id
         * @author yyt
         * @date 2024/10/4
         */
        @Async
        public void notifyConsecutiveStay(Integer propertyId) {
            log.info("notifyConsecutiveStay... start");
            String alertTitle = "[Warning] - Exceptions About Auto Notify Consecutive Stay";
            StringBuilder warningEmail = new StringBuilder("<p>There is something wrong when auto notify consecutive stay.</p>");

            try {
                // 查询邮件配置
                PastReservationEmailConfig config = pastReservationEmailConfigService.get(propertyId, PastReservationNotifyEnum.CONSECUTIVE_STAY_NOTICE.getCode(), true);
                if (Objects.isNull(config)) {
                    log.warn("notifyConsecutiveStay... no enabled mail config was found");
                    log.info("notifyConsecutiveStay... end");
                    return;
                }

                // 过滤有效酒店
                PropertyDTO property = propertyI18nRedisService.get(propertyId);
                if (!property.getStatus()) {
                    log.warn("notifyConsecutiveStay... the property is closed, no processing required");
                    log.info("notifyConsecutiveStay... end");
                    return;
                }

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

                // 查询当前在店的连续入住信息（不包含忽略状态的数据）
                List<PastReservation> pastReservations = pastReservationService.listExcludeIgnore(propertyId);
                if (CollectionUtils.isEmpty(pastReservations)) {
                    log.warn("notifyConsecutiveStay... no long-term resident users");
                    log.info("notifyConsecutiveStay... end");
                    return;
                }

                // 发送通知
                doNotify(property, config, emailTargets, packagePastReservationVO(property, pastReservations));

                log.info("notifyConsecutiveStay... send the email request; property:{}", property.getAbbr());
            } catch (Exception e) {
                log.warn("notifyConsecutiveStay... exception occurs:", e);
                warningEmail.append("<p>Exception:").append(e).append("</p>");
                mailInternalService.sendAlertEmail(alertTitle, warningEmail.toString());
            }

            log.info("notifyConsecutiveStay... end");
        }

        /**
         * 封装 PastReservationVO
         *
         * @param property         资产
         * @param pastReservations PastReservation
         * @return PastReservationVO
         * @author yyt
         * @date 2024/12/13
         **/
        private List<PastReservationVO> packagePastReservationVO(PropertyDTO property, List<PastReservation> pastReservations) {
            Integer propertyId = property.getId();
            InternationalizationDTO internationalization = property.getInternationalization();
            InternationalizationNumberDTO number = internationalization.getNumber();
            String currency = number.getCurrency();
            String calculationRule = number.getCalculationRule();

            InternationalizationDateTimeDTO dateTime = internationalization.getDateTime();
            String longDate = dateTime.getLongDate();
            String timezone = property.getTimezone();
            String language = property.getLanguage();

            List<String> fullNames = pastReservations.stream().map(PastReservation::getFullName).collect(toList());

            // credit limit
            Map<String, Map<String, PastReservationCreditLimit>> creditLimitInfosByFullName = pastReservationCreditLimitService.list(propertyId, fullNames)
                    .stream().collect(Collectors.groupingBy(item-> item.getFullName().toLowerCase(),
                            Collectors.toMap(PastReservationCreditLimit::getRoomNumber, v -> v)));

            Map<Long, String> commentById = pastReservationCommentService.listLatest(pastReservations.stream().map(PastReservation::getId).collect(toList()));

            long currentDate = DateTimeUtils.dateNowTimestamp(timezone);

            // 入住记录
            Map<Long, PastReservationOpGuestinHouse> inHouseRecordById = pastReservationOpGuestinHouseService.listByIds(pastReservations.stream()
                            .flatMap(item -> Arrays.stream(item.getMatchedIds().split(","))).collect(toList()))
                    .stream().collect(Collectors.toMap(PastReservationOpGuestinHouse::getId, v -> v));

            return pastReservations.stream().map(item -> {
                        String fullName = item.getFullName().toLowerCase();
                        Map<String, PastReservationCreditLimit> creditLimitByRoomNumber = creditLimitInfosByFullName.getOrDefault(fullName, Collections.emptyMap());

                        // 当前用户的入住记录
                        List<CheckInRecordVO> checkInRecordVOS = Arrays.stream(item.getMatchedIds().split(","))
                                .map(inHouseRecordId -> inHouseRecordById.get(Long.valueOf(inHouseRecordId)))
                                .sorted(Comparator.comparing(PastReservationOpGuestinHouse::getCheckInDate)
                                        .thenComparing(PastReservationOpGuestinHouse::getCheckOutDate).reversed())
                                .map(checkInRecord-> {
                                    String roomNumber = checkInRecord.getRoomNumber();
                                    CheckInRecordVO checkInRecordVO = new CheckInRecordVO();
                                    checkInRecordVO.setRoomNumber(StringUtils.isEmpty(roomNumber) ? "--" : roomNumber);

                                    Long checkInDate = checkInRecord.getCheckInDate();
                                    Long checkOutDate = checkInRecord.getCheckOutDate();
                                    checkInRecordVO.setCheckInDate(InternationalizationUtils.formatDateTime(checkInDate, longDate, null, timezone, language));
                                    checkInRecordVO.setCheckOutDate(InternationalizationUtils.formatDateTime(checkOutDate, longDate, null, timezone, language));
                                    checkInRecordVO.setDay(DateTimeUtils.numberOfDays(DateTimeUtils.longToLocalDate(checkInDate, timezone), DateTimeUtils.longToLocalDate(checkOutDate, timezone)));

                                    // 找到当前入住记录，设置对应房间号的 款项信息
                                    String totalBalance = "--";
                                    String totalApprovalAmount = "--";
                                    if (checkInDate <= currentDate && checkOutDate >= currentDate) {
                                        PastReservationCreditLimit creditLimit = creditLimitByRoomNumber.get(roomNumber);
                                        if (Objects.nonNull(creditLimit)) {
                                            totalBalance = InternationalizationUtils.formatAmountViewValue(creditLimit.getTotalBalance(), calculationRule, currency);
                                            totalApprovalAmount = InternationalizationUtils.formatAmountViewValue(creditLimit.getTotalApprovalAmount(), calculationRule, currency);
                                            totalApprovalAmount = StringUtils.isEmpty(totalApprovalAmount) ? "--" : totalApprovalAmount;
                                        }
                                    }
                                    checkInRecordVO.setTotalBalance(totalBalance);
                                    checkInRecordVO.setTotalApprovalAmount(totalApprovalAmount);

                                    return checkInRecordVO;
                                }).toList();

                        return new PastReservationVO(item.getFirstName(), item.getLastName(), item.getFullName(),
                                StringUtils.isEmpty(item.getRoomNumber()) ? "--" : item.getRoomNumber(), checkInRecordVOS,
                                item.getDays(), EmailUtil.changeLine(commentById.getOrDefault(item.getId(), "--")));
                    })
                    .collect(toList());
        }

        /**
         * 发送通知
         *
         * @param propertyDTO        资产
         * @param emailConfig        邮件配置
         * @param emailTargets       邮件通知人集合
         * @param pastReservationVOs 连续入住信息
         * @author yyt
         * @date 2024/10/4
         */
        private void doNotify(PropertyDTO propertyDTO, PastReservationEmailConfig emailConfig,
                              List<PastReservationEmailTarget> emailTargets, List<PastReservationVO> pastReservationVOs) {
            Integer propertyId = propertyDTO.getId();
            String propertyName = propertyDTO.getName();
            String language = propertyDTO.getLanguage();
            String functionName = i18nMessageUtils.message(FunctionCons.PAST_RESERVATION, language);
            String defaultSubject = pastReservationEmailConfigService.getDefaultSubject(propertyId, PastReservationNotifyEnum.CONSECUTIVE_STAY_NOTICE.getCode());

            // 邮件内容
            String finalHereLink = hereLink + "?property=" + propertyDTO.getAbbr() + "&tenant=" + propertyDTO.getTenantSymbol();
            Context context = new Context();
            context.setVariable("propertyName", propertyName);
            context.setVariable("functionName", functionName);
            context.setVariable("pastReservationVOs", pastReservationVOs.stream().limit(EmailUtil.LIMIT_COUNT).collect(toList()));
            context.setVariable("exceedLimit", pastReservationVOs.size() > EmailUtil.LIMIT_COUNT);
            context.setVariable("count", pastReservationVOs.size());
            context.setVariable("hereLink", finalHereLink);
            String emailContent = EmailUtil.getFullEmailContent(defaultSubject, templateEngine.process("pastreservation/consecutive-stay-notice_" + language, context), language);

            // line通知内容
            LinkedHashMap<String, String> businessMap = new LinkedHashMap<>();
            businessMap.put("Consecutive occupancy", pastReservationVOs.size() + " pieces");

            PublicFlexMsgParm publicFlexMsgParm = PublicFlexMsgParm.builder()
                    .propertyId(propertyId)
                    .propertyName(propertyName)
                    .functionName(functionName)
                    .title(PastReservationLineNotifyEnum.CONSECUTIVE_STAY_NOTICE.getI18nCode())
                    .link(finalHereLink)
                    .businessMap(businessMap)
                    .build();
            JSONObject lineContent = lineMessageUtil.generatePublicFlexMsg(publicFlexMsgParm);

            NotifyDTO notifyDTO = new NotifyDTO();
            notifyDTO.setPropertyId(propertyDTO.getId());
            notifyDTO.setFunctionPoint(FunctionPointEnum.PAST_RESERVATION_NOTIFY.getFunctionPoint());
            notifyDTO.setSubject(StringUtils.isNotBlank(emailConfig.getSubject()) ? emailConfig.getSubject() : defaultSubject);
            notifyDTO.setIdentityList(beanMapper.mapList(emailTargets, NotifyIdentityDTO.class));
            notifyDTO.setEmailContent(emailContent);
            notifyDTO.setLineContent(Lists.newArrayList(lineContent));

            //通知
            notifyInternalService.sendNotify(notifyDTO);
        }

    }