# GmwrDailySummaryOccServiceImpl.java

    package cloud.wconnect.workperformance.service.gmwr.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.MoudleEventSubjectEnum;
    import cloud.wconnect.commonlib.pojo.bo.revenue.DailySummaryDataBO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.sns.http.processor.Sender;
    import cloud.wconnect.workperformance.cons.gmwr.selloutefficiency.GmwrSelloutEfficiencyCons;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrDailySummaryOcc;
    import cloud.wconnect.workperformance.mapper.gmwr.GmwrDailySummaryOccMapper;
    import cloud.wconnect.workperformance.pojo.bo.gmwr.event.GmwrSelloutEfficiencyEventBO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.selloutefficiency.GmwrOccSelloutConfigDTO;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrDailySummaryOccService;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrOccSelloutConfigService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Set;
    import java.util.concurrent.Executor;
    import java.util.stream.Collectors;

    /**
     * <p>
     * GM Weekly Report Daily Summary OCC 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-10-12
     */
    @Service
    @DS("work_performance")
    public class GmwrDailySummaryOccServiceImpl extends ServiceImpl<GmwrDailySummaryOccMapper, GmwrDailySummaryOcc> implements IGmwrDailySummaryOccService {

        @Resource
        private IGmwrOccSelloutConfigService occSelloutConfigService;
        @Resource
        private ObjectMapper objectMapper;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        @Qualifier("moduleEventSender")
        private Sender moduleEventSender;
        @Resource
        private Executor executor;

        /**
         * 保存 Daily Summary OCC 数据
         *
         * @param propertyDTO 资产
         * @param dataList    数据
         * @author zc
         * @date 2024/10/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(PropertyDTO propertyDTO, List<DailySummaryDataBO> dataList) {
            if (CollectionUtils.isEmpty(dataList)) {
                return;
            }

            List<GmwrDailySummaryOcc> dailySummaryOccList = beanMapper.mapList(dataList,
                    GmwrDailySummaryOcc.class, (bo, item) -> {
                        item.setPropertyId(propertyDTO.getId());
                        item.setDate(Long.parseLong(DateTimeUtils.timestampToStr(bo.getTheDate(),
                                BaseConstant.DATE_FORMAT_YYYYMMDD, propertyDTO.getTimezone())));
                        item.setHour(bo.getTheHour());
                        item.setOccPercent(bo.getOcc());
                        item.setRoomsSold(bo.getTotalRoomSold());
                    });
            insertOrUpdateBatchByUnique(dailySummaryOccList);

            // 指定日期范围内 OCC hour、OCC Final hour 时刻的数据发生变化时，发送 SNS 事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                GmwrOccSelloutConfigDTO configDTO = occSelloutConfigService.getByPropertyId(propertyDTO.getId());
                List<Integer> effectiveHourList = List.of(configDTO.getOccHour(), GmwrSelloutEfficiencyCons.FINAL_OCC_HOUR);
                Set<Long> dateList = dailySummaryOccList.stream().filter(item ->
                        effectiveHourList.contains(item.getHour())).map(GmwrDailySummaryOcc::getDate).collect(Collectors.toSet());
                if (CollectionUtils.isEmpty(dateList)) {
                    return;
                }
                GmwrSelloutEfficiencyEventBO selloutEfficiency = new GmwrSelloutEfficiencyEventBO();
                selloutEfficiency.setPropertyId(propertyDTO.getId());
                selloutEfficiency.setStartDate(Collections.min(dateList));
                selloutEfficiency.setEndDate(Collections.max(dateList));

                String messages = JsonUtils.convertObjToString(objectMapper, selloutEfficiency);
                // 发送事件
                moduleEventSender.publish(MoudleEventSubjectEnum.WEEKLY_GM_REPORT_SELLOUT_EFFICIENCY.getSubject(), messages);
            });
        }

        /**
         * 批量保存数据
         *
         * @param list 数据
         * @author zc
         * @date 2026/1/27
         */
        private void insertOrUpdateBatchByUnique(List<GmwrDailySummaryOcc> list) {
            if (CollectionUtils.isEmpty(list)) {
                return;
            }
            // 按唯一索引字段排序，避免死锁
            list.sort(Comparator.comparing(GmwrDailySummaryOcc::getPropertyId)
                    .thenComparing(GmwrDailySummaryOcc::getDate)
                    .thenComparing(GmwrDailySummaryOcc::getHour));

            baseMapper.insertOrUpdateBatchByUnique(list);
        }

        /**
         * 查询数据
         *
         * @param propertyId 资产 id
         * @param startDate  开始时间(yyyyMMdd)
         * @param endDate    结束时间(yyyyMMdd)
         * @param hours      时刻
         * @return GmwrDailySummaryOcc 集合
         * @author zc
         * @date 2024/10/20
         */
        @Override
        public List<GmwrDailySummaryOcc> list(int propertyId, long startDate, long endDate, List<Integer> hours) {
            if (CollectionUtils.isEmpty(hours)) {
                return Collections.emptyList();
            }
            return baseMapper.selectList(new LambdaQueryWrapper<GmwrDailySummaryOcc>()
                    .between(GmwrDailySummaryOcc::getDate, startDate, endDate)
                    .in(GmwrDailySummaryOcc::getHour, hours)
                    .eq(GmwrDailySummaryOcc::getPropertyId, propertyId));
        }
    }