# RevenueHistoryAndForecastServiceImpl.java

    package cloud.wconnect.rateshop.service.revenue.impl;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueHistoryAndForecast;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueHistoryAndForecastMapper;
    import cloud.wconnect.rateshop.pojo.bo.revenue.HistoryAndForecastBO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueHistoryAndForecastService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;

    import java.math.RoundingMode;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-08-05
     */
    @Service
    @DS("rate_shop")
    @Slf4j
    public class RevenueHistoryAndForecastServiceImpl extends ServiceImpl<RevenueHistoryAndForecastMapper, RevenueHistoryAndForecast> implements IRevenueHistoryAndForecastService {

        /**
         * 保存History and Forecast from thirty days ago to yesterday数据
         *
         * @param propertyDTO           资产DTO
         * @param historyAndForecastBOS 报告数据
         * @param userId                操作人
         * @return void
         * @author lichen
         * @date 2025/8/5
         */
        @Override
        public void saveHistoryAndForecastData(PropertyDTO propertyDTO, List<HistoryAndForecastBO> historyAndForecastBOS, int userId) {
            Integer propertyId = propertyDTO.getId();
            long now = DateTimeUtils.nowTimestamp();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            List<RevenueHistoryAndForecast> transientGroupLyFutures = historyAndForecastBOS.stream().map(item ->
                    new RevenueHistoryAndForecast().setPropertyId(propertyId)
                            .setTargetDate(Integer.parseInt(StringUtils.replace(item.getTargetDate(), "-", "")))
                            .setOccPercent(Objects.isNull(item.getOccPercent()) ? null : item.getOccPercent().setScale(dataPrecision, roundingMode))
                            .setTransientRoomNum(item.getTransientRoomNum())
                            .setGroupRoomNum(item.getGroupRoomNum())
                            .setAdr(Objects.isNull(item.getAdr()) ? null : item.getAdr().setScale(dataPrecision, roundingMode))
                            .setUpdateBy(userId).setUpdateTime(now)
            ).collect(Collectors.toList());

            if (CollectionUtils.isNotEmpty(transientGroupLyFutures)) {
                baseMapper.saveOrUpdateBatch(transientGroupLyFutures);
            }
        }

        /**
         * 查询指定日期范围的History And Forecast数据
         *
         * @param propertyId 资产id
         * @param startDate  开始日期（yyyyMMdd）
         * @param endDate    结束日期（yyyyMMdd）
         * @return void
         * @author lichen
         * @date 2025/8/5
         */
        @Override
        public List<RevenueHistoryAndForecast> listByDate(Integer propertyId, String startDate, String endDate) {
            return list(new LambdaQueryWrapper<RevenueHistoryAndForecast>().eq(RevenueHistoryAndForecast::getPropertyId, propertyId)
                    .between(RevenueHistoryAndForecast::getTargetDate, startDate, endDate));
        }

        /**
         * 查询指定日期区间的 History And Forecast 数据
         *
         * @param propertyId 资产id
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return History And Forecast 数据
         * @author yyt
         * @date 2026/3/3
         **/
        @Override
        public List<RevenueHistoryAndForecast> listByDate(Integer propertyId, Integer startDate, Integer endDate) {
            return list(new QueryWrapper<RevenueHistoryAndForecast>().lambda()
                    .eq(RevenueHistoryAndForecast::getPropertyId, propertyId)
                    .between(RevenueHistoryAndForecast::getTargetDate, startDate, endDate));
        }

        /**
         * 保存 transient 数据
         *
         * @param historyAndForecasts transient 数据
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public void saveDataForTransientData(List<RevenueHistoryAndForecast> historyAndForecasts) {
            baseMapper.saveOrUpdateBatchForTransient(historyAndForecasts);
        }

        /**
         * 保存 group 数据
         *
         * @param historyAndForecasts group 数据
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public void saveDataForGroupData(List<RevenueHistoryAndForecast> historyAndForecasts) {
            baseMapper.saveOrUpdateBatchForGroup(historyAndForecasts);
        }

        /**
         * 计算 transient 房间数
         *
         * @param historyAndForecastBO 报告数据
         * @return transient 房间数
         * @author yyt
         * @date 2026/3/3
         */
        @Override
        public Integer calculateTransientRoomNum(HistoryAndForecastBO historyAndForecastBO) {
            // 散客房间数 = sold rooms - group rooms
            Integer soldRooms = historyAndForecastBO.getSoldRooms();
            if (soldRooms == null) return null;

            Integer groupRoomNum = historyAndForecastBO.getGroupRoomNum();
            if (Objects.nonNull(groupRoomNum)) {
                soldRooms -= groupRoomNum;
            }

            return soldRooms;
        }

    }