# BudgetDatabaseCalculateRevenueAndRoomService.java

    package cloud.wconnect.rateshop.service.budgetdatabase.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.InternationalizationUtils;
    import cloud.wconnect.commonlib.util.NumberUtil;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.CalculateMethodEnum;
    import cloud.wconnect.rateshop.cons.budgetdatabase.FbClassificationEnum;
    import cloud.wconnect.rateshop.cons.budgetdatabase.OtherClassificationEnum;
    import cloud.wconnect.rateshop.cons.budgetdatabase.RoomClassificationEnum;
    import cloud.wconnect.rateshop.entity.budgetdatabase.*;
    import cloud.wconnect.rateshop.pojo.bo.budgetdatabase.*;
    import cloud.wconnect.rateshop.service.budgetdatabase.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.time.YearMonth;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     *
     * @author: yyt
     * 2025-11-10
     */
    @Slf4j
    @Service
    @DS("rate_shop")
    public class BudgetDatabaseCalculateRevenueAndRoomService implements IBudgetDatabaseCalculateRevenueAndRoomService {

        @Lazy
        @Autowired
        private IBudgetDatabaseConfigVariableService budgetDatabaseConfigVariableService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigVariableRoomService budgetDatabaseConfigVariableRoomService;
        @Autowired
        private IBudgetDatabaseConfigVariableFbService budgetDatabaseConfigVariableFbService;
        @Autowired
        private IBudgetDatabaseConfigVariableOtherService budgetDatabaseConfigVariableOtherService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabItemService budgetDatabaseConfigRoomTabItemService;
        @Autowired
        private IBudgetDatabaseConfigRoomTabService budgetDatabaseConfigRoomTabService;
        @Autowired
        private IBudgetDatabaseConfigFbTabService budgetDatabaseConfigFbTabService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabService budgetDatabaseConfigOtherTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigFbTabItemService budgetDatabaseConfigFbTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigOtherTabItemService budgetDatabaseConfigOtherTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Lazy
        @Autowired
        private IBudgetDatabaseRoomOtherService budgetDatabaseRoomOtherService;
        @Lazy
        @Autowired
        private IBudgetDatabaseRoomService budgetDatabaseRoomService;
        @Lazy
        @Autowired
        private IBudgetDatabaseFbRevenueService budgetDatabaseFbRevenueService;
        @Lazy
        @Autowired
        private IBudgetDatabaseOtherRevenueService budgetDatabaseOtherRevenueService;
        @Autowired
        private IBudgetDatabaseFbCoversService budgetDatabaseFbCoversService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private BeanMapper beanMapper;

        /**
         * 计算 revenue 数据
         *
         * @param propertyId 资产 id
         * @param budgetYear 预算年份
         * @param variableIds 变量 id
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateBudgetRevenue(Integer propertyId, Integer budgetYear, List<Integer> variableIds) {

            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            List<Integer> canUpdateBudgetDataMonths = budgetDatabaseService.listCanUpdateBudgetDataMonths(propertyDTO.getId(), budgetYear);
            if (CollectionUtils.isEmpty(canUpdateBudgetDataMonths)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            // 当前指标作为 source，向上查找顶层 consumer
            List<BudgetDatabaseConfigRoomTabItem> topRoomItems = new ArrayList<>();
            List<BudgetDatabaseConfigFbTabItem> topFbItems = new ArrayList<>();
            List<BudgetDatabaseConfigOtherTabItem> topOtherItems = new ArrayList<>();
            summaryTopConsumerForEnableRevenue(propertyId, budgetYear, variableIds, topRoomItems, topFbItems, topOtherItems, number);

            // 计算每个层级的 consumer
            calculateRoomForEnableRevenue(propertyDTO, budgetYear, topRoomItems, canUpdateBudgetDataMonths);
            calculateFbRevenue(propertyDTO, budgetYear, topFbItems, canUpdateBudgetDataMonths);
            calculateOtherRevenue(propertyDTO, budgetYear, topOtherItems, canUpdateBudgetDataMonths);
        }

        /**
         * 计算 room 数据
         *
         * @param propertyId 资产 id
         * @param budgetYear 预算年份
         * @param variableIds 变量 id
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateBudgetRoom(Integer propertyId, Integer budgetYear, List<Integer> variableIds) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            List<Integer> canUpdateBudgetDataMonths = budgetDatabaseService.listCanUpdateBudgetDataMonths(propertyId, budgetYear);
            if (CollectionUtils.isEmpty(canUpdateBudgetDataMonths)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            List<BudgetDatabaseConfigRoomTabItem> topRoomItemsForEnableRoom = new ArrayList<>();
            List<BudgetDatabaseConfigFbTabItem> topFbItemsForEnableRoom = new ArrayList<>();
            summaryTopConsumerForEnableRoom(propertyId, budgetYear, variableIds, topRoomItemsForEnableRoom, topFbItemsForEnableRoom, number);

            calculateRoomForEnableRoom(propertyDTO, budgetYear, topRoomItemsForEnableRoom, canUpdateBudgetDataMonths);
            calculateFbRoom(propertyDTO, budgetYear, topFbItemsForEnableRoom, canUpdateBudgetDataMonths);
        }

        /**
         * 汇总 source
         *
         * @param budgetYear        年
         * @param topRoomItems      room item
         * @param topFbItems        fb item
         * @param topOtherItems     other item
         * @param allRoomItems      all room item
         * @param allFbItems        all fb item
         * @param allOtherItems     all other item
         * @param allVariableRooms  all variable room
         * @param allVariableFbs    all variable fb
         * @param allVariableOthers all variable other
         * @param allRooms          all room 数据
         * @param allRoomOthers     all room other 数据
         * @param allFbRevenues     all fb 数据
         * @param allOtherRevenues  all other 数据
         * @param number            国际化
         * @author yyt
         * @date 2025/11/26
         **/
        private void summarySourceForEnableRevenue(Integer budgetYear,List<BudgetDatabaseConfigRoomTabItem> topRoomItems,
                                                   List<BudgetDatabaseConfigFbTabItem> topFbItems,
                                                   List<BudgetDatabaseConfigOtherTabItem> topOtherItems,
                                                   List<BudgetDatabaseConfigRoomTabItem> allRoomItems,
                                                   List<BudgetDatabaseConfigFbTabItem> allFbItems,
                                                   List<BudgetDatabaseConfigOtherTabItem> allOtherItems,
                                                   List<BudgetDatabaseConfigVariableRoom> allVariableRooms,
                                                   List<BudgetDatabaseConfigVariableFb> allVariableFbs,
                                                   List<BudgetDatabaseConfigVariableOther> allVariableOthers,
                                                   List<BudgetDatabaseRoom> allRooms,
                                                   List<BudgetDatabaseRoomOther> allRoomOthers,
                                                   List<BudgetDatabaseFbRevenue> allFbRevenues,
                                                   List<BudgetDatabaseOtherRevenue> allOtherRevenues,
                                                   InternationalizationNumberDTO number) {
            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId = new HashMap<>();
            Set<String> variableIds = new HashSet<>();
            if (CollectionUtils.isNotEmpty(topRoomItems)) {
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : topRoomItems) {
                    // 递归向下查找当前指标关联变量配置所依赖的指标
                    List<BudgetDatabaseConfigRoomTabItem> roomItems = new ArrayList<>();
                    List<BudgetDatabaseConfigFbTabItem> fbItems = new ArrayList<>();
                    List<BudgetDatabaseConfigOtherTabItem> otherItems = new ArrayList<>();
                    Integer variableId = roomTabItem.getBudgetRevenueVariableId();
                    summaryConfigForEnableRevenue(budgetYear, variableId, roomItems, fbItems, otherItems, bosByVariableId, number, variableIds, allRoomItems,
                            allFbItems, allOtherItems, allVariableRooms, allVariableFbs, allVariableOthers, allRooms, allRoomOthers, allFbRevenues, allOtherRevenues);
                    roomTabItem.setBosForRevenue(bosByVariableId.getOrDefault(variableId, Collections.emptyList()));
                }
            }

            if (CollectionUtils.isNotEmpty(topFbItems)) {
                for (BudgetDatabaseConfigFbTabItem fbTabItem : topFbItems) {
                    // 递归向下查找当前指标关联变量配置所依赖的指标
                    List<BudgetDatabaseConfigRoomTabItem> roomItems = new ArrayList<>();
                    List<BudgetDatabaseConfigFbTabItem> fbItems = new ArrayList<>();
                    List<BudgetDatabaseConfigOtherTabItem> otherItems = new ArrayList<>();
                    Integer variableId = fbTabItem.getBudgetRevenueVariableId();
                    summaryConfigForEnableRevenue(budgetYear, variableId, roomItems, fbItems, otherItems, bosByVariableId, number,variableIds, allRoomItems,
                            allFbItems, allOtherItems, allVariableRooms, allVariableFbs, allVariableOthers, allRooms, allRoomOthers, allFbRevenues, allOtherRevenues);
                    fbTabItem.setBosForRevenue(bosByVariableId.getOrDefault(variableId, Collections.emptyList()));
                }
            }

            if (CollectionUtils.isNotEmpty(topOtherItems)) {
                for (BudgetDatabaseConfigOtherTabItem otherTabItem : topOtherItems) {
                    // 递归向下查找当前指标关联变量配置所依赖的指标
                    List<BudgetDatabaseConfigRoomTabItem> roomItems = new ArrayList<>();
                    List<BudgetDatabaseConfigFbTabItem> fbItems = new ArrayList<>();
                    List<BudgetDatabaseConfigOtherTabItem> otherItems = new ArrayList<>();
                    Integer variableId = otherTabItem.getBudgetRevenueVariableId();
                    summaryConfigForEnableRevenue(budgetYear, variableId, roomItems, fbItems, otherItems, bosByVariableId, number,variableIds, allRoomItems,
                            allFbItems, allOtherItems, allVariableRooms, allVariableFbs, allVariableOthers, allRooms, allRoomOthers, allFbRevenues, allOtherRevenues);
                    otherTabItem.setBos(bosByVariableId.getOrDefault(variableId, Collections.emptyList()));
                }
            }
        }

        /**
         * 汇总顶层 consumer
         *
         * @param variableIds   变量 id 列表
         * @param topRoomItems  顶层 room 指标
         * @param topFbItems    顶层 fb 指标
         * @param topOtherItems 顶层 other 指标
         * @author yyt
         * @date 2025/11/13
         **/
        private void summaryTopConsumerForEnableRevenue(Integer propertyId, Integer budgetYear, List<Integer> variableIds, List<BudgetDatabaseConfigRoomTabItem> topRoomItems,
                                                        List<BudgetDatabaseConfigFbTabItem> topFbItems, List<BudgetDatabaseConfigOtherTabItem> topOtherItems,
                                                        InternationalizationNumberDTO number) {
            // 查询 room / fb / other 配置
            List<Integer> roomTabIds = budgetDatabaseConfigRoomTabService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigRoomTab::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = budgetDatabaseConfigRoomTabItemService.listByTabIds(roomTabIds);

            List<Integer> fbTabIds = budgetDatabaseConfigFbTabService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigFbTab::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigFbTabItem> fbTabItems = budgetDatabaseConfigFbTabItemService.listByTabIds(fbTabIds);

            List<Integer> otherTabIds = budgetDatabaseConfigOtherTabService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigOtherTab::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigOtherTabItem> otherTabItems = budgetDatabaseConfigOtherTabItemService.listByTabIds(otherTabIds);

            // 查询 room / fb / other 变量配置
            List<Integer> allVariableIds = budgetDatabaseConfigVariableService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigVariable::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigVariableRoom> variableRooms = budgetDatabaseConfigVariableRoomService.listByVariableIds(allVariableIds);
            List<BudgetDatabaseConfigVariableFb> variableFbs = budgetDatabaseConfigVariableFbService.listByVariableIds(allVariableIds);
            List<BudgetDatabaseConfigVariableOther> variableOthers = budgetDatabaseConfigVariableOtherService.listByVariableIds(allVariableIds);

            // 查询 room / fb / other 数据
            List<BudgetDatabaseRoom> allRooms = budgetDatabaseRoomService.listByTabIds(roomTabIds);
            List<BudgetDatabaseRoomOther> allRoomOthers = budgetDatabaseRoomOtherService.listByTabIds(roomTabIds);
            List<BudgetDatabaseFbRevenue> allFbRevenues = budgetDatabaseFbRevenueService.listByTabIds(fbTabIds);
            List<BudgetDatabaseOtherRevenue> allOtherRevenues = budgetDatabaseOtherRevenueService.listByTabIds(otherTabIds);

            if (CollectionUtils.isNotEmpty(variableIds)) {
                // 防循环依赖
                Set<Integer> visited = new HashSet<>();
                for (Integer variableId : variableIds) {
                    summaryTopConsumerForRevenue(variableId, topRoomItems, topFbItems, topOtherItems, visited, roomTabItems, fbTabItems, otherTabItems,
                            variableRooms, variableFbs, variableOthers);
                }
            }

            // 顶层 consumer 向下找所有的 source
            summarySourceForEnableRevenue(budgetYear,topRoomItems, topFbItems, topOtherItems, roomTabItems, fbTabItems,
                    otherTabItems, variableRooms, variableFbs, variableOthers, allRooms, allRoomOthers, allFbRevenues, allOtherRevenues, number);
        }

        /**
         * 汇总顶层 consumer
         *
         * @param variableId        变量 id
         * @param topRoomItems      room item
         * @param topFbItems        fb item
         * @param topOtherItems     other item
         * @param visited           访问过的变量 id
         * @param allRoomTabItems   all room item
         * @param allFbTabItems     all fb item
         * @param allOtherTabItems  all other item
         * @param allVariableRooms  all variable room
         * @param allVariableFbs    all variable fb
         * @param allVariableOthers all variable other
         * @author yyt
         * @date 2025/11/26
         **/
        private void summaryTopConsumerForRevenue(Integer variableId, List<BudgetDatabaseConfigRoomTabItem> topRoomItems,
                                                  List<BudgetDatabaseConfigFbTabItem> topFbItems, List<BudgetDatabaseConfigOtherTabItem> topOtherItems,
                                                  Set<Integer> visited, List<BudgetDatabaseConfigRoomTabItem> allRoomTabItems,
                                                  List<BudgetDatabaseConfigFbTabItem> allFbTabItems, List<BudgetDatabaseConfigOtherTabItem> allOtherTabItems,
                                                  List<BudgetDatabaseConfigVariableRoom> allVariableRooms, List<BudgetDatabaseConfigVariableFb> allVariableFbs,
                                                  List<BudgetDatabaseConfigVariableOther> allVariableOthers) {
            if (visited.contains(variableId)) {
                return;
            }
            visited.add(variableId);

            // 找引用当前变量配置的 room / fb / other
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = allRoomTabItems.stream()
                    .filter(item -> item.getEnableBudgetRevenue() && item.getBudgetRevenueVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isNotEmpty(roomTabItems)) {
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : roomTabItems) {
                    // 如果指标在变量配置里面，则继续往上找，如果指标不在变量配置里面，则直接添加到顶层 consumer 中
                    List<Integer> variableIds = allVariableRooms.stream()
                            .filter(item -> item.getItemId().equals(roomTabItem.getId()))
                            .map(BudgetDatabaseConfigVariableRoom::getVariableId)
                            .toList();
                    if (CollectionUtils.isNotEmpty(variableIds)) {
                        for (Integer id : variableIds) {
                            summaryTopConsumerForRevenue(id, topRoomItems, topFbItems, topOtherItems, visited, allRoomTabItems, allFbTabItems, allOtherTabItems,
                                    allVariableRooms, allVariableFbs, allVariableOthers);
                        }
                    }
                    if (!topRoomItems.contains(roomTabItem)) {
                        topRoomItems.add(roomTabItem);
                    }
                }
            }
            List<BudgetDatabaseConfigFbTabItem> fbTabItems = allFbTabItems.stream()
                    .filter(item -> item.getEnableBudgetRevenue() && item.getBudgetRevenueVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isNotEmpty(fbTabItems)) {
                for (BudgetDatabaseConfigFbTabItem fbTabItem : fbTabItems) {
                    // 如果指标在变量配置里面，则继续往上找，如果指标不在变量配置里面，则直接添加到顶层 consumer 中
                    List<Integer> variableIds = allVariableFbs.stream()
                            .filter(item -> item.getItemId().equals(fbTabItem.getId()))
                            .map(BudgetDatabaseConfigVariableFb::getVariableId)
                            .toList();
                    if (CollectionUtils.isNotEmpty(variableIds)) {
                        for (Integer id : variableIds) {
                            summaryTopConsumerForRevenue(id, topRoomItems, topFbItems, topOtherItems, visited, allRoomTabItems, allFbTabItems, allOtherTabItems,
                                    allVariableRooms, allVariableFbs, allVariableOthers);
                        }
                    }
                    if (!topFbItems.contains(fbTabItem)) {
                        topFbItems.add(fbTabItem);
                    }
                }
            }
            List<BudgetDatabaseConfigOtherTabItem> otherTabItems = allOtherTabItems.stream()
                    .filter(item -> item.getEnableBudgetRevenue() && item.getBudgetRevenueVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isNotEmpty(otherTabItems)) {
                for (BudgetDatabaseConfigOtherTabItem otherTabItem : otherTabItems) {
                    // 如果指标在变量配置里面，则继续往上找，如果指标不在变量配置里面，则直接添加到顶层 consumer 中
                    List<Integer> variableIds = allVariableOthers.stream()
                            .filter(item -> item.getItemId().equals(otherTabItem.getId()))
                            .map(BudgetDatabaseConfigVariableOther::getVariableId)
                            .toList();
                    if (CollectionUtils.isNotEmpty(variableIds)) {
                        for (Integer id : variableIds) {
                            summaryTopConsumerForRevenue(id, topRoomItems, topFbItems, topOtherItems, visited, allRoomTabItems, allFbTabItems, allOtherTabItems,
                                    allVariableRooms, allVariableFbs, allVariableOthers);
                        }
                    }
                    if (!topOtherItems.contains(otherTabItem)) {
                        topOtherItems.add(otherTabItem);
                    }
                }
            }
        }

        /**
         * 汇总顶层 consumer
         *
         * @param variableIds   变量 id 列表
         * @param topRoomItems  顶层 room 指标
         * @param topFbItems    顶层 fb 指标
         * @author yyt
         * @date 2025/11/13
         **/
        private void summaryTopConsumerForEnableRoom(Integer propertyId, Integer budgetYear, List<Integer> variableIds, List<BudgetDatabaseConfigRoomTabItem> topRoomItems,
                                                     List<BudgetDatabaseConfigFbTabItem> topFbItems, InternationalizationNumberDTO number) {
            // 查询 room / fb 配置
            List<Integer> roomTabIds = budgetDatabaseConfigRoomTabService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigRoomTab::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = budgetDatabaseConfigRoomTabItemService.listByTabIds(roomTabIds);
            List<Integer> fbTabIds = budgetDatabaseConfigFbTabService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigFbTab::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigFbTabItem> fbTabItems = budgetDatabaseConfigFbTabItemService.listByTabIds(fbTabIds);
            // 查询 room / fb 变量配置
            List<Integer> allVariableIds = budgetDatabaseConfigVariableService.list(propertyId, budgetYear)
                    .stream().map(BudgetDatabaseConfigVariable::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigVariableRoom> variableRooms = budgetDatabaseConfigVariableRoomService.listByVariableIds(allVariableIds);
            // 查询 room 数据
            List<BudgetDatabaseRoom> allRooms = budgetDatabaseRoomService.listByTabIds(roomTabIds);

            if (CollectionUtils.isNotEmpty(variableIds)) {
                // 防循环依赖
                Set<Integer> visited = new HashSet<>();
                for (Integer variableId : variableIds) {
                    summaryTopConsumerForRoom(variableId, topRoomItems, topFbItems, visited, roomTabItems, fbTabItems, variableRooms);
                }
            }

            // 顶层 consumer 向下找所有的 source
            summarySourceForEnableRoom(budgetYear, topRoomItems, topFbItems, roomTabItems, variableRooms, allRooms, number);
        }

        /**
         * 汇总顶层 consumer
         *
         * @param variableId        变量 id
         * @param topRoomItems      room item
         * @param topFbItems        fb item
         * @param visited           访问过的变量 id
         * @param allRoomTabItems   all room item
         * @param allFbTabItems     all fb item
         * @param allVariableRooms  all variable room
         * @author yyt
         * @date 2025/11/26
         **/
        private void summaryTopConsumerForRoom(Integer variableId, List<BudgetDatabaseConfigRoomTabItem> topRoomItems,
                                               List<BudgetDatabaseConfigFbTabItem> topFbItems,
                                               Set<Integer> visited, List<BudgetDatabaseConfigRoomTabItem> allRoomTabItems,
                                               List<BudgetDatabaseConfigFbTabItem> allFbTabItems, List<BudgetDatabaseConfigVariableRoom> allVariableRooms) {
            if (visited.contains(variableId)) {
                return;
            }
            visited.add(variableId);

            // 找引用当前变量配置的 room / fb
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = allRoomTabItems.stream()
                    .filter(item -> item.getEnableBudgetRoom() && item.getBudgetRoomVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isNotEmpty(roomTabItems)) {
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : roomTabItems) {
                    // 如果指标在变量配置里面，则继续往上找，如果指标不在变量配置里面，则直接添加到顶层 consumer 中
                    List<Integer> variableIds = allVariableRooms.stream()
                            .filter(item -> item.getItemId().equals(roomTabItem.getId()))
                            .map(BudgetDatabaseConfigVariableRoom::getVariableId)
                            .toList();
                    if (CollectionUtils.isNotEmpty(variableIds)) {
                        for (Integer id : variableIds) {
                            summaryTopConsumerForRoom(id, topRoomItems, topFbItems, visited, allRoomTabItems, allFbTabItems, allVariableRooms);
                        }
                    }
                    if (!topRoomItems.contains(roomTabItem)) {
                        topRoomItems.add(roomTabItem);
                    }
                }
            }

            List<BudgetDatabaseConfigFbTabItem> fbTabItems = allFbTabItems.stream()
                    .filter(item -> item.getEnableBudgetRoom() && item.getBudgetRoomVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isNotEmpty(fbTabItems)) {
                for (BudgetDatabaseConfigFbTabItem fbTabItem : fbTabItems) {
                    // 引用房间数的只有 fb covers，fb covers 业务上限制不会出现在变量配置下，所以不需要再往上找 consumer
                    if (!topFbItems.contains(fbTabItem)) {
                        topFbItems.add(fbTabItem);
                    }
                }
            }
        }

        /**
         * 汇总 source
         *
         * @param budgetYear       年
         * @param topRoomItems     room item
         * @param topFbItems       fb item
         * @param allRoomItems     all room item
         * @param allVariableRooms all variable room
         * @param allRooms         all room 数据
         * @param number           国际化
         * @author yyt
         * @date 2025/11/26
         **/
        private void summarySourceForEnableRoom(Integer budgetYear,List<BudgetDatabaseConfigRoomTabItem> topRoomItems, List<BudgetDatabaseConfigFbTabItem> topFbItems,
                                                List<BudgetDatabaseConfigRoomTabItem> allRoomItems,
                                                List<BudgetDatabaseConfigVariableRoom> allVariableRooms,
                                                List<BudgetDatabaseRoom> allRooms,
                                                InternationalizationNumberDTO number) {
            Map<Integer, List<BudgetDataBaseCalculateChildRoomBO>> bosByVariableId = new HashMap<>();
            Set<String> variableIds = new HashSet<>();
            if (CollectionUtils.isNotEmpty(topRoomItems)) {
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : topRoomItems) {
                    // 递归向下查找当前指标关联变量配置所依赖的指标
                    List<BudgetDatabaseConfigRoomTabItem> roomItemsForRoom = new ArrayList<>();
                    Integer variableId = roomTabItem.getBudgetRoomVariableId();
                    summaryConfigForEnableRoom(budgetYear, variableId, roomItemsForRoom, bosByVariableId, variableIds, allRoomItems, allVariableRooms, allRooms, number);
                    roomTabItem.setBosForRooms(bosByVariableId.getOrDefault(variableId, Collections.emptyList()));
                }
            }

            if (CollectionUtils.isNotEmpty(topFbItems)) {
                for (BudgetDatabaseConfigFbTabItem fbTabItem : topFbItems) {
                    // 递归向下查找当前指标关联变量配置所依赖的指标
                    List<BudgetDatabaseConfigRoomTabItem> roomItemsForRoom = new ArrayList<>();
                    Integer variableId = fbTabItem.getBudgetRoomVariableId();
                    summaryConfigForEnableRoom(budgetYear, variableId, roomItemsForRoom, bosByVariableId, variableIds, allRoomItems, allVariableRooms, allRooms, number);
                    fbTabItem.setBosForRoom(bosByVariableId.getOrDefault(variableId, Collections.emptyList()));
                }
            }
        }

        /**
         * 批量计算 fb 分类指标的 revenue 数据
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param fbItems     fb 配置
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateFbRevenue(PropertyDTO propertyDTO, Integer budgetYear, List<BudgetDatabaseConfigFbTabItem> fbItems, List<Integer> canUpdateBudgetDataMonths) {
            if (CollectionUtils.isEmpty(fbItems)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            Map<Integer, List<BudgetDatabaseConfigFbTabItem>> fbItemsByClassification = fbItems.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigFbTabItem::getClassification));
            List<BudgetDatabaseConfigFbTabItem> revenueItems = fbItemsByClassification.getOrDefault(FbClassificationEnum.REVENUE.getCode(), Collections.emptyList());
            if (CollectionUtils.isNotEmpty(revenueItems)) {
                // 查询已存在的数据
                Map<Integer, BudgetDatabaseFbRevenue> existRevenueByRoomId = budgetDatabaseFbRevenueService.listByItemIds(revenueItems.stream().map(BudgetDatabaseConfigFbTabItem::getId).collect(Collectors.toList()))
                        .stream().collect(Collectors.toMap(BudgetDatabaseFbRevenue::getFbId, v -> v));

                int size = revenueItems.size();
                List<BudgetDatabaseFbRevenue> insert = Lists.newArrayListWithExpectedSize(size);
                List<BudgetDatabaseFbRevenue> updates = Lists.newArrayListWithExpectedSize(size);

                for (BudgetDatabaseConfigFbTabItem item : revenueItems) {
                    BudgetDatabaseFbRevenue existData = existRevenueByRoomId.get(item.getId());
                    BudgetDataBaseCalculateRevenueBO calculateRevenueBO = bosToCalculateOneYearRevenueBO(item.getBosForRevenue());
                    CalculateOneYearRevenueBO calculateOneYearRevenueBO = calculateOneYearData(budgetYear, item.getBudgetRevenueInputValue(), item.getBudgetRevenueCalculateMethod(),
                            canUpdateBudgetDataMonths, calculateRevenueBO, Objects.isNull(existData) ? null : beanMapper.map(existData, CalculateOneYearRevenueBO.class), number);
                    BudgetDatabaseFbRevenue revenue = beanMapper.map(calculateOneYearRevenueBO, BudgetDatabaseFbRevenue.class);
                    if (Objects.nonNull(existData)) {
                        revenue.setId(existData.getId());
                        updates.add(revenue);
                    } else {
                        revenue.setPropertyId(propertyDTO.getId());
                        revenue.setTabId(item.getFbTabId());
                        revenue.setFbId(item.getId());
                        insert.add(revenue);
                    }
                }

                if (CollectionUtils.isNotEmpty(insert)) {
                    budgetDatabaseFbRevenueService.saveBatch(insert);
                }
                if (CollectionUtils.isNotEmpty(updates)) {
                    budgetDatabaseFbRevenueService.updateBatchByIdRewrite(updates);
                }
            }

            List<BudgetDatabaseConfigFbTabItem> coverItems = fbItemsByClassification.getOrDefault(FbClassificationEnum.COVERS.getCode(), Collections.emptyList());
            if (CollectionUtils.isNotEmpty(coverItems)) {
                // 查询已存在的数据
                Map<Integer, BudgetDatabaseFbCovers> existRevenueByRoomId = budgetDatabaseFbCoversService.listByItemIds(coverItems.stream().map(BudgetDatabaseConfigFbTabItem::getId).collect(Collectors.toList()))
                        .stream().collect(Collectors.toMap(BudgetDatabaseFbCovers::getFbId, v -> v));

                int size = coverItems.size();
                List<BudgetDatabaseFbCovers> insert = Lists.newArrayListWithExpectedSize(size);
                List<BudgetDatabaseFbCovers> updates = Lists.newArrayListWithExpectedSize(size);

                for (BudgetDatabaseConfigFbTabItem item : coverItems) {
                    BudgetDatabaseFbCovers existData = existRevenueByRoomId.get(item.getId());
                    BudgetDataBaseCalculateRevenueBO calculateRevenueBO = bosToCalculateOneYearRevenueBO(item.getBosForRevenue());
                    CalculateOneYearBO calculateOneYearBO = calculateOneYearDataForCoversRevenue(budgetYear, item.getBudgetRevenueInputValue(), item.getBudgetRevenueCalculateMethod(),
                            canUpdateBudgetDataMonths, calculateRevenueBO, Objects.isNull(existData) ? null : beanMapper.map(existData, CalculateOneYearBO.class), number);
                    BudgetDatabaseFbCovers revenue = beanMapper.map(calculateOneYearBO, BudgetDatabaseFbCovers.class);
                    if (Objects.nonNull(existData)) {
                        revenue.setId(existData.getId());
                        updates.add(revenue);
                    } else {
                        revenue.setPropertyId(propertyDTO.getId());
                        revenue.setTabId(item.getFbTabId());
                        revenue.setFbId(item.getId());
                        insert.add(revenue);
                    }
                }

                if (CollectionUtils.isNotEmpty(insert)) {
                    budgetDatabaseFbCoversService.saveBatch(insert);
                }
                if (CollectionUtils.isNotEmpty(updates)) {
                    budgetDatabaseFbCoversService.updateBatchByIdRewrite(updates);
                }
            }
        }

        /**
         * 计算 other 分类指标的 revenue 数据
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param otherItems  other 指标
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateOtherRevenue(PropertyDTO propertyDTO, Integer budgetYear, List<BudgetDatabaseConfigOtherTabItem> otherItems, List<Integer> canUpdateBudgetDataMonths) {
            if (CollectionUtils.isEmpty(otherItems)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            // 查询已存在的数据
            Map<Integer, BudgetDatabaseOtherRevenue> existRevenueByRoomId = budgetDatabaseOtherRevenueService.listByItemIds(otherItems.stream().map(BudgetDatabaseConfigOtherTabItem::getId).collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseOtherRevenue::getOtherId, v -> v));

            int size = otherItems.size();
            List<BudgetDatabaseOtherRevenue> insert = Lists.newArrayListWithExpectedSize(size);
            List<BudgetDatabaseOtherRevenue> updates = Lists.newArrayListWithExpectedSize(size);

            for (BudgetDatabaseConfigOtherTabItem item : otherItems) {
                BudgetDatabaseOtherRevenue existData = existRevenueByRoomId.get(item.getId());
                BudgetDataBaseCalculateRevenueBO calculateRevenueBO = bosToCalculateOneYearRevenueBO(item.getBos());
                CalculateOneYearRevenueBO calculateOneYearRevenueBO = calculateOneYearData(budgetYear, item.getBudgetRevenueInputValue(), item.getBudgetRevenueCalculateMethod(),
                        canUpdateBudgetDataMonths, calculateRevenueBO, Objects.isNull(existData) ? null : beanMapper.map(existData, CalculateOneYearRevenueBO.class), number);
                BudgetDatabaseOtherRevenue revenue = beanMapper.map(calculateOneYearRevenueBO, BudgetDatabaseOtherRevenue.class);
                if (Objects.nonNull(existData)) {
                    revenue.setId(existData.getId());
                    updates.add(revenue);
                } else {
                    revenue.setPropertyId(propertyDTO.getId());
                    revenue.setTabId(item.getOtherTabId());
                    revenue.setOtherId(item.getId());
                    insert.add(revenue);
                }
            }

            if (CollectionUtils.isNotEmpty(insert)) {
                budgetDatabaseOtherRevenueService.saveBatch(insert);
            }
            if (CollectionUtils.isNotEmpty(updates)) {
                budgetDatabaseOtherRevenueService.updateBatchByIdRewrite(updates);
            }
        }

        /**
         * 计算 room revenue
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param roomItems   room 指标
         * @author yyt
         * @date 2025/11/12
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateRoomForEnableRoomAndRevenue(PropertyDTO propertyDTO, Integer budgetYear,List<BudgetDatabaseConfigRoomTabItem> roomItems, List<Integer> canUpdateBudgetDataMonths) {
            if (CollectionUtils.isEmpty(roomItems)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
            // 查询已存在的数据
            if (CollectionUtils.isNotEmpty(roomItems)) {
                Map<Integer, Map<Integer, BudgetDatabaseRoom>> existOtherRoomByYearMonth = budgetDatabaseRoomService.listByItemIds(roomItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()))
                        .stream().collect(Collectors.groupingBy(BudgetDatabaseRoom::getYearMonth, Collectors.toMap(BudgetDatabaseRoom::getRoomId, v -> v)));

                int size = roomItems.size() * canUpdateBudgetDataMonths.size();

                List<BudgetDatabaseRoom> insertRooms = Lists.newArrayListWithExpectedSize(size);
                List<BudgetDatabaseRoom> updateRooms = Lists.newArrayListWithExpectedSize(size);

                for (Integer month : canUpdateBudgetDataMonths) {

                    Integer yearMonth = Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(budgetYear, month)));
                    Map<Integer, BudgetDatabaseRoom> existRoomByRoomId = existOtherRoomByYearMonth.getOrDefault(yearMonth, Collections.emptyMap());

                    for (BudgetDatabaseConfigRoomTabItem item : roomItems) {
                        // 当前指标关联的计算指标
                        BudgetDataBaseCalculateRevenueBO boForRevenue = bosToCalculateOneYearRevenueBO(item.getBosForRevenue());
                        BudgetDataBaseCalculateRoomBO boForRoom = bosToCalculateOneYearRoomBO(item.getBosForRooms());

                        // 计算 room
                        BudgetDatabaseRoom existRoom = existRoomByRoomId.get(item.getId());
                        Boolean enableBudgetRoom = item.getEnableBudgetRoom();
                        BigDecimal roomInputValue = item.getBudgetRoomInputValue();
                        Integer roomCalculateMethod = item.getBudgetRoomCalculateMethod();
                        Long budgetRooms = enableBudgetRoom ? calculateOneMonthRoom(budgetYear, month, roomInputValue, roomCalculateMethod, boForRoom.getRoomsForCalculatingRooms(), number) :
                                Objects.nonNull(existRoom) ? existRoom.getBudgetRooms() : null;

                        // 计算 revenue
                        Boolean enableBudgetRevenue = item.getEnableBudgetRevenue();
                        BigDecimal revenueInputValue = item.getBudgetRevenueInputValue();
                        Integer revenueCalculateMethod = item.getBudgetRevenueCalculateMethod();
                        BigDecimal budgetRevenue = enableBudgetRevenue ? calculateOneMonthRevenue(budgetYear, month, revenueInputValue, revenueCalculateMethod, boForRevenue.getRooms(),
                                boForRevenue.getRoomOthers(), boForRevenue.getFbRevenues(), boForRevenue.getOtherRevenues(), number) :
                                Objects.nonNull(existRoom) ? existRoom.getBudgetRevenue() : null;
                        BigDecimal budgetAdr = null;
                        if (Objects.nonNull(budgetRooms) && Objects.nonNull(budgetRevenue)) {
                            budgetAdr = budgetRevenue.divide(new BigDecimal(budgetRooms), BaseConstant.DATA_PRECISION, roundingMode);
                        }

                        BudgetDatabaseRoom budgetDatabaseRoom = new BudgetDatabaseRoom()
                                .setPropertyId(item.getPropertyId())
                                .setTabId(item.getRoomTabId())
                                .setRoomId(item.getId())
                                .setYearMonth(yearMonth)
                                .setActualRooms(Objects.nonNull(existRoom) ? existRoom.getActualRooms() : null)
                                .setActualAdr(Objects.nonNull(existRoom) ? existRoom.getActualAdr() : null)
                                .setActualRevenue(Objects.nonNull(existRoom) ? existRoom.getActualRevenue() : null)
                                .setBudgetRooms(budgetRooms)
                                .setBudgetRevenue(budgetRevenue)
                                .setBudgetAdr(budgetAdr);

                        if (Objects.nonNull(existRoom)) {
                            budgetDatabaseRoom.setId(existRoom.getId());
                            updateRooms.add(budgetDatabaseRoom);
                        } else {
                            insertRooms.add(budgetDatabaseRoom);
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(insertRooms)) {
                    budgetDatabaseRoomService.saveBatch(insertRooms);
                }
                if (CollectionUtils.isNotEmpty(updateRooms)) {
                    budgetDatabaseRoomService.updateBatchByIdRewrite(updateRooms);
                }
            }
        }

        /**
         * 计算 revenue
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param roomItems   room 指标
         * @author yyt
         * @date 2025/11/12
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateRoomForEnableRevenue(PropertyDTO propertyDTO, Integer budgetYear,List<BudgetDatabaseConfigRoomTabItem> roomItems, List<Integer> canUpdateBudgetDataMonths) {
            if (CollectionUtils.isEmpty(roomItems)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
            // 查询已存在的数据
            Map<Integer, List<BudgetDatabaseConfigRoomTabItem>> roomItemsByClassification = roomItems.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigRoomTabItem::getClassification));
            List<BudgetDatabaseConfigRoomTabItem> otherRoomItems = roomItemsByClassification.getOrDefault(RoomClassificationEnum.OTHER.getCode(), Collections.emptyList());
            if (CollectionUtils.isNotEmpty(otherRoomItems)) {
                Map<Integer, BudgetDatabaseRoomOther> existOtherRoomByRoomId = budgetDatabaseRoomOtherService.listByItemIds(otherRoomItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()))
                        .stream().collect(Collectors.toMap(BudgetDatabaseRoomOther::getRoomId, v -> v));

                int size = otherRoomItems.size();
                List<BudgetDatabaseRoomOther> inserts = Lists.newArrayListWithExpectedSize(size);
                List<BudgetDatabaseRoomOther> updates = Lists.newArrayListWithExpectedSize(size);

                for (BudgetDatabaseConfigRoomTabItem item : otherRoomItems) {
                    BudgetDatabaseRoomOther existData = existOtherRoomByRoomId.get(item.getId());
                    CalculateOneYearRevenueBO calculateOneYearRevenueBO = calculateOneYearData(budgetYear, item.getBudgetRevenueInputValue(), item.getBudgetRevenueCalculateMethod(),
                            canUpdateBudgetDataMonths, bosToCalculateOneYearRevenueBO(item.getBosForRevenue()), Objects.isNull(existData) ? null : beanMapper.map(existData, CalculateOneYearRevenueBO.class), number);
                    BudgetDatabaseRoomOther revenue = beanMapper.map(calculateOneYearRevenueBO, BudgetDatabaseRoomOther.class);
                    if (Objects.nonNull(existData)) {
                        revenue.setId(existData.getId());
                        updates.add(revenue);
                    } else {
                        revenue.setPropertyId(propertyDTO.getId());
                        revenue.setTabId(item.getRoomTabId());
                        revenue.setRoomId(item.getId());
                        inserts.add(revenue);
                    }
                }

                if (CollectionUtils.isNotEmpty(inserts)) {
                    budgetDatabaseRoomOtherService.saveBatch(inserts);
                }
                if (CollectionUtils.isNotEmpty(updates)) {
                    budgetDatabaseRoomOtherService.updateBatchByIdRewrite(updates);
                }
            }

            // 查询已存在的数据
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = roomItemsByClassification.getOrDefault(RoomClassificationEnum.ROOM.getCode(), Collections.emptyList());
            if (CollectionUtils.isNotEmpty(roomTabItems)) {
                Map<Integer, Map<Integer, BudgetDatabaseRoom>> existOtherRoomByYearMonth = budgetDatabaseRoomService.listByItemIds(roomTabItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()))
                        .stream().collect(Collectors.groupingBy(BudgetDatabaseRoom::getYearMonth, Collectors.toMap(BudgetDatabaseRoom::getRoomId, v -> v)));

                int size = roomTabItems.size() * canUpdateBudgetDataMonths.size();

                List<BudgetDatabaseRoom> insertRooms = Lists.newArrayListWithExpectedSize(size);
                List<BudgetDatabaseRoom> updateRooms = Lists.newArrayListWithExpectedSize(size);

                for (Integer month : canUpdateBudgetDataMonths) {

                    Integer yearMonth = Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(budgetYear, month)));
                    Map<Integer, BudgetDatabaseRoom> existRoomByRoomId = existOtherRoomByYearMonth.getOrDefault(yearMonth, Collections.emptyMap());

                    for (BudgetDatabaseConfigRoomTabItem item : roomTabItems) {
                        // 当前指标关联的计算指标
                        BudgetDataBaseCalculateRevenueBO boForRevenue = bosToCalculateOneYearRevenueBO(item.getBosForRevenue());

                        // room
                        BudgetDatabaseRoom existRoom = existRoomByRoomId.get(item.getId());
                        Long budgetRooms =  Objects.nonNull(existRoom) ? existRoom.getBudgetRooms() : null;

                        // 计算 revenue
                        Boolean enableBudgetRevenue = item.getEnableBudgetRevenue();
                        BigDecimal revenueInputValue = item.getBudgetRevenueInputValue();
                        Integer revenueCalculateMethod = item.getBudgetRevenueCalculateMethod();
                        BigDecimal budgetRevenue = enableBudgetRevenue ? calculateOneMonthRevenue(budgetYear, month, revenueInputValue, revenueCalculateMethod, boForRevenue.getRooms(),
                                boForRevenue.getRoomOthers(), boForRevenue.getFbRevenues(), boForRevenue.getOtherRevenues(), number) :
                                Objects.nonNull(existRoom) ? existRoom.getBudgetRevenue() : null;
                        BigDecimal budgetAdr = null;
                        if (Objects.nonNull(budgetRooms) && Objects.nonNull(budgetRevenue)) {
                            budgetAdr = budgetRevenue.divide(new BigDecimal(budgetRooms), BaseConstant.DATA_PRECISION, roundingMode);
                        }

                        BudgetDatabaseRoom budgetDatabaseRoom = new BudgetDatabaseRoom()
                                .setPropertyId(item.getPropertyId())
                                .setTabId(item.getRoomTabId())
                                .setRoomId(item.getId())
                                .setYearMonth(yearMonth)
                                .setActualRooms(Objects.nonNull(existRoom) ? existRoom.getActualRooms() : null)
                                .setActualAdr(Objects.nonNull(existRoom) ? existRoom.getActualAdr() : null)
                                .setActualRevenue(Objects.nonNull(existRoom) ? existRoom.getActualRevenue() : null)
                                .setBudgetRooms(budgetRooms)
                                .setBudgetRevenue(budgetRevenue)
                                .setBudgetAdr(budgetAdr);

                        if (Objects.nonNull(existRoom)) {
                            budgetDatabaseRoom.setId(existRoom.getId());
                            updateRooms.add(budgetDatabaseRoom);
                        } else {
                            insertRooms.add(budgetDatabaseRoom);
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(insertRooms)) {
                    budgetDatabaseRoomService.saveBatch(insertRooms);
                }
                if (CollectionUtils.isNotEmpty(updateRooms)) {
                    budgetDatabaseRoomService.updateBatchByIdRewrite(updateRooms);
                }
            }
        }

        /**
         * 计算 room
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param roomItems   room 指标
         * @author yyt
         * @date 2025/11/12
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateRoomForEnableRoom(PropertyDTO propertyDTO, Integer budgetYear,List<BudgetDatabaseConfigRoomTabItem> roomItems, List<Integer> canUpdateBudgetDataMonths) {
            if (CollectionUtils.isEmpty(roomItems)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();
            RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
            // 查询已存在的数据
            if (CollectionUtils.isNotEmpty(roomItems)) {
                Map<Integer, Map<Integer, BudgetDatabaseRoom>> existOtherRoomByYearMonth = budgetDatabaseRoomService.listByItemIds(roomItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()))
                        .stream().collect(Collectors.groupingBy(BudgetDatabaseRoom::getYearMonth, Collectors.toMap(BudgetDatabaseRoom::getRoomId, v -> v)));

                int size = roomItems.size() * canUpdateBudgetDataMonths.size();

                List<BudgetDatabaseRoom> insertRooms = Lists.newArrayListWithExpectedSize(size);
                List<BudgetDatabaseRoom> updateRooms = Lists.newArrayListWithExpectedSize(size);

                for (Integer month : canUpdateBudgetDataMonths) {
                    Integer yearMonth = Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(budgetYear, month)));
                    Map<Integer, BudgetDatabaseRoom> existRoomByRoomId = existOtherRoomByYearMonth.getOrDefault(yearMonth, Collections.emptyMap());

                    for (BudgetDatabaseConfigRoomTabItem item : roomItems) {
                        // 当前指标关联的计算指标
                        BudgetDataBaseCalculateRoomBO boForRoom = bosToCalculateOneYearRoomBO(item.getBosForRooms());

                        // 计算 room
                        BudgetDatabaseRoom existRoom = existRoomByRoomId.get(item.getId());
                        BigDecimal roomInputValue = item.getBudgetRoomInputValue();
                        Integer roomCalculateMethod = item.getBudgetRoomCalculateMethod();
                        Long budgetRooms = calculateOneMonthRoom(budgetYear, month, roomInputValue, roomCalculateMethod, boForRoom.getRoomsForCalculatingRooms(), number);

                        // revenue
                        BigDecimal budgetRevenue = Objects.nonNull(existRoom) ? existRoom.getBudgetRevenue() : null;
                        BigDecimal budgetAdr = null;
                        if (Objects.nonNull(budgetRooms) && Objects.nonNull(budgetRevenue)) {
                            budgetAdr = budgetRevenue.divide(new BigDecimal(budgetRooms), BaseConstant.DATA_PRECISION, roundingMode);
                        }

                        BudgetDatabaseRoom budgetDatabaseRoom = new BudgetDatabaseRoom()
                                .setPropertyId(item.getPropertyId())
                                .setTabId(item.getRoomTabId())
                                .setRoomId(item.getId())
                                .setYearMonth(yearMonth)
                                .setActualRooms(Objects.nonNull(existRoom) ? existRoom.getActualRooms() : null)
                                .setActualAdr(Objects.nonNull(existRoom) ? existRoom.getActualAdr() : null)
                                .setActualRevenue(Objects.nonNull(existRoom) ? existRoom.getActualRevenue() : null)
                                .setBudgetRooms(budgetRooms)
                                .setBudgetRevenue(budgetRevenue)
                                .setBudgetAdr(budgetAdr);

                        if (Objects.nonNull(existRoom)) {
                            budgetDatabaseRoom.setId(existRoom.getId());
                            updateRooms.add(budgetDatabaseRoom);
                        } else {
                            insertRooms.add(budgetDatabaseRoom);
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(insertRooms)) {
                    budgetDatabaseRoomService.saveBatch(insertRooms);
                }
                if (CollectionUtils.isNotEmpty(updateRooms)) {
                    budgetDatabaseRoomService.updateBatchByIdRewrite(updateRooms);
                }
            }
        }

        /**
         * 计算 other 指标数据
         *
         * @param budgetYear                年份
         * @param inputValue                计算输入的值
         * @param calculateMethod           计算方式
         * @param canUpdateBudgetDataMonths 可更新预算数据的月份
         * @param calculateRevenueBO        计算数据
         * @param number                    国际化数字
         * @return other 预算数据
         * @author yyt
         * @date 2025/11/11
         **/
        private CalculateOneYearRevenueBO calculateOneYearData(Integer budgetYear, BigDecimal inputValue, Integer calculateMethod, List<Integer> canUpdateBudgetDataMonths,
                                                               BudgetDataBaseCalculateRevenueBO calculateRevenueBO,
                                                               CalculateOneYearRevenueBO existData,
                                                               InternationalizationNumberDTO number) {
            List<BudgetDatabaseRoom> rooms = calculateRevenueBO.getRooms();
            List<BudgetDatabaseRoomOther> otherRooms = calculateRevenueBO.getRoomOthers();
            List<BudgetDatabaseFbRevenue> fbRevenues = calculateRevenueBO.getFbRevenues();
            List<BudgetDatabaseOtherRevenue> otherRevenues = calculateRevenueBO.getOtherRevenues();
            CalculateOneYearRevenueBO calculateOneYearRevenueBO = new CalculateOneYearRevenueBO();
            calculateOneYearRevenueBO.setJanuary(canUpdateBudgetDataMonths.contains(1) ? calculateOneMonthRevenue(budgetYear, 1, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getJanuary() : null);
            calculateOneYearRevenueBO.setFebruary(canUpdateBudgetDataMonths.contains(2) ? calculateOneMonthRevenue(budgetYear, 2, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getFebruary() : null);
            calculateOneYearRevenueBO.setMarch(canUpdateBudgetDataMonths.contains(3) ? calculateOneMonthRevenue(budgetYear, 3, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getMarch() : null);
            calculateOneYearRevenueBO.setApril(canUpdateBudgetDataMonths.contains(4) ? calculateOneMonthRevenue(budgetYear, 4, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getApril() : null);
            calculateOneYearRevenueBO.setMay(canUpdateBudgetDataMonths.contains(5) ? calculateOneMonthRevenue(budgetYear, 5, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getMay() : null);
            calculateOneYearRevenueBO.setJune(canUpdateBudgetDataMonths.contains(6) ? calculateOneMonthRevenue(budgetYear, 6, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getJune() : null);
            calculateOneYearRevenueBO.setJuly(canUpdateBudgetDataMonths.contains(7) ? calculateOneMonthRevenue(budgetYear, 7, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getJuly() : null);
            calculateOneYearRevenueBO.setAugust(canUpdateBudgetDataMonths.contains(8) ? calculateOneMonthRevenue(budgetYear, 8, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getAugust() : null);
            calculateOneYearRevenueBO.setSeptember(canUpdateBudgetDataMonths.contains(9) ? calculateOneMonthRevenue(budgetYear, 9, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getSeptember() : null);
            calculateOneYearRevenueBO.setOctober(canUpdateBudgetDataMonths.contains(10) ? calculateOneMonthRevenue(budgetYear, 10, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getOctober() : null);
            calculateOneYearRevenueBO.setNovember(canUpdateBudgetDataMonths.contains(11) ? calculateOneMonthRevenue(budgetYear, 11, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getNovember() : null);
            calculateOneYearRevenueBO.setDecember(canUpdateBudgetDataMonths.contains(12) ? calculateOneMonthRevenue(budgetYear, 12, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number) :
                    Objects.nonNull(existData) ? existData.getDecember() : null);
            calculateOneYearRevenueBO.setActualTotal(Objects.nonNull(existData) ? existData.getActualTotal() : null);
            return calculateOneYearRevenueBO;
        }

        /**
         * 计算 cover 指标数据
         *
         * @param budgetYear                年份
         * @param inputValue                计算输入的值
         * @param calculateMethod           计算方式
         * @param canUpdateBudgetDataMonths 可更新预算数据的月份
         * @param calculateRoomBO           计算数据
         * @param number                    国际化数字
         * @return cover 预算数据
         * @author yyt
         * @date 2025/11/11
         **/
        private CalculateOneYearBO calculateOneYearDataForCoversRoom(Integer budgetYear, BigDecimal inputValue, Integer calculateMethod,
                                                                     List<Integer> canUpdateBudgetDataMonths,
                                                                     BudgetDataBaseCalculateRoomBO calculateRoomBO,
                                                                     CalculateOneYearBO existData,
                                                                     InternationalizationNumberDTO number) {
            List<BudgetDatabaseRoom> rooms = calculateRoomBO.getRoomsForCalculatingRooms();
            CalculateOneYearBO calculateOneYearBO = new CalculateOneYearBO();
            calculateOneYearBO.setJanuary(canUpdateBudgetDataMonths.contains(1) ? calculateOneMonthRoom(budgetYear, 1, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getJanuary() : null);
            calculateOneYearBO.setFebruary(canUpdateBudgetDataMonths.contains(2) ? calculateOneMonthRoom(budgetYear, 2, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getFebruary() : null);
            calculateOneYearBO.setMarch(canUpdateBudgetDataMonths.contains(3) ? calculateOneMonthRoom(budgetYear, 3, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getMarch() : null);
            calculateOneYearBO.setApril(canUpdateBudgetDataMonths.contains(4) ? calculateOneMonthRoom(budgetYear, 4, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getApril() : null);
            calculateOneYearBO.setMay(canUpdateBudgetDataMonths.contains(5) ? calculateOneMonthRoom(budgetYear, 5, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getMay() : null);
            calculateOneYearBO.setJune(canUpdateBudgetDataMonths.contains(6) ? calculateOneMonthRoom(budgetYear, 6, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getJune() : null);
            calculateOneYearBO.setJuly(canUpdateBudgetDataMonths.contains(7) ? calculateOneMonthRoom(budgetYear, 7, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getJuly() : null);
            calculateOneYearBO.setAugust(canUpdateBudgetDataMonths.contains(8) ? calculateOneMonthRoom(budgetYear, 8, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getAugust() : null);
            calculateOneYearBO.setSeptember(canUpdateBudgetDataMonths.contains(9) ? calculateOneMonthRoom(budgetYear, 9, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getSeptember() : null);
            calculateOneYearBO.setOctober(canUpdateBudgetDataMonths.contains(10) ? calculateOneMonthRoom(budgetYear, 10, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getOctober() : null);
            calculateOneYearBO.setNovember(canUpdateBudgetDataMonths.contains(11) ? calculateOneMonthRoom(budgetYear, 11, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getNovember() : null);
            calculateOneYearBO.setDecember(canUpdateBudgetDataMonths.contains(12) ? calculateOneMonthRoom(budgetYear, 12, inputValue, calculateMethod, rooms, number) :
                    Objects.nonNull(existData) ? existData.getDecember() : null);
            calculateOneYearBO.setActualTotal(Objects.nonNull(existData) ? existData.getActualTotal() : null);
            return calculateOneYearBO;
        }

        /**
         * 计算 cover 指标数据
         *
         * @param budgetYear                年份
         * @param inputValue                计算输入的值
         * @param calculateMethod           计算方式
         * @param canUpdateBudgetDataMonths 可更新预算数据的月份
         * @param calculateRevenueBO        计算数据
         * @param number                    国际化数字
         * @return cover 预算数据
         * @author yyt
         * @date 2025/11/11
         **/
        private CalculateOneYearBO calculateOneYearDataForCoversRevenue(Integer budgetYear, BigDecimal inputValue, Integer calculateMethod,
                                                                        List<Integer> canUpdateBudgetDataMonths,
                                                                        BudgetDataBaseCalculateRevenueBO calculateRevenueBO,
                                                                        CalculateOneYearBO existData,
                                                                        InternationalizationNumberDTO number) {
            List<BudgetDatabaseRoom> rooms = calculateRevenueBO.getRooms();
            List<BudgetDatabaseRoomOther> otherRooms = calculateRevenueBO.getRoomOthers();
            List<BudgetDatabaseFbRevenue> fbRevenues = calculateRevenueBO.getFbRevenues();
            List<BudgetDatabaseOtherRevenue> otherRevenues = calculateRevenueBO.getOtherRevenues();
            CalculateOneYearBO calculateOneYearBO = new CalculateOneYearBO();
            calculateOneYearBO.setJanuary(canUpdateBudgetDataMonths.contains(1) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 1, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getJanuary() : null);
            calculateOneYearBO.setFebruary(canUpdateBudgetDataMonths.contains(2) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 2, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getFebruary() : null);
            calculateOneYearBO.setMarch(canUpdateBudgetDataMonths.contains(3) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 3, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getMarch() : null);
            calculateOneYearBO.setApril(canUpdateBudgetDataMonths.contains(4) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 4, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getApril() : null);
            calculateOneYearBO.setMay(canUpdateBudgetDataMonths.contains(5) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 5, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getMay() : null);
            calculateOneYearBO.setJune(canUpdateBudgetDataMonths.contains(6) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 6, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getJune() : null);
            calculateOneYearBO.setJuly(canUpdateBudgetDataMonths.contains(7) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 7, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getJuly() : null);
            calculateOneYearBO.setAugust(canUpdateBudgetDataMonths.contains(8) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 8, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getAugust() : null);
            calculateOneYearBO.setSeptember(canUpdateBudgetDataMonths.contains(9) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 9, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getSeptember() : null);
            calculateOneYearBO.setOctober(canUpdateBudgetDataMonths.contains(10) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 10, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getOctober() : null);
            calculateOneYearBO.setNovember(canUpdateBudgetDataMonths.contains(11) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 11, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getNovember() : null);
            calculateOneYearBO.setDecember(canUpdateBudgetDataMonths.contains(12) ? NumberUtil.removeDecimal(calculateOneMonthRevenue(budgetYear, 12, inputValue, calculateMethod, rooms, otherRooms, fbRevenues, otherRevenues, number)) :
                    Objects.nonNull(existData) ? existData.getDecember() : null);
            calculateOneYearBO.setActualTotal(Objects.nonNull(existData) ? existData.getActualTotal() : null);
            return calculateOneYearBO;
        }

        /**
         * 计算某个月的 revenue 值
         *
         * @param budgetYear      预算年份
         * @param month           月份
         * @param inputValue      计算输入的值
         * @param calculateMethod 计算公式
         * @param rooms           room 指标数据
         * @param otherRooms      room other 指标数据
         * @param fbRevenues      fb 指标数据
         * @param otherRevenues   other 指标数据
         * @param number          国际化数字
         * @return 某个月的 revenue 值
         * @author yyt
         * @date 2025/11/10
         **/
        private BigDecimal calculateOneMonthRevenue(Integer budgetYear, int month, BigDecimal inputValue, Integer calculateMethod,
                                                    List<BudgetDatabaseRoom> rooms, List<BudgetDatabaseRoomOther> otherRooms,
                                                    List<BudgetDatabaseFbRevenue> fbRevenues, List<BudgetDatabaseOtherRevenue> otherRevenues,
                                                    InternationalizationNumberDTO number) {
            BigDecimal totalRevenue = null;
            if (CollectionUtils.isNotEmpty(rooms)) {
                List<BigDecimal> revenueList = rooms.stream()
                        .filter(room -> room.getYearMonth() == YearMonthUtils.parseYearMonthToLong(YearMonth.of(budgetYear, month)))
                        .map(BudgetDatabaseRoom::getBudgetRevenue)
                        .filter(Objects::nonNull)
                        .toList();
                if (CollectionUtils.isNotEmpty(revenueList)) {
                    totalRevenue = revenueList.stream().reduce(BigDecimal::add).orElse(null);
                }
            }
            if (CollectionUtils.isNotEmpty(otherRooms)) {
                BigDecimal otherRevenue = otherRooms.stream()
                        .filter(Objects::nonNull)
                        .map(item ->
                                switch (month) {
                                    case 1 -> item.getJanuary();
                                    case 2 -> item.getFebruary();
                                    case 3 -> item.getMarch();
                                    case 4 -> item.getApril();
                                    case 5 -> item.getMay();
                                    case 6 -> item.getJune();
                                    case 7 -> item.getJuly();
                                    case 8 -> item.getAugust();
                                    case 9 -> item.getSeptember();
                                    case 10 -> item.getOctober();
                                    case 11 -> item.getNovember();
                                    case 12 -> item.getDecember();
                                    default -> null;
                                })
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(null);
                if (Objects.nonNull(otherRevenue)) {
                    totalRevenue = totalRevenue == null ? otherRevenue : totalRevenue.add(otherRevenue);
                }
            }
            if (CollectionUtils.isNotEmpty(fbRevenues)) {
                BigDecimal fbRevenue = fbRevenues.stream()
                        .filter(Objects::nonNull)
                        .map(item ->
                                switch (month) {
                                    case 1 -> item.getJanuary();
                                    case 2 -> item.getFebruary();
                                    case 3 -> item.getMarch();
                                    case 4 -> item.getApril();
                                    case 5 -> item.getMay();
                                    case 6 -> item.getJune();
                                    case 7 -> item.getJuly();
                                    case 8 -> item.getAugust();
                                    case 9 -> item.getSeptember();
                                    case 10 -> item.getOctober();
                                    case 11 -> item.getNovember();
                                    case 12 -> item.getDecember();
                                    default -> null;
                                })
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(null);
                if (Objects.nonNull(fbRevenue)) {
                    totalRevenue = totalRevenue == null ? fbRevenue : totalRevenue.add(fbRevenue);
                }
            }
            if (CollectionUtils.isNotEmpty(otherRevenues)) {
                BigDecimal otherRevenue = otherRevenues.stream()
                        .filter(Objects::nonNull)
                        .map(item ->
                                switch (month) {
                                    case 1 -> item.getJanuary();
                                    case 2 -> item.getFebruary();
                                    case 3 -> item.getMarch();
                                    case 4 -> item.getApril();
                                    case 5 -> item.getMay();
                                    case 6 -> item.getJune();
                                    case 7 -> item.getJuly();
                                    case 8 -> item.getAugust();
                                    case 9 -> item.getSeptember();
                                    case 10 -> item.getOctober();
                                    case 11 -> item.getNovember();
                                    case 12 -> item.getDecember();
                                    default -> null;
                                })
                        .filter(Objects::nonNull).reduce(BigDecimal::add).orElse(null);
                if (Objects.nonNull(otherRevenue)) {
                    totalRevenue = totalRevenue == null ? otherRevenue : totalRevenue.add(otherRevenue);
                }
            }

            if (totalRevenue == null) return null;

            return calculateBudgetRevenue(inputValue, calculateMethod, totalRevenue, number);
        }

        /**
         * 计算某个月的 rooms 值
         *
         * @param budgetYear      预算年份
         * @param month           月份
         * @param inputValue      计算输入的值
         * @param calculateMethod 计算公式
         * @param rooms           room 指标数据
         * @param number          国际化数字
         * @return 某个月的 revenue 值
         * @author yyt
         * @date 2025/11/10
         **/
        private Long calculateOneMonthRoom(Integer budgetYear, int month, BigDecimal inputValue, Integer calculateMethod,
                                                 List<BudgetDatabaseRoom> rooms,
                                                 InternationalizationNumberDTO number) {
            Long totalRooms = null;
            if (CollectionUtils.isNotEmpty(rooms)) {
                List<Long> roomList = rooms.stream()
                        .filter(room -> room.getYearMonth() == YearMonthUtils.parseYearMonthToLong(YearMonth.of(budgetYear, month)))
                        .map(BudgetDatabaseRoom::getBudgetRooms)
                        .filter(Objects::nonNull)
                        .toList();
                if (CollectionUtils.isNotEmpty(roomList)) {
                    totalRooms = roomList.stream().reduce(Long::sum).orElse(null);
                }
            }

            if (totalRooms == null) return null;

            return calculateBudgetRoom(inputValue, calculateMethod, totalRooms, number);
        }

        /**
         * 计算 budget revenue 值
         *
         * @param inputValue      计算输入的值
         * @param calculateMethod 计算公式
         * @param totalRevenue    关联指标汇总 revenue
         * @param number          金额格式化
         * @return budget revenue
         * @author yyt
         * @date 2025/11/11
         **/
        private BigDecimal calculateBudgetRevenue(BigDecimal inputValue, Integer calculateMethod, BigDecimal totalRevenue, InternationalizationNumberDTO number) {
            // value = 计算输入的值  计算公式  total revenue
            BigDecimal result = null;
            if (Objects.isNull(totalRevenue)) return result;

            if (Objects.equals(calculateMethod, CalculateMethodEnum.ADD.getCode())) {
                result = inputValue.add(totalRevenue);
            } else if (Objects.equals(calculateMethod, CalculateMethodEnum.SUBTRACT.getCode())) {
                result = inputValue.subtract(totalRevenue);
            } else if (Objects.equals(calculateMethod, CalculateMethodEnum.MULTIPLY.getCode())) {
                result = inputValue.multiply(totalRevenue);
            } else if (Objects.equals(calculateMethod, CalculateMethodEnum.DIVIDE.getCode())) {
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
                result = totalRevenue.compareTo(BigDecimal.ZERO) == 0 ? null
                        : inputValue.divide(totalRevenue, BaseConstant.DATA_PRECISION, roundingMode);
            }
            return result;
        }

        /**
         * 计算 budget room 值
         *
         * @param inputValue      计算输入的值
         * @param calculateMethod 计算公式
         * @param totalRoom       关联指标汇总 room
         * @param number          金额格式化
         * @return budget room
         * @author yyt
         * @date 2025/11/11
         **/
        private Long calculateBudgetRoom(BigDecimal inputValue, Integer calculateMethod, Long totalRoom, InternationalizationNumberDTO number) {
            // value = 计算输入的值  计算公式  total revenue
            BigDecimal result = null;
            if (Objects.isNull(totalRoom)) return null;

            if (Objects.equals(calculateMethod, CalculateMethodEnum.ADD.getCode())) {
                result = inputValue.add(BigDecimal.valueOf(totalRoom));
            } else if (Objects.equals(calculateMethod, CalculateMethodEnum.SUBTRACT.getCode())) {
                result = inputValue.subtract(BigDecimal.valueOf(totalRoom));
            } else if (Objects.equals(calculateMethod, CalculateMethodEnum.MULTIPLY.getCode())) {
                result = inputValue.multiply(BigDecimal.valueOf(totalRoom));
            } else if (Objects.equals(calculateMethod, CalculateMethodEnum.DIVIDE.getCode())) {
                RoundingMode roundingMode = InternationalizationUtils.getRoundingMode(number.getCalculationRule());
                result = totalRoom == 0 ? null : inputValue.divide(BigDecimal.valueOf(totalRoom), BaseConstant.DATA_PRECISION, roundingMode);
            }
            return Objects.isNull(result) ? null : result.longValue();
        }

        /**
         * 递归查询指标
         *
         * @param variableId        变量 id
         * @param roomItems         room 指标
         * @param fbItems           fb 指标
         * @param otherItems        other 指标
         * @param bosByVariableId   每层变量配置下的指标数据
         * @param number            国际化
         * @param variableIds       防重复循环指标使用
         * @param allRoomItems      所有 room 指标
         * @param allFbItems        所有 fb 指标
         * @param allOtherItems     所有 other 指标
         * @param allVariableRooms  所有变量下的 room 指标
         * @param allVariableFbs    所有变量下的 fb 指标
         * @param allVariableOthers 所有变量下的 other 指标
         * @param allRooms          所有 room 数据
         * @param allRoomOthers     所有 room other 数据
         * @param allFbRevenues     所有 fb revenue 数据
         * @param allOtherRevenues  所有 other revenue 数据
         * @author yyt
         * @date 2025/11/10
         **/
        public void summaryConfigForEnableRevenue(Integer budgetYear,Integer variableId,
                                                  List<BudgetDatabaseConfigRoomTabItem> roomItems,
                                                  List<BudgetDatabaseConfigFbTabItem> fbItems,
                                                  List<BudgetDatabaseConfigOtherTabItem> otherItems,
                                                  Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId,
                                                  InternationalizationNumberDTO number,
                                                  Set<String> variableIds,
                                                  List<BudgetDatabaseConfigRoomTabItem> allRoomItems,
                                                  List<BudgetDatabaseConfigFbTabItem> allFbItems,
                                                  List<BudgetDatabaseConfigOtherTabItem> allOtherItems,
                                                  List<BudgetDatabaseConfigVariableRoom> allVariableRooms,
                                                  List<BudgetDatabaseConfigVariableFb> allVariableFbs,
                                                  List<BudgetDatabaseConfigVariableOther> allVariableOthers,
                                                  List<BudgetDatabaseRoom> allRooms,
                                                  List<BudgetDatabaseRoomOther> allRoomOthers,
                                                  List<BudgetDatabaseFbRevenue> allFbRevenues,
                                                  List<BudgetDatabaseOtherRevenue> allOtherRevenues) {
            // 递归处理每个顶层 consumer 下属指标数据
            // 1.找出最底层的指标后，然后计算数据，这样能能拿到基础数据
            // 2.然后递归处理父级指标
            // 3.在处理父级指标循环拿子集时，先判断下是否处理过下级（variable），若处理过，则直接取之前计算好的数据，不用再计算

            // 查询当前变量下的所有指标
            List<BudgetDatabaseConfigVariableRoom> variableRoomList = allVariableRooms.stream()
                    .filter(variableRoom -> variableRoom.getVariableId().equals(variableId))
                    .toList();
            List<BudgetDatabaseConfigVariableFb> variableFbs = allVariableFbs.stream()
                    .filter(variableFb -> variableFb.getVariableId().equals(variableId))
                    .toList();
            List<BudgetDatabaseConfigVariableOther> variableOthers = allVariableOthers.stream()
                    .filter(variableOther -> variableOther.getVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isEmpty(variableRoomList) && CollectionUtils.isEmpty(variableFbs) && CollectionUtils.isEmpty(variableOthers))
                return;

            if (CollectionUtils.isNotEmpty(variableRoomList)) {
                List<Integer> roomIds = variableRoomList.stream().map(BudgetDatabaseConfigVariableRoom::getItemId).toList();
                List<BudgetDatabaseConfigRoomTabItem> roomTabItems = allRoomItems.stream()
                        .filter(roomTabItem -> roomIds.contains(roomTabItem.getId()))
                        .toList();
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : roomTabItems) {
                    // 若开启 revenue，则递归查询下级指标
                    if (roomTabItem.getEnableBudgetRevenue()) {
                        Integer variableIdForCurrentItem = roomTabItem.getBudgetRevenueVariableId();
                        List<BudgetDataBaseCalculateChildRevenueBO> budgetDataBaseCalculateChildRevenueBOS = bosByVariableId.get(variableIdForCurrentItem);
                        if (Objects.nonNull(budgetDataBaseCalculateChildRevenueBOS)) {
                            // 当前指标加入到上层 variable 中
                            toTopVariable(variableId, budgetYear, roomTabItem, budgetDataBaseCalculateChildRevenueBOS, number, bosByVariableId);
                        } else {
                            List<BudgetDatabaseConfigRoomTabItem> roomItemsForCurrentItem = new ArrayList<>();
                            List<BudgetDatabaseConfigFbTabItem> fbItemsForCurrentItem = new ArrayList<>();
                            List<BudgetDatabaseConfigOtherTabItem> otherItemsForCurrentItem = new ArrayList<>();

                            summaryConfigForEnableRevenue(budgetYear, variableIdForCurrentItem, roomItemsForCurrentItem,
                                    fbItemsForCurrentItem, otherItemsForCurrentItem, bosByVariableId, number, variableIds,
                                    allRoomItems, allFbItems, allOtherItems, allVariableRooms, allVariableFbs, allVariableOthers,
                                    allRooms, allRoomOthers, allFbRevenues, allOtherRevenues);

                            budgetDataBaseCalculateChildRevenueBOS = bosByVariableId.get(variableIdForCurrentItem);
                            if (CollectionUtils.isNotEmpty(budgetDataBaseCalculateChildRevenueBOS)) {
                                toTopVariable(variableId, budgetYear, roomTabItem, budgetDataBaseCalculateChildRevenueBOS, number, bosByVariableId);
                            }
                        }
                    } else {
                        // 若未开启 revenue，则将当前指标作为基础数据参与计算
                        if (!roomItems.contains(roomTabItem)) {
                            roomItems.add(roomTabItem);
                        }
                    }
                }
            }

            if (CollectionUtils.isNotEmpty(variableFbs)) {
                List<Integer> fbIds = variableFbs.stream().map(BudgetDatabaseConfigVariableFb::getItemId).toList();
                List<BudgetDatabaseConfigFbTabItem> fbTabItems = allFbItems.stream()
                        .filter(fbTabItem -> fbIds.contains(fbTabItem.getId()))
                        .toList();
                for (BudgetDatabaseConfigFbTabItem fbTabItem : fbTabItems) {
                    if (fbTabItem.getEnableBudgetRevenue()) {
                        Integer variableIdForCurrentItem = fbTabItem.getBudgetRevenueVariableId();
                        List<BudgetDataBaseCalculateChildRevenueBO> budgetDataBaseCalculateChildRevenueBOS = bosByVariableId.get(variableIdForCurrentItem);
                        if (Objects.nonNull(budgetDataBaseCalculateChildRevenueBOS)) {
                            toTopVariable(variableId, budgetYear, fbTabItem, budgetDataBaseCalculateChildRevenueBOS, number, bosByVariableId);
                        } else {
                            List<BudgetDatabaseConfigRoomTabItem> roomItemsForCurrentItem = new ArrayList<>();
                            List<BudgetDatabaseConfigFbTabItem> fbItemsForCurrentItem = new ArrayList<>();
                            List<BudgetDatabaseConfigOtherTabItem> otherItemsForCurrentItem = new ArrayList<>();

                            summaryConfigForEnableRevenue(budgetYear, variableIdForCurrentItem, roomItemsForCurrentItem,
                                    fbItemsForCurrentItem, otherItemsForCurrentItem, bosByVariableId, number, variableIds,
                                    allRoomItems, allFbItems, allOtherItems, allVariableRooms, allVariableFbs, allVariableOthers,
                                    allRooms, allRoomOthers, allFbRevenues, allOtherRevenues);

                            budgetDataBaseCalculateChildRevenueBOS = bosByVariableId.get(variableIdForCurrentItem);
                            if (CollectionUtils.isNotEmpty(budgetDataBaseCalculateChildRevenueBOS)) {
                                toTopVariable(variableId, budgetYear, fbTabItem, budgetDataBaseCalculateChildRevenueBOS, number, bosByVariableId);
                            }
                        }
                    } else {
                        if (!fbItems.contains(fbTabItem)) {
                            fbItems.add(fbTabItem);
                        }
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(variableOthers)) {
                List<Integer> otherIds = variableOthers.stream().map(BudgetDatabaseConfigVariableOther::getItemId).toList();
                List<BudgetDatabaseConfigOtherTabItem> otherTabItems = allOtherItems.stream()
                        .filter(otherTabItem -> otherIds.contains(otherTabItem.getId()))
                        .toList();
                for (BudgetDatabaseConfigOtherTabItem otherTabItem : otherTabItems) {
                    if (otherTabItem.getEnableBudgetRevenue()) {
                        Integer variableIdForCurrentItem = otherTabItem.getBudgetRevenueVariableId();
                        List<BudgetDataBaseCalculateChildRevenueBO> budgetDataBaseCalculateChildRevenueBOS = bosByVariableId.get(variableIdForCurrentItem);
                        if (Objects.nonNull(budgetDataBaseCalculateChildRevenueBOS)) {
                            toTopVariable(variableId, budgetYear, otherTabItem, budgetDataBaseCalculateChildRevenueBOS, number, bosByVariableId);
                        } else {
                            List<BudgetDatabaseConfigRoomTabItem> roomItemsForCurrentItem = new ArrayList<>();
                            List<BudgetDatabaseConfigFbTabItem> fbItemsForCurrentItem = new ArrayList<>();
                            List<BudgetDatabaseConfigOtherTabItem> otherItemsForCurrentItem = new ArrayList<>();
                            summaryConfigForEnableRevenue(budgetYear, variableIdForCurrentItem, roomItemsForCurrentItem,
                                    fbItemsForCurrentItem, otherItemsForCurrentItem, bosByVariableId, number, variableIds,
                                    allRoomItems, allFbItems, allOtherItems, allVariableRooms, allVariableFbs, allVariableOthers,
                                    allRooms, allRoomOthers, allFbRevenues, allOtherRevenues);

                            budgetDataBaseCalculateChildRevenueBOS = bosByVariableId.get(variableIdForCurrentItem);
                            if (CollectionUtils.isNotEmpty(budgetDataBaseCalculateChildRevenueBOS)) {
                                // 获取子集数据
                                toTopVariable(variableId, budgetYear, otherTabItem, budgetDataBaseCalculateChildRevenueBOS, number, bosByVariableId);
                            }
                        }
                    } else {
                        if (!otherItems.contains(otherTabItem)) {
                            otherItems.add(otherTabItem);
                        }
                    }
                }
            }

            // 汇总当前变量配置层级需计算的基础数据
            if (CollectionUtils.isNotEmpty(roomItems)) {
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : roomItems) {
                    String key = variableId + ":type:1:" + roomTabItem.getId();
                    if (variableIds.contains(key)) {
                        continue;
                    }
                    variableIds.add(key);
                    if (roomTabItem.getEnableBudgetRevenue()) continue;

                    if (Objects.equals(roomTabItem.getClassification(), RoomClassificationEnum.OTHER.getCode())) {
                        BudgetDatabaseRoomOther other = allRoomOthers.stream()
                                .filter(item -> Objects.equals(item.getRoomId(), roomTabItem.getId()))
                                .findAny()
                                .orElse(null);
                        roomOtherToVariable(roomTabItem, other, variableId, bosByVariableId);
                    } else {
                        List<BudgetDatabaseRoom> rooms = allRooms.stream()
                                .filter(item -> Objects.equals(item.getRoomId(), roomTabItem.getId()))
                                .toList();
                        roomToVariable(roomTabItem, rooms, variableId, bosByVariableId);
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(fbItems)) {
                for (BudgetDatabaseConfigFbTabItem fbTabItem : fbItems) {
                    String key = variableId + ":type:2:" + fbTabItem.getId();
                    if (variableIds.contains(key)) {
                        continue;
                    }
                    variableIds.add(key);
                    if (fbTabItem.getEnableBudgetRevenue()) continue;
                    BudgetDatabaseFbRevenue fbRevenue = allFbRevenues.stream()
                            .filter(item -> Objects.equals(item.getFbId(), fbTabItem.getId()))
                            .findAny()
                            .orElse(null);
                    fbToVariable(fbTabItem, fbRevenue, variableId, bosByVariableId);
                }
            }
            if (CollectionUtils.isNotEmpty(otherItems)) {
                for (BudgetDatabaseConfigOtherTabItem otherTabItem : otherItems) {
                    String key = variableId + ":type:3:" + otherTabItem.getId();
                    if (variableIds.contains(key)) {
                        continue;
                    }
                    variableIds.add(key);
                    if (otherTabItem.getEnableBudgetRevenue()) continue;
                    BudgetDatabaseOtherRevenue otherRevenue = allOtherRevenues.stream()
                            .filter(item -> Objects.equals(item.getOtherId(), otherTabItem.getId()))
                            .findAny()
                            .orElse(null);
                    otherToVariable(otherTabItem, otherRevenue, variableId, bosByVariableId);
                }
            }
        }

        /**
         * room other to variable
         *
         * @param roomTabItem     room item
         * @param other           room other
         * @param variableId      变量 id
         * @param bosByVariableId 变量配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void roomOtherToVariable(BudgetDatabaseConfigRoomTabItem roomTabItem, BudgetDatabaseRoomOther other,
                                         Integer variableId, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            BudgetDataBaseCalculateChildRevenueBO bo = new BudgetDataBaseCalculateChildRevenueBO();
            bo.setId(roomTabItem.getId());
            bo.setType(1);
            bo.setName(roomTabItem.getName());
            bo.setClassification(1);
            bo.setRoomOther(other);
            List<BudgetDataBaseCalculateChildRevenueBO> bos1 = bosByVariableId.getOrDefault(variableId, new ArrayList<>());
            if (bos1.stream().noneMatch(item -> item.getType() == 1 && item.getId().equals(roomTabItem.getId()))) {
                bos1.add(bo);
            }
            bosByVariableId.put(variableId, bos1);
        }

        /**
         * room to variable
         *
         * @param roomTabItem     room item
         * @param rooms           room list
         * @param variableId      变量 id
         * @param bosByVariableId 变量配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void roomToVariable(BudgetDatabaseConfigRoomTabItem roomTabItem, List<BudgetDatabaseRoom> rooms, Integer variableId,
                                    Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            BudgetDataBaseCalculateChildRevenueBO bo = new BudgetDataBaseCalculateChildRevenueBO();
            bo.setId(roomTabItem.getId());
            bo.setType(1);
            bo.setClassification(2);
            bo.setName(roomTabItem.getName());
            bo.setRooms(rooms);
            List<BudgetDataBaseCalculateChildRevenueBO> bos1 = bosByVariableId.getOrDefault(variableId, new ArrayList<>());
            if (bos1.stream().noneMatch(item -> item.getType() == 1 && item.getId().equals(roomTabItem.getId()))) {
                bos1.add(bo);
            }
            bosByVariableId.put(variableId, bos1);
        }

        /**
         * fb to variable
         *
         * @param fbTabItem       fb item
         * @param fbRevenue       fb revenue
         * @param variableId      变量 id
         * @param bosByVariableId 变量配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void fbToVariable(BudgetDatabaseConfigFbTabItem fbTabItem, BudgetDatabaseFbRevenue fbRevenue,
                                  Integer variableId, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            BudgetDataBaseCalculateChildRevenueBO bo = new BudgetDataBaseCalculateChildRevenueBO();
            bo.setId(fbTabItem.getId());
            bo.setType(2);
            bo.setClassification(1);
            bo.setName(fbTabItem.getName());
            bo.setFbRevenue(fbRevenue);
            List<BudgetDataBaseCalculateChildRevenueBO> bos1 = bosByVariableId.getOrDefault(variableId, new ArrayList<>());
            if (bos1.stream().noneMatch(item -> item.getType() == 2 && item.getId().equals(fbTabItem.getId()))) {
                bos1.add(bo);
            }
            bosByVariableId.put(variableId, bos1);
        }

        /**
         * other to variable
         *
         * @param otherTabItem    other item
         * @param otherRevenue    other revenue
         * @param variableId      变量 id
         * @param bosByVariableId 变量配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void otherToVariable(BudgetDatabaseConfigOtherTabItem otherTabItem, BudgetDatabaseOtherRevenue otherRevenue,
                                     Integer variableId, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            BudgetDataBaseCalculateChildRevenueBO bo = new BudgetDataBaseCalculateChildRevenueBO();
            bo.setId(otherTabItem.getId());
            bo.setType(3);
            bo.setClassification(1);
            bo.setName(otherTabItem.getName());
            bo.setOtherRevenue(otherRevenue);
            List<BudgetDataBaseCalculateChildRevenueBO> bos1 = bosByVariableId.getOrDefault(variableId, new ArrayList<>());
            if (bos1.stream().noneMatch(item -> item.getType() == 3 && item.getId().equals(otherTabItem.getId()))) {
                bos1.add(bo);
            }
            bosByVariableId.put(variableId, bos1);
        }

        /**
         * to top variable
         *
         * @param variableId                             变量 id
         * @param budgetYear                             预算年份
         * @param otherTabItem                           other item
         * @param budgetDataBaseCalculateChildRevenueBOS budget data base calculate child revenue bos
         * @param number                                 数字国际化
         * @param bosByVariableId                        预算配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void toTopVariable(Integer variableId, Integer budgetYear, BudgetDatabaseConfigOtherTabItem otherTabItem,
                                   List<BudgetDataBaseCalculateChildRevenueBO> budgetDataBaseCalculateChildRevenueBOS,
                                   InternationalizationNumberDTO number, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            BudgetDatabaseOtherRevenue otherRevenue = beanMapper.map(calculateOneYearRevenueAllMonth(budgetYear, otherTabItem.getBudgetRevenueInputValue(),
                    otherTabItem.getBudgetRevenueCalculateMethod(), budgetDataBaseCalculateChildRevenueBOS, number), BudgetDatabaseOtherRevenue.class);
            otherToVariable(otherTabItem, otherRevenue, variableId, bosByVariableId);
        }

        /**
         * to top variable
         *
         * @param variableId                             变量 id
         * @param budgetYear                             预算年份
         * @param fbTabItem                              fb item
         * @param budgetDataBaseCalculateChildRevenueBOS budget data base calculate child revenue bos
         * @param number                                 数字国际化
         * @param bosByVariableId                        预算配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void toTopVariable(Integer variableId, Integer budgetYear, BudgetDatabaseConfigFbTabItem fbTabItem,
                                   List<BudgetDataBaseCalculateChildRevenueBO> budgetDataBaseCalculateChildRevenueBOS,
                                   InternationalizationNumberDTO number, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            BudgetDatabaseFbRevenue fbRevenue = beanMapper.map(calculateOneYearRevenueAllMonth(budgetYear, fbTabItem.getBudgetRevenueInputValue(),
                    fbTabItem.getBudgetRevenueCalculateMethod(), budgetDataBaseCalculateChildRevenueBOS, number), BudgetDatabaseFbRevenue.class);
            fbToVariable(fbTabItem, fbRevenue, variableId, bosByVariableId);
        }

        /**
         * to top variable
         *
         * @param variableId                             变量 id
         * @param budgetYear                             预算年份
         * @param roomTabItem                            room item
         * @param budgetDataBaseCalculateChildRevenueBOS budget data base calculate child revenue bos
         * @param number                                 数字国际化
         * @param bosByVariableId                        预算配置下的数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void toTopVariable(Integer variableId,Integer budgetYear, BudgetDatabaseConfigRoomTabItem roomTabItem,
                                   List<BudgetDataBaseCalculateChildRevenueBO> budgetDataBaseCalculateChildRevenueBOS,
                                   InternationalizationNumberDTO number, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByVariableId) {
            if (Objects.equals(roomTabItem.getClassification(), RoomClassificationEnum.OTHER.getCode())) {
                BudgetDatabaseRoomOther other = beanMapper.map(calculateOneYearRevenueAllMonth(budgetYear, roomTabItem.getBudgetRevenueInputValue(),
                        roomTabItem.getBudgetRevenueCalculateMethod(), budgetDataBaseCalculateChildRevenueBOS, number), BudgetDatabaseRoomOther.class);
                roomOtherToVariable(roomTabItem, other, variableId, bosByVariableId);
            } else {
                List<BudgetDatabaseRoom> rooms = calculateRoomDataForRevenue(budgetYear, roomTabItem.getBudgetRevenueInputValue(),
                        roomTabItem.getBudgetRevenueCalculateMethod(), budgetDataBaseCalculateChildRevenueBOS, number);
                roomToVariable(roomTabItem, rooms, variableId, bosByVariableId);
            }
        }

        /**
         * calculate room revenue
         *
         * @param budgetYear                   预算年份
         * @param budgetRevenueInputValue      计算 revenue 输入的值
         * @param budgetRevenueCalculateMethod 计算方式
         * @param revenueBOS                   计算数据
         * @param number                       国际化
         * @return room revenue
         * @author yyt
         * @date 2025/11/26
         **/
        private List<BudgetDatabaseRoom> calculateRoomDataForRevenue(Integer budgetYear, BigDecimal budgetRevenueInputValue, Integer budgetRevenueCalculateMethod,
                                                           List<BudgetDataBaseCalculateChildRevenueBO> revenueBOS,
                                                           InternationalizationNumberDTO number) {
            Map<Integer, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>>> bosByType = revenueBOS.stream()
                    .collect(Collectors.groupingBy(BudgetDataBaseCalculateChildRevenueBO::getType,
                            Collectors.groupingBy(BudgetDataBaseCalculateChildRevenueBO::getClassification)));

            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByRoom = bosByType.getOrDefault(1, Collections.emptyMap());
            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByFb = bosByType.getOrDefault(2, Collections.emptyMap());
            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByOther = bosByType.getOrDefault(3, Collections.emptyMap());
            BudgetDataBaseCalculateRevenueBO bo = new BudgetDataBaseCalculateRevenueBO();
            bo.setRooms(bosByRoom.getOrDefault(RoomClassificationEnum.ROOM.getCode(), Collections.emptyList())
                    .stream().filter(item -> CollectionUtils.isNotEmpty(item.getRooms()))
                    .flatMap(item -> item.getRooms().stream())
                    .toList());
            bo.setRoomOthers(bosByRoom.getOrDefault(RoomClassificationEnum.OTHER.getCode(), Collections.emptyList())
                    .stream().map(BudgetDataBaseCalculateChildRevenueBO::getRoomOther)
                    .filter(Objects::nonNull)
                    .toList());
            bo.setFbRevenues(bosByFb.getOrDefault(FbClassificationEnum.REVENUE.getCode(), Collections.emptyList())
                    .stream().map(BudgetDataBaseCalculateChildRevenueBO::getFbRevenue)
                    .filter(Objects::nonNull)
                    .toList());
            bo.setOtherRevenues(bosByOther.getOrDefault(OtherClassificationEnum.REVENUE.getCode(), Collections.emptyList())
                    .stream().map(BudgetDataBaseCalculateChildRevenueBO::getOtherRevenue)
                    .filter(Objects::nonNull)
                    .toList());
            List<BudgetDatabaseRoom> rooms = Lists.newArrayListWithExpectedSize(12);
            // 处理12个月的数据
            for (int i = 1; i <= 12; i++) {
                BigDecimal budgetRevenue = calculateOneMonthRevenue(budgetYear, i, budgetRevenueInputValue, budgetRevenueCalculateMethod, bo.getRooms(),
                        bo.getRoomOthers(), bo.getFbRevenues(), bo.getOtherRevenues(), number);

                BudgetDatabaseRoom roomOther = new BudgetDatabaseRoom();
                roomOther.setYearMonth(Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(budgetYear, i))));
                roomOther.setBudgetRevenue(budgetRevenue);
                rooms.add(roomOther);
            }
            return rooms;
        }

        /**
         * calculate room
         *
         * @param budgetYear                   预算年份
         * @param budgetRevenueInputValue      计算 revenue 输入的值
         * @param budgetRevenueCalculateMethod 计算方式
         * @param roomBOS                      计算数据
         * @param number                       国际化
         * @return room revenue
         * @author yyt
         * @date 2025/11/26
         **/
        private List<BudgetDatabaseRoom> calculateRoomDataForRoom(Integer budgetYear, BigDecimal budgetRevenueInputValue, Integer budgetRevenueCalculateMethod,
                                                           List<BudgetDataBaseCalculateChildRoomBO> roomBOS, InternationalizationNumberDTO number) {
            BudgetDataBaseCalculateRoomBO bo = bosToCalculateOneYearRoomBO(roomBOS);

            List<BudgetDatabaseRoom> rooms = Lists.newArrayListWithExpectedSize(12);
            // 处理12个月的数据
            for (int i = 1; i <= 12; i++) {
                Long room = calculateOneMonthRoom(budgetYear, i, budgetRevenueInputValue, budgetRevenueCalculateMethod, bo.getRoomsForCalculatingRooms(), number);

                BudgetDatabaseRoom roomOther = new BudgetDatabaseRoom();
                roomOther.setYearMonth(Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(budgetYear, i))));
                roomOther.setBudgetRooms(room);
                rooms.add(roomOther);
            }
            return rooms;
        }

        /**
         * BudgetDataBaseCalculateChildRevenueBO to BudgetDataBaseCalculateRevenueBO
         *
         * @param bos bos
         * @return BudgetDataBaseCalculateRevenueBO
         * @author yyt
         * @date 2025/11/26
         **/
        private BudgetDataBaseCalculateRevenueBO bosToCalculateOneYearRevenueBO(List<BudgetDataBaseCalculateChildRevenueBO> bos) {
            if (CollectionUtils.isEmpty(bos)) return new BudgetDataBaseCalculateRevenueBO();

            Map<Integer, Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>>> bosByType = bos.stream()
                    .collect(Collectors.groupingBy(BudgetDataBaseCalculateChildRevenueBO::getType,
                            Collectors.groupingBy(BudgetDataBaseCalculateChildRevenueBO::getClassification)));

            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByRoom = bosByType.getOrDefault(1, Collections.emptyMap());
            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByFb = bosByType.getOrDefault(2, Collections.emptyMap());
            Map<Integer, List<BudgetDataBaseCalculateChildRevenueBO>> bosByOther = bosByType.getOrDefault(3, Collections.emptyMap());
            BudgetDataBaseCalculateRevenueBO bo = new BudgetDataBaseCalculateRevenueBO();
            bo.setRooms(bosByRoom.getOrDefault(RoomClassificationEnum.ROOM.getCode(), Collections.emptyList())
                    .stream().filter(item-> CollectionUtils.isNotEmpty(item.getRooms()))
                    .flatMap(item -> item.getRooms().stream())
                    .toList());
            bo.setRoomOthers(bosByRoom.getOrDefault(RoomClassificationEnum.OTHER.getCode(), Collections.emptyList())
                    .stream().map(BudgetDataBaseCalculateChildRevenueBO::getRoomOther)
                    .filter(Objects::nonNull)
                    .toList());
            bo.setFbRevenues(bosByFb.getOrDefault(FbClassificationEnum.REVENUE.getCode(), Collections.emptyList())
                    .stream().map(BudgetDataBaseCalculateChildRevenueBO::getFbRevenue)
                    .filter(Objects::nonNull)
                    .toList());
            bo.setOtherRevenues(bosByOther.getOrDefault(OtherClassificationEnum.REVENUE.getCode(), Collections.emptyList())
                    .stream().map(BudgetDataBaseCalculateChildRevenueBO::getOtherRevenue)
                    .filter(Objects::nonNull)
                    .toList());
            return bo;
        }

        /**
         * BudgetDataBaseCalculateChildRoomBO to BudgetDataBaseCalculateRoomBO
         *
         * @param bos bos
         * @return BudgetDataBaseCalculateRoomBO
         * @author yyt
         * @date 2025/11/26
         **/
        private BudgetDataBaseCalculateRoomBO bosToCalculateOneYearRoomBO(List<BudgetDataBaseCalculateChildRoomBO> bos) {
            if (CollectionUtils.isEmpty(bos)) return new BudgetDataBaseCalculateRoomBO();

            BudgetDataBaseCalculateRoomBO bo = new BudgetDataBaseCalculateRoomBO();
            bo.setRoomsForCalculatingRooms(bos.stream()
                    .filter(item -> CollectionUtils.isNotEmpty(item.getRoomsForCalculatingRooms()))
                    .flatMap(item -> item.getRoomsForCalculatingRooms().stream())
                    .toList());
            return bo;
        }

        /**
         * calculate one year revenue
         *
         * @param budgetYear                   预算年份
         * @param budgetRevenueInputValue      计算 revenue 输入的值
         * @param budgetRevenueCalculateMethod 计算方式
         * @param revenueBOS                   计算数据
         * @param number                       国际化
         * @return CalculateOneYearRevenueBO
         * @author yyt
         * @date 2025/11/26
         **/
        private CalculateOneYearRevenueBO calculateOneYearRevenueAllMonth(Integer budgetYear, BigDecimal budgetRevenueInputValue,
                                                                        Integer budgetRevenueCalculateMethod, List<BudgetDataBaseCalculateChildRevenueBO> revenueBOS,
                                                                        InternationalizationNumberDTO number) {
            BudgetDataBaseCalculateRevenueBO bo = bosToCalculateOneYearRevenueBO(revenueBOS);
            // 计算 room other
            return calculateOneYearData(budgetYear, budgetRevenueInputValue, budgetRevenueCalculateMethod,
                    List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), bo, null, number);
        }

        /**
         * 递归查询指标
         *
         * @param variableId       变量 id
         * @param roomItems        room 指标
         * @param bosByVariableId  每层变量配置下的指标数据
         * @param variableIds      防重复循环指标使用
         * @param allRoomItems     所有 room 指标
         * @param allVariableRooms 所有变量下的 room 指标
         * @param allRooms         所有 room 数据
         * @param number           国际化
         * @author yyt
         * @date 2025/11/10
         **/
        public void summaryConfigForEnableRoom(Integer budgetYear, Integer variableId, List<BudgetDatabaseConfigRoomTabItem> roomItems,
                                               Map<Integer, List<BudgetDataBaseCalculateChildRoomBO>> bosByVariableId,
                                               Set<String> variableIds,
                                               List<BudgetDatabaseConfigRoomTabItem> allRoomItems,
                                               List<BudgetDatabaseConfigVariableRoom> allVariableRooms,
                                               List<BudgetDatabaseRoom> allRooms,
                                               InternationalizationNumberDTO number) {
            // 递归处理每个顶层 consumer 下属指标数据
            // 1.找出最底层的指标后，然后计算数据，这样能能拿到基础数据
            // 2.然后递归处理父级指标
            // 3.在处理父级指标循环拿子集时，先判断下是否处理过下级（variable），若处理过，则直接取之前计算好的数据，不用再计算

            // 查询当前变量下的所有指标
            List<BudgetDatabaseConfigVariableRoom> variableRoomList = allVariableRooms.stream()
                    .filter(item -> item.getVariableId().equals(variableId))
                    .toList();
            if (CollectionUtils.isEmpty(variableRoomList))
                return;

            if (CollectionUtils.isNotEmpty(variableRoomList)) {
                List<Integer> roomIds = variableRoomList.stream().map(BudgetDatabaseConfigVariableRoom::getItemId).toList();
                List<BudgetDatabaseConfigRoomTabItem> roomTabItems = allRoomItems.stream()
                        .filter(item -> roomIds.contains(item.getId()))
                        .toList();
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : roomTabItems) {
                    // 若开启 room，则递归查询下级指标
                    if (roomTabItem.getEnableBudgetRoom()) {
                        Integer variableIdForCurrentItem = roomTabItem.getBudgetRoomVariableId();
                        List<BudgetDataBaseCalculateChildRoomBO> bos = bosByVariableId.get(variableIdForCurrentItem);
                        if (Objects.nonNull(bos)) {
                            // 当前指标加入到上层 variable 中
                            toTopVariableForCalculateRoom(variableId, budgetYear, roomTabItem,  bos, number, bosByVariableId);
                        } else {
                            List<BudgetDatabaseConfigRoomTabItem> roomItemsForCurrentItem = new ArrayList<>();
                            summaryConfigForEnableRoom(budgetYear, variableIdForCurrentItem, roomItemsForCurrentItem, bosByVariableId,
                                    variableIds, allRoomItems, allVariableRooms, allRooms, number);

                            bos = bosByVariableId.get(variableIdForCurrentItem);
                            if(CollectionUtils.isNotEmpty(bos)){
                                toTopVariableForCalculateRoom(variableId, budgetYear, roomTabItem, bos, number, bosByVariableId);
                            }
                        }
                    } else {
                        // 若未开启 room，则将当前指标作为基础数据参与计算
                        if (!roomItems.contains(roomTabItem)) {
                            roomItems.add(roomTabItem);
                        }
                    }
                }
            }

            // 汇总当前变量配置层级需计算的基础数据
            if(CollectionUtils.isNotEmpty(roomItems)) {
                for (BudgetDatabaseConfigRoomTabItem roomTabItem : roomItems) {
                    String key = variableId + ":" + roomTabItem.getId();
                    if (variableIds.contains(key)) {
                        continue;
                    }
                    variableIds.add(key);
                    if (roomTabItem.getEnableBudgetRoom()) continue;

                    List<BudgetDatabaseRoom> rooms = allRooms.stream()
                            .filter(item -> item.getRoomId().equals(roomTabItem.getId()))
                            .toList();
                    BudgetDataBaseCalculateChildRoomBO bo = new BudgetDataBaseCalculateChildRoomBO();
                    bo.setId(roomTabItem.getId());
                    bo.setName(roomTabItem.getName());
                    bo.setRoomsForCalculatingRooms(rooms);
                    List<BudgetDataBaseCalculateChildRoomBO> bos1 = bosByVariableId.getOrDefault(variableId, new ArrayList<>());
                    if (bos1.stream().noneMatch(item -> item.getId().equals(roomTabItem.getId()))) {
                        bos1.add(bo);
                    }
                    bosByVariableId.put(variableId, bos1);
                }
            }
        }

        /**
         * to top variable for calculate room
         *
         * @param variableId      变量 id
         * @param budgetYear      预算年份
         * @param roomTabItem     room 指标
         * @param bos             计算数据
         * @param number          国际化
         * @param bosByVariableId 变量 id 对应的计算数据
         * @author yyt
         * @date 2025/11/26
         **/
        private void toTopVariableForCalculateRoom(Integer variableId, Integer budgetYear, BudgetDatabaseConfigRoomTabItem roomTabItem,
                                                   List<BudgetDataBaseCalculateChildRoomBO> bos, InternationalizationNumberDTO number,
                                                   Map<Integer, List<BudgetDataBaseCalculateChildRoomBO>> bosByVariableId) {
            List<BudgetDatabaseRoom> rooms = calculateRoomDataForRoom(budgetYear, roomTabItem.getBudgetRoomInputValue(), roomTabItem.getBudgetRoomCalculateMethod(), bos, number);
            BudgetDataBaseCalculateChildRoomBO bo = new BudgetDataBaseCalculateChildRoomBO();
            bo.setId(roomTabItem.getId());
            bo.setName(roomTabItem.getName());
            bo.setRoomsForCalculatingRooms(rooms);
            List<BudgetDataBaseCalculateChildRoomBO> bos1 = bosByVariableId.getOrDefault(variableId, new ArrayList<>());
            if (bos1.stream().noneMatch(item -> item.getId().equals(roomTabItem.getId()))) {
                bos1.add(bo);
            }
            bosByVariableId.put(variableId, bos1);
        }

        /**
         * 计算 room 分类指标的 revenue 数据和 room 数据
         *
         * @param propertyId               资产 id
         * @param budgetYear               预算年
         * @param handleRevenueVariableIds 处理 revenue 的变量 id
         * @param handleRoomVariableIds    处理 room 的变量 id
         * @author yyt
         * @date 2025/11/11
         **/
        @Override
        @Transactional
        public void updateBudgetRevenueAndRoomForRoom(Integer propertyId, Integer budgetYear, List<Integer> handleRevenueVariableIds, List<Integer> handleRoomVariableIds) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
            List<Integer> canUpdateBudgetDataMonths = budgetDatabaseService.listCanUpdateBudgetDataMonths(propertyDTO.getId(), budgetYear);
            if (CollectionUtils.isEmpty(canUpdateBudgetDataMonths)) return;

            // 开启 revenue
            List<BudgetDatabaseConfigRoomTabItem> topRoomItemsForEnableRevenue = new ArrayList<>();
            List<BudgetDatabaseConfigRoomTabItem> topRoomItemsForEnableRevenueAndRoom = new ArrayList<>();
            List<BudgetDatabaseConfigFbTabItem> topFbItemsForEnableRevenue = new ArrayList<>();
            List<BudgetDatabaseConfigOtherTabItem> topOtherItems = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(handleRevenueVariableIds)) {
                summaryTopConsumerForEnableRevenue(propertyId, budgetYear, handleRevenueVariableIds, topRoomItemsForEnableRevenue,
                        topFbItemsForEnableRevenue, topOtherItems, propertyDTO.getInternationalization().getNumber());
            }

            // 开启 rooms
            List<BudgetDatabaseConfigRoomTabItem> topRoomItemsForEnableRoom = new ArrayList<>();
            List<BudgetDatabaseConfigFbTabItem> topFbItemsForEnableRoom = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(handleRoomVariableIds)) {
                summaryTopConsumerForEnableRoom(propertyId, budgetYear, handleRoomVariableIds, topRoomItemsForEnableRoom, topFbItemsForEnableRoom, propertyDTO.getInternationalization().getNumber());
            }

            if (CollectionUtils.isNotEmpty(topRoomItemsForEnableRevenue) && CollectionUtils.isNotEmpty(topRoomItemsForEnableRoom)) {
                // 汇总 有交集数据，计算 revenue 时同步计算 rooms
                Map<Integer, BudgetDatabaseConfigRoomTabItem> roomById = topRoomItemsForEnableRoom.stream().collect(Collectors.toMap(BudgetDatabaseConfigRoomTabItem::getId, v -> v));
                Iterator<BudgetDatabaseConfigRoomTabItem> iterator = topRoomItemsForEnableRevenue.iterator();
                while (iterator.hasNext()) {
                    BudgetDatabaseConfigRoomTabItem roomTabItem = iterator.next();
                    BudgetDatabaseConfigRoomTabItem IntersectionData = roomById.get(roomTabItem.getId());
                    if (IntersectionData != null) {
                        roomTabItem.setBosForRooms(IntersectionData.getBosForRooms());
                        topRoomItemsForEnableRevenueAndRoom.add(roomTabItem);

                        // 排除掉有交集的数据，然后单独计算这指标的 revenue / rooms
                        iterator.remove();
                        topRoomItemsForEnableRoom.remove(IntersectionData);
                    }
                }
            }

            calculateRoomForEnableRevenue(propertyDTO, budgetYear, topRoomItemsForEnableRevenue, canUpdateBudgetDataMonths);
            calculateRoomForEnableRoomAndRevenue(propertyDTO, budgetYear, topRoomItemsForEnableRevenueAndRoom, canUpdateBudgetDataMonths);
            calculateRoom(propertyDTO, budgetYear, topRoomItemsForEnableRoom, canUpdateBudgetDataMonths);
            calculateFbRevenue(propertyDTO, budgetYear, topFbItemsForEnableRevenue, canUpdateBudgetDataMonths);
            calculateFbRoom(propertyDTO, budgetYear, topFbItemsForEnableRoom, canUpdateBudgetDataMonths);
            calculateOtherRevenue(propertyDTO, budgetYear, topOtherItems, canUpdateBudgetDataMonths);
        }

        /**
         * 计算 room 分类指标的 revenue 数据和 room 数据
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param roomTabItems room 配置
         * @author yyt
         * @date 2025/11/11
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateRoom(PropertyDTO propertyDTO, Integer budgetYear, List<BudgetDatabaseConfigRoomTabItem> roomTabItems, List<Integer> canUpdateBudgetDataMonths) {
            if(CollectionUtils.isEmpty(roomTabItems)) return;

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

            Map<Integer, Map<Integer, BudgetDatabaseRoom>> existOtherRoomByYearMonth = budgetDatabaseRoomService.listByItemIds(roomTabItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()))
                    .stream().collect(Collectors.groupingBy(BudgetDatabaseRoom::getYearMonth, Collectors.toMap(BudgetDatabaseRoom::getRoomId, v -> v)));

            int size = roomTabItems.size() * canUpdateBudgetDataMonths.size();

            List<BudgetDatabaseRoom> insertRooms = Lists.newArrayListWithExpectedSize(size);
            List<BudgetDatabaseRoom> updateRooms = Lists.newArrayListWithExpectedSize(size);

            for (Integer month : canUpdateBudgetDataMonths) {

                Integer yearMonth = Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(budgetYear, month)));
                Map<Integer, BudgetDatabaseRoom> existRoomByRoomId = existOtherRoomByYearMonth.getOrDefault(yearMonth, Collections.emptyMap());

                for (BudgetDatabaseConfigRoomTabItem item : roomTabItems) {
                     BudgetDataBaseCalculateRoomBO calculateRoomBO = bosToCalculateOneYearRoomBO(item.getBosForRooms());
                    // 计算 room
                    BudgetDatabaseRoom existRoom = existRoomByRoomId.get(item.getId());
                    Boolean enableBudgetRoom = item.getEnableBudgetRoom();
                    BigDecimal roomInputValue = item.getBudgetRoomInputValue();
                    Integer roomCalculateMethod = item.getBudgetRoomCalculateMethod();
                    Long budgetRooms = enableBudgetRoom ? calculateOneMonthRoom(budgetYear, month, roomInputValue, roomCalculateMethod, calculateRoomBO.getRoomsForCalculatingRooms(), number) :
                            Objects.nonNull(existRoom) ? existRoom.getBudgetRooms() : null;

                    // 计算 revenue
                    BigDecimal budgetRevenue = Objects.nonNull(existRoom) ? existRoom.getBudgetRevenue() : null;
                    BigDecimal budgetAdr = null;
                    if (Objects.nonNull(budgetRooms) && Objects.nonNull(budgetRevenue)) {
                        budgetAdr = budgetRevenue.divide(new BigDecimal(budgetRooms), BaseConstant.DATA_PRECISION, roundingMode);
                    }

                    BudgetDatabaseRoom budgetDatabaseRoom = new BudgetDatabaseRoom()
                            .setPropertyId(item.getPropertyId())
                            .setTabId(item.getRoomTabId())
                            .setRoomId(item.getId())
                            .setYearMonth(yearMonth)
                            .setActualRooms(Objects.nonNull(existRoom) ? existRoom.getActualRooms() : null)
                            .setActualAdr(Objects.nonNull(existRoom) ? existRoom.getActualAdr() : null)
                            .setActualRevenue(Objects.nonNull(existRoom) ? existRoom.getActualRevenue() : null)
                            .setBudgetRooms(budgetRooms)
                            .setBudgetRevenue(budgetRevenue)
                            .setBudgetAdr(budgetAdr);

                    if (Objects.nonNull(existRoom)) {
                        budgetDatabaseRoom.setId(existRoom.getId());
                        updateRooms.add(budgetDatabaseRoom);
                    } else {
                        insertRooms.add(budgetDatabaseRoom);
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(insertRooms)) {
                budgetDatabaseRoomService.saveBatch(insertRooms);
            }
            if (CollectionUtils.isNotEmpty(updateRooms)) {
                budgetDatabaseRoomService.updateBatchByIdRewrite(updateRooms);
            }
        }

        /**
         * 计算 fb 分类指标的 covers 数据
         *
         * @param propertyDTO 资产
         * @param budgetYear  预算年份
         * @param fbItems     fb 配置
         * @author yyt
         * @date 2025/11/11
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void calculateFbRoom(PropertyDTO propertyDTO, Integer budgetYear, List<BudgetDatabaseConfigFbTabItem> fbItems, List<Integer> canUpdateBudgetDataMonths) {
            if (CollectionUtils.isEmpty(fbItems)) return;

            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            // 查询已存在的数据
            Map<Integer, BudgetDatabaseFbCovers> existRevenueByRoomId = budgetDatabaseFbCoversService.listByItemIds(fbItems.stream().map(BudgetDatabaseConfigFbTabItem::getId).collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseFbCovers::getFbId, v -> v));

            int size = fbItems.size();
            List<BudgetDatabaseFbCovers> insert = Lists.newArrayListWithExpectedSize(size);
            List<BudgetDatabaseFbCovers> updates = Lists.newArrayListWithExpectedSize(size);

            for (BudgetDatabaseConfigFbTabItem item : fbItems) {
                BudgetDatabaseFbCovers existData = existRevenueByRoomId.get(item.getId());
                BudgetDataBaseCalculateRoomBO calculateRoomBO = bosToCalculateOneYearRoomBO(item.getBosForRoom());
                CalculateOneYearBO calculateOneYearBO = calculateOneYearDataForCoversRoom(budgetYear, item.getBudgetRoomInputValue(), item.getBudgetRoomCalculateMethod(),
                        canUpdateBudgetDataMonths, calculateRoomBO, Objects.isNull(existData) ? null : beanMapper.map(existData, CalculateOneYearBO.class), number);
                BudgetDatabaseFbCovers revenue = beanMapper.map(calculateOneYearBO, BudgetDatabaseFbCovers.class);
                if (Objects.nonNull(existData)) {
                    revenue.setId(existData.getId());
                    updates.add(revenue);
                } else {
                    revenue.setPropertyId(propertyDTO.getId());
                    revenue.setTabId(item.getFbTabId());
                    revenue.setFbId(item.getId());
                    insert.add(revenue);
                }
            }

            if (CollectionUtils.isNotEmpty(insert)) {
                budgetDatabaseFbCoversService.saveBatch(insert);
            }
            if (CollectionUtils.isNotEmpty(updates)) {
                budgetDatabaseFbCoversService.updateBatchByIdRewrite(updates);
            }
        }

    }