# GmwrMonthEndServiceImpl.java

    package cloud.wconnect.workperformance.service.gmwr.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.RoomsConfigDTO;
    import cloud.wconnect.commonlib.service.RoomsConfigInternalService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.workperformance.cons.gmwr.monthend.GmwrMonthEndEnum;
    import cloud.wconnect.workperformance.entity.gmwr.*;
    import cloud.wconnect.workperformance.mapper.gmwr.GmwrMonthEndMapper;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthend.*;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.monthend.basic.GmwrMonthEndBasicDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.note.GmwrNoteDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.valuedetail.GmwrBasicValueDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.gmwr.valuedetail.GmwrValueDetailDTO;
    import cloud.wconnect.workperformance.pojo.dto.hotelkpi.budgetdb.HotelKpiBudgetDatabaseDTO;
    import cloud.wconnect.workperformance.pojo.dto.monthlyreport.internal.MonthlyReportForecastInternalDTO;
    import cloud.wconnect.workperformance.pojo.dto.sva.internal.SaleForecastInternalDTO;
    import cloud.wconnect.workperformance.service.gmwr.*;
    import cloud.wconnect.workperformance.service.hotelkpi.internal.IHotelKpiInternalService;
    import cloud.wconnect.workperformance.service.monthlyreport.internal.IMonthlyReportInternalService;
    import cloud.wconnect.workperformance.service.sva.internal.ISvaInternalService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import jakarta.annotation.Resource;
    import org.springframework.stereotype.Service;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.LocalDate;
    import java.time.temporal.TemporalAdjusters;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * GM Weekly Report Month End 服务实现类
     * </p>
     *
     * @author zc
     * @since 2024-10-12
     */
    @Service
    @DS("work_performance")
    public class GmwrMonthEndServiceImpl extends ServiceImpl<GmwrMonthEndMapper, GmwrMonthEnd> implements IGmwrMonthEndService {
        @Resource
        private IGmwrWeeksService weeksService;
        @Resource
        private IGmwrHistoryAndForecastService historyAndForecastService;
        @Resource
        private IGmwrManagerFlashService managerFlashService;
        @Resource
        private IGmwrMonthEndForecastConfigService monthEndForecastConfigService;
        @Resource
        private IGmwrMonthEndNoteService monthEndNoteService;
        @Resource
        private ISvaInternalService svaInternalService;
        @Resource
        private IMonthlyReportInternalService monthlyReportInternalService;
        @Resource
        private UserInternalService userInternalService;
        @Resource
        private RoomsConfigInternalService roomsConfigInternalService;
        @Resource
        private IHotelKpiInternalService hotelKpiInternalService;
        @Resource
        private BeanMapper beanMapper;
        @Resource
        private ObjectMapper objectMapper;

        /**
         * 根据周报 ID 获取 Month End
         *
         * @param reportId 周报 ID
         * @return GmwrMonthEnd
         * @author zc
         * @date 2024/10/15
         */
        @Override
        public GmwrMonthEnd getByReportId(int reportId) {
            return getOne(new LambdaQueryWrapper<GmwrMonthEnd>()
                    .eq(GmwrMonthEnd::getReportId, reportId));
        }

        /**
         * 获取 Month End
         *
         * @param propertyDTO 资产
         * @param gmwrWeeks   周报数据
         * @return GmwrMonthEndDTO
         * @author zc
         * @date 2024/10/15
         */
        @Override
        public GmwrMonthEndDTO get(PropertyDTO propertyDTO, GmwrWeeks gmwrWeeks) {
            Set<Integer> userIds = Sets.newHashSet();
            GmwrMonthEndDTO monthEndDTO = new GmwrMonthEndDTO();
            GmwrMonthEnd monthEnd = getByReportId(gmwrWeeks.getId());
            if (Objects.nonNull(monthEnd)) {
                List<GmwrMonthEndBasicDetailDTO> monthEndBasicValueList = JsonUtils.convertStringToList(objectMapper,
                        monthEnd.getDetail(), GmwrMonthEndBasicDetailDTO.class);

                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                        .getInternationalization().getNumber().getCalculationRule());

                List<GmwrMonthEndDetailDTO> list = beanMapper.mapList(monthEndBasicValueList, GmwrMonthEndDetailDTO.class,
                        (item, dto) -> {
                            dto.setCategoryName(GmwrMonthEndEnum.getCategoryName(item.getCategory()));
                            dto.setForecastChg(weeksService.calculateChg(item.getCurrentForecast().getValue(),
                                    item.getFirstForecast().getValue(), roundingMode));
                            dto.setBudgetChg(weeksService.calculateChg(item.getCurrentForecast().getValue(),
                                    item.getBudget().getValue(), roundingMode));
                            dto.setLastYearChg(weeksService.calculateChg(item.getCurrentForecast().getValue(),
                                    item.getLastYear().getValue(), roundingMode));
                        });
                // 获取 Month End Total
                list.add(getMonthEndTotal(list, roundingMode));
                // 汇总 userIds
                for (GmwrMonthEndBasicDetailDTO basicDetailDTO : monthEndBasicValueList) {
                    userIds.add(basicDetailDTO.getCurrentForecast().getUpdatedBy());
                    userIds.add(basicDetailDTO.getFirstForecast().getUpdatedBy());
                    userIds.add(basicDetailDTO.getBudget().getUpdatedBy());
                    userIds.add(basicDetailDTO.getLastYear().getUpdatedBy());
                }

                monthEndDTO.setFirstForecastUpdatedTime(monthEnd.getFirstForecastUpdatedTime());
                monthEndDTO.setList(list);
            }

            // 获取备注
            List<GmwrMonthEndNote> noteList = monthEndNoteService.listByReportId(gmwrWeeks.getId());
            Map<Integer, GmwrMonthEndNote> category2Note = noteList.stream()
                    .collect(Collectors.toMap(GmwrMonthEndNote::getCategory, Function.identity()));
            userIds.addAll(noteList.stream().map(GmwrMonthEndNote::getUpdatedBy).toList());

            // 获取用户名
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(propertyDTO.getTenantId(), userIds);
            monthEndDTO.getList().forEach(item -> {
                // 设置更新人
                item.getCurrentForecast().setUpdatedName(userId2Name.get(item.getCurrentForecast().getUpdatedBy()));
                item.getFirstForecast().setUpdatedName(userId2Name.get(item.getFirstForecast().getUpdatedBy()));
                item.getBudget().setUpdatedName(userId2Name.get(item.getBudget().getUpdatedBy()));
                item.getLastYear().setUpdatedName(userId2Name.get(item.getLastYear().getUpdatedBy()));

                // 设置备注
                GmwrMonthEndNote note = category2Note.get(item.getCategory());
                if (Objects.nonNull(note)) {
                    GmwrNoteDTO noteDTO = beanMapper.map(note, GmwrNoteDTO.class, dto ->
                            dto.setUpdatedName(userId2Name.get(dto.getUpdatedBy())));
                    item.setNote(noteDTO);
                }
            });
            return monthEndDTO;
        }

        /**
         * 获取 Month End Total
         *
         * @param list         数值集合
         * @param roundingMode 舍入模式
         * @return GmwrMonthEndValueDTO
         * @author zc
         * @date 2025/2/28
         */
        private GmwrMonthEndDetailDTO getMonthEndTotal(List<GmwrMonthEndDetailDTO> list, RoundingMode roundingMode) {
            List<Integer> revenueCategoryList = List.of(GmwrMonthEndEnum.ROOM_REVENUE.getCategory(),
                    GmwrMonthEndEnum.FB_REVENUE.getCategory(), GmwrMonthEndEnum.OTHERS_REVENUE.getCategory());

            BigDecimal totalCurrentForecast = null;
            BigDecimal totalFirstForecast = null;
            BigDecimal totalBudget = null;
            BigDecimal totalLastYear = null;
            for (GmwrMonthEndDetailDTO item : list) {
                if (!revenueCategoryList.contains(item.getCategory())) {
                    continue;
                }
                // Total Current Forecast
                if (Objects.nonNull(item.getCurrentForecast().getValue())) {
                    if (Objects.isNull(totalCurrentForecast)) {
                        totalCurrentForecast = BigDecimal.ZERO;
                    }
                    totalCurrentForecast = totalCurrentForecast.add(item.getCurrentForecast().getValue());
                }
                // Total First Forecast
                if (Objects.nonNull(item.getFirstForecast().getValue())) {
                    if (Objects.isNull(totalFirstForecast)) {
                        totalFirstForecast = BigDecimal.ZERO;
                    }
                    totalFirstForecast = totalFirstForecast.add(item.getFirstForecast().getValue());
                }
                // Total Budget
                if (Objects.nonNull(item.getBudget().getValue())) {
                    if (Objects.isNull(totalBudget)) {
                        totalBudget = BigDecimal.ZERO;
                    }
                    totalBudget = totalBudget.add(item.getBudget().getValue());
                }
                // Total Last Year
                if (Objects.nonNull(item.getLastYear().getValue())) {
                    if (Objects.isNull(totalLastYear)) {
                        totalLastYear = BigDecimal.ZERO;
                    }
                    totalLastYear = totalLastYear.add(item.getLastYear().getValue());
                }
            }

            GmwrMonthEndDetailDTO totalDTO = new GmwrMonthEndDetailDTO();
            totalDTO.setCategory(GmwrMonthEndEnum.TOTAL_REVENUE.getCategory());
            totalDTO.setCategoryName(GmwrMonthEndEnum.TOTAL_REVENUE.getCategoryName());
            totalDTO.setCurrentForecast(new GmwrValueDetailDTO().setValue(totalCurrentForecast));
            totalDTO.setFirstForecast(new GmwrValueDetailDTO().setValue(totalFirstForecast));
            BigDecimal totalForecastChg = weeksService.calculateChg(totalCurrentForecast, totalFirstForecast, roundingMode);
            totalDTO.setForecastChg(totalForecastChg);
            totalDTO.setBudget(new GmwrValueDetailDTO().setValue(totalBudget));
            BigDecimal totalBudgetChg = weeksService.calculateChg(totalCurrentForecast, totalBudget, roundingMode);
            totalDTO.setBudgetChg(totalBudgetChg);
            totalDTO.setLastYear(new GmwrValueDetailDTO().setValue(totalLastYear));
            BigDecimal totalLastYearChg = weeksService.calculateChg(totalCurrentForecast, totalLastYear, roundingMode);
            totalDTO.setLastYearChg(totalLastYearChg);

            return totalDTO;
        }

        /**
         * 保存 Month End
         *
         * @param propertyId    资产 id
         * @param reportId      报告 id
         * @param budgetDTOList 数据集合
         * @param operatorId    操作人 id
         * @author zc
         * @date 2025/3/1
         */
        @Override
        public void saveMonthEnd(int propertyId, int reportId, List<GmwrMonthEndSaveBudgetDTO> budgetDTOList, int operatorId) {
            Map<Integer, GmwrMonthEndSaveBudgetDTO> category2BudgetDTO = budgetDTOList.stream()
                    .collect(Collectors.toMap(GmwrMonthEndSaveBudgetDTO::getCategory, Function.identity()));

            GmwrMonthEnd monthEnd = getByReportId(reportId);
            List<GmwrMonthEndBasicDetailDTO> monthEndBasicValueDTOList;
            if (Objects.nonNull(monthEnd)) {
                monthEndBasicValueDTOList = JsonUtils.convertStringToList(objectMapper, monthEnd.getDetail(), GmwrMonthEndBasicDetailDTO.class);
            } else {
                monthEnd = new GmwrMonthEnd();
                monthEnd.setPropertyId(propertyId);
                monthEnd.setReportId(reportId);
                monthEnd.setFirstForecastUpdatedTime(0L);

                // 生成 BasicValueDTO 初始值
                monthEndBasicValueDTOList = generateInitBasicValueDTOList();
            }
            long nowTimestamp = DateTimeUtils.nowTimestamp();
            monthEndBasicValueDTOList.forEach(item -> {
                GmwrMonthEndSaveBudgetDTO saveBudgetDTO = category2BudgetDTO.get(item.getCategory());
                if (Objects.isNull(saveBudgetDTO)) {
                    return;
                }
                weeksService.editValue(item.getCurrentForecast(), saveBudgetDTO.getCurrentForecast(), operatorId, nowTimestamp);
                weeksService.editValue(item.getFirstForecast(), saveBudgetDTO.getFirstForecast(), operatorId, nowTimestamp);
                weeksService.editValue(item.getBudget(), saveBudgetDTO.getBudget(), operatorId, nowTimestamp);
                weeksService.editValue(item.getLastYear(), saveBudgetDTO.getLastYear(), operatorId, nowTimestamp);
            });
            monthEnd.setDetail(JsonUtils.convertObjListToString(objectMapper, monthEndBasicValueDTOList));
            baseMapper.insertOrUpdateByUnique(monthEnd);
        }

        /**
         * 生成 Month End
         *
         * @param propertyDTO 资产
         * @param gmwrWeeks   周报数据
         * @author zc
         * @date 2024/10/16
         */
        @Override
        public void generate(PropertyDTO propertyDTO, GmwrWeeks gmwrWeeks) {
            LocalDate weekStartDate = DateTimeUtils.strToLocalDate(gmwrWeeks.getWeek().toString(), BaseConstant.DATE_FORMAT_YYYYMMDD);

            GmwrMonthEnd monthEnd = getByReportId(gmwrWeeks.getId());
            List<GmwrMonthEndBasicDetailDTO> monthEndBasicValueDTOList;
            if (Objects.nonNull(monthEnd)) {
                monthEndBasicValueDTOList = JsonUtils.convertStringToList(objectMapper, monthEnd.getDetail(), GmwrMonthEndBasicDetailDTO.class);
            } else {
                monthEnd = new GmwrMonthEnd();
                monthEnd.setPropertyId(propertyDTO.getId());
                monthEnd.setReportId(gmwrWeeks.getId());

                // 生成 BasicValueDTO 初始值
                monthEndBasicValueDTOList = generateInitBasicValueDTOList();
            }
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(propertyDTO
                    .getInternationalization().getNumber().getCalculationRule());

            // 获取资产房间数配置
            RoomsConfigDTO roomsConfigDTO = roomsConfigInternalService.getRoomsConfig(propertyDTO.getId());
            Integer rooms = roomsConfigDTO.getRooms();

            // 获取月初到月末的 Current Forecast
            long startDate = Long.parseLong(DateTimeUtils.localDateToStr(weekStartDate.withDayOfMonth(1), BaseConstant.DATE_FORMAT_YYYYMMDD));
            long endDate = Long.parseLong(DateTimeUtils.localDateToStr(weekStartDate.with(TemporalAdjusters.lastDayOfMonth()),
                    BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<SaleForecastInternalDTO> saleForecastInternalDTOList = svaInternalService.listSaleForecast(propertyDTO, startDate, endDate);
            // 组装 Current Forecast
            packageCurrentForecast(rooms, saleForecastInternalDTOList, roundingMode, monthEndBasicValueDTOList);
            List<GmwrMonthEndForecastConfigDTO> forecastConfigDTOList = monthEndForecastConfigService.get(propertyDTO.getId());

            // 获取 First Forecast
            long yearMonth = Long.parseLong(DateTimeUtils.localDateToStr(weekStartDate, BaseConstant.DATE_FORMAT_YYYYMM));
            List<MonthlyReportForecastInternalDTO> forecastInternalDTOList = monthlyReportInternalService.listFirstForecast(propertyDTO, yearMonth);
            // 组装 First Forecast
            packageFirstForecast(forecastConfigDTOList, forecastInternalDTOList, monthEndBasicValueDTOList);

            // 获取去年同月的月初到月末的 History and Forecast 数据
            long lastYearStartDate = Long.parseLong(DateTimeUtils.localDateToStr(weekStartDate.minusYears(1)
                    .withDayOfMonth(1), BaseConstant.DATE_FORMAT_YYYYMMDD));
            long lastYearEndDate = Long.parseLong(DateTimeUtils.localDateToStr(weekStartDate.minusYears(1)
                    .with(TemporalAdjusters.lastDayOfMonth()), BaseConstant.DATE_FORMAT_YYYYMMDD));
            List<GmwrHistoryAndForecast> historyAndForecastList = historyAndForecastService.list(propertyDTO, lastYearStartDate, lastYearEndDate);
            // 获取去年同月的 Manager Flash 数据
            long lastYearMonth = Long.parseLong(DateTimeUtils.localDateToStr(weekStartDate.minusYears(1), BaseConstant.DATE_FORMAT_YYYYMM));
            GmwrManagerFlash managerFlash = managerFlashService.get(propertyDTO, lastYearMonth);
            // 组装 Last Year
            packageLastYear(rooms, historyAndForecastList, managerFlash, roundingMode, monthEndBasicValueDTOList);

            // 获取 Budget Database 数据
            HotelKpiBudgetDatabaseDTO budgetDTO = hotelKpiInternalService.getBudgetByYear(propertyDTO.getId(), weekStartDate.getYear());
            // 组装 Budget
            packageBudget(rooms, weekStartDate.lengthOfMonth(), yearMonth, budgetDTO, roundingMode, monthEndBasicValueDTOList);

            monthEnd.setDetail(JsonUtils.convertObjListToString(objectMapper, monthEndBasicValueDTOList));

            // 设置 FirstForecastUpdatedTime
            MonthlyReportForecastInternalDTO maxUpdatedTimeData = forecastInternalDTOList.stream()
                    .max(Comparator.comparing(MonthlyReportForecastInternalDTO::getUpdateTime)).orElse(new MonthlyReportForecastInternalDTO());
            long firstForecastUpdatedTime = Objects.isNull(maxUpdatedTimeData.getUpdateTime()) ? 0L :
                    Long.parseLong(DateTimeUtils.timestampToStr(maxUpdatedTimeData.getUpdateTime(),
                            BaseConstant.DATE_FORMAT_YYYYMMDD, propertyDTO.getTimezone()));
            monthEnd.setFirstForecastUpdatedTime(firstForecastUpdatedTime);
            baseMapper.insertOrUpdateByUnique(monthEnd);
        }

        /**
         * 组装 Budget
         *
         * @param rooms        房间数
         * @param days         天数
         * @param budgetDTO    Budget 数据
         * @param roundingMode 舍入模式
         * @param valueDTOList 数据DTO集合
         * @author zc
         * @date 2025/8/28
         */
        private void packageBudget(Integer rooms, int days, long yearMonth, HotelKpiBudgetDatabaseDTO budgetDTO,
                                   RoundingMode roundingMode, List<GmwrMonthEndBasicDetailDTO> valueDTOList) {
            valueDTOList.forEach(item -> {
                int category = item.getCategory();
                if (category == GmwrMonthEndEnum.OCC_PERCENT.getCategory()) {
                    Long roomsSold = hotelKpiInternalService.getRoomsSoldBudget(budgetDTO, yearMonth, yearMonth);
                    BigDecimal occPercent = calculateOccPercent(rooms, roomsSold, days, roundingMode);
                    item.getBudget().setValue(occPercent);
                } else if (category == GmwrMonthEndEnum.REV_PAR.getCategory()) {
                    BigDecimal roomRevenue = hotelKpiInternalService.getRoomRevenueBudget(budgetDTO, yearMonth, yearMonth);
                    BigDecimal revPar = calculateRevPar(rooms, roomRevenue, days, roundingMode);
                    item.getBudget().setValue(revPar);
                } else if (category == GmwrMonthEndEnum.ADR.getCategory()) {
                    Long roomsSold = hotelKpiInternalService.getRoomsSoldBudget(budgetDTO, yearMonth, yearMonth);
                    BigDecimal roomRevenue = hotelKpiInternalService.getRoomRevenueBudget(budgetDTO, yearMonth, yearMonth);
                    BigDecimal adr = calculateAdr(roomRevenue, roomsSold, roundingMode);
                    item.getBudget().setValue(adr);
                } else if (category == GmwrMonthEndEnum.ROOM_REVENUE.getCategory()) {
                    BigDecimal roomRevenue = hotelKpiInternalService.getRoomRevenueBudget(budgetDTO, yearMonth, yearMonth);
                    item.getBudget().setValue(roomRevenue);
                } else if (category == GmwrMonthEndEnum.FB_REVENUE.getCategory()) {
                    BigDecimal fbRevenue = hotelKpiInternalService.getFBRevenueBudget(budgetDTO, yearMonth, yearMonth);
                    item.getBudget().setValue(fbRevenue);
                } else if (category == GmwrMonthEndEnum.OTHERS_REVENUE.getCategory()) {
                    BigDecimal otherRevenue = hotelKpiInternalService.getOtherRevenueBudget(budgetDTO, yearMonth, yearMonth);
                    item.getBudget().setValue(otherRevenue);
                }
            });
        }

        /**
         * 组装 Current Forecast
         *
         * @param rooms            房间数
         * @param saleForecastList Sale Forecast 数据
         * @param roundingMode     舍入模式
         * @param valueDTOList     数据DTO集合
         * @author zc
         * @date 2024/10/16
         */
        private void packageCurrentForecast(Integer rooms, List<SaleForecastInternalDTO> saleForecastList,
                                            RoundingMode roundingMode, List<GmwrMonthEndBasicDetailDTO> valueDTOList) {
            List<Integer> roomsSoldList = saleForecastList.stream().map(SaleForecastInternalDTO::getEstiRoomSales)
                    .filter(Objects::nonNull).toList();
            int effectiveDaysForRoomsSold = roomsSoldList.size();
            Long roomsSoldTotal = roomsSoldList.stream().reduce(Integer::sum).map(Integer::longValue).orElse(null);

            // 房间售出数量 * 平均房价 的集合
            List<BigDecimal> roomRevenueList = saleForecastList.stream().filter(item -> Objects.nonNull(item.getEstiAdr())
                    && Objects.nonNull(item.getEstiRoomSales())).map(item ->
                    new BigDecimal(String.valueOf(item.getEstiRoomSales())).multiply(item.getEstiAdr())).toList();
            int effectiveDaysForRoomRevenue = roomRevenueList.size();
            BigDecimal roomRevenueTotal = roomRevenueList.stream().reduce(BigDecimal::add).orElse(null);

            valueDTOList.forEach(item -> {
                int category = item.getCategory();
                BigDecimal currentForecast = null;
                if (category == GmwrMonthEndEnum.OCC_PERCENT.getCategory()) {
                    currentForecast = calculateOccPercent(rooms, roomsSoldTotal, effectiveDaysForRoomsSold, roundingMode);
                } else if (category == GmwrMonthEndEnum.REV_PAR.getCategory()) {
                    currentForecast = calculateRevPar(rooms, roomRevenueTotal, effectiveDaysForRoomRevenue, roundingMode);
                } else if (category == GmwrMonthEndEnum.ADR.getCategory()) {
                    currentForecast = calculateAdr(roomRevenueTotal, roomsSoldTotal, roundingMode);
                } else if (category == GmwrMonthEndEnum.ROOM_REVENUE.getCategory()) {
                    if (Objects.nonNull(roomRevenueTotal)) {
                        currentForecast = roomRevenueTotal.setScale(BaseConstant.DATA_PRECISION, roundingMode);
                    }
                } else if (category == GmwrMonthEndEnum.FB_REVENUE.getCategory()) {
                    currentForecast = saleForecastList.stream().map(SaleForecastInternalDTO::getEstiFbRev)
                            .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(null);
                }
                item.setCurrentForecast(new GmwrBasicValueDetailDTO().setValue(currentForecast));
            });
        }

        /**
         * 组装 First Forecast
         *
         * @param configDTOList 配置集合
         * @param forecastList  Forecast 数据
         * @param valueDTOList  数据DTO集合
         * @author zc
         * @date 2024/10/16
         */
        private void packageFirstForecast(List<GmwrMonthEndForecastConfigDTO> configDTOList, List<MonthlyReportForecastInternalDTO> forecastList,
                                          List<GmwrMonthEndBasicDetailDTO> valueDTOList) {
            Map<String, MonthlyReportForecastInternalDTO> categoryName2ForecastDTO = forecastList.stream()
                    .collect(Collectors.toMap(MonthlyReportForecastInternalDTO::getCategoryName, Function.identity()));

            Map<Integer, GmwrMonthEndForecastConfigDTO> category2ConfigDTO = configDTOList.stream()
                    .collect(Collectors.toMap(GmwrMonthEndForecastConfigDTO::getCategory, Function.identity()));

            valueDTOList.forEach(item -> {
                GmwrMonthEndForecastConfigDTO configDTO = category2ConfigDTO.getOrDefault(item.getCategory(), new GmwrMonthEndForecastConfigDTO());
                MonthlyReportForecastInternalDTO internalDTO = categoryName2ForecastDTO.getOrDefault(configDTO.getMappingName(),
                        new MonthlyReportForecastInternalDTO());
                item.setFirstForecast(new GmwrBasicValueDetailDTO().setValue(internalDTO.getForecast()));
            });
        }

        /**
         * 组装 Last Year
         *
         * @param rooms        房间数
         * @param hfList       History and Forecast 数据
         * @param managerFlash Manager Flash 数据
         * @param roundingMode 舍入模式
         * @param valueDTOList 数据DTO集合
         * @author zc
         * @date 2024/10/16
         */
        private void packageLastYear(Integer rooms, List<GmwrHistoryAndForecast> hfList, GmwrManagerFlash managerFlash,
                                     RoundingMode roundingMode, List<GmwrMonthEndBasicDetailDTO> valueDTOList) {
            List<Integer> roomsSoldList = hfList.stream().map(GmwrHistoryAndForecast::getRoomsSold)
                    .filter(Objects::nonNull).toList();
            int effectiveDaysForRoomsSold = roomsSoldList.size();
            Long roomsSoldTotal = roomsSoldList.stream().reduce(Integer::sum).map(Integer::longValue).orElse(null);

            // 房间售出数量 * 平均房价 的集合
            List<BigDecimal> roomRevenueList = hfList.stream().map(GmwrHistoryAndForecast::getRoomRevenue)
                    .filter(Objects::nonNull).toList();
            int effectiveDaysForRoomRevenue = roomRevenueList.size();
            BigDecimal roomRevenueTotal = roomRevenueList.stream().reduce(BigDecimal::add).orElse(null);

            valueDTOList.forEach(item -> {
                int category = item.getCategory();
                BigDecimal lastYear = null;
                if (category == GmwrMonthEndEnum.OCC_PERCENT.getCategory()) {
                    lastYear = calculateOccPercent(rooms, roomsSoldTotal, effectiveDaysForRoomsSold, roundingMode);
                } else if (category == GmwrMonthEndEnum.REV_PAR.getCategory()) {
                    lastYear = calculateRevPar(rooms, roomRevenueTotal, effectiveDaysForRoomRevenue, roundingMode);
                } else if (category == GmwrMonthEndEnum.ADR.getCategory()) {
                    lastYear = calculateAdr(roomRevenueTotal, roomsSoldTotal, roundingMode);
                } else if (category == GmwrMonthEndEnum.ROOM_REVENUE.getCategory()) {
                    lastYear = roomRevenueTotal;
                } else if (category == GmwrMonthEndEnum.FB_REVENUE.getCategory()) {
                    if (Objects.nonNull(managerFlash)) {
                        lastYear = managerFlash.getFbRevenue();
                    }
                } else if (category == GmwrMonthEndEnum.OTHERS_REVENUE.getCategory()) {
                    if (Objects.nonNull(managerFlash)) {
                        lastYear = managerFlash.getOthersRevenue();
                    }
                }
                item.setLastYear(new GmwrBasicValueDetailDTO().setValue(lastYear));
            });
        }

        /**
         * 计算 OCC %
         *
         * @param rooms        房间数
         * @param roomsSold    售出房间数
         * @param days         天数
         * @param roundingMode 舍入模式
         * @return java.math.BigDecimal
         * @author zc
         * @date 2024/10/16
         */
        private BigDecimal calculateOccPercent(Integer rooms, Long roomsSold, int days, RoundingMode roundingMode) {
            if (days == 0) {
                return null;
            }
            if (Objects.isNull(rooms) || rooms == 0) {
                return null;
            }
            if (Objects.isNull(roomsSold)) {
                return null;
            }
            return new BigDecimal(String.valueOf(roomsSold)).multiply(new BigDecimal("100"))
                    .divide(new BigDecimal(String.valueOf(rooms)).multiply(new BigDecimal(String.valueOf(days))),
                            BaseConstant.DATA_PRECISION, roundingMode);
        }

        /**
         * 计算 Rev Par
         *
         * @param rooms        房间数
         * @param roomRevenue  房间收入
         * @param days         天数
         * @param roundingMode 舍入模式
         * @return java.math.BigDecimal
         * @author zc
         * @date 2024/10/16
         */
        private BigDecimal calculateRevPar(Integer rooms, BigDecimal roomRevenue, int days, RoundingMode roundingMode) {
            if (days == 0) {
                return null;
            }
            if (Objects.isNull(rooms) || rooms == 0) {
                return null;
            }
            if (Objects.isNull(roomRevenue)) {
                return null;
            }
            return roomRevenue.divide(new BigDecimal(String.valueOf(rooms))
                    .multiply(new BigDecimal(String.valueOf(days))), BaseConstant.DATA_PRECISION, roundingMode);
        }

        /**
         * 计算 ADR
         *
         * @param roomRevenue  房间收入
         * @param roomsSold    售出房间数
         * @param roundingMode 舍入模式
         * @return java.math.BigDecimal
         * @author zc
         * @date 2024/10/16
         */
        private BigDecimal calculateAdr(BigDecimal roomRevenue, Long roomsSold, RoundingMode roundingMode) {
            if (Objects.isNull(roomsSold) || roomsSold == 0) {
                return null;
            }
            if (Objects.isNull(roomRevenue)) {
                return null;
            }
            return roomRevenue.divide(new BigDecimal(String.valueOf(roomsSold)), BaseConstant.DATA_PRECISION, roundingMode);
        }

        /**
         * 生成 BasicValueDTO 初始值
         *
         * @return List<GmwrMonthEndBasicValueDTO>
         * @author zc
         * @date 2025/3/1
         */
        private List<GmwrMonthEndBasicDetailDTO> generateInitBasicValueDTOList() {
            return Lists.newArrayList(GmwrMonthEndEnum.OCC_PERCENT, GmwrMonthEndEnum.REV_PAR,
                    GmwrMonthEndEnum.ADR, GmwrMonthEndEnum.ROOM_REVENUE, GmwrMonthEndEnum.FB_REVENUE,
                    GmwrMonthEndEnum.OTHERS_REVENUE).stream().map(item -> {
                GmwrMonthEndBasicDetailDTO basicValueDTO = new GmwrMonthEndBasicDetailDTO();
                basicValueDTO.setCategory(item.getCategory());
                return basicValueDTO;
            }).toList();
        }

    }