# RevenueSegmentRevenueFutureServiceImpl.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.commonlib.util.YearMonthUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegment;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegmentRevenueFuture;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueSegmentRevenueFutureMapper;
    import cloud.wconnect.rateshop.pojo.bo.revenue.RoomsAndRevenueBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.SegmentBO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSegmentRevenueFutureService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Maps;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.ibatis.session.ExecutorType;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.YearMonth;
    import java.time.format.DateTimeFormatter;
    import java.time.temporal.TemporalAdjusters;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2024-05-08
     */
    @Service
    @DS("rate_shop")
    public class RevenueSegmentRevenueFutureServiceImpl extends ServiceImpl<RevenueSegmentRevenueFutureMapper, RevenueSegmentRevenueFuture> implements IRevenueSegmentRevenueFutureService {

        @Autowired
        private SqlSessionFactory sqlSessionFactory;

        public static final String TOTAL_ROOMS = "totalRooms";

        public static final String TOTAL_REVENUE = "totalRevenue";

        /**
         * 批量修改
         *
         * @param updateSegmentRevenues segment revenue
         * @author yyt
         * @date 2024/5/11
         */
        @Override
        public void updateBatch(List<RevenueSegmentRevenueFuture> updateSegmentRevenues) {
            baseMapper.updateBatch(updateSegmentRevenues);
        }

        /**
         * 保存 forecast segment 信息
         *
         * @param propertyDTO    资产
         * @param segments      segment
         * @param segmentBOList segment bo
         * @param userId        操作人
         * @author yyt
         * @date 2024/5/22
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdateForecastSegment(PropertyDTO propertyDTO, List<RevenueSegment> segments,
                                                List<SegmentBO> segmentBOList, int userId) {
            // 查询已存在的数据
            Integer propertyId = propertyDTO.getId();
            List<Integer> dates = segmentBOList.stream()
                    .map(item -> Integer.parseInt(StringUtils.replace(item.getDate(), "-", "")))
                    .collect(Collectors.toList());
            List<Integer> segmentIds = segments.stream().map(RevenueSegment::getId).collect(Collectors.toList());
            Map<Integer, Map<Integer, RevenueSegmentRevenueFuture>> existSegmentRevenueFutureByTheDate =
                    listExistData(propertyId, dates, segmentIds);

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            long now = DateTimeUtils.nowTimestamp();
            int size = segmentBOList.size();
            List<RevenueSegmentRevenueFuture> segmentRevenueFutures = Lists.newArrayListWithExpectedSize(size);

            // 没有的新增、有的重置 rooms、revenue、adr
            if (MapUtils.isEmpty(existSegmentRevenueFutureByTheDate)) {
                for (SegmentBO segmentBO : segmentBOList) {
                    segmentRevenueFutures.addAll(segmentBO.getDetail().stream()
                            .map(item-> initSegmentForecastRevenueFuture(propertyDTO, segmentBO.getDate(), item, userId, now))
                            .toList());
                }
            } else {
                for (SegmentBO segmentBO : segmentBOList) {
                    int date = Integer.parseInt(StringUtils.replace(segmentBO.getDate(), "-", ""));
                    Map<Integer, RevenueSegmentRevenueFuture> existSegmentRevenueFutureBySegmentId =
                            existSegmentRevenueFutureByTheDate.getOrDefault(date, Collections.emptyMap());
                    for (RoomsAndRevenueBO roomsAndRevenueBO : segmentBO.getDetail()) {
                        RevenueSegmentRevenueFuture existSegmentRevenueFuture = existSegmentRevenueFutureBySegmentId.get(roomsAndRevenueBO.getId());
                        if (Objects.isNull(existSegmentRevenueFuture)) {
                            segmentRevenueFutures.add(initSegmentForecastRevenueFuture(propertyDTO, segmentBO.getDate(), roomsAndRevenueBO, userId, now));
                        } else {
                            // 重置 rooms、revenue、adr
                            Integer rooms = roomsAndRevenueBO.getRooms();
                            BigDecimal revenue = roomsAndRevenueBO.getRevenue();
                            existSegmentRevenueFuture.setTotalRoomsForecast(rooms)
                                    .setRevenueForecast(Objects.isNull(revenue) ? null : revenue.setScale(dataPrecision, roundingMode))
                                    .setUpdatedBy(userId)
                                    .setUpdatedTime(now);

                            // 计算 revenue + total rooms
                            calculateRevenueAndRooms(propertyDTO, existSegmentRevenueFuture);

                            // 重新计算 adr
                            calculateAdr(propertyDTO, existSegmentRevenueFuture);

                            segmentRevenueFutures.add(existSegmentRevenueFuture);
                        }
                    }
                }
            }

            if (CollectionUtils.isNotEmpty(segmentRevenueFutures)) {
                segmentRevenueFutures.sort(Comparator.comparing(RevenueSegmentRevenueFuture::getTheDate).thenComparing(RevenueSegmentRevenueFuture::getSegmentId));
                saveOrUpdateSegmentRevenueFutureBatch(segmentRevenueFutures);
            }
        }

        /**
         * 批量新增
         *
         * @param segmentRevenueFutures segment revenue future
         * @author yyt
         * @date 2024/5/27
         **/
        private void insertSegmentRevenueFutureBatch(List<RevenueSegmentRevenueFuture> segmentRevenueFutures) {
            SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
            RevenueSegmentRevenueFutureMapper mapper = sqlSession.getMapper(RevenueSegmentRevenueFutureMapper.class);
            for (int i = 0; i < segmentRevenueFutures.size(); i++) {
                RevenueSegmentRevenueFuture revenueFuture = segmentRevenueFutures.get(i);
                mapper.insert(revenueFuture);

                //每 50 条提交一次
                if ((i + 1) % 50 == 0) {
                    sqlSession.flushStatements();
                }
            }

            sqlSession.flushStatements();
        }

        /**
         * 批量修改
         *
         * @param segmentRevenueFutures segment revenue future
         * @author yyt
         * @date 2024/5/27
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdateSegmentRevenueFutureBatch(List<RevenueSegmentRevenueFuture> segmentRevenueFutures) {
            // 分批保存，每批 50 条
            int size = segmentRevenueFutures.size();
            int batchSize = 50;

            // 使用更安全的方式计算批次数量，避免整数溢出
            int batchCount = (int) Math.ceil((double) size / batchSize);
            for (int i = 0; i < batchCount; i++) {
                int start = i * batchSize;
                int end = Math.min(start + batchSize, size);
                List<RevenueSegmentRevenueFuture> batchList = segmentRevenueFutures.subList(start, end);
                baseMapper.saveOrUpdateBatch(batchList);
            }
        }

        /**
         * 查询 segment 信息
         *
         * @param propertyId 资产 id
         * @param dates      指定日期
         * @param segmentIds segment id
         * @return segment 信息
         * @author yyt
         * @date 2024/5/22
         **/
        @Override
        public Map<Integer, Map<Integer, RevenueSegmentRevenueFuture>> listExistData(Integer propertyId, List<Integer> dates,
                                                                                     List<Integer> segmentIds) {
            return list(new QueryWrapper<RevenueSegmentRevenueFuture>()
                    .lambda().eq(RevenueSegmentRevenueFuture::getPropertyId, propertyId)
                    .in(RevenueSegmentRevenueFuture::getTheDate, dates)
                    .in(RevenueSegmentRevenueFuture::getSegmentId, segmentIds))
                    .stream().collect(Collectors.groupingBy(RevenueSegmentRevenueFuture::getTheDate,
                            Collectors.toMap(RevenueSegmentRevenueFuture::getSegmentId, v -> v)));
        }

        /**
         * 初始 segment revenue future 信息
         *
         * @param propertyDTO 资产
         * @param date        指定日期（yyyy-MM-dd）
         * @param item        RoomsAndRevenueBO
         * @param userId      操作人
         * @param now         当前时间
         * @return segment revenue future 信息
         * @author yyt
         * @date 2024/5/22
         */
        private RevenueSegmentRevenueFuture initSegmentForecastRevenueFuture(PropertyDTO propertyDTO, String date,
                                                                       RoomsAndRevenueBO item, int userId, long now) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            BigDecimal revenue = (Objects.isNull(item.getRevenue()) ? null  : item.getRevenue().setScale(dataPrecision, roundingMode));
            Integer rooms = item.getRooms();
            RevenueSegmentRevenueFuture segmentRevenue = new RevenueSegmentRevenueFuture()
                    .setPropertyId(propertyDTO.getId())
                    .setTheDate(Integer.parseInt(StringUtils.replace(date, "-", "")))
                    .setSegmentId(item.getId())
                    .setRevenue(revenue)
                    .setRevenueForecast(revenue)
                    .setTotalRooms(rooms)
                    .setTotalRoomsForecast(rooms)
                    .setUpdatedBy(userId)
                    .setUpdatedTime(now);

            // 计算 adr
            calculateAdr(propertyDTO, segmentRevenue);

            return segmentRevenue;
        }

        /**
         * 初始化 segment revenue
         *
         * @param propertyDTO    资产
         * @param date           日期
         * @param item           房间和收入
         * @param userId         操作人
         * @param now            当前时间戳
         * @return segment revenue
         * @author yyt
         * @date 2026/1/12
         */
        private RevenueSegmentRevenueFuture initSegmentStatisticsRevenueFuture(PropertyDTO propertyDTO, Integer date, RoomsAndRevenueBO item, int userId, long now) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            BigDecimal revenue = Objects.isNull(item.getRevenue()) ? null : item.getRevenue().setScale(dataPrecision, roundingMode);
            Integer rooms = item.getRooms();
            RevenueSegmentRevenueFuture segmentRevenue = new RevenueSegmentRevenueFuture()
                    .setPropertyId(propertyDTO.getId())
                    .setTheDate(date)
                    .setSegmentId(item.getId())
                    .setRevenue(revenue)
                    .setRevenueStatistics(revenue)
                    .setTotalRooms(rooms)
                    .setTotalRoomsStatistics(rooms)
                    .setUpdatedBy(userId)
                    .setUpdatedTime(now);

            // 计算 adr
            calculateAdr(propertyDTO, segmentRevenue);

            return segmentRevenue;
        }

        /**
         * 计算 adr
         *
         * @param propertyDTO    资产
         * @param segmentRevenue segment revenue
         * @author yyt
         * @date 2024/5/22
         */
        private void calculateAdr(PropertyDTO propertyDTO, RevenueSegmentRevenueFuture segmentRevenue) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            BigDecimal revenue = segmentRevenue.getRevenue();
            Integer rooms = segmentRevenue.getTotalRooms();
            if (Objects.nonNull(revenue) && Objects.nonNull(rooms) && rooms != BaseConstant.ZERO) {
                // adr = revenue / rooms
                segmentRevenue.setAdr(revenue.divide(BigDecimal.valueOf(rooms), dataPrecision, roundingMode));
            } else {
                segmentRevenue.setAdr(null);
            }
        }

        /**
         * 处理设置 next month 相关内容（revenue、total rooms、adr）
         * 汇总每个月的数据，分别更新到每个月的上个月的最后一天的记录中，用于 页面计算 pick up
         *
         * @param propertyDTO    资产
         * @param segmentBOList segment bo
         * @param userId        操作人
         * @author yyt
         * @date 2024/5/22
         **/
        @Override
        public void saveOrUpdateForecastSegmentNextMonth(PropertyDTO propertyDTO, List<RevenueSegment> segments,
                                                         List<SegmentBO> segmentBOList, int userId) {
            // 每天的 segment 数据以月分组
            Map<Long, List<SegmentBO>> segmentBOListByYearMonth = segmentBOList.stream().peek(item -> {
                LocalDate localDate = DateTimeUtils.strToLocalDate(item.getDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD);
                item.setYearMonth(YearMonthUtils.parseYearMonthToLong(YearMonth.of(localDate.getYear(), localDate.getMonthValue())));
            }).collect(Collectors.groupingBy(SegmentBO::getYearMonth));

            // 查询每个月的上个月的最后一天的数据，没有则新增，有则修改
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYYMMDD);
            List<Integer> selectMonthEnds = segmentBOListByYearMonth.keySet().stream().map(item -> {
                YearMonth yearMonth = YearMonthUtils.getYearMonth(item.toString());
                return Integer.parseInt(LocalDate.of(yearMonth.getYear(), yearMonth.getMonthValue(), 1)
                        .minusDays(1).format(dateTimeFormatter));
            }).collect(Collectors.toList());
            Integer propertyId = propertyDTO.getId();
            List<Integer> segmentIds = segments.stream().map(RevenueSegment::getId).collect(Collectors.toList());
            Map<Integer, Map<Integer, RevenueSegmentRevenueFuture>> existSegmentRevenueFutureByTheDate =
                    listExistData(propertyId, selectMonthEnds, segmentIds);

            long now = DateTimeUtils.nowTimestamp();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
            List<RevenueSegmentRevenueFuture> segmentRevenueFutures = Lists.newArrayListWithExpectedSize(segmentBOListByYearMonth.size() * segmentIds.size());

            if(existSegmentRevenueFutureByTheDate.isEmpty()){
                // 全部新增
                segmentBOListByYearMonth.forEach((yearMonthLong, segmentBOs)->{
                    YearMonth yearMonth = YearMonthUtils.getYearMonth(yearMonthLong.toString());
                    int date = Integer.parseInt(LocalDate.of(yearMonth.getYear(), yearMonth.getMonthValue(), 1)
                            .minusDays(1).format(dateTimeFormatter));

                    segmentRevenueFutures.addAll(initSegmentRevenueFutureBySegmentBOs(propertyDTO, date, segmentBOs, userId, now));
                });
            } else {
                segmentBOListByYearMonth.forEach((yearMonthLong, segmentBOs) -> {
                    YearMonth yearMonth = YearMonthUtils.getYearMonth(yearMonthLong.toString());
                    int date = Integer.parseInt(LocalDate.of(yearMonth.getYear(), yearMonth.getMonthValue(), 1)
                            .minusDays(1).format(dateTimeFormatter));

                    Map<Integer, RevenueSegmentRevenueFuture> existSegmentRevenueFutureByTheSegmentId = existSegmentRevenueFutureByTheDate.get(date);
                    if (Objects.isNull(existSegmentRevenueFutureByTheSegmentId)) {
                        segmentRevenueFutures.addAll(initSegmentRevenueFutureBySegmentBOs(propertyDTO, date, segmentBOs, userId, now));
                    } else {
                        // 一个月的 segmentBO 以 segment id 分组
                        Map<Integer, List<RoomsAndRevenueBO>> segmentBOListBySegmentId = segmentBOs.stream()
                                .flatMap(item -> item.getDetail().stream()).collect(Collectors.groupingBy(RoomsAndRevenueBO::getId));

                        segmentBOListBySegmentId.forEach((segmentId, value) -> {
                            Map<String, Object> roomsAndRevenue = statisticsRoomsAndRevenue(value);
                            Object totalRoomsObj = roomsAndRevenue.get(TOTAL_ROOMS);
                            Integer totalRooms = Objects.isNull(totalRoomsObj) ? null : Integer.parseInt(totalRoomsObj.toString());
                            Object totalRevenueObj = roomsAndRevenue.get(TOTAL_REVENUE);
                            BigDecimal totalRevenue = Objects.isNull(totalRevenueObj) ? null : new BigDecimal(totalRevenueObj.toString());

                            RevenueSegmentRevenueFuture existSegmentRevenueFuture = existSegmentRevenueFutureByTheSegmentId.get(segmentId);
                            if (Objects.isNull(existSegmentRevenueFuture)) {
                                segmentRevenueFutures.add(setSegmentRevenueFutureNextMonthData(propertyId, number, date, segmentId, totalRooms,
                                        totalRevenue, userId, now));
                            } else {
                                // 重置 next month 相关内容（rooms、revenue、adr）
                                existSegmentRevenueFuture.setTotalRoomsNextMonth(totalRooms).setUpdatedBy(userId).setUpdatedTime(now);
                                existSegmentRevenueFuture.setRevenueNextMonth(Objects.isNull(totalRevenue) ? null
                                                : totalRevenue.setScale(dataPrecision, roundingMode))
                                        .setUpdatedBy(userId).setUpdatedTime(now);
                                calculateNextMonthAdr(number, existSegmentRevenueFuture);

                                segmentRevenueFutures.add(existSegmentRevenueFuture);
                            }
                        });
                    }
                });
            }

            segmentRevenueFutures.sort(Comparator.comparing(RevenueSegmentRevenueFuture::getTheDate).thenComparing(RevenueSegmentRevenueFuture::getSegmentId));

            baseMapper.saveOrUpdateBatchForNextMonth(segmentRevenueFutures);
        }

        /**
         * 设置 segment revenue future next month 相关内容
         *
         * @param propertyId   资产 id
         * @param number       国际化
         * @param date         指定日期
         * @param segmentId    segment id
         * @param totalRooms   rooms
         * @param totalRevenue revenue
         * @param userId       操作人
         * @param now          操作时间
         * @return segment revenue future
         * @author yyt
         * @date 2024/5/22
         **/
        private RevenueSegmentRevenueFuture setSegmentRevenueFutureNextMonthData(Integer propertyId, InternationalizationNumberDTO number, int date,
                                                                    Integer segmentId, Integer totalRooms, BigDecimal totalRevenue,
                                                                    int userId, long now) {
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            RevenueSegmentRevenueFuture segmentRevenue = new RevenueSegmentRevenueFuture().setPropertyId(propertyId)
                    .setTheDate(date).setSegmentId(segmentId).setRevenueNextMonth(Objects.isNull(totalRevenue) ? null
                            : totalRevenue.setScale(dataPrecision, roundingMode))
                    .setTotalRoomsNextMonth(totalRooms).setUpdatedBy(userId).setUpdatedTime(now);

            // 计算 adr
            calculateNextMonthAdr(number, segmentRevenue);

            return segmentRevenue;
        }

        /**
         * 初始 segment revenue future
         *
         * @param propertyDTO propertyDTO
         * @param date        date
         * @param segmentBOs  segmentBOs
         * @param userId      userId
         * @param now         now
         * @return segment revenue future
         * @author yyt
         * @date 2024/5/22
         **/
        private List<RevenueSegmentRevenueFuture> initSegmentRevenueFutureBySegmentBOs(PropertyDTO propertyDTO, int date,
                                                                                       List<SegmentBO> segmentBOs, int userId, long now) {
            // 一个月的 segmentBO 以 segment id 分组、
            Map<Integer, List<RoomsAndRevenueBO>> segmentBOListBySegmentId = segmentBOs.stream().flatMap(item -> item.getDetail().stream())
                    .collect(Collectors.groupingBy(RoomsAndRevenueBO::getId));
            List<RevenueSegmentRevenueFuture> segmentRevenueFutures = Lists.newArrayListWithExpectedSize(segmentBOListBySegmentId.size());

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            // 统计 segment rooms、revenue、adr
            segmentBOListBySegmentId.forEach((segmentId, segmentBOList) -> {
                Map<String, Object> roomsAndRevenue = statisticsRoomsAndRevenue(segmentBOList);
                Object totalRoomsObj = roomsAndRevenue.get(TOTAL_ROOMS);
                Object totalRevenueObj = roomsAndRevenue.get(TOTAL_REVENUE);
                segmentRevenueFutures.add(setSegmentRevenueFutureNextMonthData(propertyDTO.getId(), number, date, segmentId,
                        Objects.isNull(totalRoomsObj) ? null : Integer.parseInt(totalRoomsObj.toString()),
                        Objects.isNull(totalRevenueObj) ? null : new BigDecimal(totalRevenueObj.toString()),
                        userId, now));
            });

            return segmentRevenueFutures;
        }

        /**
         * 统计 rooms、revenue
         *
         * @param segmentBOList segment bo
         * @return rooms、revenue
         * @author yyt
         * @date 2024/5/22
         **/
        private Map<String, Object> statisticsRoomsAndRevenue(List<RoomsAndRevenueBO> segmentBOList) {
            Integer totalRooms = null;
            BigDecimal totalRevenue = null;
            for (RoomsAndRevenueBO roomsAndRevenueBO : segmentBOList) {
                Integer rooms = roomsAndRevenueBO.getRooms();
                if (Objects.nonNull(rooms)) {
                    if (Objects.isNull(totalRooms)) {
                        totalRooms = BaseConstant.ZERO;
                    }
                    totalRooms += rooms;
                }

                BigDecimal revenue = roomsAndRevenueBO.getRevenue();
                if (Objects.nonNull(revenue)) {
                    if (Objects.isNull(totalRevenue)) {
                        totalRevenue = BigDecimal.ZERO;
                    }
                    totalRevenue = totalRevenue.add(revenue);
                }
            }

            Map<String, Object> result = Maps.newHashMapWithExpectedSize(2);
            if (Objects.nonNull(totalRooms)) {
                result.put(TOTAL_ROOMS, totalRooms);
            }
            if (Objects.nonNull(totalRooms)) {
                result.put(TOTAL_REVENUE, totalRevenue);
            }
            return result;
        }

        /**
         * 计算 adr next month
         *
         * @param number         国际化金额配置
         * @param segmentRevenue segment revenue
         * @author yyt
         * @date 2024/5/22
         */
        private void calculateNextMonthAdr(InternationalizationNumberDTO number, RevenueSegmentRevenueFuture segmentRevenue) {
            BigDecimal revenueNextMonth = segmentRevenue.getRevenueNextMonth();
            Integer roomsNextMonth = segmentRevenue.getTotalRoomsNextMonth();
            if (Objects.nonNull(revenueNextMonth) && Objects.nonNull(roomsNextMonth) && roomsNextMonth != BaseConstant.ZERO) {
                int dataPrecision = BaseConstant.DATA_PRECISION;
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
                // adr = revenue / rooms
                segmentRevenue.setAdrNextMonth(revenueNextMonth.divide(BigDecimal.valueOf(roomsNextMonth), dataPrecision, roundingMode));
            } else {
                segmentRevenue.setAdrNextMonth(null);
            }
        }

        /**
         * 保存 segment 信息
         *
         * @param propertyDTO 资产
         * @param segments    segment
         * @param segmentBO   segment bo
         * @param userId      操作人
         * @author yyt
         * @date 2026/1/12
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdateStatisticsSegment(PropertyDTO propertyDTO, List<RevenueSegment> segments, SegmentBO segmentBO, int userId) {
            // 查询数据范围（当天至月底）已存在的 segment revenue，进行 save or update
            Integer propertyId = propertyDTO.getId();
            List<Integer> dates = listDateRanges(propertyDTO);
            if (CollectionUtils.isEmpty(dates)) return;

            List<Integer> segmentIds = segments.stream().map(RevenueSegment::getId).collect(Collectors.toList());
            Map<Integer, Map<Integer, RevenueSegmentRevenueFuture>> existSegmentRevenueFutureByTheDate = listExistData(propertyId, dates, segmentIds);

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            long now = DateTimeUtils.nowTimestamp();
            int size = segments.size();
            List<RevenueSegmentRevenueFuture> segmentRevenueFutures = Lists.newArrayListWithExpectedSize(size);

            // 没有的新增、有的重置 rooms、revenue、adr
            if (MapUtils.isEmpty(existSegmentRevenueFutureByTheDate)) {
                for (Integer date : dates) {
                    segmentRevenueFutures.addAll(segmentBO.getDetail().stream()
                            .map(item-> initSegmentStatisticsRevenueFuture(propertyDTO, date, item, userId, now))
                            .toList());
                }
            } else {
                for (Integer date : dates) {
                    Map<Integer, RevenueSegmentRevenueFuture> existSegmentRevenueFutureBySegmentId = existSegmentRevenueFutureByTheDate.getOrDefault(date, Collections.emptyMap());
                    for (RoomsAndRevenueBO roomsAndRevenueBO : segmentBO.getDetail()) {
                        RevenueSegmentRevenueFuture existSegmentRevenueFuture = existSegmentRevenueFutureBySegmentId.get(roomsAndRevenueBO.getId());
                        if (Objects.isNull(existSegmentRevenueFuture)) {
                            segmentRevenueFutures.add(initSegmentStatisticsRevenueFuture(propertyDTO, date, roomsAndRevenueBO, userId, now));
                        } else {
                            // 重置 rooms、revenue、adr
                            Integer rooms = roomsAndRevenueBO.getRooms();
                            BigDecimal revenue = roomsAndRevenueBO.getRevenue();
                            existSegmentRevenueFuture.setTotalRoomsStatistics(rooms)
                                    .setRevenueStatistics(Objects.isNull(revenue) ? null : revenue.setScale(dataPrecision, roundingMode))
                                    .setUpdatedBy(userId)
                                    .setUpdatedTime(now);

                            // 计算 revenue + total rooms
                            calculateRevenueAndRooms(propertyDTO, existSegmentRevenueFuture);

                            // 重新计算 adr
                            calculateAdr(propertyDTO, existSegmentRevenueFuture);

                            segmentRevenueFutures.add(existSegmentRevenueFuture);
                        }
                    }
                }
            }

            if (CollectionUtils.isNotEmpty(segmentRevenueFutures)) {
                segmentRevenueFutures.sort(Comparator.comparing(RevenueSegmentRevenueFuture::getTheDate).thenComparing(RevenueSegmentRevenueFuture::getSegmentId));
                baseMapper.saveOrUpdateBatch(segmentRevenueFutures);
            }
        }

        /**
         * 计算 revenue + total rooms
         *
         * @param propertyDTO    资产
         * @param segmentRevenue segment revenue
         * @author yyt
         * @date 2026/1/12
         */
        private void calculateRevenueAndRooms(PropertyDTO propertyDTO, RevenueSegmentRevenueFuture segmentRevenue) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            BigDecimal revenue = null;
            BigDecimal revenueForecast = segmentRevenue.getRevenueForecast();
            if (Objects.nonNull(revenueForecast)) {
                revenue = revenueForecast;
            }
            BigDecimal revenueStatistics = segmentRevenue.getRevenueStatistics();
            if (Objects.nonNull(revenueStatistics)) {
                revenue = Objects.isNull(revenue) ? revenueStatistics : revenue.add(revenueStatistics);
            }
            segmentRevenue.setRevenue(Objects.isNull(revenue) ? null : revenue.setScale(dataPrecision, roundingMode));

            Integer totalRooms = null;
            Integer totalRoomsForecast = segmentRevenue.getTotalRoomsForecast();
            if (Objects.nonNull(totalRoomsForecast)) {
                totalRooms = totalRoomsForecast;
            }
            Integer totalRoomsStatistics = segmentRevenue.getTotalRoomsStatistics();
            if (Objects.nonNull(totalRoomsStatistics)) {
                totalRooms = Objects.isNull(totalRooms) ? totalRoomsStatistics : totalRooms + totalRoomsStatistics;
            }
            segmentRevenue.setTotalRooms(totalRooms);
        }

        /**
         * 汇总日期区间（明天-月底）
         *
         * @param propertyDTO 资产
         * @return 日期区间
         * @author yyt
         * @date 2026/1/12
         **/
        private List<Integer> listDateRanges(PropertyDTO propertyDTO) {
            // 若当天是月底，则return 空，不处理
            String timezone = propertyDTO.getTimezone();
            if (DateTimeUtils.isLastDayOfThisMonth(DateTimeUtils.dateNow(timezone).toLocalDate())) {
                return Collections.emptyList();
            }

            DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYYMMDD);
            List<Integer> dates = Lists.newArrayListWithExpectedSize(31);
            LocalDate start = DateTimeUtils.dateNow(timezone).plusDays(1).toLocalDate();
            LocalDate end = start.with(TemporalAdjusters.lastDayOfMonth());
            for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
                dates.add(Integer.parseInt(dateFormat.format(date)));
            }
            return dates;
        }

    }