# WeeklyMonthlyRevenueVersionDataServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.rateshop.entity.weeklymonthlyrevenue.WeeklyMonthlyRevenueRoom;
    import cloud.wconnect.rateshop.entity.weeklymonthlyrevenue.WeeklyMonthlyRevenueSegment;
    import cloud.wconnect.rateshop.entity.weeklymonthlyrevenue.WeeklyMonthlyRevenueVersionData;
    import cloud.wconnect.rateshop.mapper.weeklymonthlyrevenue.WeeklyMonthlyRevenueVersionDataMapper;
    import cloud.wconnect.rateshop.pojo.dto.weeklymonthlyrevenue.versiondata.*;
    import cloud.wconnect.rateshop.pojo.query.weeklymonthlyrevenue.WMRevenueVersionDataQuery;
    import cloud.wconnect.rateshop.pojo.vo.monthlyrevenue.*;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueRoomService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueSegmentService;
    import cloud.wconnect.rateshop.service.weeklymonthlyrevenue.IWeeklyMonthlyRevenueVersionDataService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Maps;
    import jakarta.annotation.Resource;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.compress.utils.Lists;
    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.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * Weekly Monthly Revenue 版本数据 服务实现类
     * </p>
     *
     * @author zc
     * @since 2025-05-21
     */
    @Service
    @DS("rate_shop")
    public class WeeklyMonthlyRevenueVersionDataServiceImpl extends ServiceImpl<WeeklyMonthlyRevenueVersionDataMapper, WeeklyMonthlyRevenueVersionData> implements IWeeklyMonthlyRevenueVersionDataService {
        @Resource
        private IWeeklyMonthlyRevenueRoomService roomService;
        @Resource
        private IWeeklyMonthlyRevenueSegmentService segmentService;
        @Resource
        private PropertyI18nRedisService propertyI18nRedisService;
        @Resource
        private ObjectMapper objectMapper;

        /**
         * 根据日期范围获取版本数据
         *
         * @param propertyId 资产 id
         * @param startDate  开始日期（yyyyMMdd）
         * @param endDate    结束日期（yyyyMMdd）
         * @return 版本数据列表
         * @author zc
         * @date 2025/5/22
         */
        @Override
        public List<WeeklyMonthlyRevenueVersionData> listByDateRange(int propertyId, long startDate, long endDate) {
            return list(new LambdaQueryWrapper<WeeklyMonthlyRevenueVersionData>()
                    .between(WeeklyMonthlyRevenueVersionData::getDate, startDate, endDate)
                    .eq(WeeklyMonthlyRevenueVersionData::getPropertyId, propertyId));
        }

        /**
         * 批量插入或更新版本数据
         *
         * @param versionDataList 版本数据列表
         * @author zc
         * @date 2025/5/22
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void insertOrUpdateBatchByUnique(List<WeeklyMonthlyRevenueVersionData> versionDataList) {
            if (CollectionUtils.isEmpty(versionDataList)) {
                return;
            }
            // 按唯一索引字段排序，避免死锁
            versionDataList.sort(Comparator.comparing(WeeklyMonthlyRevenueVersionData::getPropertyId)
                    .thenComparing(WeeklyMonthlyRevenueVersionData::getDate));

            baseMapper.insertOrUpdateBatchByUnique(versionDataList);
        }

        /**
         * 根据资产 id 和日期范围获取版本数据
         *
         * @param propertyId 资产 id
         * @param query      查询条件
         * @return WMRevenueVersionVO 集合
         * @author zc
         * @date 2025/5/22
         */
        @Override
        public ListPagedDTO<WMRevenueVersionVO> list(int propertyId, WMRevenueVersionDataQuery query) {
            if (Objects.isNull(query.getStartDate()) || Objects.isNull(query.getEndDate())) {
                return new ListPagedDTO<>();
            }
            if (CollectionUtils.isEmpty(query.getDataAsOf()) &&
                    (Objects.isNull(query.getDataAsOfStartDate()) || Objects.isNull(query.getDataAsOfEndDate()))) {
                return new ListPagedDTO<>();
            }

            Page<WeeklyMonthlyRevenueVersionData> page = page(new Page<>(query.getPage(), query.getSize()),
                    new LambdaQueryWrapper<WeeklyMonthlyRevenueVersionData>()
                            .between(WeeklyMonthlyRevenueVersionData::getDate, query.getStartDate(), query.getEndDate())
                            .eq(WeeklyMonthlyRevenueVersionData::getPropertyId, propertyId)
                            .orderByAsc(WeeklyMonthlyRevenueVersionData::getDate));
            if (CollectionUtils.isEmpty(page.getRecords())) {
                return new ListPagedDTO<>();
            }

            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                    .getInternationalization().getNumber().getCalculationRule());

            // 获取实际数据，当前日期之前的历史数据属于实际数据
            long yesterday = Long.parseLong(DateTimeUtils.localDateToStr(LocalDate.now(ZoneId.of(propertyDTO.getTimezone()))
                    .minusDays(1), BaseConstant.DATE_FORMAT_YYYYMMDD));
            Map<Long, WeeklyMonthlyRevenueRoom> date2MtdOverall = Maps.newHashMap();
            Map<Long, WeeklyMonthlyRevenueSegment> date2MtdSegment = Maps.newHashMap();
            if (query.getStartDate() <= yesterday) {
                date2MtdOverall = roomService.listByDateRange(propertyId, query.getStartDate(), yesterday).stream()
                        .collect(Collectors.toMap(WeeklyMonthlyRevenueRoom::getTheDate, Function.identity()));

                date2MtdSegment = segmentService.listByDateRange(propertyId, query.getStartDate(), yesterday).stream()
                        .collect(Collectors.toMap(WeeklyMonthlyRevenueSegment::getTheDate, Function.identity()));
            }

            // 获取版本数据
            Map<Long, WeeklyMonthlyRevenueRoom> finalDate2MtdOverall = date2MtdOverall;
            Map<Long, WeeklyMonthlyRevenueSegment> finalDate2MtdSegment = date2MtdSegment;
            List<WMRevenueVersionVO> list = page.getRecords().stream().map(item -> {
                List<WMRevenueVersionDataDTO> versionDataDTOList = JsonUtils.convertStringToList(objectMapper,
                        item.getVersionData(), WMRevenueVersionDataDTO.class);

                Map<Long, WMRevenueVersionDataDTO> version2VersionDataDTO = versionDataDTOList.stream()
                        .collect(Collectors.toMap(WMRevenueVersionDataDTO::getVersion, Function.identity()));

                List<WMRevenueVersionDataDTO> queryVersionDataDTOList = Lists.newArrayList();
                if (CollectionUtils.isNotEmpty(query.getDataAsOf())) {
                    for (long version : query.getDataAsOf()) {
                        WMRevenueVersionDataDTO versionDataDTO = version2VersionDataDTO.getOrDefault(version, new WMRevenueVersionDataDTO());
                        if (Objects.isNull(versionDataDTO.getVersion())) {
                            versionDataDTO.setVersion(version);
                        }
                        queryVersionDataDTOList.add(versionDataDTO);
                    }
                } else {
                    LocalDate dataAsOfStartLocalDate = DateTimeUtils.strToLocalDate(query.getDataAsOfStartDate().toString(),
                            BaseConstant.DATE_FORMAT_YYYYMMDD);
                    LocalDate dataAsOfEndLocalDate = DateTimeUtils.strToLocalDate(query.getDataAsOfEndDate().toString(),
                            BaseConstant.DATE_FORMAT_YYYYMMDD);
                    for (LocalDate versionLocalDate = dataAsOfStartLocalDate; !versionLocalDate.isAfter(dataAsOfEndLocalDate);
                         versionLocalDate = versionLocalDate.plusDays(1)) {
                        long version = Long.parseLong(DateTimeUtils.localDateToStr(versionLocalDate, BaseConstant.DATE_FORMAT_YYYYMMDD));
                        WMRevenueVersionDataDTO versionDataDTO = version2VersionDataDTO.getOrDefault(version, new WMRevenueVersionDataDTO());
                        if (Objects.isNull(versionDataDTO.getVersion())) {
                            versionDataDTO.setVersion(version);
                        }
                        queryVersionDataDTOList.add(versionDataDTO);
                    }
                }

                List<WMRevenueVersionDataVO> versionDataVOList = new ArrayList<>(queryVersionDataDTOList.stream()
                        .map(versionData -> {
                            WMRevenueVersionDataVO versionDataVO = new WMRevenueVersionDataVO();
                            versionDataVO.setVersionDate(versionData.getVersion());
                            versionDataVO.setOverall(generateOverallVO(roundingMode, versionData.getOverall()));
                            versionDataVO.setGroup(generateGroupVO(roundingMode, versionData.getGroup()));
                            versionDataVO.setContract(generateContractVO(roundingMode, versionData.getContract()));
                            versionDataVO.setTransients(generateTransientVO(roundingMode, versionData.getTransients()));
                            return versionDataVO;
                        }).toList());

                // 添加实际数据
                versionDataVOList.add(generateActual(item.getDate(), roundingMode, finalDate2MtdOverall, finalDate2MtdSegment));

                WMRevenueVersionVO versionVO = new WMRevenueVersionVO();
                versionVO.setDate(item.getDate());
                versionVO.setList(versionDataVOList);
                return versionVO;
            }).toList();
            return new ListPagedDTO<>(page.getTotal(), list);
        }

        /**
         * 生成实际数据
         *
         * @param date            日期（yyyyMMdd）
         * @param roundingMode    舍入模式
         * @param date2MtdOverall MTD Overall
         * @param date2MtdSegment MTD Segment
         * @return WMRevenueVersionDataVO 实际数据
         * @author zc
         * @date 2025/5/23
         */
        private WMRevenueVersionDataVO generateActual(long date, RoundingMode roundingMode,
                                                      Map<Long, WeeklyMonthlyRevenueRoom> date2MtdOverall,
                                                      Map<Long, WeeklyMonthlyRevenueSegment> date2MtdSegment) {
            WeeklyMonthlyRevenueRoom overall = date2MtdOverall.getOrDefault(date, new WeeklyMonthlyRevenueRoom());
            WMRevenueVersionOverallVO overallVO = new WMRevenueVersionOverallVO();
            overallVO.setRoomsSold(overall.getRoomsSold());
            overallVO.setAdr(overall.getAdr());
            overallVO.setRoomRevenue(overall.getRoomRevenue());
            overallVO.setOoo(overall.getOoo());
            overallVO.setComplimentaryRooms(overall.getComplimentaryRooms());
            overallVO.setNoShowRooms(overall.getNoShowRooms());
            if (Objects.nonNull(overall.getRoomsSold()) && Objects.nonNull(overall.getPhysicalCapacity()) && overall.getPhysicalCapacity() != 0) {
                overallVO.setPcOcc(new BigDecimal(overall.getRoomsSold().toString()).multiply(new BigDecimal("100"))
                        .divide(new BigDecimal(overall.getPhysicalCapacity().toString()), BaseConstant.DATA_PRECISION, roundingMode));
            }
            if (Objects.nonNull(overall.getPhysicalCapacity())) {
                overallVO.setSellableCapacity(overall.getPhysicalCapacity() - (Objects.isNull(overall.getOoo()) ? 0 : overall.getOoo()));
            }
            Integer sellableCapacity = overallVO.getSellableCapacity();
            if (Objects.nonNull(overall.getRoomsSold()) && Objects.nonNull(sellableCapacity) && sellableCapacity != 0) {
                overallVO.setScOcc(new BigDecimal(overall.getRoomsSold().toString()).multiply(new BigDecimal("100"))
                        .divide(new BigDecimal(sellableCapacity.toString()), BaseConstant.DATA_PRECISION, roundingMode));
            }
            if (Objects.nonNull(overall.getRoomRevenue()) && Objects.nonNull(overall.getPhysicalCapacity()) && overall.getPhysicalCapacity() != 0) {
                overallVO.setRevPar(overall.getRoomRevenue().divide(new BigDecimal(overall.getPhysicalCapacity().toString()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }
            if (Objects.nonNull(sellableCapacity)) {
                overallVO.setRoomLeftToSell(sellableCapacity - (Objects.isNull(overall.getRoomsSold()) ? 0 : overall.getRoomsSold()));
            }
            overallVO.setPhysicalCapacity(overall.getPhysicalCapacity());
            if (Objects.nonNull(overall.getRoomsSold()) || Objects.nonNull(overall.getNoShowRooms())) {
                overallVO.setOversold((Objects.isNull(overall.getRoomsSold()) ? 0 : overall.getRoomsSold()) +
                        (Objects.isNull(overall.getNoShowRooms()) ? 0 : overall.getNoShowRooms()) -
                        (Objects.isNull(sellableCapacity) ? 0 : sellableCapacity));
            }

            WeeklyMonthlyRevenueSegment segment = date2MtdSegment.getOrDefault(date, new WeeklyMonthlyRevenueSegment());

            WMRevenueVersionGroupVO groupVO = new WMRevenueVersionGroupVO();
            groupVO.setContracted(segment.getGroupContracted());
            groupVO.setSold(segment.getGroupSold());
            groupVO.setRevenue(segment.getGroupRevenue());
            if (Objects.nonNull(segment.getGroupRevenue()) && Objects.nonNull(segment.getGroupSold())
                    && segment.getGroupSold() != 0) {
                groupVO.setAdr(segment.getGroupRevenue().divide(new BigDecimal(segment.getGroupSold()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }

            WMRevenueVersionContractVO contractVO = new WMRevenueVersionContractVO();
            contractVO.setSold(segment.getContractSold());
            contractVO.setRevenue(segment.getContractRevenue());
            if (Objects.nonNull(segment.getContractRevenue()) && Objects.nonNull(segment.getContractSold())
                    && segment.getContractSold() != 0) {
                contractVO.setAdr(segment.getContractRevenue().divide(new BigDecimal(segment.getContractSold()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }

            WMRevenueVersionTransientVO transientVO = new WMRevenueVersionTransientVO();
            transientVO.setSold(segment.getTransientSold());
            transientVO.setRevenue(segment.getTransientRevenue());
            if (Objects.nonNull(segment.getTransientRevenue()) && Objects.nonNull(segment.getTransientSold())
                    && segment.getTransientSold() != 0) {
                transientVO.setAdr(segment.getTransientRevenue().divide(new BigDecimal(segment.getTransientSold()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }

            WMRevenueVersionDataVO actual = new WMRevenueVersionDataVO();
            actual.setOverall(overallVO);
            actual.setGroup(groupVO);
            actual.setContract(contractVO);
            actual.setTransients(transientVO);
            return actual;
        }

        /**
         * 生成 transientVO
         *
         * @param roundingMode 舍入模式
         * @param transientDTO transientDTO
         * @return WMRevenueVersionTransientVO
         * @author zc
         * @date 2025/5/23
         */
        private WMRevenueVersionTransientVO generateTransientVO(RoundingMode roundingMode, WMRevenueVersionDataTransientDTO transientDTO) {
            WMRevenueVersionTransientVO transientVO = new WMRevenueVersionTransientVO();
            transientVO.setSold(transientDTO.getSold());
            if (Objects.nonNull(transientDTO.getRevenue()) && Objects.nonNull(transientDTO.getSold()) && transientDTO.getSold() != 0) {
                transientVO.setAdr(transientDTO.getRevenue().divide(new BigDecimal(transientDTO.getSold()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }
            transientVO.setRevenue(transientDTO.getRevenue());
            return transientVO;
        }

        /**
         * 生成 contractVO
         *
         * @param roundingMode 舍入模式
         * @param contractDTO  contractDTO
         * @return WMRevenueVersionContractVO
         * @author zc
         * @date 2025/5/23
         */
        private WMRevenueVersionContractVO generateContractVO(RoundingMode roundingMode, WMRevenueVersionDataContractDTO contractDTO) {
            WMRevenueVersionContractVO contractVO = new WMRevenueVersionContractVO();
            contractVO.setSold(contractDTO.getSold());
            if (Objects.nonNull(contractDTO.getRevenue()) && Objects.nonNull(contractDTO.getSold()) && contractDTO.getSold() != 0) {
                contractVO.setAdr(contractDTO.getRevenue().divide(new BigDecimal(contractDTO.getSold()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }
            contractVO.setRevenue(contractDTO.getRevenue());
            return contractVO;
        }

        /**
         * 生成 groupVO
         *
         * @param roundingMode 舍入模式
         * @param groupDTO     groupDTO
         * @return WMRevenueVersionGroupVO
         * @author zc
         * @date 2025/5/23
         */
        private WMRevenueVersionGroupVO generateGroupVO(RoundingMode roundingMode, WMRevenueVersionDataGroupDTO groupDTO) {
            WMRevenueVersionGroupVO groupVO = new WMRevenueVersionGroupVO();
            groupVO.setContracted(groupDTO.getContracted());
            groupVO.setSold(groupDTO.getSold());
            if (Objects.nonNull(groupDTO.getRevenue()) && Objects.nonNull(groupDTO.getSold()) && groupDTO.getSold() != 0) {
                groupVO.setAdr(groupDTO.getRevenue().divide(new BigDecimal(groupDTO.getSold()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }
            groupVO.setRevenue(groupDTO.getRevenue());
            return groupVO;
        }

        /**
         * 生成 overallVO
         *
         * @param roundingMode 舍入模式
         * @param overallDTO   overallDTO
         * @return WMRevenueVersionOverallVO
         * @author zc
         * @date 2025/5/23
         */
        private WMRevenueVersionOverallVO generateOverallVO(RoundingMode roundingMode, WmRevenueVersionDataOverallDTO overallDTO) {
            WMRevenueVersionOverallVO overallVO = new WMRevenueVersionOverallVO();

            Integer totalOcc = overallDTO.getTotalOcc();
            Integer inventoryRooms = overallDTO.getInventoryRooms();
            if (Objects.nonNull(totalOcc) && Objects.nonNull(inventoryRooms) && inventoryRooms != 0) {
                overallVO.setPcOcc(new BigDecimal(totalOcc.toString()).multiply(new BigDecimal("100"))
                        .divide(new BigDecimal(inventoryRooms.toString()), BaseConstant.DATA_PRECISION, roundingMode));
            }

            Integer sellInventoryRooms = overallDTO.getSellInventoryRooms();
            if (Objects.nonNull(totalOcc) && Objects.nonNull(sellInventoryRooms) && sellInventoryRooms != 0) {
                overallVO.setScOcc(new BigDecimal(totalOcc.toString()).multiply(new BigDecimal("100"))
                        .divide(new BigDecimal(sellInventoryRooms.toString()), BaseConstant.DATA_PRECISION, roundingMode));
            }
            overallVO.setRoomsSold(totalOcc);
            overallVO.setAdr(overallDTO.getAverageRate());

            if (Objects.nonNull(overallDTO.getRoomRevenue()) && Objects.nonNull(inventoryRooms) && inventoryRooms != 0) {
                overallVO.setRevPar(overallDTO.getRoomRevenue().divide(new BigDecimal(inventoryRooms.toString()),
                        BaseConstant.DATA_PRECISION, roundingMode));
            }
            overallVO.setRoomRevenue(overallDTO.getRoomRevenue());
            overallVO.setPhysicalCapacity(inventoryRooms);
            overallVO.setSellableCapacity(sellInventoryRooms);
            overallVO.setOoo(overallDTO.getOooRooms());
            overallVO.setComplimentaryRooms(overallDTO.getComplimentaryRooms());
            if (Objects.nonNull(sellInventoryRooms)) {
                overallVO.setRoomLeftToSell(sellInventoryRooms - (Objects.isNull(totalOcc) ? 0 : totalOcc));
            }
            overallVO.setNoShowRooms(overallDTO.getNoShowRooms());
            if (Objects.nonNull(totalOcc) || Objects.nonNull(overallDTO.getNoShowRooms())) {
                overallVO.setOversold((Objects.isNull(totalOcc) ? 0 : totalOcc) +
                        (Objects.isNull(overallDTO.getNoShowRooms()) ? 0 : overallDTO.getNoShowRooms()) -
                        (Objects.isNull(sellInventoryRooms) ? 0 : sellInventoryRooms));
            }
            return overallVO;
        }
    }