# GenerateDailyOperatingStatisticsTask.java

    package cloud.wconnect.finance.businesstask.m3;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.m3Config.PropertyM3ConfigDTO;
    import cloud.wconnect.commonlib.pojo.dto.m3Config.TenantM3ConfigDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.M3ConfigInternalService;
    import cloud.wconnect.commonlib.service.MailInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.finance.entity.pl.M3Account;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.JournalEntryDTO;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.JournalEntryDataDTO;
    import cloud.wconnect.finance.service.m3.CalculationIndexService;
    import cloud.wconnect.finance.service.m3.IM3JournalEntryService;
    import cloud.wconnect.finance.service.m3.IM3MappingService;
    import cloud.wconnect.finance.service.pl.IM3AccountService;
    import cloud.wconnect.finance.service.pl.M3ApiService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.Collectors;

    /**
     * 生成日报运营统计-定时任务
     *
     * @author lichen
     * @date 2025/10/14
     */
    @Component
    @Slf4j
    public class GenerateDailyOperatingStatisticsTask {

        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Value("${default.timezone}")
        private String defaultTimezone;
        @Autowired
        private MailInternalService mailInternalService;
        @Autowired
        private M3ConfigInternalService m3ConfigInternalService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private M3ApiService m3ApiService;
        @Autowired
        private IM3JournalEntryService m3JournalEntryService;
        @Autowired
        private CalculationIndexService calculationIndexService;
        @Autowired
        private IM3MappingService m3MappingService;
        @Autowired
        private IM3AccountService m3AccountService;
        //并发前缀
        private static final String PREFIX_KEY = "wconnect:task:m3:generate-daily-operating-statistics:";

        /**
         * 每日下午10点生成数据
         *
         * @return void
         * @author lichen
         * @date 2025/10/14
         */
        @Scheduled(cron = "${schedule.cron.m3.generate-daily-operating-statistics}", zone = "${default.timezone}")
        public void generate() {
            String currentDateStr = DateTimeUtils.dateNowStr(defaultTimezone, BaseConstant.DATE_FORMAT_YYYYMMDD);
            String key = PREFIX_KEY + currentDateStr;
            try {
                if (!preventConcurrency(key)) {
                    return;
                }
                log.info("GenerateDailyOperatingStatisticsTask.generate start");
                //查询配置M3的有效资产
                List<TenantM3ConfigDTO> tenantM3ConfigDTOS = m3ConfigInternalService.listPropertyM3Config();

                for (TenantM3ConfigDTO tenantM3ConfigDTO : tenantM3ConfigDTOS) {
                    List<PropertyM3ConfigDTO> propertyM3ConfigDTOS = tenantM3ConfigDTO.getPropertyM3ConfigDTOS();
                    if (CollectionUtils.isEmpty(propertyM3ConfigDTOS)) {
                        log.info("GenerateDailyOperatingStatisticsTask.generate end, propertyM3ConfigDTOS is empty, tenantId:" + tenantM3ConfigDTO.getTenantId());
                        continue;
                    }

                    for (PropertyM3ConfigDTO propertyM3ConfigDTO : propertyM3ConfigDTOS) {
                        PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyM3ConfigDTO.getPropertyId());
                        if (!propertyDTO.getStatus()) {
                            log.info("GenerateDailyOperatingStatisticsTask.generate end, property is close, propertyId:" + propertyM3ConfigDTO.getPropertyId());
                            continue;
                        }

                        //生成日报运营统计
                        doGenerateDailyOperatingStatistics(tenantM3ConfigDTO, propertyM3ConfigDTO, propertyDTO);
                    }
                }
                log.info("GenerateDailyOperatingStatisticsTask.generate end success");
            } catch (Exception e) {
                String alertTitle = "[Warning] - Generate Daily Operating Statistics Index Data By M3 Task";
                //发送预警邮件
                mailInternalService.sendAlertEmail(alertTitle,
                        "<p>There is something wrong when Generate Daily Operating Statistics.</p>"
                                + "<p>Exception:" + e + "</p>"
                );
                //删除并发key
                redisTemplate.delete(key);
            }
        }

        /**
         * 生成日报运营统计
         *
         * @param tenantM3ConfigDTO   租户M3ConfigDTO
         * @param propertyM3ConfigDTO 资产M3ConfigDTO
         * @param propertyDTO         资产DTO
         * @return void
         * @author lichen
         * @date 2025/10/14
         */
        private void doGenerateDailyOperatingStatistics(TenantM3ConfigDTO tenantM3ConfigDTO, PropertyM3ConfigDTO propertyM3ConfigDTO, PropertyDTO propertyDTO) {
            String abbr = propertyDTO.getAbbr();
            try {
                ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
                LocalDate currentLocalDate = LocalDate.now(zoneId);
                //计算15天前的日期
                LocalDate startLocalDate = currentLocalDate.minusDays(15);

                String fromDate = DateTimeUtils.localDateToStr(startLocalDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                String toDate = DateTimeUtils.localDateToStr(currentLocalDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD);

                //调用M3接口
                JournalEntryDataDTO journalEntryDataDTO = m3ApiService.listJournalEntry(tenantM3ConfigDTO, propertyM3ConfigDTO, fromDate, toDate);
                if (journalEntryDataDTO == null || CollectionUtils.isEmpty(journalEntryDataDTO.getData())) {
                    log.info("GenerateDailyOperatingStatisticsTask.generate end, journalEntryDataDTO is empty, propertyId:" + propertyDTO.getId());
                    return;
                }
                List<JournalEntryDTO> journalEntryDTOS = journalEntryDataDTO.getData();

                //保存基础数据
                m3JournalEntryService.save(propertyDTO, journalEntryDTOS, startLocalDate, currentLocalDate);

                //查询公式中使用的accountNumber集合
                List<String> accountNumberList = m3MappingService.listFormulaUsedAccountNumber(propertyDTO.getId());
                if(CollectionUtils.isEmpty(accountNumberList)){
                    log.error("GenerateDailyOperatingStatisticsTask.generate end, m3 mapping formula used accountNumberList is empty, propertyId:" + propertyDTO.getId());
                    throw new RuntimeException("M3 mapping formula used accountNumberList is empty");
                }

                //查询存在的accountNumber
                List<M3Account> existAccountNumbers = m3AccountService.listByAccountNumber(propertyDTO.getId(), new ArrayList<>(accountNumberList));
                if(CollectionUtils.isEmpty(existAccountNumbers)){
                    log.error("M3 Account was not found, propertyId:{}", propertyDTO.getId());
                    throw new RuntimeException("M3 Account was not found");
                }
                Map<String, Integer> accountNumber2AccountType = existAccountNumbers.stream().collect(Collectors.toMap(M3Account::getAccountNumber, M3Account::getAccountType));

                //m3返回的数据日期范围
                List<LocalDate> postDateList = journalEntryDTOS.stream()
                        .map(JournalEntryDTO::getPostDate)
                        .distinct()
                        .map(postDate -> DateTimeUtils.strToLocalDate(postDate, BaseConstant.DATE_FORMAT_YYYY_MM_DD))
                        .toList();
                LocalDate dataStartDate = Collections.min(postDateList);
                LocalDate dataEndDate = Collections.max(postDateList);

                //计算指标数据
                calculationIndexService.calculationIndex(propertyDTO, accountNumber2AccountType, dataStartDate, dataEndDate);
            } catch (Exception e) {
                log.error("GenerateDailyOperatingStatisticsTask.generate end, error, propertyId:" + propertyDTO.getId(), e);
                String alertTitle = "[Warning] - " + abbr + " - Generate Daily Operating Statistics Index Data By M3 Task";
                //发送预警邮件
                mailInternalService.sendAlertEmail(alertTitle,
                        "<p>There is something wrong when Generate Daily Operating Statistics.</p>"
                                + "<p>Exception:" + e + "</p>"
                );
            }
        }

        /**
         * 阻止并发操作
         *
         * @param key 缓存key
         * @return java.lang.Boolean
         * @author lichen
         * @date 2023/10/16
         */
        private Boolean preventConcurrency(String key) {
            return redisTemplate.opsForValue().setIfAbsent(key, 0, 1, TimeUnit.DAYS);
        }

    }