# BudgetDatabaseExpirationNoticeTask.java

    package cloud.wconnect.rateshop.businesstask.budgetdatabase;

    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.line.PublicFlexMsgParm;
    import cloud.wconnect.commonlib.pojo.query.RoleUserQuery;
    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.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.EmailUtil;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.commonlib.util.LineMessageUtil;
    import cloud.wconnect.rateshop.cons.FunctionPointEnum;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseLineTitleCons;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseNotifyCons;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseStatusEnum;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabase;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigEmail;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigNotifyDate;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseConfigEmailService;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseConfigNotifyDateService;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseService;
    import com.alibaba.fastjson.JSONObject;
    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.LocalDate;
    import java.time.MonthDay;
    import java.time.format.DateTimeFormatter;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * Budget Database Task
     *
     * @author yyt
     * 2023/9/29
     */
    @Service
    @Slf4j
    public class BudgetDatabaseExpirationNoticeTask {

        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private RoleInternalService roleInternalService;
        @Autowired
        private TemplateEngine templateEngine;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Autowired
        private IBudgetDatabaseConfigEmailService budgetDatabaseConfigEmailService;
        @Autowired
        private IBudgetDatabaseConfigNotifyDateService budgetDatabaseConfigNotifyDateService;
        @Autowired
        private LineMessageUtil lineMessageUtil;
        @Autowired
        private NotifyInternalService notifyInternalService;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        @Value("${here.link.budget-database}")
        private String hereLink;

        /**
         * 检查来年预测数据是否填写，未填写则发送通知
         *
         * @param propertyId 资产 id
         * @author yyt
         * @date 2025/8/18
         */
        @Async
        public void notifyExpired(Integer propertyId) {
            log.info("BudgetDatabaseExpirationNoticeTask... start");
            String alertTitle = "[Warning] - Exceptions About Notify Budget Data Was Not Filled In By The Due Date";
            StringBuilder warningEmail = new StringBuilder("<p>There is something wrong when notify the budget data was not filled in by the due date.</p>");
            try {
                BudgetDatabaseConfigEmail emailConfig = budgetDatabaseConfigEmailService.getEmailConfig(propertyId, BudgetDatabaseNotifyCons.EXPIRATION_NOTICE, true);
                if (Objects.isNull(emailConfig)) {
                    log.warn("BudgetDatabaseExpirationNoticeTask... no enabled mail config was found");
                    log.info("BudgetDatabaseExpirationNoticeTask... end");
                    return;
                }
                // 获取开始通知日期，若未设置，则不通知
                BudgetDatabaseConfigNotifyDate notifyDate = budgetDatabaseConfigNotifyDateService.get(propertyId);
                if(Objects.isNull(notifyDate)){
                    log.info("BudgetDatabaseExpirationNoticeTask... no start notify date was found");
                    log.info("BudgetDatabaseExpirationNoticeTask... end");
                    return;
                }

                // 若未到开始通知日期，则不通知
                PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
                if (!propertyDTO.getStatus()) {
                    log.info("BudgetDatabaseExpirationNoticeTask... property was not enabled");
                    log.info("BudgetDatabaseExpirationNoticeTask... end");
                    return;
                }

                LocalDate currentDate = DateTimeUtils.dateNow(propertyDTO.getTimezone()).toLocalDate();
                MonthDay monthDay = MonthDay.parse(notifyDate.getDate(), DateTimeFormatter.ofPattern("MMdd"));
                LocalDate startNotifyDate = LocalDate.of(currentDate.getYear(), monthDay.getMonthValue(), monthDay.getDayOfMonth());
                if(startNotifyDate.isAfter(currentDate)){
                    log.info("BudgetDatabaseExpirationNoticeTask... start notify date was not reached");
                    log.info("BudgetDatabaseExpirationNoticeTask... end");
                    return;
                }

                // 若 已完成，则不通知
                int targetYear = currentDate.getYear() + 1;
                BudgetDatabase budgetDatabase = budgetDatabaseService.get(propertyId, targetYear);
                if (Objects.nonNull(budgetDatabase) && Objects.equals(budgetDatabase.getStatus(), BudgetDatabaseStatusEnum.COMPLETE.getCode())) {
                    log.info("BudgetDatabaseExpirationNoticeTask... budgetDatabase was completed");
                    log.info("BudgetDatabaseExpirationNoticeTask... end");
                    return;
                }

                // 发送通知
                // 通知人（管理者（类似GM）、Consolidated 、F&B、Other 这几个角色下的人）
                RoleUserQuery roleUserQuery = new RoleUserQuery();
                roleUserQuery.setTenantId(propertyDTO.getTenantId());
                roleUserQuery.setPropertyId(propertyDTO.getId());
                roleUserQuery.setRoleIds(Lists.newArrayList(BaseConstant.BUDGET_DATABASE_ADMIN_ROLE,
                        BaseConstant.BUDGET_DATABASE_CONSOLIDATED_ROLE,
                        BaseConstant.BUDGET_DATABASE_FB_ROLE,
                        BaseConstant.BUDGET_DATABASE_OTHER_ROLE));
                List<UserDTO> users = roleInternalService.getUsers(roleUserQuery);
                if (CollectionUtils.isEmpty(users)) {
                    log.info("BudgetDatabaseExpirationNoticeTask... no user was found");
                    log.info("BudgetDatabaseExpirationNoticeTask... end");
                    return;
                }

                doNotify(propertyDTO, emailConfig, users.stream().map(UserDTO::getId).collect(Collectors.toList()), targetYear);

                log.info("BudgetDatabaseExpirationNoticeTask... send email,property:{}", propertyDTO.getAbbr());
            } catch (Exception e) {
                log.error("BudgetDatabaseExpirationNoticeTask...  exception occurs:", e);
                warningEmail.append("<p>Exception:").append(e).append("</p>");
                mailInternalService.sendAlertEmail(alertTitle, warningEmail.toString());
            }
            log.info("BudgetDatabaseExpirationNoticeTask... end");
        }

        /**
         * 发送通知
         *
         * @param propertyDTO 资产
         * @param emailConfig 邮件配置
         * @param toUserIds   收件人
         * @param targetYear  指定年
         * @author yyt
         * @date 2025/8/18
         */
        private void doNotify(PropertyDTO propertyDTO, BudgetDatabaseConfigEmail emailConfig, List<Integer> toUserIds, Integer targetYear) {
            Integer propertyId = propertyDTO.getId();
            String propertyName = propertyDTO.getName();
            String language = propertyDTO.getLanguage();
            String functionName = i18nMessageUtils.message(FunctionCons.BUDGET_DATABASE, language);

            String finalHereLink = hereLink + "?property=" + propertyDTO.getAbbr() + "&tenant=" + propertyDTO.getTenantSymbol();
            String defaultSubject = budgetDatabaseConfigEmailService.getDefaultSubject(propertyDTO, BudgetDatabaseNotifyCons.EXPIRATION_NOTICE);

            //邮件通知内容
            Context context = new Context();
            context.setVariable("propertyName", propertyName);
            context.setVariable("functionName", functionName);
            context.setVariable("targetYear", targetYear);
            context.setVariable("hereLink", finalHereLink);
            String mailContent = EmailUtil.getFullEmailContent(defaultSubject, templateEngine.process("budgetdatabase/expire_" + language, context), language);

            //line通知内容
            LinkedHashMap<String, String> businessMap = new LinkedHashMap<>();
            businessMap.put("Years without filled in the budget data", targetYear.toString());

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

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

            String subject = defaultSubject;
            if (Objects.nonNull(emailConfig) && StringUtils.isNotBlank(emailConfig.getSubject())) {
                subject = emailConfig.getSubject();
            }
            notifyDTO.setSubject(subject);

            notifyDTO.setIdentityList(toUserIds.stream().map(item -> new NotifyIdentityDTO().setIdentityId(item)
                    .setSource(EmailConfigCons.SOURCE_USER).setTargetType(EmailConfigCons.TARGET_TYPE_TO).setEmail("")).toList());
            notifyDTO.setEmailContent(mailContent);
            notifyDTO.setLineContent(Lists.newArrayList(lineContent));
            notifyInternalService.sendNotify(notifyDTO);
        }

    }