# RevenueSegmentRevenueServiceImpl.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.RevenueSegment;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegmentRevenue;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueSegmentRevenueMapper;
    import cloud.wconnect.rateshop.pojo.bo.revenue.RoomsAndRevenueBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.SegmentBO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSegmentRevenueFutureService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSegmentRevenueService;
    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 org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    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.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-10-26
     */
    @Service
    @DS("rate_shop")
    public class RevenueSegmentRevenueServiceImpl extends ServiceImpl<RevenueSegmentRevenueMapper, RevenueSegmentRevenue> implements IRevenueSegmentRevenueService {

        @Autowired
        private IRevenueSegmentRevenueFutureService revenueSegmentRevenueFutureService;

        /**
         * 查询 segment revenue
         *
         * @param propertyId 资产 id
         * @param dates      日期
         * @param segmentIds segment id
         * @return segment revenue
         * @author yyt
         * @date 2023/10/30
         */
        private List<RevenueSegmentRevenue> list(Integer propertyId, List<Long> dates, List<Integer> segmentIds) {
            return list(new QueryWrapper<RevenueSegmentRevenue>().lambda().eq(RevenueSegmentRevenue::getPropertyId, propertyId)
                    .in(RevenueSegmentRevenue::getTheDate, dates).in(RevenueSegmentRevenue::getSegmentId, segmentIds));
        }

        /**
         * 查询 segment revenue
         *
         * @param propertyId 资产 id
         * @param dates      日期
         * @param segmentIds segment id
         * @return segment revenue
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        public Map<Integer, Map<Long, RevenueSegmentRevenue>> listMap(Integer propertyId, List<Long> dates, List<Integer> segmentIds) {
            return list(propertyId, dates, segmentIds).stream().collect(Collectors.groupingBy(RevenueSegmentRevenue::getSegmentId,
                            Collectors.toMap(RevenueSegmentRevenue::getTheDate, v -> v)));
        }

        /**
         * 保存 forecast segment 信息
         *
         * @param propertyDTO 资产
         * @param segments    segment
         * @param segmentBO   segment bo
         * @param userId      操作人
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdateForecastSegment(PropertyDTO propertyDTO, List<RevenueSegment> segments,
                                          SegmentBO segmentBO, int userId) {
            List<RoomsAndRevenueBO> detail = segmentBO.getDetail();
            if (CollectionUtils.isEmpty(detail)) {
                return;
            }

            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            String dateStr = segmentBO.getDate();
            List<Long> dates = Lists.newArrayList(LocalDate.parse(dateStr, dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond());
            Integer propertyId = propertyDTO.getId();
            Map<Integer, RevenueSegmentRevenue> existSegmentRevenueBySegmentId = list(propertyId, dates,
                    segments.stream().map(RevenueSegment::getId).collect(Collectors.toList())).stream()
                    .collect(Collectors.toMap(RevenueSegmentRevenue::getSegmentId, v -> v));

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

            long now = DateTimeUtils.nowTimestamp();
            List<RevenueSegmentRevenue> segmentRevenues = Lists.newArrayListWithExpectedSize(detail.size());
            long date = LocalDate.parse(dateStr, dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond();

            // 没有的新增、有的重置 rooms、revenue
            if (MapUtils.isEmpty(existSegmentRevenueBySegmentId)) {
                segmentRevenues.addAll(detail.stream().map(item ->
                        initForecastSegmentRevenue(propertyDTO, date, item, userId, now)).toList());
            } else {
                for (RoomsAndRevenueBO roomsAndRevenueBO : detail) {
                    RevenueSegmentRevenue existSegmentRevenue = existSegmentRevenueBySegmentId.get(roomsAndRevenueBO.getId());
                    if (Objects.isNull(existSegmentRevenue)) {
                        segmentRevenues.add(initForecastSegmentRevenue(propertyDTO, date, roomsAndRevenueBO, userId, now));
                    } else {
                        // 重置 rooms、revenue
                        Integer rooms = roomsAndRevenueBO.getRooms();
                        existSegmentRevenue.setTotalRoomsForecast(rooms).setUpdatedBy(userId).setUpdatedTime(now);
                        BigDecimal revenue = roomsAndRevenueBO.getRevenue();
                        existSegmentRevenue.setRevenueForecast(Objects.isNull(revenue) ? null : revenue.setScale(dataPrecision, roundingMode))
                                .setUpdatedBy(userId).setUpdatedTime(now);

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

                        // 重新计算 adr
                        calculateAdr(propertyDTO, existSegmentRevenue);
                        segmentRevenues.add(existSegmentRevenue);
                    }
                }
            }
            segmentRevenues.sort(Comparator.comparing(RevenueSegmentRevenue::getTheDate)
                    .thenComparing(RevenueSegmentRevenue::getSegmentId));
            baseMapper.saveOrUpdateBatchForecastSegment(segmentRevenues);
        }

        /**
         * 保存 statistics segment 信息
         *
         * @param propertyDTO 资产
         * @param segments    segment
         * @param segmentBO   segment bo
         * @param userId      操作人
         * @author yyt
         * @date 2024/2/28
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdateStatisticsSegment(PropertyDTO propertyDTO, List<RevenueSegment> segments,
                                                SegmentBO segmentBO, int userId) {
            List<RoomsAndRevenueBO> detail = segmentBO.getDetail();
            if (CollectionUtils.isEmpty(detail)) {
                return;
            }

            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            List<Long> dates = Lists.newArrayList(LocalDate.parse(segmentBO.getDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond());
            Integer propertyId = propertyDTO.getId();
            Map<Integer, RevenueSegmentRevenue> existSegmentRevenueBySegmentId = list(propertyId, dates,
                    segments.stream().map(RevenueSegment::getId).collect(Collectors.toList())).stream()
                    .collect(Collectors.toMap(RevenueSegmentRevenue::getSegmentId, v -> v));

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

            long now = DateTimeUtils.nowTimestamp();
            List<RevenueSegmentRevenue> segmentRevenues = Lists.newArrayListWithExpectedSize(detail.size());
            long date = LocalDate.parse(segmentBO.getDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond();

            // 没有的新增、有的重置 rooms、revenue
            if (MapUtils.isEmpty(existSegmentRevenueBySegmentId)) {
                segmentRevenues.addAll(detail.stream().map(item ->
                        initStatisticsSegmentRevenue(propertyDTO, date, item, userId, now)).toList());
            } else {
                for (RoomsAndRevenueBO roomsAndRevenueBO : detail) {
                    RevenueSegmentRevenue existSegmentRevenue = existSegmentRevenueBySegmentId.get(roomsAndRevenueBO.getId());
                    if (Objects.isNull(existSegmentRevenue)) {
                        segmentRevenues.add(initStatisticsSegmentRevenue(propertyDTO, date, roomsAndRevenueBO, userId, now));
                    } else {
                        // 重置 rooms、revenue
                        Integer rooms = roomsAndRevenueBO.getRooms();
                        existSegmentRevenue.setTotalRoomsStatistics(rooms).setUpdatedBy(userId).setUpdatedTime(now);
                        BigDecimal revenue = roomsAndRevenueBO.getRevenue();
                        existSegmentRevenue.setRevenueStatistics(Objects.isNull(revenue) ? null : revenue.setScale(dataPrecision, roundingMode))
                                .setUpdatedBy(userId).setUpdatedTime(now);

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

                        // 重新计算 adr
                        calculateAdr(propertyDTO, existSegmentRevenue);
                        segmentRevenues.add(existSegmentRevenue);
                    }
                }
            }
            segmentRevenues.sort(Comparator.comparing(RevenueSegmentRevenue::getTheDate).thenComparing(RevenueSegmentRevenue::getSegmentId));
            baseMapper.saveOrUpdateBatchStatisticsSegment(segmentRevenues);

            // 若导入数据日期是当前日期，则更新 statistics 数据至未来数据表
            String timezone = propertyDTO.getTimezone();
            if (DateTimeUtils.localDateToStr(DateTimeUtils.dateNow(timezone).toLocalDate(), BaseConstant.DATE_FORMAT_YYYY_MM_DD).equals(segmentBO.getDate())) {
                revenueSegmentRevenueFutureService.saveOrUpdateStatisticsSegment(propertyDTO, segments, segmentBO, userId);
            }
        }

        /**
         * 初始 forecast 报告 segment 信息
         *
         * @param propertyDTO 资产
         * @param date        指定日期
         * @param item        RoomsAndRevenueBO
         * @param userId      操作人
         * @param now         当前时间
         * @return segment 信息
         * @author yyt
         * @date 2023/10/30
         */
        private RevenueSegmentRevenue initForecastSegmentRevenue(PropertyDTO propertyDTO, long date, RoomsAndRevenueBO item, int userId, long now) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            RevenueSegmentRevenue segmentRevenue = new RevenueSegmentRevenue().setPropertyId(propertyDTO.getId()).setTheDate(date)
                    .setSegmentId(item.getId()).setRevenueForecast(Objects.isNull(item.getRevenue()) ? null
                            : item.getRevenue().setScale(dataPrecision, roundingMode))
                    .setTotalRoomsForecast(item.getRooms()).setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now);
            // 计算 revenue + total rooms
            calculateRevenueAndRooms(propertyDTO, segmentRevenue);

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

        /**
         * 初始 statistics 报告 segment 信息
         *
         * @param propertyDTO 资产
         * @param date        指定日期
         * @param item        RoomsAndRevenueBO
         * @param userId      操作人
         * @param now         当前时间
         * @return segment 信息
         * @author yyt
         * @date 2023/10/30
         */
        private RevenueSegmentRevenue initStatisticsSegmentRevenue(PropertyDTO propertyDTO, long date, RoomsAndRevenueBO item, int userId, long now) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            RevenueSegmentRevenue segmentRevenue = new RevenueSegmentRevenue().setPropertyId(propertyDTO.getId()).setTheDate(date)
                    .setSegmentId(item.getId()).setRevenueStatistics(Objects.isNull(item.getRevenue()) ? null
                            : item.getRevenue().setScale(dataPrecision, roundingMode))
                    .setTotalRoomsStatistics(item.getRooms()).setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now);
            // 计算 revenue + total rooms
            calculateRevenueAndRooms(propertyDTO, segmentRevenue);

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

        /**
         * 计算 revenue + total rooms
         *
         * @param propertyDTO    资产
         * @param segmentRevenue segment revenue
         * @author yyt
         * @date 2024/2/28
         */
        private void calculateRevenueAndRooms(PropertyDTO propertyDTO, RevenueSegmentRevenue 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);
        }

        /**
         * 计算 adr
         *
         * @param propertyDTO    资产
         * @param segmentRevenue segment revenue
         * @author yyt
         * @date 2023/10/30
         */
        private void calculateAdr(PropertyDTO propertyDTO, RevenueSegmentRevenue 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);
            }
        }

        /**
         * 计算 adr next month
         *
         * @param number         国际化金额配置
         * @param segmentRevenue segment revenue
         * @author yyt
         * @date 2023/10/30
         */
        private void calculateNextMonthAdr(InternationalizationNumberDTO number, RevenueSegmentRevenue 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 revenue
         *
         * @param propertyDTO 资产
         * @param segmentBO   segment bo
         * @param userId      操作人
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOrUpdateSegmentForNextMonth(PropertyDTO propertyDTO, SegmentBO segmentBO, int userId) {
            List<RoomsAndRevenueBO> detail = segmentBO.getDetail();
            if (CollectionUtils.isEmpty(detail)) {
                return;
            }

            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            Integer propertyId = propertyDTO.getId();
            long now = DateTimeUtils.nowTimestamp();

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

            long date = LocalDate.parse(segmentBO.getDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond();
            baseMapper.saveOrUpdateBatchForNextMonth(detail.stream().map(item -> {
                RevenueSegmentRevenue segmentRevenue = new RevenueSegmentRevenue().setPropertyId(propertyId)
                        .setTheDate(date).setSegmentId(item.getId())
                        .setRevenueNextMonth(Objects.isNull(item.getRevenue()) ? null
                                : item.getRevenue().setScale(dataPrecision, roundingMode))
                        .setTotalRoomsNextMonth(item.getRooms()).setCreatedBy(userId).setCreatedTime(now)
                        .setUpdatedBy(userId).setUpdatedTime(now);
                // 计算 adr next month
                calculateNextMonthAdr(number, segmentRevenue);
                return segmentRevenue;
            }).sorted(Comparator.comparing(RevenueSegmentRevenue::getSegmentId)).toList());
        }

        /**
         * 批量修改
         *
         * @param updateSegmentRevenues segment revenue
         * @author yyt
         * @date 2023/12/6
         */
        @Override
        public void updateBatch(List<RevenueSegmentRevenue> updateSegmentRevenues) {
            baseMapper.updateBatch(updateSegmentRevenues);
        }

    }