# RevenueSegmentServiceImpl.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.RevenueBudgetSegment;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegment;
    import cloud.wconnect.rateshop.entity.revenue.RevenueSegmentRevenue;
    import cloud.wconnect.rateshop.mapper.revenue.RevenueSegmentMapper;
    import cloud.wconnect.rateshop.pojo.dto.revenue.SegmentDTO;
    import cloud.wconnect.rateshop.service.revenue.IRevenueBudgetSegmentService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSegmentRevenueService;
    import cloud.wconnect.rateshop.service.revenue.IRevenueSegmentService;
    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.BigDecimal;
    import java.math.RoundingMode;
    import java.time.ZonedDateTime;
    import java.util.Collections;
    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 RevenueSegmentServiceImpl extends ServiceImpl<RevenueSegmentMapper, RevenueSegment> implements IRevenueSegmentService {

        @Autowired
        private IRevenueSegmentRevenueService revenueSegmentRevenueService;
        @Autowired
        private IRevenueBudgetSegmentService revenueBudgetSegmentService;

        /**
         * segment
         *
         * @param propertyId 资产 id
         * @return segment
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        public List<RevenueSegment> listActiveSegment(Integer propertyId) {
            return list(new QueryWrapper<RevenueSegment>().lambda().eq(RevenueSegment::getPropertyId, propertyId)
                    .eq(RevenueSegment::getIsRemoved, false).orderByAsc(RevenueSegment::getSortNum));
        }

        /**
         * 获取 segment
         *
         * @param propertyDTO 资产
         * @param targetDate 目标日期
         * @param yesterday  昨天
         * @return segment
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        public List<SegmentDTO> listSegmentDTO(PropertyDTO propertyDTO, Long targetDate, Long yesterday) {
            Integer propertyId = propertyDTO.getId();
            List<RevenueSegment> segments = listActiveSegment(propertyId);
            if (CollectionUtils.isEmpty(segments)) {
                return Collections.emptyList();
            }

            List<Integer> segmentIds = segments.stream().map(RevenueSegment::getId).collect(Collectors.toList());
            // 获取指定日期 + 昨天的 Segment Revenue 数据
            Map<Integer, Map<Long, RevenueSegmentRevenue>> segmentRevenueBySegmentId =
                    revenueSegmentRevenueService.listMap(propertyId, Lists.newArrayList(targetDate, yesterday), segmentIds);
            // 获取 segment budget
            String timezone = propertyDTO.getTimezone();
            ZonedDateTime targetDateZonedDateTime = DateTimeUtils.toZonedDateTime(targetDate, timezone);
            Map<Integer, BigDecimal> budgetRevenueBySegmentId = revenueBudgetSegmentService.list(new QueryWrapper<RevenueBudgetSegment>()
                            .lambda().eq(RevenueBudgetSegment::getPropertyId, propertyId)
                            .eq(RevenueBudgetSegment::getTheYear, targetDateZonedDateTime.getYear())
                            .eq(RevenueBudgetSegment::getTheMonth, targetDateZonedDateTime.getMonthValue())
                            .in(RevenueBudgetSegment::getSegmentId, segmentIds)).stream()
                    .filter(item -> Objects.nonNull(item.getBudgetRevenue()))
                    .collect(Collectors.toMap(RevenueBudgetSegment::getSegmentId, RevenueBudgetSegment::getBudgetRevenue));

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

            return segments.stream().map(item -> {
                int segmentId = item.getId();
                SegmentDTO segmentDTO = new SegmentDTO();
                segmentDTO.setSegmentId(segmentId);
                segmentDTO.setSegment(item.getSegment());

                // 获取 Segment对应的 Budget 值
                BigDecimal budget = budgetRevenueBySegmentId.get(segmentId);
                segmentDTO.setBudget(budget);

                Map<Long, RevenueSegmentRevenue> segmentRevenueByDate = segmentRevenueBySegmentId.getOrDefault(segmentId, Collections.emptyMap());

                RevenueSegmentRevenue targetDateSegmentRevenue = segmentRevenueByDate.getOrDefault(targetDate, new RevenueSegmentRevenue());
                BigDecimal targetRevenue = targetDateSegmentRevenue.getRevenue();
                segmentDTO.setOtbRevenue(targetRevenue);
                segmentDTO.setAdr(targetDateSegmentRevenue.getAdr());
                if (Objects.nonNull(targetRevenue)) {
                    // 如果前一天是月底，则使用前一日中的 Revenue Next Month 计算 pick-up
                    BigDecimal yesterdayRevenue;
                    boolean isLastDay = DateTimeUtils.isLastDayOfThisMonth(DateTimeUtils.toZonedDateTime(yesterday, timezone).toLocalDate());
                    RevenueSegmentRevenue yesterdaySegmentRevenue = segmentRevenueByDate.getOrDefault(yesterday, new RevenueSegmentRevenue());
                    if (isLastDay) {
                        yesterdayRevenue = yesterdaySegmentRevenue.getRevenueNextMonth();
                    } else {
                        yesterdayRevenue = yesterdaySegmentRevenue.getRevenue();
                    }
                    if (Objects.nonNull(yesterdayRevenue)) {
                        segmentDTO.setPickup(targetRevenue.subtract(yesterdayRevenue));
                    }

                    // 计算 achievement = revenue / budget
                    if (Objects.nonNull(budget) && budget.signum() != 0) {
                        segmentDTO.setAchievement(targetRevenue.divide(budget, dataPrecision, roundingMode)
                                .multiply(BigDecimal.valueOf(100L)));
                    }
                }
                return segmentDTO;
            }).collect(Collectors.toList());
        }

        /**
         * 保存 segment
         *
         * @param propertyDTO 资产
         * @param targetDate  目标日期
         * @param segments    segment
         * @param userId      操作人
         * @author yyt
         * @date 2023/10/27
         */
        @Override
        public void save(PropertyDTO propertyDTO, Long targetDate, List<SegmentDTO> segments, int userId) {
            int size = segments.size();
            List<RevenueBudgetSegment> budgetSegments = Lists.newArrayListWithExpectedSize(size);

            ZonedDateTime zonedDateTime = DateTimeUtils.toZonedDateTime(targetDate, propertyDTO.getTimezone());
            int year = zonedDateTime.getYear();
            int monthValue = zonedDateTime.getMonthValue();

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

            // 获取指定日期 Segment Revenue 数据
            List<Integer> segmentIds = segments.stream().map(SegmentDTO::getSegmentId).collect(Collectors.toList());
            Map<Integer, Map<Long, RevenueSegmentRevenue>> segmentRevenueBySegmentId =
                    revenueSegmentRevenueService.listMap(propertyId, Lists.newArrayList(targetDate), segmentIds);
            long now = DateTimeUtils.nowTimestamp();
            List<RevenueSegmentRevenue> insertSegmentRevenues = Lists.newArrayListWithExpectedSize(size);
            List<RevenueSegmentRevenue> updateSegmentRevenues = Lists.newArrayListWithExpectedSize(size);
            for (SegmentDTO item : segments) {
                Integer segmentId = item.getSegmentId();
                BigDecimal revenue = item.getOtbRevenue();
                RevenueSegmentRevenue existSegmentRevenue = segmentRevenueBySegmentId.getOrDefault(segmentId,
                        Collections.emptyMap()).get(targetDate);
                if (Objects.isNull(existSegmentRevenue)) {
                    insertSegmentRevenues.add(new RevenueSegmentRevenue().setPropertyId(propertyId)
                            .setTheDate(targetDate).setSegmentId(segmentId).setRevenue(revenue)
                            .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now));
                } else {
                    existSegmentRevenue.setRevenue(revenue);
                    if (Objects.isNull(revenue)) {
                        existSegmentRevenue.setAdr(null);
                    } else {
                        Integer totalRooms = existSegmentRevenue.getTotalRooms();
                        if (Objects.nonNull(totalRooms) && totalRooms != BaseConstant.ZERO) {
                            // 重新计算 adr = revenue / rooms
                            existSegmentRevenue.setAdr(revenue.divide(BigDecimal.valueOf(totalRooms), dataPrecision, roundingMode));
                        } else {
                            existSegmentRevenue.setAdr(null);
                        }
                    }
                    updateSegmentRevenues.add(existSegmentRevenue);
                }

                // 更新 segment budget
                budgetSegments.add(new RevenueBudgetSegment().setPropertyId(propertyId).setTheYear(year)
                        .setTheMonth(monthValue).setSegmentId(segmentId).setBudgetRevenue(item.getBudget())
                        .setCreatedBy(userId).setCreatedTime(now).setUpdatedBy(userId).setUpdatedTime(now));
            }
            if (CollectionUtils.isNotEmpty(insertSegmentRevenues)) {
                revenueSegmentRevenueService.saveBatch(insertSegmentRevenues);
            }
            if (CollectionUtils.isNotEmpty(updateSegmentRevenues)) {
                revenueSegmentRevenueService.updateBatch(updateSegmentRevenues);
            }

            // 更新 segment budget
            revenueBudgetSegmentService.saveOrUpdateBatchRewrite(budgetSegments);
        }

    }