# RevenueOtbRevenueServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.rateshop.entity.revenue.RevenueOtbRevenue;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueOtbRevenueMapper;
    import cloud.wconnect.rateshop.pojo.bo.revenue.BanquetAndFBOTBRevenueBO;
    import cloud.wconnect.rateshop.pojo.bo.revenue.OtbRevenueBO;
    import cloud.wconnect.rateshop.pojo.dto.revenue.OtbRevenueDTO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueOtbRevenueService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSendEventService;
    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 org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.ZoneId;
    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.Executor;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author yyt
     * @since 2023-10-26
     */
    @Service
    @DS("rate_shop")
    public class RevenueOtbRevenueServiceImpl extends ServiceImpl<RevenueOtbRevenueMapper, RevenueOtbRevenue> implements IRevenueOtbRevenueService {

        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private Executor executor;
        @Autowired
        private IRevenueSendEventService revenueSendEventService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 获取 otb revenue
         *
         * @param propertyDTO 资产
         * @param targetDate  目标日期
         * @param yesterday   昨天
         * @return otb revenue
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        public OtbRevenueDTO getOtbRevenueDTO(PropertyDTO propertyDTO, Long targetDate, Long yesterday) {
            // 获取指定日期 + 前一天的数据
            Map<Long, RevenueOtbRevenue> otbRevenueByDate = list(new QueryWrapper<RevenueOtbRevenue>().lambda()
                    .eq(RevenueOtbRevenue::getPropertyId, propertyDTO.getId())
                    .in(RevenueOtbRevenue::getTheDate, Lists.newArrayList(targetDate, yesterday)))
                    .stream().collect(Collectors.toMap(RevenueOtbRevenue::getTheDate, v -> v));
            RevenueOtbRevenue targetDateOtb = otbRevenueByDate.get(targetDate);
            if (Objects.isNull(targetDateOtb)) {
                return new OtbRevenueDTO();
            }

            OtbRevenueDTO otbRevenueDTO = beanMapper.map(targetDateOtb, OtbRevenueDTO.class);
            otbRevenueDTO.setOtbRevPar(targetDateOtb.getOtbRevpar());
            // 计算 pick up = 当天的 otb revenue - 昨天的 otb revenue（如果昨天是月底，则减去昨天的 Revenue Next Month）
            RevenueOtbRevenue yesterdayOtb = otbRevenueByDate.get(yesterday);
            if (Objects.nonNull(yesterdayOtb)) {
                boolean isLastDay = DateTimeUtils.isLastDayOfThisMonth(DateTimeUtils.toZonedDateTime(yesterday, propertyDTO.getTimezone()).toLocalDate());
                BigDecimal yesterdayOtbRevenue = isLastDay ? yesterdayOtb.getRevenueNextMonth() : yesterdayOtb.getOtbRevenue();
                otbRevenueDTO.setOtbPickup(Objects.nonNull(targetDateOtb.getOtbRevenue()) && Objects.nonNull(yesterdayOtbRevenue)
                        ? targetDateOtb.getOtbRevenue().subtract(yesterdayOtbRevenue) : null);
            }
            return otbRevenueDTO;
        }

        /**
         * 保存 otb
         *
         * @param propertyDTO 资产
         * @param targetDate  目标日期
         * @param otb         修改内容
         * @param userId      操作人
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(PropertyDTO propertyDTO, Long targetDate, OtbRevenueDTO otb, int userId) {
            long now = DateTimeUtils.nowTimestamp();
            RevenueOtbRevenue otbRevenue = beanMapper.map(otb, RevenueOtbRevenue.class);
            otbRevenue.setPropertyId(propertyDTO.getId()).setTheDate(targetDate).setCreatedBy(userId).setCreatedTime(now)
                    .setUpdatedBy(userId).setUpdatedTime(now);
            // 计算 revpar
            calculateRevpar(otbRevenue);
            baseMapper.saveOrUpdate(otbRevenue);

            // 发送 otb revenue 事件
            sentOtbRevenueEvent(propertyDTO, Lists.newArrayList(otbRevenue));
        }

        /**
         * 计算 revpar
         *
         * @param otbRevenue otb revenue
         * @author yyt
         * @date 2023/10/30
         */
        private void calculateRevpar(RevenueOtbRevenue otbRevenue) {
            InternationalizationNumberDTO number = propertyI18nRedisService.get(otbRevenue.getPropertyId())
                    .getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            BigDecimal occPercent = otbRevenue.getOtbOccPercent();
            BigDecimal adr = otbRevenue.getOtbAdr();
            if (Objects.nonNull(occPercent) && Objects.nonNull(adr)) {
                // revpar = occ * adr
                BigDecimal otbRevpar = occPercent.multiply(adr).divide(BigDecimal.valueOf(100L), dataPrecision, roundingMode);
                otbRevenue.setOtbRevpar(otbRevpar);
            } else {
                otbRevenue.setOtbRevpar(null);
            }
        }

        /**
         * 保存 otb revenue
         *
         * @param propertyDTO      资产
         * @param otbRevenueBOList otb revenue 集合
         * @param userId           操作人
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveOtbRevenueBO(PropertyDTO propertyDTO, List<OtbRevenueBO> otbRevenueBOList, int userId) {
            if (CollectionUtils.isEmpty(otbRevenueBOList)) {
                return;
            }
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            long now = DateTimeUtils.nowTimestamp();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            List<RevenueOtbRevenue> revenueOtbRevenueList = otbRevenueBOList.stream().map(otbRevenueBO -> {
                long theDate = LocalDate.parse(otbRevenueBO.getDate(), dateTimeFormatter)
                        .atStartOfDay(ZoneId.of(propertyDTO.getTimezone())).toEpochSecond();
                RevenueOtbRevenue otbRevenue = new RevenueOtbRevenue().setPropertyId(propertyDTO.getId()).setTheDate(theDate)
                        .setOtbRevenue(Objects.isNull(otbRevenueBO.getOtbRevenue()) ? null
                                : otbRevenueBO.getOtbRevenue().setScale(dataPrecision, roundingMode))
                        .setOtbAdr(Objects.isNull(otbRevenueBO.getOtbAdr()) ? null
                                : otbRevenueBO.getOtbAdr().setScale(dataPrecision, roundingMode))
                        .setOtbOccPercent(Objects.isNull(otbRevenueBO.getOtbOccPercent()) ? null :
                                otbRevenueBO.getOtbOccPercent().setScale(dataPrecision, roundingMode))
                        .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now);
                // 计算 revpar
                calculateRevpar(otbRevenue);
                return otbRevenue;
            }).sorted(Comparator.comparing(RevenueOtbRevenue::getTheDate)).toList();

            // 保存数据
            baseMapper.saveOrUpdateBatchForImport(revenueOtbRevenueList);

            // 发送 otb revenue 事件
            sentOtbRevenueEvent(propertyDTO, revenueOtbRevenueList);
        }

        /**
         * 保存 otb revenue
         *
         * @param propertyDTO  资产
         * @param otbRevenueBO otb revenue
         * @param userId       操作人
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        public void saveOtbRevenueBONextMonth(PropertyDTO propertyDTO, OtbRevenueBO otbRevenueBO, int userId) {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            long theDate = LocalDate.parse(otbRevenueBO.getDate(), dateTimeFormatter).atStartOfDay(ZoneId.of(propertyDTO.getTimezone()))
                    .toEpochSecond();
            long now = DateTimeUtils.nowTimestamp();
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            RevenueOtbRevenue otbRevenue = new RevenueOtbRevenue().setPropertyId(propertyDTO.getId()).setTheDate(theDate)
                    .setRevenueNextMonth(Objects.isNull(otbRevenueBO.getOtbRevenue()) ? null
                            : otbRevenueBO.getOtbRevenue().setScale(dataPrecision, roundingMode))
                    .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now);
            baseMapper.saveOrUpdateForNextMonth(otbRevenue);
        }

        /**
         * 发送 otb revenue 事件
         *
         * @param propertyDTO           资产
         * @param revenueOtbRevenueList otb revenue 集合
         * @author yyt
         * @date 2023/10/30
         */
        @Override
        public void sentOtbRevenueEvent(PropertyDTO propertyDTO, List<RevenueOtbRevenue> revenueOtbRevenueList) {
            List<RevenueOtbRevenue> effectiveRevenueList = revenueOtbRevenueList.stream().filter(otbRevenue -> {
                ZonedDateTime zonedDateTime = DateTimeUtils.toZonedDateTime(otbRevenue.getTheDate(), propertyDTO.getTimezone());
                // 月底才需要通知
                return DateTimeUtils.isLastDayOfThisMonth(zonedDateTime.toLocalDate());
            }).toList();

            if (CollectionUtils.isEmpty(effectiveRevenueList)) {
                return;
            }

            // otb revenue 改动发送事件
            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                revenueSendEventService.sendOtbRevenueEvent(propertyDTO, effectiveRevenueList);
            });
        }

        /**
         * 保存 Banquet 和 F&B OTB Revenue
         *
         * @param propertyDTO 资产
         * @param data        Banquet 和 F&B OTB Revenue
         * @param userId      操作人
         * @author yyt
         * @date 2024/4/9
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void saveBanquetAndFBOTBRevenue(PropertyDTO propertyDTO, List<BanquetAndFBOTBRevenueBO> data, int userId) {
            Integer propertyId = propertyDTO.getId();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(BaseConstant.DATE_FORMAT_YYYY_MM_DD);
            ZoneId zoneId = ZoneId.of(propertyDTO.getTimezone());
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            int dataPrecision = BaseConstant.DATA_PRECISION;
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());

            long now = DateTimeUtils.nowTimestamp();
            List<RevenueOtbRevenue> otbRevenues = data.stream().map(item -> new RevenueOtbRevenue().setPropertyId(propertyId)
                            .setTheDate(LocalDate.parse(item.getDate(), dateTimeFormatter).atStartOfDay(zoneId).toEpochSecond())
                            .setBanquetRevenue(Objects.isNull(item.getBanquetRevenue()) ? null : item.getBanquetRevenue().setScale(dataPrecision, roundingMode))
                            .setFbOtbRevenue(Objects.isNull(item.getFbOtbRevenue()) ? null : item.getFbOtbRevenue().setScale(dataPrecision, roundingMode))
                            .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now))
                    .sorted(Comparator.comparing(RevenueOtbRevenue::getTheDate))
                    .toList();

            baseMapper.saveOrUpdateBanquetAndFBOTBRevenueBatch(otbRevenues);
        }

    }