# RevenueTransientGroupServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.SystemPlatformEnum;
    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.cons.revenue.RevenueCons;
    import cloud.wconnect.rateshop.entity.revenue.RevenueMarketCode;
    import cloud.wconnect.rateshop.entity.revenue.RevenueMarketCodeRooms;
    import cloud.wconnect.rateshop.entity.revenue.RevenueTransientGroup;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueTransientGroupMapper;
    import cloud.wconnect.rateshop.pojo.bo.revenue.TransientGroupAdrsBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.TransientGroupsBO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.TransientAndGroupDTO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueMarketCodeRoomsService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueMarketCodeService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueTransientGroupService;
    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.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-10-26
     */
    @Service
    @DS("rate_shop")
    public class RevenueTransientGroupServiceImpl extends ServiceImpl<RevenueTransientGroupMapper, RevenueTransientGroup> implements IRevenueTransientGroupService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IRevenueMarketCodeService revenueMarketCodeService;
        @Autowired
        private IRevenueMarketCodeRoomsService revenueMarketCodeRoomsService;

        /**
         * 获取 Transient And Group
         *
         * @param propertyDTO 资产
         * @param targetDate  目标日期
         * @param yesterday   目标日期的昨天
         * @return Transient And Group
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        public List<TransientAndGroupDTO> listTransientAndGroupDTO(PropertyDTO propertyDTO, Long targetDate, Long yesterday) {
            // 查询当前日期、昨天 对应的未来 14 天
            Integer propertyId = propertyDTO.getId();
            ZonedDateTime currentDay = DateTimeUtils.toZonedDateTime(targetDate, propertyDTO.getTimezone());
            ZonedDateTime nextDayDate = currentDay.plusDays(13);
            List<RevenueTransientGroup> transientGroups = list(new QueryWrapper<RevenueTransientGroup>().lambda()
                    .eq(RevenueTransientGroup::getPropertyId, propertyId)
                    .in(RevenueTransientGroup::getTheDate, Lists.newArrayList(targetDate, yesterday))
                    .between(RevenueTransientGroup::getTargetDate, targetDate, nextDayDate.toEpochSecond()));

            // 按照日期进行分组
            Map<Long, Map<Long, RevenueTransientGroup>> transientGroupByTheDate = transientGroups.stream()
                    .collect(Collectors.groupingBy(RevenueTransientGroup::getTheDate, Collectors.toMap(RevenueTransientGroup::getTargetDate, v -> v)));
            Map<Long, RevenueTransientGroup> transientGroupByTargetDate = transientGroupByTheDate.getOrDefault(targetDate, Collections.emptyMap());
            Map<Long, RevenueTransientGroup> transientGroupByYesterday = transientGroupByTheDate.getOrDefault(yesterday, Collections.emptyMap());

            // 查询 当前日期、昨天 对应的 market code，及 market code 对应计算规则（加 、减）
            Map<Integer, Map<Integer, Integer>> calculationRuleByType = Collections.emptyMap();
            List<RevenueMarketCodeRooms> marketCodeRooms = revenueMarketCodeRoomsService.list(new QueryWrapper<RevenueMarketCodeRooms>().lambda()
                    .eq(RevenueMarketCodeRooms::getPropertyId, propertyId)
                    .in(RevenueMarketCodeRooms::getTheDate, Lists.newArrayList(targetDate, yesterday)));
            Map<Long, Map<Long, List<RevenueMarketCodeRooms>>> marketCodeRoomsByDate = marketCodeRooms.stream()
                    .collect(Collectors.groupingBy(RevenueMarketCodeRooms::getTheDate,
                            Collectors.groupingBy(RevenueMarketCodeRooms::getTargetDate)));
            Map<Long, List<RevenueMarketCodeRooms>> marketCodeRoomsByTargetDate = marketCodeRoomsByDate.getOrDefault(targetDate, Collections.emptyMap());
            Map<Long, List<RevenueMarketCodeRooms>> marketCodeRoomsByYesterday = marketCodeRoomsByDate.getOrDefault(yesterday, Collections.emptyMap());
            if (CollectionUtils.isNotEmpty(marketCodeRooms)) {
                calculationRuleByType = revenueMarketCodeService.listByIds(marketCodeRooms.stream().map(RevenueMarketCodeRooms::getMarketCodeId)
                                .collect(Collectors.toSet())).stream().collect(Collectors.groupingBy(RevenueMarketCode::getType,
                                Collectors.toMap(RevenueMarketCode::getId, RevenueMarketCode::getCalculationRule)));
            }

            List<TransientAndGroupDTO> transientAndGroupDTOList = Lists.newArrayListWithExpectedSize(14);
            for (ZonedDateTime date = currentDay; !date.isAfter(nextDayDate); date = date.plusDays(1)) {
                long currentDate = date.toEpochSecond();
                RevenueTransientGroup transientGroup = transientGroupByTargetDate.getOrDefault(currentDate,
                        new RevenueTransientGroup().setTheDate(targetDate).setTargetDate(currentDate));
                TransientAndGroupDTO transientAndGroupDTO = beanMapper.map(transientGroup, TransientAndGroupDTO.class);

                // 设置 Transient Pick-up from last report：（当日散客预定数 +/- 当日market code 房间数）-（昨日散客预定数 +/- 昨日mark code 房间数）
                RevenueTransientGroup yesterdayTransientGroup = transientGroupByYesterday.getOrDefault(currentDate, new RevenueTransientGroup());
                transientAndGroupDTO.setTransientPickup(calculationTransientPickup(currentDate, transientGroup.getTransientRoomNum(),
                        yesterdayTransientGroup.getTransientRoomNum(), calculationRuleByType.getOrDefault(RevenueCons.TRANSIENT, Collections.emptyMap()),
                        marketCodeRoomsByTargetDate, marketCodeRoomsByYesterday));

                // 设置 Group Pick-up from last report：（当日团客预定数 +/- 当日market code 房间数）-（昨日团客预定数 +/- 昨日marketcode 房间数）
                transientAndGroupDTO.setGroupPickup(calculationTransientPickup(currentDate, transientGroup.getGroupRoomNum(),
                        yesterdayTransientGroup.getGroupRoomNum(), calculationRuleByType.getOrDefault(RevenueCons.GROUP, Collections.emptyMap()),
                        marketCodeRoomsByTargetDate, marketCodeRoomsByYesterday));
                transientAndGroupDTOList.add(transientAndGroupDTO);
            }
            return transientAndGroupDTOList;
        }

        /**
         * 计算 Transient / Group pick up
         *
         * @param targetDate                    指定日期
         * @param targetDateRoomNum             指定日期预定房间数
         * @param yesterdayRoomsNum             指定日期昨日预定房间数
         * @param calculationRuleByMarketCodeId market code 计算规则
         * @param marketCodeRoomsByTargetDate   当前日期对应未来14天的 market code 房间数
         * @param marketCodeRoomsByYesterday    昨天对应未来14天的 market code 房间数
         * @return Transient / Group pick up
         * @author yyt
         * @date 2024/3/23
         **/
        private Integer calculationTransientPickup(Long targetDate, Integer targetDateRoomNum, Integer yesterdayRoomsNum,
                                                   Map<Integer, Integer> calculationRuleByMarketCodeId,
                                                   Map<Long, List<RevenueMarketCodeRooms>> marketCodeRoomsByTargetDate,
                                                   Map<Long, List<RevenueMarketCodeRooms>> marketCodeRoomsByYesterday) {
            if (Objects.isNull(targetDateRoomNum) || Objects.isNull(yesterdayRoomsNum)) {
                return null;
            }

            // （当日预定数 +/- 当日mark code 房间数）-（昨日预定数 +/- 昨日mark code 房间数）
            List<RevenueMarketCodeRooms> targetDateMarketCodeRooms = marketCodeRoomsByTargetDate.get(targetDate);
            if (Objects.nonNull(targetDateMarketCodeRooms) && CollectionUtils.isNotEmpty(targetDateMarketCodeRooms)) {
                for (RevenueMarketCodeRooms item : targetDateMarketCodeRooms) {
                    Integer calculationRule = calculationRuleByMarketCodeId.get(item.getMarketCodeId());
                    if (Objects.nonNull(calculationRule)) {
                        if (calculationRule == RevenueCons.ADD) {
                            targetDateRoomNum = targetDateRoomNum + item.getRooms();
                        } else {
                            targetDateRoomNum = targetDateRoomNum - item.getRooms();
                        }
                    }
                }
            }
            List<RevenueMarketCodeRooms> yesterdayMarketCodeRooms = marketCodeRoomsByYesterday.get(targetDate);
            if (Objects.nonNull(targetDateMarketCodeRooms) && CollectionUtils.isNotEmpty(yesterdayMarketCodeRooms)) {
                for (RevenueMarketCodeRooms item : yesterdayMarketCodeRooms) {
                    Integer calculationRule = calculationRuleByMarketCodeId.get(item.getMarketCodeId());
                    if (Objects.nonNull(calculationRule)) {
                        if (calculationRule == RevenueCons.ADD) {
                            yesterdayRoomsNum = yesterdayRoomsNum + item.getRooms();
                        } else {
                            yesterdayRoomsNum = yesterdayRoomsNum - item.getRooms();
                        }
                    }
                }
            }
            return targetDateRoomNum - yesterdayRoomsNum;
        }

        /**
         * 保存 Transient And Group
         *
         * @param propertyDTO        资产
         * @param targetDate         目标日期
         * @param transientAndGroups 修改内容
         * @param userId             操作人
         * @author yyt
         * @date 2023/10/28
         */
        @Override
        public void save(PropertyDTO propertyDTO, Long targetDate, List<TransientAndGroupDTO> transientAndGroups, int userId) {
            Integer propertyId = propertyDTO.getId();
            long now = DateTimeUtils.nowTimestamp();
            baseMapper.saveOrUpdateBatch(transientAndGroups.stream().map(item ->
                    new RevenueTransientGroup().setPropertyId(propertyId).setTheDate(targetDate).setTargetDate(item.getTargetDate())
                            .setOccPercent(item.getOccPercent()).setAdr(item.getAdr()).setTransientRoomNum(item.getTransientRoomNum())
                            .setTransientAdr(item.getTransientAdr()).setGroupRoomNum(item.getGroupRoomNum()).setGroupAdr(item.getGroupAdr())
                            .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now)
            ).sorted(Comparator.comparing(RevenueTransientGroup::getTargetDate)).collect(Collectors.toList()));
        }

        /**
         * 保存 Transient And Group
         *
         * @param propertyDTO           资产
         * @param transientGroupsBOList Transient And Groups
         * @param userId                操作人
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        public void saveTransientGroupBO(PropertyDTO propertyDTO, List<TransientGroupsBO> transientGroupsBOList, int userId) {
            if (CollectionUtils.isEmpty(transientGroupsBOList)) return;

            Integer propertyId = propertyDTO.getId();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            long now = DateTimeUtils.nowTimestamp();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            String pmsType = propertyDTO.getPmsType();

            List<RevenueTransientGroup> transientGroupList = transientGroupsBOList.stream().map(transientGroups -> {
                long theDate = LocalDate.parse(transientGroups.getDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond();
                return transientGroups.getDetail().stream().map(item -> {
                    RevenueTransientGroup revenueTransientGroup = new RevenueTransientGroup().setPropertyId(propertyId).setTheDate(theDate)
                            .setTargetDate(LocalDate.parse(item.getTargetDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond())
                            .setOccPercent(Objects.isNull(item.getOccPercent()) ? null
                                    : item.getOccPercent().setScale(dataPrecision, roundingMode))
                            .setAdr(Objects.isNull(item.getAdr()) ? null : item.getAdr().setScale(dataPrecision, roundingMode))
                            .setTransientRoomNum(item.getTransientRoomNum())
                            .setGroupRoomNum(item.getGroupRoomNum()).setRoomsAvailable(item.getRoomsAvailable())
                            .setCreatedBy(userId).setCreatedTime(now)
                            .setUpdatedBy(userId).setUpdatedTime(now);

                     if (pmsType.equals(SystemPlatformEnum.SHI_JI.getDesc())) {
                         revenueTransientGroup.setTransientAdr(Objects.isNull(item.getTransientAdr()) ? null
                                         : item.getTransientAdr().setScale(dataPrecision, roundingMode))
                                 .setGroupAdr(Objects.isNull(item.getGroupAdr()) ? null : item.getGroupAdr().setScale(dataPrecision, roundingMode));
                     }

                    return revenueTransientGroup;
                }).collect(Collectors.toList());
            }).flatMap(Collection::stream).sorted(Comparator.comparing(RevenueTransientGroup::getTheDate).thenComparing(RevenueTransientGroup::getTargetDate)).toList();

            baseMapper.saveOrUpdateBatchForOcc(transientGroupList);

            if(pmsType.equals(SystemPlatformEnum.SHI_JI.getDesc())){
                baseMapper.saveOrUpdateBatchForAdr(transientGroupList);
            }
        }

        /**
         * 保存 Transient Group Adr
         *
         * @param propertyDTO              资产
         * @param transientGroupAdrsBOList Transient And Group Adrs 集合
         * @param userId                   操作人
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        public void saveTransientGroupAdrBO(PropertyDTO propertyDTO, List<TransientGroupAdrsBO> transientGroupAdrsBOList, int userId) {
            if (CollectionUtils.isEmpty(transientGroupAdrsBOList)) return;

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

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

            List<RevenueTransientGroup> transientGroupList = transientGroupAdrsBOList.stream().map(transientGroups -> {
                long theDate = LocalDate.parse(transientGroups.getDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond();
                return transientGroups.getDetail().stream().map(item ->
                        new RevenueTransientGroup().setPropertyId(propertyId).setTheDate(theDate)
                                .setTargetDate(LocalDate.parse(item.getTargetDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond())
                                .setTransientAdr(Objects.isNull(item.getTransientAdr()) ? null
                                        : item.getTransientAdr().setScale(dataPrecision, roundingMode))
                                .setGroupAdr(Objects.isNull(item.getGroupAdr()) ? null : item.getGroupAdr().setScale(dataPrecision, roundingMode))
                                .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now)
                ).collect(Collectors.toList());
            }).flatMap(Collection::stream).sorted(Comparator.comparing(RevenueTransientGroup::getTheDate).thenComparing(RevenueTransientGroup::getTargetDate)).toList();

            baseMapper.saveOrUpdateBatchForAdr(transientGroupList);
        }

        /**
         * 查询 Transient Group
         *
         * @param propertyId 资产 id
         * @param theDate    指定日期
         * @param startDate  开始日期
         * @param endDate    结束日期
         * @return Transient Group
         * @author yyt
         * @date 2024/12/2
         **/
        @Override
        public List<RevenueTransientGroup> list(Integer propertyId, Long theDate, Long startDate, Long endDate) {
            return list(new QueryWrapper<RevenueTransientGroup>().lambda()
                    .eq(RevenueTransientGroup::getPropertyId, propertyId)
                    .eq(RevenueTransientGroup::getTheDate, theDate)
                    .between(RevenueTransientGroup::getTargetDate, startDate, endDate));
        }

    }