# OooRoomTask.java

    package cloud.wconnect.frontoffice.businesstask.oooroom;

    import cloud.wconnect.commonlib.businesstask.CheckReportMissedService;
    import cloud.wconnect.commonlib.businesstask.rule.MissedRuleByThreshold;
    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.FunctionCons;
    import cloud.wconnect.commonlib.cons.NotifySubjectI18nCons;
    import cloud.wconnect.commonlib.cons.PmsReportEnum;
    import cloud.wconnect.commonlib.pojo.dto.*;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationDateTimeDTO;
    import cloud.wconnect.commonlib.pojo.dto.line.PublicFlexMsgParm;
    import cloud.wconnect.commonlib.pojo.dto.line.ReportMissedMsgParm;
    import cloud.wconnect.commonlib.pojo.dto.scheduler.JobParameterDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.MailInternalService;
    import cloud.wconnect.commonlib.service.NotifyInternalService;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.BusinessEnum;
    import cloud.wconnect.frontoffice.cons.EventSubjectEnum;
    import cloud.wconnect.frontoffice.cons.FunctionPointEnum;
    import cloud.wconnect.frontoffice.cons.oooroom.OOORoomCons;
    import cloud.wconnect.frontoffice.cons.oooroom.OOORoomLineNotifyTitleEnum;
    import cloud.wconnect.frontoffice.entity.oooroom.*;
    import cloud.wconnect.frontoffice.pojo.dto.oooroom.OooRoomSchedulerDTO;
    import cloud.wconnect.frontoffice.pojo.vo.oooroom.OooRoomTypeCountVO;
    import cloud.wconnect.frontoffice.pojo.vo.oooroom.OooRoomVO;
    import cloud.wconnect.frontoffice.service.oooroom.IOooEmailConfigService;
    import cloud.wconnect.frontoffice.service.oooroom.IOooEmailTargetService;
    import cloud.wconnect.frontoffice.service.oooroom.IOooRoomService;
    import cloud.wconnect.frontoffice.service.oooroom.IOooRoomTypeConfigService;
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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.Service;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;

    import java.time.ZonedDateTime;
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;

    /**
     * OOO Room
     *
     * @author yyt
     * 2023/8/8
     */
    @Service
    @Slf4j
    public class OooRoomTask {

        @Autowired
        private PropertyInternalService propertyInternalService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Value("${default.timezone}")
        private String defaultTimezone;
        @Value("${here.link.ooo-room}")
        private String hereLink;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private CheckReportMissedService checkReportMissedService;
        @Autowired
        private IOooEmailConfigService oooEmailConfigService;
        @Autowired
        private IOooEmailTargetService oooEmailTargetService;
        @Autowired
        private IOooRoomService oooRoomService;
        @Autowired
        private IOooRoomTypeConfigService oooRoomTypeConfigService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private LineMessageUtil lineMessageUtil;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Autowired
        private BeanMapper beanMapper;

        private static final String SUBJECT = EventSubjectEnum.OUT_OF_ORDER.getSubject();

        private static final String BUSINESS = BusinessEnum.OUT_OF_ORDER.getBusiness();

        /**
         * 检查报告缺失
         *
         * @param schedulerJob 定时任务
         * @author yyt
         * @date 2023/8/9
         */
        @Async
        public void checkOutOfOrderReportMissing(OooRoomSchedulerJob schedulerJob) {
            log.info("OooRoomTask.OutOfOrderReportMissCheck start...");

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

            // 查询邮件配置
            OooEmailConfig emailConfig = oooEmailConfigService.get(propertyId, OooRoomSchedulerDTO.MISSING_OUT_OF_ORDER_REPORT, true);
            List<OooEmailTarget> targets = Collections.emptyList();
            if (Objects.nonNull(emailConfig)) {
                targets = oooEmailTargetService.list(emailConfig.getId());
            }

            //获取通知配置DTO
            NotifyDTO notifyDTO = getNotifyDTO(propertyDTO, emailConfig, targets);

            //资产配置的阈值集合
            JobParameterDTO jobParameterDTO = JsonUtils.convertStringToObject(objectMapper, schedulerJob.getParameters(), JobParameterDTO.class);
            ThresholdDTO thresholdDTO = new ThresholdDTO(jobParameterDTO.getThreshold(), TimeUnit.MINUTES, defaultTimezone);

            CheckReportMissedDTO check = CheckReportMissedDTO.builder()
                    .subject(SUBJECT)
                    .business(BUSINESS)
                    .propertyDTO(propertyDTO)
                    .ruleClazzList(Collections.singletonList(MissedRuleByThreshold.class))
                    .notifyDTO(notifyDTO)
                    .thresholdDTO(thresholdDTO)
                    .build();
            checkReportMissedService.doCheck(check);

            log.info("OooRoomTask.OutOfOrderReportMissCheck end...");
        }

        /**
         * 通知未完成的 ooo room
         *
         * @param propertyId 资产 id
         * @author yyt
         * @date 2023/8/8
         */
        @Async
        public void notifyNotComplete(Integer propertyId) {
            log.info("notifyNotCompleteOOORoom... start");
            String alertTitle = "[Warning] - Exceptions About Notify Not Complete OOO Room";
            StringBuilder warningEmail = new StringBuilder("<p>There is something wrong when notify not complete ooo room.</p>");

            try {
                // 查询邮件配置
                OooEmailConfig config = oooEmailConfigService.get(propertyId, OooRoomSchedulerDTO.NOTIFY_NOT_CLOSED, true);
                if (Objects.isNull(config)) {
                    log.warn("notifyNotCompleteOOORoom... no enabled mail config was found");
                    log.info("notifyNotCompleteOOORoom... end");
                    return;
                }

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

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

                // 查询所有未关闭的房间
                Map<Integer, List<OooRoom>> notCompleteRoomsByPropertyId = oooRoomService.listAllNotComplete(propertyId)
                        .stream().collect(Collectors.groupingBy(OooRoom::getPropertyId));

                // 查询房间类型信息
                Map<Integer, String> roomTypeById = oooRoomTypeConfigService.list(new QueryWrapper<OooRoomTypeConfig>().lambda()
                                .eq(OooRoomTypeConfig::getPropertyId, propertyId)).stream()
                        .collect(Collectors.toMap(OooRoomTypeConfig::getId, OooRoomTypeConfig::getRoomType));

                long dateNowTime = DateTimeUtils.dateNowTimestamp(defaultTimezone);

                // 结束日期小于当天的 未完成的各个房间类型的数量
                List<OooRoomTypeCountVO> lessThanCurrentDayNotCompleteRoomTypeCountVOs = Lists.newArrayList();
                // 结束日期小于当天的 未完成的邮件列表内容
                List<OooRoomVO> lessThanCurrentDayNotCompleteRoomVOs = Collections.emptyList();
                // 结束日期是当天的 未完成的各个房间类型的数量
                List<OooRoomTypeCountVO> currentDayNotCompleteRoomTypeCountVOs = Lists.newArrayList();
                // 结束日期是当天的 未完成的邮件列表内容
                List<OooRoomVO> currentDayNotCompleteRoomVOs = Collections.emptyList();
                // 所有未完成的 各个房间类型的数量
                List<OooRoomTypeCountVO> greaterThanCurrentDayNotCompleteRoomTypeCountVOs = Lists.newArrayList();
                // 所有未完成的邮件列表内容
                List<OooRoomVO> greaterThanCurrentDayNotCompleteRoomVOs = Collections.emptyList();

                // 汇总邮件内容
                List<OooRoom> notCompleteRooms = notCompleteRoomsByPropertyId.getOrDefault(propertyDTO.getId(), Collections.emptyList());
                if (CollectionUtils.isNotEmpty(notCompleteRooms)) {

                    int size = notCompleteRooms.size();
                    List<OooRoom> lessThanCurrentDayNotCompleteRooms = Lists.newArrayListWithExpectedSize(size);
                    List<OooRoom> currentDayNotCompleteRooms = Lists.newArrayListWithExpectedSize(size);
                    List<OooRoom> greaterThanCurrentDayCompleteRooms = Lists.newArrayListWithExpectedSize(size);

                    for (OooRoom notCompleteRoom : notCompleteRooms) {
                        Long toDate = notCompleteRoom.getToDate();
                        if (toDate < dateNowTime) {
                            lessThanCurrentDayNotCompleteRooms.add(notCompleteRoom);
                            continue;
                        }
                        if (toDate == dateNowTime) {
                            currentDayNotCompleteRooms.add(notCompleteRoom);
                            continue;
                        }
                        greaterThanCurrentDayCompleteRooms.add(notCompleteRoom);
                    }

                    // 结束日期小于当天的 未完成的各个房间类型的数量
                    Map<Integer, Long> lessThanCurrentDayNotCompleteCountByRoomType = lessThanCurrentDayNotCompleteRooms.stream()
                            .collect(Collectors.groupingBy(OooRoom::getRoomType,
                                    Collectors.mapping(item -> Arrays.stream(item.getRoomNumbers().split(",")),
                                            Collectors.counting())));
                    lessThanCurrentDayNotCompleteCountByRoomType.forEach((item, count) ->
                            lessThanCurrentDayNotCompleteRoomTypeCountVOs.add(new OooRoomTypeCountVO(roomTypeById.get(item), count)));
                    // 结束日期小于当天的 未完成的邮件列表内容
                    lessThanCurrentDayNotCompleteRoomVOs = lessThanCurrentDayNotCompleteRooms.stream()
                            .map(item -> generateEmailVO(item, roomTypeById, propertyDTO)).collect(Collectors.toList());

                    // 结束日期是当天的 未完成的各个房间类型的数量
                    Map<Integer, Long> currentDayNotCompleteCountByRoomType = currentDayNotCompleteRooms.stream()
                            .collect(Collectors.groupingBy(OooRoom::getRoomType,
                                    Collectors.mapping(item -> Arrays.stream(item.getRoomNumbers().split(",")),
                                            Collectors.counting())));
                    currentDayNotCompleteCountByRoomType.forEach((item, count) ->
                            currentDayNotCompleteRoomTypeCountVOs.add(new OooRoomTypeCountVO(roomTypeById.get(item), count)));
                    // 结束日期是当天的 未完成的邮件列表内容
                    currentDayNotCompleteRoomVOs = currentDayNotCompleteRooms.stream()
                            .map(item -> generateEmailVO(item, roomTypeById, propertyDTO)).collect(Collectors.toList());

                    //  结束日期大于当天的 未完成的房间信息
                    Map<Integer, Long> greaterThanCurrentDayNotCompleteCountByRoomType = greaterThanCurrentDayCompleteRooms.stream()
                            .collect(Collectors.groupingBy(OooRoom::getRoomType,
                                    Collectors.mapping(item -> Arrays.stream(item.getRoomNumbers().split(",")),
                                            Collectors.counting())));
                    // 结束日期大于当天的 未完成的各个房间类型的数量
                    greaterThanCurrentDayNotCompleteCountByRoomType.forEach((item, count) ->
                            greaterThanCurrentDayNotCompleteRoomTypeCountVOs.add(new OooRoomTypeCountVO(roomTypeById.get(item), count)));
                    // 结束日期大于当天的 未完成的邮件列表内容
                    greaterThanCurrentDayNotCompleteRoomVOs = greaterThanCurrentDayCompleteRooms.stream()
                            .map(item -> generateEmailVO(item, roomTypeById, propertyDTO)).collect(Collectors.toList());
                }

                //执行通知
                doNotifyNotComplete(propertyDTO, config, emailTargets,
                        lessThanCurrentDayNotCompleteRoomTypeCountVOs, lessThanCurrentDayNotCompleteRoomVOs,
                        currentDayNotCompleteRoomTypeCountVOs, currentDayNotCompleteRoomVOs,
                        greaterThanCurrentDayNotCompleteRoomTypeCountVOs, greaterThanCurrentDayNotCompleteRoomVOs);
                log.info("notifyNotCompleteOOORoom... send email,property:{}", propertyDTO.getAbbr());
            } catch (Exception e) {
                log.error("notifyNotCompleteOOORoom...  exception occurs:", e);
                warningEmail.append("<p>Exception:").append(e).append("</p>");
                mailInternalService.sendAlertEmail(alertTitle, warningEmail.toString());
            }
            log.info("notifyNotCompleteOOORoom... end");
        }

        /**
         * 发送通知
         *
         * @param propertyDTO                                      资产
         * @param oooEmailConfig                                   邮件配置
         * @param emailTargets                                     邮件通知人
         * @param lessThanCurrentDayNotCompleteRoomTypeCountVOs    结束日期小于当天的 未完成的各个房间类型的数量
         * @param lessThanCurrentDayNotCompleteRoomVOs             结束日期小于当天的 未完成的房间信息
         * @param currentDayNotCompleteRoomTypeCountVOs            结束日期是当天的 未完成的各个房间类型的数量
         * @param currentDayNotCompleteRoomVOs                     结束日期大于当天的 未完成的房间信息
         * @param greaterThanCurrentDayNotCompleteRoomTypeCountVOs 结束日期大于当天的 未完成的各个房间类型的数量
         * @param greaterThanCurrentDayNotCompleteRoomVOs          结束日期大于当天的 未完成的邮件列表内容
         * @author lichen
         * @date 2023/12/15
         */
        private void doNotifyNotComplete(PropertyDTO propertyDTO, OooEmailConfig oooEmailConfig,
                                         List<OooEmailTarget> emailTargets,
                                         List<OooRoomTypeCountVO> lessThanCurrentDayNotCompleteRoomTypeCountVOs,
                                         List<OooRoomVO> lessThanCurrentDayNotCompleteRoomVOs,
                                         List<OooRoomTypeCountVO> currentDayNotCompleteRoomTypeCountVOs,
                                         List<OooRoomVO> currentDayNotCompleteRoomVOs,
                                         List<OooRoomTypeCountVO> greaterThanCurrentDayNotCompleteRoomTypeCountVOs,
                                         List<OooRoomVO> greaterThanCurrentDayNotCompleteRoomVOs) {
            //生成line通知内容
            LinkedHashMap<String, String> businessMap = new LinkedHashMap<>();
            boolean haveOOORoom = true;
            String title = OOORoomLineNotifyTitleEnum.HAVE_OOO_ROOM.getI18nCode();
            if (CollectionUtils.isEmpty(lessThanCurrentDayNotCompleteRoomVOs)
                    && CollectionUtils.isEmpty(currentDayNotCompleteRoomVOs)
                    && CollectionUtils.isEmpty(greaterThanCurrentDayNotCompleteRoomVOs)) {
                title = OOORoomLineNotifyTitleEnum.NO_OOO_ROOM.getI18nCode();
                haveOOORoom = false;
            }

            if (CollectionUtils.isNotEmpty(lessThanCurrentDayNotCompleteRoomVOs)) {
                businessMap.put("Have Been postponed", lessThanCurrentDayNotCompleteRoomVOs.size() + " rooms");
            }
            if (CollectionUtils.isNotEmpty(currentDayNotCompleteRoomVOs)) {
                businessMap.put("Expected to Return Today", currentDayNotCompleteRoomVOs.size() + " rooms");
            }
            if (CollectionUtils.isNotEmpty(greaterThanCurrentDayNotCompleteRoomVOs)) {
                businessMap.put("Expected to Return In the future", greaterThanCurrentDayNotCompleteRoomVOs.size() + " rooms");
            }

            Integer propertyId = propertyDTO.getId();
            String propertyName = propertyDTO.getName();
            String language = propertyDTO.getLanguage();
            String timezone = propertyDTO.getTimezone();
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            String finalLink = hereLink + "?property=" + propertyDTO.getAbbr() + "&tenant=" + propertyDTO.getTenantSymbol();

            PublicFlexMsgParm publicFlexMsgParm = PublicFlexMsgParm.builder()
                    .propertyId(propertyId)
                    .propertyName(propertyName)
                    .functionName(i18nMessageUtils.message(FunctionCons.OOO_ROOM, language))
                    .title(title)
                    .link(finalLink)
                    .businessMap(businessMap)
                    .build();
            JSONObject lineContent = lineMessageUtil.generatePublicFlexMsg(publicFlexMsgParm);

            NotifyDTO notifyDTO = new NotifyDTO();
            notifyDTO.setPropertyId(propertyId);
            notifyDTO.setFunctionPoint(FunctionPointEnum.OOO_ROOMS_NOT_COMPLETE.getFunctionPoint());

            String defaultSubject = i18nMessageUtils.message(haveOOORoom ? OOORoomCons.HAVE_OOO_ROOM : OOORoomCons.NO_OOO_ROOM, language,
                    i18nMessageUtils.message(FunctionCons.OOO_ROOM, language), propertyName);
            notifyDTO.setSubject(StringUtils.isNotBlank(oooEmailConfig.getSubject()) ? oooEmailConfig.getSubject() : defaultSubject);
            notifyDTO.setIdentityList(beanMapper.mapList(emailTargets, NotifyIdentityDTO.class));

            // 生成邮件通知内容
            Context context = new Context();
            context.setVariable("hereLink", finalLink);
            context.setVariable("propertyName", propertyName);
            context.setVariable("functionName", i18nMessageUtils.message(FunctionCons.OOO_ROOM, language));
            context.setVariable("lessThanCurrentDayNotCompleteRoomCount", lessThanCurrentDayNotCompleteRoomTypeCountVOs.stream()
                    .map(OooRoomTypeCountVO::getCount).filter(Objects::nonNull).reduce(BaseConstant.LONG_ZERO, Long::sum));
            context.setVariable("lessThanCurrentDayNotCompleteRoomVOs", lessThanCurrentDayNotCompleteRoomVOs);
            context.setVariable("currentDayNotCompleteRoomCount", currentDayNotCompleteRoomTypeCountVOs.stream()
                    .map(OooRoomTypeCountVO::getCount).filter(Objects::nonNull).reduce(BaseConstant.LONG_ZERO, Long::sum));
            context.setVariable("currentDayNotCompleteRoomVOs", currentDayNotCompleteRoomVOs);
            context.setVariable("greaterThanCurrentDayNotCompleteRoomCount", greaterThanCurrentDayNotCompleteRoomTypeCountVOs.stream()
                    .map(OooRoomTypeCountVO::getCount).filter(Objects::nonNull).reduce(BaseConstant.LONG_ZERO, Long::sum));
            context.setVariable("greaterThanCurrentDayNotCompleteRoomVOs", greaterThanCurrentDayNotCompleteRoomVOs);
            context.setVariable("currentTime", InternationalizationUtils.formatDateTime(DateTimeUtils.dateTimeNowOfSecond(defaultTimezone),
                    dateTime.getLongDate(), dateTime.getHourClock(), timezone, language));
            String template = haveOOORoom ? "oooroom/have-ooo-room_" : "oooroom/no-ooo-room_";
            String mailContent = EmailUtil.getFullEmailContent(defaultSubject, templateEngine.process(template + language, context), language);
            notifyDTO.setEmailContent(mailContent);
            notifyDTO.setLineContent(Lists.newArrayList(lineContent));

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

        /**
         * 生成邮件列表内容
         *
         * @param oooRoom      ooo 房间
         * @param roomTypeById 房间类型
         * @param propertyDTO  资产
         * @return 邮件列表内容
         * @author yyt
         * @date 2023/8/8
         */
        private OooRoomVO generateEmailVO(OooRoom oooRoom, Map<Integer, String> roomTypeById, PropertyDTO propertyDTO) {
            OooRoomVO oooRoomVO = new OooRoomVO();
            // 多个房间号在中间间隔添加空格
            String roomNumbers = oooRoom.getRoomNumbers();
            roomNumbers = StringUtils.replace(roomNumbers, ",", ", ");
            oooRoomVO.setRoomNumbers(StringUtils.isEmpty(roomNumbers) ? "--" : roomNumbers);

            oooRoomVO.setRemark(StringUtils.isEmpty(oooRoom.getRemarks()) ? "--" : EmailUtil.changeLine(oooRoom.getRemarks()));
            oooRoomVO.setRoomType(roomTypeById.get(oooRoom.getRoomType()));

            String timezone = propertyDTO.getTimezone();
            String language = propertyDTO.getLanguage();
            String longDate = propertyDTO.getInternationalization().getDateTime().getLongDate();
            oooRoomVO.setFromDate(InternationalizationUtils.formatDateTime(oooRoom.getFromDate(), longDate, StringUtils.EMPTY, timezone, language));
            oooRoomVO.setToDate(InternationalizationUtils.formatDateTime(oooRoom.getToDate(), longDate, StringUtils.EMPTY, timezone, language));

            return oooRoomVO;
        }

        /**
         * 获取通知配置DTO
         *
         * @param propertyDTO 资产集合
         * @param emailConfig 邮件配置集合
         * @param targets     邮件通知人集合
         * @return 通知配置DTO
         * @author lichen
         * @date 2023/12/14
         */
        private NotifyDTO getNotifyDTO(PropertyDTO propertyDTO, OooEmailConfig emailConfig, List<OooEmailTarget> targets) {
            if (CollectionUtils.isEmpty(targets)) {
                return null;
            }

            Integer propertyId = propertyDTO.getId();
            String propertyName = propertyDTO.getName();
            String language = propertyDTO.getLanguage();

            NotifyDTO notifyDTO = new NotifyDTO();
            notifyDTO.setPropertyId(propertyId);
            notifyDTO.setFunctionPoint(FunctionPointEnum.OOO_ROOMS_PMS_REPORT_MISSED.getFunctionPoint());
            String defaultSubject = i18nMessageUtils.message(NotifySubjectI18nCons.MISSING_REPORT, language,
                    i18nMessageUtils.message(FunctionCons.OOO_ROOM, language), propertyName);
            notifyDTO.setSubject(StringUtils.isNotBlank(emailConfig.getSubject()) ? emailConfig.getSubject() : defaultSubject);
            notifyDTO.setIdentityList(beanMapper.mapList(targets, NotifyIdentityDTO.class));

            //报告缺失日期 -1d ~ 0d
            String timezone = propertyDTO.getTimezone();
            String longDate = propertyDTO.getInternationalization().getDateTime().getLongDate();
            ZonedDateTime zonedDateTime = DateTimeUtils.dateNow(timezone);
            String missDate = InternationalizationUtils.formatDateTime(zonedDateTime.minusDays(1).toEpochSecond(),
                    longDate, StringUtils.EMPTY, timezone, language) + " ~ " +
                    InternationalizationUtils.formatDateTime(zonedDateTime.toEpochSecond(), longDate,
                            StringUtils.EMPTY, timezone, language);

            // 获取资产配置的 PMS 报告名称
            String reportName = propertyInternalService.getPmsReportConfig(propertyId,
                    propertyDTO.getPmsType(), PmsReportEnum.OUT_OF_ORDER.getSymbol()).getReportName();
            String functionName = i18nMessageUtils.message(FunctionCons.OOO_ROOM, language);

            Context context = new Context();
            context.setVariable("propertyName", propertyName);
            context.setVariable("hereLink", hereLink + "?property=" + propertyDTO.getAbbr() + "&tenant=" + propertyDTO.getTenantSymbol());
            context.setVariable("functionName", functionName);
            context.setVariable("reportName", reportName);
            context.setVariable("reportDate", missDate);
            String emailContent = templateEngine.process(EmailUtil.getMissReportTemplateName(language), context);
            notifyDTO.setEmailContent(EmailUtil.getFullEmailContent(defaultSubject, emailContent, language));

            //line通知内容
            ReportMissedMsgParm reportMissedMsgParm = new ReportMissedMsgParm(propertyName, propertyId, functionName, reportName, missDate);
            JSONObject lineMsg = lineMessageUtil.generateReportMissedMsg(reportMissedMsgParm);
            notifyDTO.setLineContent(Lists.newArrayList(lineMsg));
            return notifyDTO;
        }

    }