# BudgetDatabaseConfigVariableServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.*;
    import cloud.wconnect.rateshop.entity.budgetdatabase.*;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseConfigVariableMapper;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.*;
    import cloud.wconnect.rateshop.pojo.query.budgetdatabase.VariableConfigQuery;
    import cloud.wconnect.rateshop.service.budgetdatabase.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    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.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2025-11-10
     */
    @Service
    @DS("rate_shop")
    public class BudgetDatabaseConfigVariableServiceImpl extends ServiceImpl<BudgetDatabaseConfigVariableMapper, BudgetDatabaseConfigVariable> implements IBudgetDatabaseConfigVariableService {

        @Lazy
        @Autowired
        private IBudgetDatabaseConfigVariableRoomService budgetDatabaseConfigVariableRoomService;
        @Autowired
        private IBudgetDatabaseConfigVariableFbService budgetDatabaseConfigVariableFbService;
        @Autowired
        private IBudgetDatabaseConfigVariableOtherService budgetDatabaseConfigVariableOtherService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabService budgetDatabaseConfigRoomTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabItemService budgetDatabaseConfigRoomTabItemService;
        @Autowired
        private IBudgetDatabaseConfigFbTabService budgetDatabaseConfigFbTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigFbTabItemService budgetDatabaseConfigFbTabItemService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabService budgetDatabaseConfigOtherTabService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigOtherTabItemService budgetDatabaseConfigOtherTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseCalculateRevenueAndRoomService budgetDatabaseCalculateRevenueAndRoomService;
        @Autowired
        private IBudgetDatabaseCheckCircularDependencyService budgetDatabaseCheckCircularDependencyService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询变量配置
         *
         * @param propertyId 资产 id
         * @param budgetYear 预算年份
         * @return 变量配置
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<BudgetDatabaseConfigVariable> list(Integer propertyId, Integer budgetYear) {
            return list(new QueryWrapper<BudgetDatabaseConfigVariable>().lambda()
                    .eq(BudgetDatabaseConfigVariable::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigVariable::getBudgetYear, budgetYear));
        }

        /**
         * 检查变量是否存在
         *
         * @param propertyId 资产 id
         * @param type       类型
         * @param id         变量配置 id
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public void checkExist(Integer propertyId, Integer type, Integer id) {
            BudgetDatabaseConfigVariable variable = getOne(new QueryWrapper<BudgetDatabaseConfigVariable>().lambda()
                    .eq(BudgetDatabaseConfigVariable::getType, type)
                    .eq(BudgetDatabaseConfigVariable::getId, id));
            if (Objects.isNull(variable)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the variable is not found");
            }
        }

        /**
         * 查询变量配置
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param query      查询条件
         * @return 配置
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        public ListPagedDTO<VariableConfigDTO> list(Integer propertyId, int year, VariableConfigQuery query) {
            LambdaQueryWrapper<BudgetDatabaseConfigVariable> baseSql = new QueryWrapper<BudgetDatabaseConfigVariable>().lambda()
                    .eq(BudgetDatabaseConfigVariable::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigVariable::getBudgetYear, year);

            long total;
            List<BudgetDatabaseConfigVariable> list;
            if (Objects.nonNull(query.getPage()) && Objects.nonNull(query.getSize())) {
                Page<BudgetDatabaseConfigVariable> page = page(new Page<>(query.getPage(), query.getSize()),
                        baseSql.like(StringUtils.isNotEmpty(query.getName()), BudgetDatabaseConfigVariable::getName, query.getName())
                                .eq(Objects.nonNull(query.getType()), BudgetDatabaseConfigVariable::getType, query.getType())
                                .orderByAsc(BudgetDatabaseConfigVariable::getId));
                total = page.getTotal();
                list = page.getRecords();
            } else {
                list = list(baseSql.eq(StringUtils.isNotEmpty(query.getName()), BudgetDatabaseConfigVariable::getName, query.getName())
                        .eq(Objects.nonNull(query.getType()), BudgetDatabaseConfigVariable::getType, query.getType())
                        .ne(Objects.nonNull(query.getId()), BudgetDatabaseConfigVariable::getId, query.getId()));
                total = list.size();
            }

            if (CollectionUtils.isEmpty(list)) return new ListPagedDTO<>();

            List<Integer> variableIds = list.stream().map(BudgetDatabaseConfigVariable::getId).toList();
            // 查询绑定过的指标
            Map<Integer, String> roomTabNameById = Collections.emptyMap();
            Map<Integer, BudgetDatabaseConfigRoomTabItem> roomById;
            Map<Integer, List<BudgetDatabaseConfigVariableRoom>> roomByVariableId;
            List<BudgetDatabaseConfigVariableRoom> variableRooms = budgetDatabaseConfigVariableRoomService.listByVariableIds(variableIds);
            if (CollectionUtils.isNotEmpty(variableRooms)) {
                roomTabNameById = budgetDatabaseConfigRoomTabService.listByIds(variableRooms.stream().map(BudgetDatabaseConfigVariableRoom::getTabId).distinct().toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseConfigRoomTab::getId, BudgetDatabaseConfigRoomTab::getName));

                roomById = budgetDatabaseConfigRoomTabItemService.listByIds(variableRooms.stream().map(BudgetDatabaseConfigVariableRoom::getItemId).toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseConfigRoomTabItem::getId, v -> v));

                roomByVariableId = variableRooms.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigVariableRoom::getVariableId));
            } else {
                roomById = Collections.emptyMap();
                roomByVariableId = Collections.emptyMap();
            }

            Map<Integer, String> fbTabNameById = Collections.emptyMap();
            Map<Integer, BudgetDatabaseConfigFbTabItem> fbById;
            Map<Integer, List<BudgetDatabaseConfigVariableFb>> fbByVariableId;
            List<BudgetDatabaseConfigVariableFb> variableFbs = budgetDatabaseConfigVariableFbService.listByVariableIds(variableIds);
            if (CollectionUtils.isNotEmpty(variableFbs)) {
                fbTabNameById = budgetDatabaseConfigFbTabService.listByIds(variableFbs.stream().map(BudgetDatabaseConfigVariableFb::getTabId).distinct().toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseConfigFbTab::getId, BudgetDatabaseConfigFbTab::getName));

                fbById = budgetDatabaseConfigFbTabItemService.listByIds(variableFbs.stream().map(BudgetDatabaseConfigVariableFb::getItemId).toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseConfigFbTabItem::getId, v -> v));

                fbByVariableId = variableFbs.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigVariableFb::getVariableId));
            } else {
                fbById = Collections.emptyMap();
                fbByVariableId = Collections.emptyMap();
            }

            Map<Integer, String> otherTabNameById = Collections.emptyMap();
            Map<Integer, BudgetDatabaseConfigOtherTabItem> otherById;
            Map<Integer, List<BudgetDatabaseConfigVariableOther>> otherByVariableId;
            List<BudgetDatabaseConfigVariableOther> variableOthers = budgetDatabaseConfigVariableOtherService.listByVariableIds(variableIds);
            if (CollectionUtils.isNotEmpty(variableOthers)) {
                otherTabNameById = budgetDatabaseConfigOtherTabService.listByIds(variableOthers.stream().map(BudgetDatabaseConfigVariableOther::getTabId).distinct().toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseConfigOtherTab::getId, BudgetDatabaseConfigOtherTab::getName));

                otherById = budgetDatabaseConfigOtherTabItemService.listByIds(variableOthers.stream().map(BudgetDatabaseConfigVariableOther::getItemId).toList())
                        .stream().collect(Collectors.toMap(BudgetDatabaseConfigOtherTabItem::getId, v -> v));

                otherByVariableId = variableOthers.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigVariableOther::getVariableId));
            } else {
                otherById = Collections.emptyMap();
                otherByVariableId = Collections.emptyMap();
            }

            Map<Integer, String> finalRoomTabNameById = roomTabNameById;
            Map<Integer, String> finalFbTabNameById = fbTabNameById;
            Map<Integer, String> finalOtherTabNameById = otherTabNameById;
            return new ListPagedDTO<>(total, beanMapper.mapList(list, VariableConfigDTO.class,
                    (entity, dto) -> {
                        List<FormulaDTO> formula = new ArrayList<>();
                        formula.addAll(roomByVariableId.getOrDefault(entity.getId(), Collections.emptyList())
                                .stream().map(item -> {
                                    FormulaDTO formulaDTO = new FormulaDTO();
                                    formulaDTO.setType(ClassificationCons.ROOM);
                                    formulaDTO.setTypeName(ClassificationCons.ROOM_NAME);
                                    formulaDTO.setTabId(item.getTabId());
                                    formulaDTO.setTabName(finalRoomTabNameById.get(item.getTabId()));
                                    BudgetDatabaseConfigRoomTabItem room = roomById.get(item.getItemId());
                                    formulaDTO.setClassification(room.getClassification());
                                    formulaDTO.setClassificationName(RoomClassificationEnum.getByCode(room.getClassification()));
                                    formulaDTO.setItemId(item.getItemId());
                                    formulaDTO.setItemName(room.getName());
                                    return formulaDTO;
                                }).toList());

                        formula.addAll(fbByVariableId.getOrDefault(entity.getId(), Collections.emptyList())
                                .stream().map(item -> {
                                    FormulaDTO formulaDTO = new FormulaDTO();
                                    formulaDTO.setType(ClassificationCons.FB);
                                    formulaDTO.setTypeName(ClassificationCons.FB_NAME);
                                    formulaDTO.setTabId(item.getTabId());
                                    formulaDTO.setTabName(finalFbTabNameById.get(item.getTabId()));
                                    BudgetDatabaseConfigFbTabItem fb = fbById.get(item.getItemId());
                                    formulaDTO.setClassification(fb.getClassification());
                                    formulaDTO.setClassificationName(FbClassificationEnum.getByCode(fb.getClassification()));
                                    formulaDTO.setItemId(item.getItemId());
                                    formulaDTO.setItemName(fb.getName());
                                    return formulaDTO;
                                }).toList());

                        formula.addAll(otherByVariableId.getOrDefault(entity.getId(), Collections.emptyList())
                                .stream().map(item -> {
                                    FormulaDTO formulaDTO = new FormulaDTO();
                                    formulaDTO.setType(ClassificationCons.OTHER);
                                    formulaDTO.setTypeName(ClassificationCons.OTHER_NAME);
                                    formulaDTO.setTabId(item.getTabId());
                                    formulaDTO.setTabName(finalOtherTabNameById.get(item.getTabId()));
                                    BudgetDatabaseConfigOtherTabItem other = otherById.get(item.getItemId());
                                    formulaDTO.setClassification(other.getClassification());
                                    formulaDTO.setClassificationName(OtherClassificationEnum.getByCode(other.getClassification()));
                                    formulaDTO.setItemId(item.getItemId());
                                    formulaDTO.setItemName(other.getName());
                                    return formulaDTO;
                                }).toList());

                        dto.setFormula(formula);
                    }));
        }

        /**
         * 检查变量配置是否重复
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param type       类型
         * @param name       名称
         * @param id         排除自身
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        public void checkRepeat(Integer propertyId, Integer year, Integer type, String name, Integer id) {
            BudgetDatabaseConfigVariable variable = getOne(new QueryWrapper<BudgetDatabaseConfigVariable>().lambda()
                    .eq(BudgetDatabaseConfigVariable::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigVariable::getBudgetYear, year)
                    .eq(BudgetDatabaseConfigVariable::getType, type)
                    .eq(BudgetDatabaseConfigVariable::getName, name)
                    .ne(id != null, BudgetDatabaseConfigVariable::getId, id)
                    .last("limit 1"));
            if (Objects.nonNull(variable)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_EXISTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the variable is repeat");
            }
        }

        /**
         * 保存变量配置
         *
         * @param propertyId              资产 id
         * @param year                    年
         * @param updateVariableConfigDTO 变量配置
         * @param userId                  操作人
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void save(Integer propertyId, int year, UpdateVariableConfigDTO updateVariableConfigDTO, int userId) {
            // 保存主表
            BudgetDatabaseConfigVariable variable = new BudgetDatabaseConfigVariable()
                    .setPropertyId(propertyId)
                    .setBudgetYear(year)
                    .setType(updateVariableConfigDTO.getType())
                    .setName(updateVariableConfigDTO.getName())
                    .setUpdatedBy(userId).setUpdatedTime(DateTimeUtils.nowTimestamp());
            save(variable);

            Integer id = variable.getId();
            // 保存指标记录
            budgetDatabaseConfigVariableRoomService.save(propertyId, id, updateVariableConfigDTO.getRoomFormula());
            budgetDatabaseConfigVariableFbService.save(propertyId, id, updateVariableConfigDTO.getFbFormula());
            budgetDatabaseConfigVariableOtherService.save(propertyId, id, updateVariableConfigDTO.getOtherFormula());
        }

        /**
         * 检查变量配置是否存在
         *
         * @param id 配置 id
         * @return 配置
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        public BudgetDatabaseConfigVariable checkExist(int id) {
            BudgetDatabaseConfigVariable variable = getById(id);
            if (Objects.isNull(variable)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("variable"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the variable is not exist");
            }
            return variable;
        }

        /**
         * 检查变量配置是否属于资产
         *
         * @param config     配置
         * @param propertyId 资产 id
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        public void checkBelong(BudgetDatabaseConfigVariable config, Integer propertyId) {
            if (!config.getPropertyId().equals(propertyId)) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the property id parameter is different from " +
                        "the property id of the variable;propertyId:" + propertyId + ",variablePropertyId:" +
                        config.getPropertyId() + ",variableId:" + config.getId());
            }
        }

        /**
         * 更新变量配置
         *
         * @param config                  配置
         * @param updateVariableConfigDTO 配置
         * @param userId                  操作人
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(BudgetDatabaseConfigVariable config, UpdateVariableConfigDTO updateVariableConfigDTO, int userId) {
            // 修改主表
            Integer configId = config.getId();
            Integer propertyId = config.getPropertyId();
            updateById(new BudgetDatabaseConfigVariable().setId(configId).setName(updateVariableConfigDTO.getName())
                    .setType(updateVariableConfigDTO.getType())
                    .setUpdatedBy(userId).setUpdatedTime(DateTimeUtils.nowTimestamp()));
            // 修改指标记录
            budgetDatabaseConfigVariableRoomService.save(propertyId, configId, updateVariableConfigDTO.getRoomFormula());
            budgetDatabaseConfigVariableFbService.save(propertyId, configId, updateVariableConfigDTO.getFbFormula());
            budgetDatabaseConfigVariableOtherService.save(propertyId, configId, updateVariableConfigDTO.getOtherFormula());

            // 变更变量配置，需要重新计算启用当前变量配置的指标数据
            if (Objects.equals(updateVariableConfigDTO.getType(), VariableTypeEnum.ROOM.getCode())) {
                budgetDatabaseCalculateRevenueAndRoomService.updateBudgetRoom(propertyId, config.getBudgetYear(), Lists.newArrayList(configId));
            } else {
                budgetDatabaseCalculateRevenueAndRoomService.updateBudgetRevenue(propertyId, config.getBudgetYear(), Lists.newArrayList(configId));
            }
        }

        /**
         * 删除变量配置
         *
         * @param config 配置
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void delete(BudgetDatabaseConfigVariable config) {
            // 删除主表
            Integer variableId = config.getId();
            removeById(variableId);

            // 删除指标记录
            budgetDatabaseConfigVariableRoomService.remove(new QueryWrapper<BudgetDatabaseConfigVariableRoom>()
                    .lambda().eq(BudgetDatabaseConfigVariableRoom::getVariableId, variableId));
            budgetDatabaseConfigVariableFbService.remove(new QueryWrapper<BudgetDatabaseConfigVariableFb>()
                    .lambda().eq(BudgetDatabaseConfigVariableFb::getVariableId, variableId));
            budgetDatabaseConfigVariableOtherService.remove(new QueryWrapper<BudgetDatabaseConfigVariableOther>()
                    .lambda().eq(BudgetDatabaseConfigVariableOther::getVariableId, variableId));
        }

        /**
         * 获取所有变量配置
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param variable   变量配置
         * @param type       1、revenue，2、room
         * @return 配置
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        public List<ConfigDTO> listAllConfig(int propertyId, int year, BudgetDatabaseConfigVariable variable, Integer type) {
            // revenue 类型时，不能返回 covers
            // room 类型时，只能返回 room
            List<ConfigDTO> configDTOS = new ArrayList<>();

            // 当前有变量配置时，则需要检查两种情况，出现任一情况，则页面禁止选择当前指标
            // 1.指标是否启用了当前变量配置
            // 2.指标之间是否有循环依赖情况
            // 查询 room / fb / other 配置
            List<Integer> roomTabIds = budgetDatabaseConfigRoomTabService.list(propertyId, year)
                    .stream().map(BudgetDatabaseConfigRoomTab::getId).collect(Collectors.toList());
            List<BudgetDatabaseConfigRoomTabItem> allRoomTabItems = budgetDatabaseConfigRoomTabItemService.listByTabIds(roomTabIds);

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

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

            // 查询 room / fb / other 变量配置
            List<Integer> allVariableIds = list(propertyId, year).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);

            List<BudgetDatabaseConfigRoomTab> roomTabs = budgetDatabaseConfigRoomTabService.list(propertyId, year);
            if (CollectionUtils.isNotEmpty(roomTabs)) {
                List<BudgetDatabaseConfigRoomTabItem> roomTabItems = budgetDatabaseConfigRoomTabItemService.listByTabIds(roomTabs.stream().map(BudgetDatabaseConfigRoomTab::getId).toList(),
                        Objects.equals(type, VariableTypeEnum.REVENUE.getCode()) ? null : RoomClassificationEnum.ROOM.getCode());
                List<RoomClassificationEnum> roomClassificationEnums = Objects.equals(type, VariableTypeEnum.REVENUE.getCode()) ? List.of(RoomClassificationEnum.values())
                        : Collections.singletonList(RoomClassificationEnum.ROOM);
                Map<Integer, Map<Integer, List<BudgetDatabaseConfigRoomTabItem>>> itemsByTabId = roomTabItems.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigRoomTabItem::getRoomTabId,
                        Collectors.groupingBy(BudgetDatabaseConfigRoomTabItem::getClassification)));
                List<ConfigSecondDTO> roomChild = roomTabs.stream()
                        .filter(item -> Objects.nonNull(itemsByTabId.get(item.getId())))
                        .map(tab -> {
                            ConfigSecondDTO childDTO = new ConfigSecondDTO();
                            childDTO.setId(tab.getId());
                            childDTO.setName(tab.getName());
                            List<ConfigThreeDTO> threeDTOList = Lists.newArrayListWithExpectedSize(2);
                            for (RoomClassificationEnum value : roomClassificationEnums) {
                                Map<Integer, List<BudgetDatabaseConfigRoomTabItem>> itemsByClassification = itemsByTabId.getOrDefault(tab.getId(), Collections.emptyMap());
                                List<BudgetDatabaseConfigRoomTabItem> items = itemsByClassification.getOrDefault(value.getCode(), Collections.emptyList());
                                if (CollectionUtils.isEmpty(items)) continue;

                                ConfigThreeDTO configThreeDTO = new ConfigThreeDTO();
                                configThreeDTO.setId(value.getCode());
                                configThreeDTO.setName(value.getDesc());
                                configThreeDTO.setChild(items.stream()
                                        .map(item -> {
                                            ConfigFourDTO itemDTO = new ConfigFourDTO();
                                            itemDTO.setId(item.getId());
                                            itemDTO.setName(item.getName());
                                            // 若当前指标启用了当前变量配置，则禁用
                                            // 若指标之间有循环依赖，则禁用
                                            boolean enableCurrentVariable = false;
                                            boolean circularDependency = false;
                                            if (Objects.nonNull(variable)) {
                                                Integer checkVariableId = variable.getId();
                                                if (Objects.equals(variable.getType(), VariableTypeEnum.ROOM.getCode())) {
                                                    if (item.getEnableBudgetRoom()) {
                                                        Integer currentVariableId = item.getBudgetRoomVariableId();
                                                        enableCurrentVariable = Objects.equals(currentVariableId, checkVariableId);

                                                        if (!enableCurrentVariable) {
                                                            circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(allRoomTabItems, allFbTabItems, allOtherTabItems, variableRooms,
                                                                    variableFbs, variableOthers, currentVariableId, checkVariableId, VariableTypeEnum.ROOM.getCode());
                                                        }
                                                    }
                                                } else {
                                                    if (item.getEnableBudgetRevenue()) {
                                                        Integer currentVariableId = item.getBudgetRevenueVariableId();
                                                        enableCurrentVariable = Objects.equals(currentVariableId, checkVariableId);

                                                        if (!enableCurrentVariable) {
                                                            circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(allRoomTabItems, allFbTabItems, allOtherTabItems, variableRooms,
                                                                    variableFbs, variableOthers, currentVariableId, checkVariableId, VariableTypeEnum.REVENUE.getCode());
                                                        }
                                                    }
                                                }
                                            }
                                            itemDTO.setEnableCurrentVariable(enableCurrentVariable);
                                            itemDTO.setCircularDependency(circularDependency);
                                            return itemDTO;
                                        }).toList());
                                threeDTOList.add(configThreeDTO);
                            }
                            childDTO.setChild(threeDTOList);
                            return childDTO;
                        }).toList();

                if (CollectionUtils.isNotEmpty(roomChild)) {
                    ConfigDTO roomConfigDTO = new ConfigDTO();
                    roomConfigDTO.setId(ClassificationCons.ROOM);
                    roomConfigDTO.setName(ClassificationCons.ROOM_NAME);
                    roomConfigDTO.setChild(roomChild);
                    configDTOS.add(roomConfigDTO);
                }
            }

            if (Objects.equals(type, VariableTypeEnum.REVENUE.getCode())) {
                List<BudgetDatabaseConfigFbTab> fbTabs = budgetDatabaseConfigFbTabService.list(propertyId, year);
                if (CollectionUtils.isNotEmpty(fbTabs)) {
                    List<BudgetDatabaseConfigFbTabItem> fbTabItems = budgetDatabaseConfigFbTabItemService.listByTabIdsAndClassification(fbTabs.stream().map(BudgetDatabaseConfigFbTab::getId).toList(),
                            FbClassificationEnum.REVENUE.getCode());
                    Map<Integer, List<BudgetDatabaseConfigFbTabItem>> itemsByTabId = fbTabItems.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigFbTabItem::getFbTabId));
                    List<ConfigSecondDTO> fbChild = fbTabs.stream()
                            .filter(tab -> Objects.nonNull(itemsByTabId.get(tab.getId())))
                            .map(tab -> {
                                ConfigSecondDTO childDTO = new ConfigSecondDTO();
                                childDTO.setId(tab.getId());
                                childDTO.setName(tab.getName());
                                List<ConfigThreeDTO> threeDTOList = Lists.newArrayListWithExpectedSize(1);
                                ConfigThreeDTO configThreeDTO = new ConfigThreeDTO();
                                configThreeDTO.setId(FbClassificationEnum.REVENUE.getCode());
                                configThreeDTO.setName(FbClassificationEnum.REVENUE.getDesc());
                                configThreeDTO.setChild(itemsByTabId.getOrDefault(tab.getId(), Collections.emptyList())
                                        .stream().map(item -> {
                                            ConfigFourDTO itemDTO = new ConfigFourDTO();
                                            itemDTO.setId(item.getId());
                                            itemDTO.setName(item.getName());
                                            // 若当前指标启用了当前变量配置，则禁用
                                            // 若指标之间有循环依赖，则禁用
                                            boolean enableCurrentVariable = false;
                                            boolean circularDependency = false;
                                            if (Objects.nonNull(variable)) {
                                                Integer checkVariableId = variable.getId();
                                                if (Objects.equals(variable.getType(), VariableTypeEnum.ROOM.getCode())) {
                                                    Integer currentVariableId = item.getBudgetRoomVariableId();
                                                    if (item.getEnableBudgetRoom()) {
                                                        enableCurrentVariable = Objects.equals(currentVariableId, checkVariableId);
                                                        if (!enableCurrentVariable) {
                                                            circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(allRoomTabItems, allFbTabItems, allOtherTabItems, variableRooms,
                                                                    variableFbs, variableOthers, currentVariableId, checkVariableId, VariableTypeEnum.ROOM.getCode());
                                                        }
                                                    }
                                                } else {
                                                    Integer currentVariableId = item.getBudgetRevenueVariableId();
                                                    if (item.getEnableBudgetRevenue()) {
                                                        enableCurrentVariable = Objects.equals(currentVariableId, checkVariableId);
                                                        if (!enableCurrentVariable) {
                                                            circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(allRoomTabItems, allFbTabItems, allOtherTabItems, variableRooms,
                                                                    variableFbs, variableOthers, currentVariableId, checkVariableId, VariableTypeEnum.REVENUE.getCode());
                                                        }
                                                    }
                                                }
                                            }
                                            itemDTO.setEnableCurrentVariable(enableCurrentVariable);
                                            itemDTO.setCircularDependency(circularDependency);
                                            return itemDTO;
                                        }).toList());
                                threeDTOList.add(configThreeDTO);
                                childDTO.setChild(threeDTOList);
                                return childDTO;
                            }).toList();

                    if (CollectionUtils.isNotEmpty(fbChild)) {
                        ConfigDTO fbConfigDTO = new ConfigDTO();
                        fbConfigDTO.setId(ClassificationCons.FB);
                        fbConfigDTO.setName(ClassificationCons.FB_NAME);
                        fbConfigDTO.setChild(fbChild);
                        configDTOS.add(fbConfigDTO);
                    }
                }

                List<BudgetDatabaseConfigOtherTab> otherTabs = budgetDatabaseConfigOtherTabService.list(propertyId, year);
                if (CollectionUtils.isNotEmpty(otherTabs)) {
                    List<BudgetDatabaseConfigOtherTabItem> otherTabItems = budgetDatabaseConfigOtherTabItemService.listByTabIds(otherTabs.stream().map(BudgetDatabaseConfigOtherTab::getId).toList());
                    Map<Integer, Map<Integer, List<BudgetDatabaseConfigOtherTabItem>>> itemsByTabId = otherTabItems.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigOtherTabItem::getOtherTabId,
                            Collectors.groupingBy(BudgetDatabaseConfigOtherTabItem::getClassification)));
                    List<ConfigSecondDTO> otherChild = otherTabs.stream()
                            .filter(item -> Objects.nonNull(itemsByTabId.get(item.getId())))
                            .map(tab -> {
                                ConfigSecondDTO childDTO = new ConfigSecondDTO();
                                childDTO.setId(tab.getId());
                                childDTO.setName(tab.getName());
                                List<ConfigThreeDTO> threeDTOList = Lists.newArrayListWithExpectedSize(1);
                                for (OtherClassificationEnum value : OtherClassificationEnum.values()) {
                                    Map<Integer, List<BudgetDatabaseConfigOtherTabItem>> itemsByClassification = itemsByTabId.getOrDefault(tab.getId(), Collections.emptyMap());
                                    ConfigThreeDTO configThreeDTO = new ConfigThreeDTO();
                                    configThreeDTO.setId(value.getCode());
                                    configThreeDTO.setName(value.getDesc());
                                    configThreeDTO.setChild(itemsByClassification.getOrDefault(value.getCode(), Collections.emptyList())
                                            .stream().map(item -> {
                                                ConfigFourDTO itemDTO = new ConfigFourDTO();
                                                itemDTO.setId(item.getId());
                                                itemDTO.setName(item.getName());
                                                // 若当前指标启用了当前变量配置，则禁用
                                                // 若指标之间有循环依赖，则禁用
                                                boolean enableCurrentVariable = false;
                                                boolean circularDependency = false;
                                                if (Objects.nonNull(variable)) {
                                                    Integer checkVariableId = variable.getId();
                                                    if (Objects.equals(variable.getType(), VariableTypeEnum.ROOM.getCode())) {
                                                        if (item.getEnableBudgetRoom()) {
                                                            Integer currentVariableId = item.getBudgetRoomVariableId();
                                                            enableCurrentVariable = Objects.equals(currentVariableId, checkVariableId);
                                                            if (!enableCurrentVariable) {
                                                                circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(allRoomTabItems, allFbTabItems, allOtherTabItems, variableRooms,
                                                                        variableFbs, variableOthers, currentVariableId, checkVariableId, VariableTypeEnum.ROOM.getCode());
                                                            }
                                                        }
                                                    } else {
                                                        if (item.getEnableBudgetRevenue()) {
                                                            Integer currentVariableId = item.getBudgetRevenueVariableId();
                                                            enableCurrentVariable = Objects.equals(currentVariableId, checkVariableId);

                                                            if (!enableCurrentVariable) {
                                                                circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(allRoomTabItems, allFbTabItems, allOtherTabItems, variableRooms,
                                                                        variableFbs, variableOthers, currentVariableId, checkVariableId, VariableTypeEnum.REVENUE.getCode());
                                                            }
                                                        }
                                                    }
                                                }
                                                itemDTO.setEnableCurrentVariable(enableCurrentVariable);
                                                itemDTO.setCircularDependency(circularDependency);
                                                return itemDTO;
                                            }).toList());
                                    threeDTOList.add(configThreeDTO);
                                }
                                childDTO.setChild(threeDTOList);
                                return childDTO;
                            }).toList();

                    if (CollectionUtils.isNotEmpty(otherChild)) {
                        ConfigDTO otherConfigDTO = new ConfigDTO();
                        otherConfigDTO.setId(ClassificationCons.OTHER);
                        otherConfigDTO.setName(ClassificationCons.OTHER_NAME);
                        otherConfigDTO.setChild(otherChild);
                        configDTOS.add(otherConfigDTO);
                    }
                }
            }

            return configDTOS;
        }

        /**
         * 复制上一年的配置至下一年
         *
         * @param propertyId   资产 id
         * @param previousYear 上一年
         * @param nextYear     下一年
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void copy(Integer propertyId, int previousYear, int nextYear, List<BudgetDatabaseConfigRoomTabItem> nextYearRoomTabItems,
                         List<BudgetDatabaseConfigFbTabItem> nextYearFbTabItems, List<BudgetDatabaseConfigOtherTabItem> nextYearOtherTabItems) {
            // 若上一年没有配置，则不处理
            List<BudgetDatabaseConfigVariable> variables = list(propertyId, previousYear);
            if (CollectionUtils.isEmpty(variables)) {
                return;
            }

            long now = DateTimeUtils.nowTimestamp();

            List<BudgetDatabaseConfigVariable> newVariables = variables.stream().map(item -> {
                BudgetDatabaseConfigVariable variable = beanMapper.map(item, BudgetDatabaseConfigVariable.class);
                variable.setId(null);
                variable.setBudgetYear(nextYear);
                variable.setOldVariableId(item.getId());
                variable.setUpdatedBy(BaseConstant.ZERO);
                variable.setUpdatedTime(now);
                return variable;
            }).toList();
            saveBatch(newVariables);

            Map<Integer, Integer> newVariableIdByOldVariableId = newVariables.stream().collect(Collectors.toMap(BudgetDatabaseConfigVariable::getOldVariableId, BudgetDatabaseConfigVariable::getId));

            List<Integer> variableIds = variables.stream().map(BudgetDatabaseConfigVariable::getId).toList();
            List<BudgetDatabaseConfigVariableRoom> variableRooms = budgetDatabaseConfigVariableRoomService.listByVariableIds(variableIds);
            if (CollectionUtils.isNotEmpty(variableRooms)) {
                Map<Integer, BudgetDatabaseConfigRoomTabItem> newRoomByOldRoomId = nextYearRoomTabItems.stream().collect(Collectors.toMap(BudgetDatabaseConfigRoomTabItem::getOldItemId, v -> v));
                budgetDatabaseConfigVariableRoomService.saveBatch(variableRooms.stream().map(item -> {
                    BudgetDatabaseConfigVariableRoom variableRoom = new BudgetDatabaseConfigVariableRoom();
                    variableRoom.setId(null);
                    variableRoom.setPropertyId(propertyId);
                    variableRoom.setVariableId(newVariableIdByOldVariableId.get(item.getVariableId()));
                    BudgetDatabaseConfigRoomTabItem newRoom = newRoomByOldRoomId.get(item.getItemId());
                    variableRoom.setTabId(newRoom.getRoomTabId());
                    variableRoom.setItemId(newRoom.getId());
                    return variableRoom;
                }).toList());
            }
            List<BudgetDatabaseConfigVariableFb> variableFbs = budgetDatabaseConfigVariableFbService.listByVariableIds(variableIds);
            if (CollectionUtils.isNotEmpty(variableFbs)) {
                Map<Integer, BudgetDatabaseConfigFbTabItem> newFbByOldFbId = nextYearFbTabItems.stream().collect(Collectors.toMap(BudgetDatabaseConfigFbTabItem::getOldItemId, v -> v));
                budgetDatabaseConfigVariableFbService.saveBatch(variableFbs.stream().map(item -> {
                    BudgetDatabaseConfigVariableFb variableFb = new BudgetDatabaseConfigVariableFb();
                    variableFb.setId(null);
                    variableFb.setPropertyId(propertyId);
                    variableFb.setVariableId(newVariableIdByOldVariableId.get(item.getVariableId()));
                    BudgetDatabaseConfigFbTabItem newFb = newFbByOldFbId.get(item.getItemId());
                    variableFb.setTabId(newFb.getFbTabId());
                    variableFb.setItemId(newFb.getId());
                    return variableFb;
                }).toList());
            }
            List<BudgetDatabaseConfigVariableOther> variableOthers = budgetDatabaseConfigVariableOtherService.listByVariableIds(variableIds);
            if (CollectionUtils.isNotEmpty(variableOthers)) {
                Map<Integer, BudgetDatabaseConfigOtherTabItem> newOtherByOldOtherId = nextYearOtherTabItems.stream().collect(Collectors.toMap(BudgetDatabaseConfigOtherTabItem::getOldItemId, v -> v));
                budgetDatabaseConfigVariableOtherService.saveBatch(variableOthers.stream().map(item -> {
                    BudgetDatabaseConfigVariableOther variableOther = new BudgetDatabaseConfigVariableOther();
                    variableOther.setId(null);
                    variableOther.setPropertyId(propertyId);
                    variableOther.setVariableId(newVariableIdByOldVariableId.get(item.getVariableId()));
                    BudgetDatabaseConfigOtherTabItem newFb = newOtherByOldOtherId.get(item.getItemId());
                    variableOther.setTabId(newFb.getOtherTabId());
                    variableOther.setItemId(newFb.getId());
                    return variableOther;
                }).toList());
            }
        }

    }