# RateShopOccForecastServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.calculateoperation.CalculateOOORoomsDTO;
    import cloud.wconnect.commonlib.pojo.dto.calculateoperation.CalculateScOccDTO;
    import cloud.wconnect.commonlib.service.CalculatePropertyOperationService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.rateshop.entity.rateshop.RateShopOccForecast;
    import cloud.wconnect.rateshop.mapper.rateshop.RateShopOccForecastMapper;
    import cloud.wconnect.rateshop.pojo.bo.rateshop.HistoryAndForecastForRateShopBO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.HistoryAndForecastOccDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.occforcast.OccForecastDTO;
    import cloud.wconnect.rateshop.service.rateshop.IRateShopOccForecastService;
    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 com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.RoundingMode;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2024-06-18
     */
    @Service
    @DS("rate_shop")
    public class RateShopOccForecastServiceImpl extends ServiceImpl<RateShopOccForecastMapper, RateShopOccForecast> implements IRateShopOccForecastService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private CalculatePropertyOperationService calculatePropertyOperationService;

        /**
         * 批量保存occ数据
         *
         * @param propertyDTO           资产DTO
         * @param operatorId            操作人id
         * @param historyAndForecastBOS occ数据
         * @return void
         * @author lichen
         * @date 2024/6/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOccForecast(PropertyDTO propertyDTO, int operatorId, List<HistoryAndForecastForRateShopBO> historyAndForecastBOS) {
            if (CollectionUtils.isEmpty(historyAndForecastBOS)) return;

            int propertyId = propertyDTO.getId();
            String timezone = propertyDTO.getTimezone();

            //转化为DTO
            List<HistoryAndForecastOccDTO> historyAndForecastOccDTOS = beanMapper.mapList(historyAndForecastBOS, HistoryAndForecastOccDTO.class,
                    (bo, dto) -> dto.setTheDate(Long.parseLong(bo.getDate().replace("-", ""))));

            List<Long> theDateList = historyAndForecastOccDTOS.stream().map(HistoryAndForecastOccDTO::getTheDate).toList();
            long startDateQuery = Collections.min(theDateList);
            long endDateQuery = Collections.max(theDateList);

            // 查找指定时间范围内的数据
            List<RateShopOccForecast> occForecastList = list(new LambdaQueryWrapper<RateShopOccForecast>()
                    .between(RateShopOccForecast::getTheDate, startDateQuery, endDateQuery)
                    .eq(RateShopOccForecast::getPropertyId, propertyId)
                    .last("for update"));
            Map<Long, RateShopOccForecast> theDate2OccForecast = occForecastList.stream()
                    .collect(Collectors.toMap(RateShopOccForecast::getTheDate, Function.identity()));

            // 获取当前时间的 key
            String currentKey = DateTimeUtils.parseToLong(DateTimeUtils.dateTimeNow(timezone),
                    BaseConstant.DATE_FORMAT_YYYYMMDDHH).toString();
            long nowTimestamp = DateTimeUtils.nowTimestamp();

            //循环报告中的Occ数据
            for (HistoryAndForecastOccDTO historyAndForecastOccDTO : historyAndForecastOccDTOS) {
                // 构造当前时刻的 occForecastDTO
                OccForecastDTO currentOccForecastDTO = OccForecastDTO.builder()
                        .key(currentKey)
                        .roomsSold(historyAndForecastOccDTO.getRoomsSold())
                        .pcOcc(historyAndForecastOccDTO.getPcOcc())
                        .scOcc(historyAndForecastOccDTO.getScOcc())
                        .oooRooms(historyAndForecastOccDTO.getOooRooms())
                        .updatedBy(operatorId)
                        .updatedTime(nowTimestamp)
                        .build();

                //获取数据库中指定营业日期的occ数据
                long theDate = historyAndForecastOccDTO.getTheDate();
                RateShopOccForecast oriRateShopOccForecast = theDate2OccForecast.get(theDate);
                //数据库中的数据为空时，新增
                if (Objects.isNull(oriRateShopOccForecast)) {
                    occForecastList.add(new RateShopOccForecast()
                            .setPropertyId(propertyId)
                            .setTheDate(theDate)
                            .setValues(JsonUtils.convertObjListToString(objectMapper, Lists.newArrayList(currentOccForecastDTO))));
                } else {//更新
                    //获取数据库中不同时刻的Occ数据
                    List<OccForecastDTO> oriValuesDTOList = JsonUtils.convertStringToList(objectMapper,
                            oriRateShopOccForecast.getValues(), OccForecastDTO.class);

                    Optional<OccForecastDTO> optional = oriValuesDTOList.stream().filter(dto -> dto.getKey().equals(currentKey)).findFirst();
                    if (optional.isPresent()) {
                        //存在当前时刻的occ数据，更新
                        OccForecastDTO occForecastDTO = optional.get();
                        occForecastDTO.setRoomsSold(historyAndForecastOccDTO.getRoomsSold());
                        occForecastDTO.setPcOcc(historyAndForecastOccDTO.getPcOcc());
                        occForecastDTO.setScOcc(historyAndForecastOccDTO.getScOcc());
                        occForecastDTO.setOooRooms(historyAndForecastOccDTO.getOooRooms());
                        occForecastDTO.setUpdatedBy(operatorId);
                        occForecastDTO.setUpdatedTime(nowTimestamp);
                    } else {
                        //不存在当前时刻的occ数据，新增
                        oriValuesDTOList.add(currentOccForecastDTO);
                    }
                    oriRateShopOccForecast.setValues(JsonUtils.convertObjListToString(objectMapper, oriValuesDTOList));
                }
            }
            // 批量保存
            saveOrUpdateBatch(occForecastList);
        }

        /**
         * 根据日期范围查询occ数据
         *
         * @param startDate  开始时间
         * @param endDate    结束时间
         * @param propertyId 资产id
         * @return java.util.List<cloud.wconnect.rateshop.entity.rateshop.RateShopOccForecast>
         * @author lichen
         * @date 2024/6/21
         */
        @Override
        public List<RateShopOccForecast> listByDateRange(long startDate, long endDate, Integer propertyId) {
            return list(new LambdaQueryWrapper<RateShopOccForecast>()
                    .between(RateShopOccForecast::getTheDate, startDate, endDate)
                    .eq(RateShopOccForecast::getPropertyId, propertyId));
        }

        /**
         * 根据日期范围查询occ数据
         *
         * @param propertyId    资产id
         * @param startDate 开始时间
         * @param endDate   结束时间
         * @return java.util.List<cloud.wconnect.rateshop.entity.rateshop.RateShopOccForecast>
         * @author yyt
         * @date 2026/3/10
         */
        @Override
        public List<RateShopOccForecast> listByDateRangeForUpdate(int propertyId, long startDate, long endDate) {
            return list(new LambdaQueryWrapper<RateShopOccForecast>()
                    .eq(RateShopOccForecast::getPropertyId, propertyId)
                    .between(RateShopOccForecast::getTheDate, startDate, endDate)
                    .last(" for update "));
        }

        /**
         * 包装occ数据
         *
         * @param currentKey               当前key
         * @param historyAndForecastOccDTO occ数据
         * @param roundingMode             舍入模式
         * @param operatorId               操作人id
         * @param nowTimestamp             当前时间戳
         * @return cloud.wconnect.rateshop.pojo.dto.rateshop.occforcast.OccForecastDTO
         * @author yyt
         * @date 2026/3/10
         */
        @Override
        public OccForecastDTO packageOccForecastDTO(String currentKey, HistoryAndForecastOccDTO historyAndForecastOccDTO, RoundingMode roundingMode, int operatorId, long nowTimestamp) {

            // 重新计算 ooo rooms
            historyAndForecastOccDTO.setOooRooms(calculatePropertyOperationService.calculateOooRooms(beanMapper.map(historyAndForecastOccDTO, CalculateOOORoomsDTO.class)));
            // 重新计算 sc occ
            historyAndForecastOccDTO.setScOcc(calculatePropertyOperationService.calculateScOcc(beanMapper.map(historyAndForecastOccDTO, CalculateScOccDTO.class), roundingMode));

           return OccForecastDTO.builder()
                    .key(currentKey)
                    .roomsSold(historyAndForecastOccDTO.getRoomsSold())
                    .pcOcc(historyAndForecastOccDTO.getPcOcc())
                    .scOcc(historyAndForecastOccDTO.getScOcc())
                    .oooRooms(historyAndForecastOccDTO.getOooRooms())
                    .totalRooms(historyAndForecastOccDTO.getTotalRooms())
                    .availableRooms(historyAndForecastOccDTO.getRoomsAvailable())
                    .compRooms(historyAndForecastOccDTO.getCompRooms())
                    .updatedBy(operatorId)
                    .updatedTime(nowTimestamp)
                    .build();
        }

    }