# GmwrHistoryAndForecastServiceImpl.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.EventBodyParamEnum;
    import cloud.wconnect.commonlib.exception.ListenerException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.workperformance.entity.gmwr.GmwrHistoryAndForecast;
    import cloud.wconnect.workperformance.mapper.gmwr.GmwrHistoryAndForecastMapper;
    import cloud.wconnect.workperformance.pojo.bo.gmwr.GmwrHistoryAndForecastBO;
    import cloud.wconnect.workperformance.pojo.bo.gmwr.OccupancySummaryReportBO;
    import cloud.wconnect.workperformance.service.gmwr.IGmwrHistoryAndForecastService;
    import cloud.wconnect.workperformance.service.gmwr.historyandforecast.IGmwrHistoryAndForecastParseService;
    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.google.common.collect.Lists;
    import jakarta.annotation.Resource;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.io.InputStream;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * GM Weekly Report History and Forecast 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-10-12
     */
    @Slf4j
    @Service
    @DS("work_performance")
    public class GmwrHistoryAndForecastServiceImpl extends ServiceImpl<GmwrHistoryAndForecastMapper, GmwrHistoryAndForecast> implements IGmwrHistoryAndForecastService {
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ApplicationContext applicationContext;

        /**
         * 获取指定日期范围的 History and Forecast 数据
         *
         * @param propertyDTO 资产
         * @param startDate   开始日期(yyyyMMdd)
         * @param endDate     结束日期(yyyyMMdd)
         * @return GmwrHistoryAndForecast 集合
         * @author zc
         * @date 2024/10/16
         */
        @Override
        public List<GmwrHistoryAndForecast> list(PropertyDTO propertyDTO, long startDate, long endDate) {
            return list(new LambdaQueryWrapper<GmwrHistoryAndForecast>()
                    .between(GmwrHistoryAndForecast::getDate, startDate, endDate)
                    .eq(GmwrHistoryAndForecast::getPropertyId, propertyDTO.getId()));
        }

        /**
         * 删除指定日期范围的 History and Forecast 数据
         *
         * @param propertyId 资产 id
         * @param startDate  开始日期(yyyyMMdd)
         * @param endDate    结束日期(yyyyMMdd)
         * @author zc
         * @date 2024/10/16
         */
        @Override
        public void delete(int propertyId, long startDate, long endDate) {
            remove(new LambdaQueryWrapper<GmwrHistoryAndForecast>()
                    .between(GmwrHistoryAndForecast::getDate, startDate, endDate)
                    .eq(GmwrHistoryAndForecast::getPropertyId, propertyId));
        }

        /**
         * 解析报告 History and Forecast
         *
         * @param propertyDTO 资产
         * @param in          报告文件流
         * @param source      报告来源
         * @param dateRange   日期范围(yyyyMMdd)
         * @author zc
         * @date 2024/10/16
         */
        @Override
        public void handleReport(PropertyDTO propertyDTO, InputStream in, String source, List<Long> dateRange) {
            Collection<IGmwrHistoryAndForecastParseService> parserList = applicationContext.getBeansOfType(IGmwrHistoryAndForecastParseService.class).values();
            for (IGmwrHistoryAndForecastParseService parser : parserList) {
                if (parser.supports(source)) {
                     parser.handleHistoryAndForecast(propertyDTO, parser.parse(propertyDTO, in, dateRange));
                     return;
                }
            }
            throw new ListenerException("Not found report parser");
        }

        /**
         * 处理 History and Forecast 数据
         *
         * @param propertyDTO              资产
         * @param historyAndForecastBOList 报告数据
         * @author zc
         * @date 2024/10/16
         */
        @Override
        public void handleHistoryAndForecast(PropertyDTO propertyDTO, List<GmwrHistoryAndForecastBO> historyAndForecastBOList) {
            if (CollectionUtils.isEmpty(historyAndForecastBOList)) {
                return;
            }
            List<GmwrHistoryAndForecast> list = beanMapper.mapList(historyAndForecastBOList,
                    GmwrHistoryAndForecast.class, (bo, entity) -> {
                        entity.setPropertyId(propertyDTO.getId());
                        LocalDate localDate = DateTimeUtils.strToLocalDate(bo.getDateStr(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                        entity.setDate(Long.parseLong(DateTimeUtils.localDateToStr(localDate, BaseConstant.DATE_FORMAT_YYYYMMDD)));
                    });

            insertOrUpdateBatchByUnique(list);
        }

        /**
         * 计算 OOO 房间数
         *
         * @param historyAndForecast 历史和预测数据
         * @return OOO 房间数
         * @author yyt
         * @date 2026/2/6
         */
        @Override
        public Integer calculateOooRooms(GmwrHistoryAndForecast historyAndForecast) {
            // OOO 房间数 = total rooms - sold rooms - available rooms - comp rooms
            Integer totalRooms = historyAndForecast.getTotalRooms();
            if (Objects.isNull(totalRooms)) {
                return 0;
            }
            int oooRooms = totalRooms;
            Integer roomsSold = historyAndForecast.getRoomsSold();
            if (Objects.nonNull(roomsSold)) {
                oooRooms -= roomsSold;
            }
            Integer availableRooms = historyAndForecast.getAvailableRooms();
            if (Objects.nonNull(availableRooms)) {
                oooRooms -= availableRooms;
            }
            Integer compRooms = historyAndForecast.getCompRooms();
            if (Objects.nonNull(compRooms)) {
                oooRooms -= compRooms;
            }
            return Math.max(oooRooms, 0);
        }

        /**
         * 批量插入或更新
         *
         * @param historyAndForecasts 数据
         * @author yyt
         * @date 2026/2/6
         */
        @Override
        public void insertOrUpdateBatchByUnique(List<GmwrHistoryAndForecast> historyAndForecasts) {
            // 按唯一索引字段排序，避免死锁
            historyAndForecasts.sort(Comparator.comparing(GmwrHistoryAndForecast::getPropertyId)
                    .thenComparing(GmwrHistoryAndForecast::getDate));

            baseMapper.insertOrUpdateBatchByUnique(historyAndForecasts);
        }

        /**
         * 获取处理报告日期范围
         *
         * @param propertyDTO 资产
         * @param parameter   事件体参数
         * @param operatorId  操作人 id
         * @return java.util.List<java.lang.Long>
         * @author yyt
         * @date 2026/2/5
         */
        @Override
        public List<Long> getDateRange(PropertyDTO propertyDTO, Map<String, Object> parameter, int operatorId) {
            if (operatorId == 0) {
                // 系统下发
                LocalDate nowLocalDate = LocalDate.now(ZoneId.of(propertyDTO.getTimezone()));
                // 数据范围是 -30d ~ -1d
                long startDate = Long.parseLong(DateTimeUtils.localDateToStr(nowLocalDate.minusDays(30),
                        BaseConstant.DATE_FORMAT_YYYYMMDD));
                long endDate = Long.parseLong(DateTimeUtils.localDateToStr(nowLocalDate.minusDays(1),
                        BaseConstant.DATE_FORMAT_YYYYMMDD));
                return Lists.newArrayList(startDate, endDate);
            }
            // 手动导入
            Object startDateObj = parameter.get(EventBodyParamEnum.START_DATE.getDesc());
            Object endDateObj = parameter.get(EventBodyParamEnum.END_DATE.getDesc());
            if (Objects.isNull(startDateObj) || Objects.isNull(endDateObj)) {
                return Collections.emptyList();
            }
            return List.of(Long.parseLong(startDateObj.toString()), Long.parseLong(endDateObj.toString()));
        }

        /**
         * 更新 occupancy summary 数据
         *
         * @param propertyDTO                  资产
         * @param occupancySummaryReportBOList 报告数据
         * @author yyt
         * @date 2026/2/5
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateOccupancySummaryData(PropertyDTO propertyDTO, List<OccupancySummaryReportBO> occupancySummaryReportBOList) {
            if (CollectionUtils.isEmpty(occupancySummaryReportBOList)) {
                return;
            }

            List<GmwrHistoryAndForecast> historyAndForecasts = beanMapper.mapList(occupancySummaryReportBOList, GmwrHistoryAndForecast.class);

            // 查询已存在的数据
            historyAndForecasts.sort(Comparator.comparing(GmwrHistoryAndForecast::getDate));
            Map<Long, GmwrHistoryAndForecast> existDateByDate = list(propertyDTO, historyAndForecasts.get(0).getDate(), historyAndForecasts.get(historyAndForecasts.size() - 1).getDate())
                    .stream().collect(Collectors.toMap(GmwrHistoryAndForecast::getDate, v -> v));

            for (GmwrHistoryAndForecast historyAndForecast : historyAndForecasts) {
                GmwrHistoryAndForecast existData = existDateByDate.get(historyAndForecast.getDate());
                if (Objects.nonNull(existData)) {
                    // 重置 OOO 房数
                    existData.setCompRooms(historyAndForecast.getCompRooms());
                    historyAndForecast.setOoo(calculateOooRooms(existData));
                }
            }

            historyAndForecasts.sort(Comparator.comparing(GmwrHistoryAndForecast::getDate));

            baseMapper.insertOrUpdateOccupancySummaryBatchByUnique(historyAndForecasts);
        }

    }