# HotelKpiMetricsInitTask.java

    package cloud.wconnect.workperformance.businesstask.hotelkpi;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.FunctionIdConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.query.OpenFunctionPropertyQuery;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.workperformance.cons.hotelkpi.HotelKpiMetricsEnum;
    import cloud.wconnect.workperformance.service.hotelkpi.IHotelKpiCommonSummaryService;
    import com.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

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

    /**
     * Task: Hotel KPI metrics init
     *
     * @author zc
     * @date 2024/07/23
     */
    @Slf4j
    @Component
    public class HotelKpiMetricsInitTask {

        @Resource
        private IHotelKpiCommonSummaryService commonSummaryService;
        @Resource
        private PropertyInternalService propertyInternalService;
        @Resource
        private RedisTemplate<String, Object> jsonRedisTemplate;

        private static final String PREFIX_KEY = "wconnect:task:hotel-kpi:metrics-init:";

        /**
         * 定时任务处理数据
         *
         * @author zc
         * @date 2024/7/23
         */
        @Scheduled(cron = "${schedule.cron.hotel-kpi.staffing-level-full-time}", zone = "${default.timezone}")
        public void handle() {
            log.info("HotelKpiMetricsInitTask start...");

            // 获取资产数据
            List<PropertyDTO> openPropertyList = propertyInternalService.getOpenFunctionProperty(OpenFunctionPropertyQuery.builder()
                            .openFunctionIds(Lists.newArrayList(FunctionIdConstant.HOTEL_KPI)).build()).stream()
                    .filter(PropertyDTO::getStatus).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(openPropertyList)) {
                log.warn("HotelKpiMetricsInitTask end... property is empty.");
                return;
            }

            for (PropertyDTO propertyDTO : openPropertyList) {
                LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of(propertyDTO.getTimezone()));
                LocalDate nowLocalDate = localDateTime.toLocalDate();
                // 仅凌晨四点时执行
                if (localDateTime.getHour() != 4) {
                    continue;
                }
                // 防止并发操作（wconnect:task:hotel-kpi:metrics-init:250108:2）
                String concurrencyKey = PREFIX_KEY + DateTimeUtils.localDateToStr(nowLocalDate,
                        BaseConstant.DATE_FORMAT_YYMMDD) + ":" + propertyDTO.getId();
                if (!preventConcurrency(concurrencyKey)) {
                    // 若某资产当天已执行过，则跳过
                    continue;
                }
                // 异步处理各个资产的指标数据
                handleInit(propertyDTO, nowLocalDate);
            }

            log.info("HotelKpiMetricsInitTask end...");
        }

        /**
         * 异步处理指标数据
         *
         * @param propertyDTO  资产信息
         * @param nowLocalDate 当前时间
         * @author zc
         * @date 2025/8/26
         */
        @Async
        public void handleInit(PropertyDTO propertyDTO, LocalDate nowLocalDate) {
            try {
                List<Integer> metricsTypeList = List.of(
                        HotelKpiMetricsEnum.GOP.getMetricsType(),
                        HotelKpiMetricsEnum.ADR.getMetricsType(),
                        HotelKpiMetricsEnum.OCC.getMetricsType(),
                        HotelKpiMetricsEnum.MPI.getMetricsType(),
                        HotelKpiMetricsEnum.ARI.getMetricsType(),
                        HotelKpiMetricsEnum.RGI.getMetricsType(),
                        HotelKpiMetricsEnum.FB_BANQUET.getMetricsType(),
                        HotelKpiMetricsEnum.FB_RESTAURANT.getMetricsType(),
                        HotelKpiMetricsEnum.FB_TOTAL.getMetricsType(),
                        HotelKpiMetricsEnum.FB_REVENUE.getMetricsType(),
                        HotelKpiMetricsEnum.OTHER_REVENUE.getMetricsType(),
                        HotelKpiMetricsEnum.RM_REVENUE.getMetricsType(),
                        HotelKpiMetricsEnum.RM_OTHER.getMetricsType(),
                        HotelKpiMetricsEnum.TOTAL_REVENUE.getMetricsType(),
                        HotelKpiMetricsEnum.KIPSU_CONVERSATION.getMetricsType(),
                        HotelKpiMetricsEnum.QUORE_CLEANINGS_CHECKOUT_INSPECTED.getMetricsType(),
                        HotelKpiMetricsEnum.LOSS_OF_REVENUE_ADJUSTMENT.getMetricsType(),
                        HotelKpiMetricsEnum.FTE.getMetricsType(),
                        HotelKpiMetricsEnum.OVERTIME_EXPENSE.getMetricsType(),
                        HotelKpiMetricsEnum.MPR.getMetricsType(),
                        HotelKpiMetricsEnum.STAFFING_LEVEL_FULL_TIME.getMetricsType());
                for (int metricsType : metricsTypeList) {
                    commonSummaryService.initMetricsData(propertyDTO, nowLocalDate, metricsType);
                }
            } catch (Exception e) {
                log.error("HotelKpiMetricsInitTask error... Error processing hotel KPI metrics for property: {}, date: {}",
                        propertyDTO.getId(), nowLocalDate, e);
            }
        }

        /**
         * 校验是否并发执行
         *
         * @param key 缓存前缀
         * @return true:是；false:否
         * @author zc
         * @date 2024/7/23
         */
        private Boolean preventConcurrency(String key) {
            return jsonRedisTemplate.opsForValue().setIfAbsent(key, 0, 1, TimeUnit.DAYS);
        }
    }