# NotifyDraftNumberTask.java

    package cloud.wconnect.frontoffice.businesstask.adjustment;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.EmailConfigCons;
    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.UserDTO;
    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.service.RoleInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.FunctionPointEnum;
    import cloud.wconnect.frontoffice.cons.I18nConstant;
    import cloud.wconnect.frontoffice.cons.adjustment.AdjFieldI18nConstant;
    import cloud.wconnect.frontoffice.cons.adjustment.AdjustmentStatusEnum;
    import cloud.wconnect.frontoffice.cons.adjustment.FlowStatusEnum;
    import cloud.wconnect.frontoffice.cons.adjustment.AdjNotifyConfigTypeEnum;
    import cloud.wconnect.frontoffice.entity.adjustment.AdRequest;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjEmailConfig;
    import cloud.wconnect.frontoffice.entity.adjustment.AdjNotifyRule;
    import cloud.wconnect.frontoffice.entity.adjustment.SchedulerJob;
    import cloud.wconnect.frontoffice.pojo.dto.adjustment.AdjEmailDTO;
    import cloud.wconnect.frontoffice.service.adjustment.IAdRequestService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjEmailConfigService;
    import cloud.wconnect.frontoffice.service.adjustment.IAdjNotifyRuleService;
    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.text.MessageFormat;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * draft数量通知:每日通知责任人，有多少draft数据需要处理
     *
     * @author lichen
     * @date 2024/8/20
     */
    @Slf4j
    @Component
    public class NotifyDraftNumberTask {

        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IAdjEmailConfigService emailConfigService;
        @Autowired
        private IAdRequestService adRequestService;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private LineMessageUtil lineMessageUtil;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;
        @Value("${here.link.adjustment-requests-other}")
        private String adjustmentOtherHereLink;
        @Value("${here.link.adjustment-requests}")
        private String adjustmentHereLink;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IAdjNotifyRuleService notifyRuleService;
        @Autowired
        private RoleInternalService roleInternalService;

        /**
         * 每日通知责任人，有多少draft数据需要处理
         *
         * @param schedulerJob 调度任务
         * @author lichen
         * @date 2024/3/15
         */
        @Async
        public void notifyDraftNumber(SchedulerJob schedulerJob) {
            try {
                PropertyDTO propertyDTO = propertyI18nRedisService.get(schedulerJob.getPropertyId());
                if (!propertyDTO.getStatus()) return;

                //查询邮件配置信息
                AdjEmailConfig emailConfig = emailConfigService.getByPropertyIdAndType(schedulerJob.getPropertyId(), schedulerJob.getCategory());
                if (Objects.isNull(emailConfig)) {
                    emailConfig = new AdjEmailConfig();
                    emailConfig.setStatus(true);
                }

                if(!emailConfig.getStatus()){
                    log.info("NotifyDraftNumberTask.end, email config status is false, propertyId:{},schedulerJobId:{},type:{}",
                            propertyDTO.getId(), schedulerJob.getId(), schedulerJob.getCategory());
                    return;
                }

                final AdjEmailConfig finalEmailConfig = emailConfig;

                //查询通知规则
                Long startDate = null;
                AdjNotifyRule notifyRule = notifyRuleService.getByPropertyIdAndType(propertyDTO.getId(), schedulerJob.getCategory());
                if (Objects.nonNull(notifyRule)) {
                    Object startDateObj = JSONObject.parseObject(notifyRule.getRule()).get(AdjNotifyRule.START_DATE);
                    if (Objects.nonNull(startDateObj)) {
                        startDate = DateTimeUtils.dateStrToEpochSecond(startDateObj.toString(), BaseConstant.DATE_FORMAT_YYYYMMDD, propertyDTO.getTimezone());
                    }
                }

                //查询draft的数据
                List<AdRequest> adRequests = adRequestService.list(new LambdaQueryWrapper<AdRequest>()
                        .eq(AdRequest::getPropertyId, propertyDTO.getId())
                        .eq(AdRequest::getFlowStatus, FlowStatusEnum.DRAFT.getFlowStatus())
                        .ne(AdRequest::getStatus, AdjustmentStatusEnum.CANCEL.getStatus())
                        .ge(Objects.nonNull(startDate), AdRequest::getCreateTime, startDate)
                        .orderByDesc(AdRequest::getId));

                //没有数据时，无需通知
                if (CollectionUtils.isEmpty(adRequests)) return;

                //查询拥有"待分配权限"的用户
                List<Integer> userIdsByNeedAssign = roleInternalService.getUsers(propertyDTO.getTenantId(), propertyDTO.getId(), BaseConstant.ADJUSTMENT_ASSIGN_ROLE).
                        stream().map(UserDTO::getId).toList();

                Set<Integer> dutyUserIds = adRequests.stream().map(AdRequest::getDutyUser).collect(Collectors.toSet());
                ArrayList<Integer> allUserIds = Lists.newArrayList(dutyUserIds);
                allUserIds.addAll(userIdsByNeedAssign);
                //查询用户姓名
                Map<Integer, String> userId2Name = userInternalService.getUsers(propertyDTO.getTenantId(), allUserIds)
                        .stream().collect(Collectors.toMap(UserDTO::getId, UserDTO::getName));

                //按照责任人分组
                Map<Integer, List<AdjEmailDTO>> dutyUser2AdjList = convert(adRequests, propertyDTO, userId2Name)
                        .stream().collect(Collectors.groupingBy(AdjEmailDTO::getDutyUser));

                for (Map.Entry<Integer, List<AdjEmailDTO>> entry : dutyUser2AdjList.entrySet()) {
                    //责任人
                    Integer dutyUser = entry.getKey();
                    if (dutyUser == 0) {
                        /*
                         * dutyUser=0是待分配的调账数据，通知给拥有分配权限的用户
                         *
                         * 当用户既是dutyUser也拥有待分配权限时，以用户作为dutyUser的身份进行通知，否则会通知重复
                         * 所以此处通知的用户集合 = 拥有待分配权限的用户 - dutyUser用户
                         */
                        ArrayList<Integer> userIdsByFilter = Lists.newArrayList(userIdsByNeedAssign);
                        userIdsByFilter.removeAll(dutyUserIds);
                        if (CollectionUtils.isNotEmpty(userIdsByFilter)) {
                            //跳转链接
                            String link = adjustmentOtherHereLink + MessageFormat.format("?property={0}&tenant={1}&activeKey=3",
                                    propertyDTO.getAbbr(), propertyDTO.getTenantSymbol());

                            userIdsByFilter.forEach(item -> sendNotify(propertyDTO, item, entry.getValue(), finalEmailConfig, link, schedulerJob.getCategory()));
                        }
                    } else {
                        /*
                         * 责任人有分配权限：待通知的数据=本身+待分配数据总数
                         * 责任人没有分配权限：待通知的数据=本身
                         */
                        List<AdjEmailDTO> adjList = entry.getValue();
                        if (userIdsByNeedAssign.contains(dutyUser)) {
                            //有分配权限时，把待分配数据加到列表中
                            adjList.addAll(dutyUser2AdjList.getOrDefault(0, Collections.emptyList()));
                        }
                        //按照创建日期倒序排列
                        adjList.sort(Comparator.comparing(AdjEmailDTO::getCreateTime).reversed());

                        //跳转链接
                        String link = adjustmentHereLink + MessageFormat.format("?fromPage=email&property={0}&tenant={1}&dutyUser={2}&queryStatusList={3}",
                                propertyDTO.getAbbr(), propertyDTO.getTenantSymbol(), dutyUser.toString(), 1);
                        //发送通知
                        sendNotify(propertyDTO, dutyUser, adjList, finalEmailConfig, link, schedulerJob.getCategory());
                    }
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                String alertEmailSubject = "[Warning] - Notify Draft number task";
                String alertEmailContent = MessageFormat.format("<p>propertyId:{0}</p>", schedulerJob.getPropertyId()) +
                        "<p>Exception occurred when notify Draft number task</p>" +
                        e.getMessage();
                mailInternalService.sendAlertEmail(alertEmailSubject, alertEmailContent);
            }
        }

        /**
         * 发送通知
         *
         * @param propertyDTO 资产DTO
         * @param dutyUser    责任人
         * @param adjList     待处理的调账数据
         * @param emailConfig 邮件config
         * @param notifyType  通知类型
         * @author lichen
         * @date 2024/8/20
         */
        private void sendNotify(PropertyDTO propertyDTO, Integer dutyUser, List<AdjEmailDTO> adjList,
                                AdjEmailConfig emailConfig, String link, int notifyType) {
            String language = propertyDTO.getLanguage();
            String functionName = i18nMessageUtils.message(FunctionCons.ADJUSTMENT_REQUESTS, language);

            List<NotifyIdentityDTO> identityDTOS = new ArrayList<>();
            identityDTOS.add(new NotifyIdentityDTO().setEmail("").setIdentityId(dutyUser)
                    .setSource(EmailConfigCons.SOURCE_USER).setTargetType(EmailConfigCons.TARGET_TYPE_TO));

            //默认主题的国际化code
            String defaultSubjectI18nCode = AdjNotifyConfigTypeEnum.getDefaultSubjectI18nCode(notifyType);
            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.ADJ_DRAFT_NUMBER.getFunctionPoint());
            notifyDTO.setSubject(subject);
            notifyDTO.setIdentityList(identityDTOS);

            Context context = new Context();
            context.setVariable("propertyName", propertyDTO.getName());
            context.setVariable("functionName", functionName);
            context.setVariable("hereLink", link);
            context.setVariable("adjList", adjList.subList(0, Math.min(adjList.size(), 10)));
            String mailBody = templateEngine.process("adjustment/adjustment-draft_" + language, context);
            notifyDTO.setEmailContent(EmailUtil.getFullEmailContent(defaultSubject, mailBody, language));

            LinkedHashMap<String, String> businessMap = new LinkedHashMap<>();
            businessMap.put(i18nMessageUtils.message(I18nConstant.I18N_TOTAL, language), adjList.size() + "");

            PublicFlexMsgParm publicFlexMsgParm = PublicFlexMsgParm.builder()
                    .propertyName(propertyDTO.getName())
                    .propertyId(propertyDTO.getId())
                    .functionName(functionName)
                    .title("Number of drafts statistics")
                    .link(link)
                    .businessMap(businessMap)
                    .build();
            JSONObject lineContent = lineMessageUtil.generatePublicFlexMsg(publicFlexMsgParm);
            notifyDTO.setLineContent(Lists.newArrayList(lineContent));

            notifyInternalService.sendNotify(notifyDTO);
        }

        /**
         * AdRequest -> AdjEmailDTO
         *
         * @param adRequests  adjustment 集合
         * @param propertyDTO 资产信息
         * @return cloud.wconnect.frontoffice.pojo.dto.adjustment.AdjEmailDTO
         * @author lichen
         * @date 2024/9/11
         */
        private List<AdjEmailDTO> convert(List<AdRequest> adRequests, PropertyDTO propertyDTO, Map<Integer, String> userId2Name) {
            String timezone = propertyDTO.getTimezone();
            //配置的日期格式
            InternationalizationDateTimeDTO dateTime = propertyDTO.getInternationalization().getDateTime();
            String longDateFormat = dateTime.getLongDate();
            String hourClock = dateTime.getHourClock();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            //获取货币符号
            String currency = number.getCurrency();
            //语言
            String language = propertyDTO.getLanguage();

            String matched = i18nMessageUtils.message(AdjFieldI18nConstant.MATCHED, language);
            String unMatched = i18nMessageUtils.message(AdjFieldI18nConstant.UN_MATCHED, language);
            return beanMapper.mapList(adRequests, AdjEmailDTO.class, (adRequest, adjEmailDTO) -> {
                if(StringUtils.isBlank(adRequest.getRoomNumber())){
                    adjEmailDTO.setRoomNumber("--");
                }
                if(StringUtils.isBlank(adRequest.getGuestName())){
                    adjEmailDTO.setGuestName("--");
                }
                //格式化金额
                String totalAmountStr = InternationalizationUtils.formatAmountViewValue(adRequest.getAdjustedDollar(), number.getCalculationRule(), currency);
                adjEmailDTO.setFormatAdjustedDollar(totalAmountStr);
                //拼装dutyUser
                Integer dutyUser = adRequest.getDutyUser();
                adjEmailDTO.setDutyByName(userId2Name.getOrDefault(dutyUser, "--"));
                //拼装createTimeStr
                adjEmailDTO.setCreateTimeStr(InternationalizationUtils.formatDateTime(adRequest.getCreateTime(),
                        longDateFormat, hourClock, timezone, language));
                //拼装businessDateStr
                if (adRequest.getBusinessDate() > 0) {
                    adjEmailDTO.setBusinessDateStr(InternationalizationUtils.formatDateTime(
                            DateTimeUtils.dateStrToEpochSecond(adRequest.getBusinessDate().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD, timezone),
                            longDateFormat, "", timezone, language));
                } else {
                    adjEmailDTO.setBusinessDateStr("--");
                }
                //设置匹配状态
                adjEmailDTO.setMatchStatusStr(adRequest.getBusinessDate() > 0 ? matched : unMatched);
            });
        }

    }