# TaSendNotifyServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.CommonFieldI18nConstant;
    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.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.pojo.dto.line.PublicFlexMsgParm;
    import cloud.wconnect.commonlib.pojo.dto.line.ReportMissedMsgParm;
    import cloud.wconnect.commonlib.service.NotifyInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.finance.cons.FunctionPointEnum;
    import cloud.wconnect.finance.cons.ta.TaNotifyTypeEnum;
    import cloud.wconnect.finance.cons.ta.TaRecordsStatusEnum;
    import cloud.wconnect.finance.entity.ta.TaCreditCard;
    import cloud.wconnect.finance.entity.ta.TaEmailConfig;
    import cloud.wconnect.finance.entity.ta.TaPortal;
    import cloud.wconnect.finance.entity.ta.TaRecordsStatus;
    import cloud.wconnect.finance.pojo.dto.ta.TaPortalJsonDTO;
    import cloud.wconnect.finance.pojo.vo.ta.TaEmailDetailVO;
    import cloud.wconnect.finance.service.ta.ITaEmailConfigService;
    import cloud.wconnect.finance.service.ta.ITaEmailTargetService;
    import cloud.wconnect.finance.service.ta.ITaRecordsStatusService;
    import cloud.wconnect.finance.service.ta.ITaSendNotifyService;
    import com.alibaba.fastjson.JSONObject;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;

    import java.math.BigDecimal;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * Service: 发送通知
     *
     * @author zc
     * @date 2023/12/04
     */
    @Service
    @DS("finance")
    public class TaSendNotifyServiceImpl implements ITaSendNotifyService {
        @Resource
        private ITaEmailConfigService emailConfigService;
        @Resource
        private ITaEmailTargetService emailTargetService;
        @Resource
        private ITaRecordsStatusService taRecordsStatusService;
        @Resource
        private LineMessageUtil lineMessageUtil;
        @Resource
        private NotifyInternalService notifyInternalService;
        @Resource
        private TemplateEngine templateEngine;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private I18nMessageUtils i18nMessageUtils;
        @Resource
        private BeanMapper beanMapper;

        @Value("${here.link.ta.compare-contrast}")
        private String link;

        /**
         * 获取通知配置DTO
         *
         * @param propertyDTO 资产
         * @param reportName  报告名称
         * @param missDate    缺失日期
         * @param notifyType  通知类型
         * @return NotifyDTO
         * @author zc
         * @date 2024/11/14
         */
        @Override
        public NotifyDTO getNotifyDTO(PropertyDTO propertyDTO, String reportName, String missDate, int notifyType) {
            // 获取邮件配置
            TaEmailConfig emailConfig = emailConfigService.getEmailConfig(propertyDTO.getId(), notifyType);
            if (Objects.isNull(emailConfig)) {
                return null;
            }
            if (!emailConfig.getStatus()) {
                return null;
            }
            List<NotifyIdentityDTO> identityDTOList = emailTargetService.listByConfigId(emailConfig.getId()).stream()
                    .map(item -> beanMapper.map(item, NotifyIdentityDTO.class)).toList();
            if (CollectionUtils.isEmpty(identityDTOList)) {
                return null;
            }

            String hereLink = link + "?tenant=" + propertyDTO.getTenantSymbol() + "&property=" + propertyDTO.getAbbr();
            String functionName = i18nMessageUtils.message(FunctionCons.TRAVELLIANCE_COMMISSION, propertyDTO.getLanguage());
            String defaultSubject = i18nMessageUtils.message(TaNotifyTypeEnum.getDefaultSubjectI18nCode(notifyType),
                    propertyDTO.getLanguage(), functionName, propertyDTO.getName());
            String subject = StringUtils.isNotBlank(emailConfig.getSubject()) ? emailConfig.getSubject() : defaultSubject;

            // 邮件通知内容
            Context context = new Context();
            context.setVariable("propertyName", propertyDTO.getName());
            context.setVariable("functionName", functionName);
            context.setVariable("reportName", reportName);
            context.setVariable("reportDate", missDate);
            context.setVariable("hereLink", hereLink);
            String processContent = templateEngine.process(EmailUtil.getMissReportTemplateName(propertyDTO.getLanguage()), context);
            String emailContent = EmailUtil.getFullEmailContent(defaultSubject, processContent, propertyDTO.getLanguage());

            // LINE 通知内容
            JSONObject lineMsg = lineMessageUtil.generateReportMissedMsg(ReportMissedMsgParm.builder()
                    .propertyId(propertyDTO.getId())
                    .propertyName(propertyDTO.getName())
                    .functionName(functionName)
                    .reportName(reportName)
                    .missDate(missDate)
                    .build());

            return NotifyDTO.builder()
                    .propertyId(propertyDTO.getId())
                    .subject(subject)
                    .emailContent(emailContent)
                    .lineContent(Lists.newArrayList(lineMsg))
                    .identityList(identityDTOList)
                    .functionPoint(FunctionPointEnum.TA_PMS_REPORT_MISSED.getFunctionPoint())
                    .build();
        }

        /**
         * 发送报告比对异常数据通知
         *
         * @param propertyDTO       资产
         * @param status2Total      各状态数据数量
         * @param recordsStatusList 通知内容
         * @param statusList        状态
         * @param notifyType        通知类型
         * @author zc
         * @date 2024/11/14
         */
        @Override
        public void sendNotifyByCompareException(PropertyDTO propertyDTO, Map<Integer, Long> status2Total, List<TaRecordsStatus> recordsStatusList,
                                                 List<Integer> statusList, int notifyType) {
            // 获取邮件配置
            TaEmailConfig emailConfig = emailConfigService.getEmailConfig(propertyDTO.getId(), notifyType);
            if (Objects.isNull(emailConfig)) {
                return;
            }
            if (!emailConfig.getStatus()) {
                return;
            }
            List<NotifyIdentityDTO> identityDTOList = emailTargetService.listByConfigId(emailConfig.getId()).stream()
                    .map(item -> beanMapper.map(item, NotifyIdentityDTO.class)).toList();
            if (CollectionUtils.isEmpty(identityDTOList)) {
                return;
            }

            String language = propertyDTO.getLanguage();
            String propertyName = propertyDTO.getName();
            String hereLink = link + "?tenant=" + propertyDTO.getTenantSymbol() + "&property=" + propertyDTO.getAbbr()
                    + "&status=" + statusList.stream().map(Object::toString).collect(Collectors.joining(","));
            String functionName = i18nMessageUtils.message(FunctionCons.TRAVELLIANCE_COMMISSION, language);
            String defaultSubject = i18nMessageUtils.message(TaNotifyTypeEnum.getDefaultSubjectI18nCode(notifyType),
                    propertyDTO.getLanguage(), functionName, propertyDTO.getName());
            String subject = StringUtils.isNotBlank(emailConfig.getSubject()) ? emailConfig.getSubject() : defaultSubject;

            // 邮件通知内容
            Map<Integer, List<TaEmailDetailVO>> status2Details = packageEmailDetailVo(propertyDTO, recordsStatusList);

            Long missTaTotal = status2Total.getOrDefault(TaRecordsStatusEnum.MISS_TA.getCode(), BaseConstant.LONG_ZERO);
            Long missPmsTotal = status2Total.getOrDefault(TaRecordsStatusEnum.MISS_PMS.getCode(), BaseConstant.LONG_ZERO);
            Long priceVarianceTotal = status2Total.getOrDefault(TaRecordsStatusEnum.PRICE_VARIANCE.getCode(), BaseConstant.LONG_ZERO);
            List<TaEmailDetailVO> missTaDetails = status2Details.getOrDefault(TaRecordsStatusEnum.MISS_TA.getCode(), Collections.emptyList());
            List<TaEmailDetailVO> missPmsDetails = status2Details.getOrDefault(TaRecordsStatusEnum.MISS_PMS.getCode(), Collections.emptyList());
            List<TaEmailDetailVO> priceVarianceDetails = status2Details.getOrDefault(TaRecordsStatusEnum.PRICE_VARIANCE.getCode(), Collections.emptyList());

            Context context = new Context();
            context.setVariable("propertyName", propertyName);
            context.setVariable("functionName", functionName);
            context.setVariable("hereLink", hereLink);
            context.setVariable("missTaTotal", missTaTotal);
            context.setVariable("missPmsTotal", missPmsTotal);
            context.setVariable("priceVarianceTotal", priceVarianceTotal);
            context.setVariable("missTaDetails", missTaDetails);
            context.setVariable("missPmsDetails", missPmsDetails);
            context.setVariable("priceVarianceDetails", priceVarianceDetails);
            String processContent = templateEngine.process("ta/ta-comparison-exception_" + language, context);
            String emailContent = EmailUtil.getFullEmailContent(defaultSubject, processContent, language);

            // LINE 通知内容
            LinkedHashMap<String, String> businessMap = new LinkedHashMap<>();
            businessMap.put(i18nMessageUtils.message(CommonFieldI18nConstant.TOTAL, language), String.valueOf(recordsStatusList.size()));
            businessMap.put(i18nMessageUtils.message(TaRecordsStatusEnum.MISS_PMS.getI18nCode(), language), missTaTotal.toString());
            businessMap.put(i18nMessageUtils.message(TaRecordsStatusEnum.MISS_TA.getI18nCode(), language), missPmsTotal.toString());
            businessMap.put(i18nMessageUtils.message(TaRecordsStatusEnum.PRICE_VARIANCE.getI18nCode(), language), priceVarianceTotal.toString());
            JSONObject lineMsg = lineMessageUtil.generatePublicFlexMsg(PublicFlexMsgParm.builder()
                    .propertyId(propertyDTO.getId())
                    .propertyName(propertyName)
                    .functionName(functionName)
                    .title("Data comparison exception")
                    .link(hereLink)
                    .businessMap(businessMap)
                    .build());

            // 封装通知DTO
            NotifyDTO notifyDTO = NotifyDTO.builder()
                    .propertyId(propertyDTO.getId())
                    .subject(subject)
                    .emailContent(emailContent)
                    .lineContent(Lists.newArrayList(lineMsg))
                    .identityList(identityDTOList)
                    .functionPoint(FunctionPointEnum.TA_REPORT_COMPARE_EXCEPTION.getFunctionPoint())
                    .build();

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

        /**
         * 封装邮件中显示的列表信息
         *
         * @param propertyDTO          资产
         * @param recordsStatusExpList 比对记录信息
         * @return TaEmailDetailVO
         * @author zc
         * @date 2023/12/4
         */
        private Map<Integer, List<TaEmailDetailVO>> packageEmailDetailVo(PropertyDTO propertyDTO, List<TaRecordsStatus> recordsStatusExpList) {
            if (CollectionUtils.isEmpty(recordsStatusExpList)) {
                return Collections.emptyMap();
            }
            // 获取 PMS Credit Card 数据
            List<TaCreditCard> taCreditCardList = taRecordsStatusService.getCreditCardByRecords(recordsStatusExpList);
            // 获取 Portal 数据
            Map<Long, TaPortal> id2Portal = taRecordsStatusService.getPortalByRecords(recordsStatusExpList).stream()
                    .collect(Collectors.toMap(TaPortal::getId, Function.identity()));

            String longDateFormat = propertyDTO.getInternationalization().getDateTime().getLongDate();
            InternationalizationNumberDTO numberDTO = propertyDTO.getInternationalization().getNumber();
            String currency = numberDTO.getCurrency();
            String calculationRule = numberDTO.getCalculationRule();

            return recordsStatusExpList.stream().map(record -> {
                TaEmailDetailVO emailDetailVO = new TaEmailDetailVO();
                Long departureDate;
                if (record.getStatus().equals(TaRecordsStatusEnum.MISS_TA.getCode())) {
                    TaCreditCard maxDepartureDateCreditCard = taRecordsStatusService.getMaxDepartureDate(record, taCreditCardList);
                    departureDate = maxDepartureDateCreditCard.getDepartureDate();
                    emailDetailVO.setDepartureDate(InternationalizationUtils.formatDateTime(departureDate, longDateFormat,
                            StringUtils.EMPTY, propertyDTO.getTimezone(), propertyDTO.getLanguage()));
                    emailDetailVO.setLastFourDigits(maxDepartureDateCreditCard.getCardNo());
                } else if (record.getStatus().equals(TaRecordsStatusEnum.MISS_PMS.getCode())) {
                    TaPortal taPortal = id2Portal.get(record.getPId());
                    if (StringUtils.isNotBlank(taPortal.getDetails())) {
                        TaPortalJsonDTO taPortalJsonDTO = JsonUtils.convertStringToObject(objectMapper,
                                taPortal.getDetails(), TaPortalJsonDTO.class);
                        departureDate = taPortalJsonDTO.getDepartDate();
                        emailDetailVO.setDepartureDate(InternationalizationUtils.formatDateTime(departureDate, longDateFormat,
                                StringUtils.EMPTY, propertyDTO.getTimezone(), propertyDTO.getLanguage()));
                    }
                    emailDetailVO.setLastFourDigits(taPortal.getCardNo());
                    emailDetailVO.setRoomRate(InternationalizationUtils.formatAmountViewValue(taPortal.getRate(),
                            calculationRule, currency));
                    emailDetailVO.setNights(InternationalizationUtils.formatViewValue(taPortal.getNights(),
                            0, calculationRule, StringUtils.EMPTY));
                    BigDecimal totalRoomRevenue = taPortal.getRate()
                            .multiply(new BigDecimal(taPortal.getNights().toString()))
                            .multiply(new BigDecimal(taPortal.getRooms().toString()));
                    emailDetailVO.setTotalRoomRevenue(InternationalizationUtils.formatAmountViewValue(totalRoomRevenue,
                            calculationRule, currency));
                } else {
                    TaPortal taPortal = id2Portal.get(record.getPId());
                    emailDetailVO.setRoomRate(InternationalizationUtils.formatAmountViewValue(taPortal.getRate(),
                            calculationRule, currency));
                    emailDetailVO.setNights(InternationalizationUtils.formatViewValue(taPortal.getNights(),
                            0, calculationRule, StringUtils.EMPTY));
                    BigDecimal totalRoomRevenue = taPortal.getRate()
                            .multiply(new BigDecimal(taPortal.getNights().toString()))
                            .multiply(new BigDecimal(taPortal.getRooms().toString()));
                    emailDetailVO.setTotalRoomRevenue(InternationalizationUtils.formatAmountViewValue(totalRoomRevenue,
                            calculationRule, currency));

                    TaCreditCard maxDepartureDateCreditCard = taRecordsStatusService.getMaxDepartureDate(record, taCreditCardList);
                    departureDate = maxDepartureDateCreditCard.getDepartureDate();
                    emailDetailVO.setDepartureDate(InternationalizationUtils.formatDateTime(departureDate, longDateFormat,
                            StringUtils.EMPTY, propertyDTO.getTimezone(), propertyDTO.getLanguage()));
                    emailDetailVO.setLastFourDigits(maxDepartureDateCreditCard.getCardNo());
                }
                emailDetailVO.setName(record.getName());
                emailDetailVO.setRoomNo(record.getRoomsNo());
                emailDetailVO.setArrivalDate(InternationalizationUtils.formatDateTime(record.getArrivalDate(), longDateFormat,
                        StringUtils.EMPTY, propertyDTO.getTimezone(), propertyDTO.getLanguage()));
                emailDetailVO.setAirline(record.getAirline());
                emailDetailVO.setStatus(record.getStatus());

                return emailDetailVO;
            }).collect(Collectors.groupingBy(TaEmailDetailVO::getStatus));
        }
    }