# BudgetDatabaseConfigVariableController.java

    package cloud.wconnect.rateshop.controller.budgetdatabase;

    import cloud.wconnect.commonlib.annotation.ApiId;
    import cloud.wconnect.commonlib.component.SessionContext;
    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.I18nMessageUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.*;
    import cloud.wconnect.rateshop.entity.budgetdatabase.*;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.ConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.UpdateVariableConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.VariableConfigDTO;
    import cloud.wconnect.rateshop.pojo.query.budgetdatabase.VariableConfigQuery;
    import cloud.wconnect.rateshop.service.budgetdatabase.*;
    import com.google.common.collect.ImmutableMap;
    import jakarta.validation.Valid;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  前端控制器
     * </p>
     *
     * @author yyt
     * @since 2025-11-10
     */
    @RestController
    @RequestMapping("/budget-database")
    public class BudgetDatabaseConfigVariableController {

        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Autowired
        private IBudgetDatabaseConfigVariableService budgetDatabaseConfigVariableService;
        @Autowired
        private IBudgetDatabaseConfigRoomTabService budgetDatabaseConfigRoomTabService;
        @Autowired
        private IBudgetDatabaseConfigRoomTabItemService budgetDatabaseConfigRoomTabItemService;
        @Autowired
        private IBudgetDatabaseConfigFbTabService budgetDatabaseConfigFbTabService;
        @Autowired
        private IBudgetDatabaseConfigFbTabItemService budgetDatabaseConfigFbTabItemService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabService budgetDatabaseConfigOtherTabService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabItemService budgetDatabaseConfigOtherTabItemService;
        @Autowired
        private IBudgetDatabaseCheckCircularDependencyService budgetDatabaseCheckCircularDependencyService;
        @Autowired
        private SessionContext sessionContext;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 1639 - 获取变量配置
         *
         * @param year    年
         * @param property 资产标识
         * @param query    查询条件
         * @return 配置列表
         * @author yyt
         * @date 2025/11/13
         */
        @ApiId(1639)
        @GetMapping("/{year:\\d+}/variable-config")
        public ListPagedDTO<VariableConfigDTO> list(@PathVariable int year, String property, VariableConfigQuery query) {
            Integer propertyId = sessionContext.getPropertyId(property);
            return budgetDatabaseConfigVariableService.list(propertyId, year, query);
        }

        /**
         * 1640 - 新增/编辑 变量配置
         *
         * @param year                    年
         * @param id                      数据 id
         * @param property                资产标识
         * @param updateVariableConfigDTO 配置
         * @return {}
         * @author yyt
         * @date 2025/11/13
         */
        @ApiId(1640)
        @PostMapping("/{year:\\d+}/variable-config/{id:\\d+}")
        public Map<String, Boolean> save(@PathVariable int year, @PathVariable int id, String property, @Valid @RequestBody UpdateVariableConfigDTO updateVariableConfigDTO) {
            Integer propertyId = sessionContext.getPropertyId(property);

            // budget database 完成后，则不支持任何操作
            budgetDatabaseService.checkComplete(propertyId, year);

            List<Integer> roomFormula = updateVariableConfigDTO.getRoomFormula();
            checkRoomFormula(propertyId, year, roomFormula);
            List<Integer> fbFormula = updateVariableConfigDTO.getFbFormula();
            checkFbFormula(propertyId, year, fbFormula);
            List<Integer> otherFormula = updateVariableConfigDTO.getOtherFormula();
            checkOtherFormula(propertyId, year, otherFormula);

            Integer type = updateVariableConfigDTO.getType();
            if (Objects.equals(type, VariableTypeEnum.REVENUE.getCode())) {
                // revenue 类型，不能选择 covers 指标
                if (CollectionUtils.isNotEmpty(fbFormula)) {
                    List<BudgetDatabaseConfigFbTabItem> fbTabItems = budgetDatabaseConfigFbTabItemService.listByIds(fbFormula);
                    if (fbTabItems.stream().anyMatch(item -> Objects.equals(item.getClassification(), FbClassificationEnum.COVERS.getCode()))) {
                        throw new BadRequestException(BudgetDatabaseErrorCons.REVENUE_TYPE_NOT_SUPPORT_COVERS,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Revenue type, cannot select covers indicator.");
                    }
                }
            } else {
                // room 类型，只能选择 room 指标
                if (CollectionUtils.isNotEmpty(fbFormula) || CollectionUtils.isNotEmpty(otherFormula)) {
                    throw new BadRequestException(BudgetDatabaseErrorCons.ROOM_TYPE_ONLY_SUPPORT_ROOM,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Room type, only room indicator can be selected.");
                }
                if (CollectionUtils.isNotEmpty(roomFormula)) {
                    List<BudgetDatabaseConfigRoomTabItem> roomTabItems = budgetDatabaseConfigRoomTabItemService.listByIds(roomFormula);
                    if (roomTabItems.stream().anyMatch(item -> !Objects.equals(item.getClassification(), RoomClassificationEnum.ROOM.getCode()))) {
                        throw new BadRequestException(BudgetDatabaseErrorCons.ROOM_TYPE_ONLY_SUPPORT_ROOM,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Room type, only room indicator can be selected.");
                    }
                }
            }

            if (id == BaseConstant.ZERO) {
                budgetDatabaseConfigVariableService.checkRepeat(propertyId, year, updateVariableConfigDTO.getType(), updateVariableConfigDTO.getName(), null);

                budgetDatabaseConfigVariableService.save(propertyId, year, updateVariableConfigDTO, sessionContext.getLoginUserId());

                return ImmutableMap.of("configEnabledTheVariable", false);
            } else {
                budgetDatabaseConfigVariableService.checkRepeat(propertyId, year, updateVariableConfigDTO.getType(), updateVariableConfigDTO.getName(), id);

                BudgetDatabaseConfigVariable config = budgetDatabaseConfigVariableService.checkExist(id);

                budgetDatabaseConfigVariableService.checkBelong(config, propertyId);

                // 若选择指标，已经开启了此变量配置，则不能保存（防止循环依赖，没有意义）
                boolean configEnabledTheVariable = false;
                if (CollectionUtils.isNotEmpty(roomFormula)) {
                    configEnabledTheVariable = budgetDatabaseConfigRoomTabItemService.checkEnabledTheVariable(roomFormula, id, updateVariableConfigDTO.getType());
                }
                if (!configEnabledTheVariable) {
                    if (CollectionUtils.isNotEmpty(fbFormula)) {
                        configEnabledTheVariable = budgetDatabaseConfigFbTabItemService.checkEnabledTheVariable(fbFormula, id, updateVariableConfigDTO.getType());
                    }
                    if (!configEnabledTheVariable) {
                        if (CollectionUtils.isNotEmpty(otherFormula)) {
                            configEnabledTheVariable = budgetDatabaseConfigOtherTabItemService.checkEnabledTheVariable(otherFormula, id, updateVariableConfigDTO.getType());
                        }
                    }
                }
                if (configEnabledTheVariable) {
                    return ImmutableMap.of("configEnabledTheVariable", true);
                }

                // 若选择指标跟下属指标成循环依赖，则不能保存
                if (CollectionUtils.isNotEmpty(roomFormula)) {
                    boolean circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(ClassificationCons.ROOM, config, roomFormula, type);
                    if (circularDependency) {
                        throw new BadRequestException(BudgetDatabaseErrorCons.ENABLE_VARIABLE_CONFIG_FAIL_FOR_LOOP_DEPENDENCY,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Circular dependency found.");
                    }
                }
                if (CollectionUtils.isNotEmpty(fbFormula)) {
                    boolean circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(ClassificationCons.FB, config, fbFormula, type);
                    if (circularDependency) {
                        throw new BadRequestException(BudgetDatabaseErrorCons.ENABLE_VARIABLE_CONFIG_FAIL_FOR_LOOP_DEPENDENCY,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Circular dependency found.");
                    }
                }
                if (CollectionUtils.isNotEmpty(otherFormula)) {
                    boolean circularDependency = budgetDatabaseCheckCircularDependencyService.checkCircularDependencyByVariable(ClassificationCons.OTHER, config, otherFormula, type);
                    if (circularDependency) {
                        throw new BadRequestException(BudgetDatabaseErrorCons.ENABLE_VARIABLE_CONFIG_FAIL_FOR_LOOP_DEPENDENCY,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Circular dependency found.");
                    }
                }

                budgetDatabaseConfigVariableService.update(config, updateVariableConfigDTO, sessionContext.getLoginUserId());

                return ImmutableMap.of("configEnabledTheVariable", false);
            }
        }

        /**
         * 1641 - 删除变量配置
         *
         * @param id       变量配置 id
         * @param property 资产标识
         * @return 是否被指标使用
         * @author yyt
         * @date 2025/11/13
         **/
        @ApiId(1641)
        @DeleteMapping("/variable-config/{id:\\d+}")
        public Map<String, Boolean> deleteVariableConfig(@PathVariable int id, String property) {
            Integer propertyId = sessionContext.getPropertyId(property);

            BudgetDatabaseConfigVariable config = budgetDatabaseConfigVariableService.checkExist(id);

            budgetDatabaseConfigVariableService.checkBelong(config, propertyId);

            // 被指标使用时不可以删除
            Integer type = config.getType();
            boolean usedTheVariable = budgetDatabaseConfigRoomTabItemService.checkUsedTheVariable(id, type);
            if (!usedTheVariable) {
                usedTheVariable = budgetDatabaseConfigFbTabItemService.checkUsedTheVariable(id, type);
                if (!usedTheVariable) {
                    usedTheVariable = budgetDatabaseConfigOtherTabItemService.checkUsedTheVariable(id, type);
                }
            }
            if (usedTheVariable) {
                return ImmutableMap.of("usedByConfig", true);
            }

            budgetDatabaseConfigVariableService.delete(config);

            return Collections.emptyMap();
        }

        /**
         * 1642 - 获取所有分类配置
         *
         * @param year       年
         * @param property   资产标识
         * @param variableId 变量配置 id
         * @param type       1、revenue，2、room
         * @return 分类配置
         * @author yyt
         * @date 2025/11/13
         **/
        @ApiId(1642)
        @GetMapping("/{year:\\d+}/config/all")
        public List<ConfigDTO> listAllConfig(@PathVariable int year, String property, @RequestParam(required = false) Integer variableId, @RequestParam Integer type) {
            int propertyId = sessionContext.getPropertyId(property);

            BudgetDatabaseConfigVariable variable = null;
            if (Objects.nonNull(variableId)) {
                 variable = budgetDatabaseConfigVariableService.checkExist(variableId);
            }

            return budgetDatabaseConfigVariableService.listAllConfig(propertyId, year, variable, type);
        }

        /**
         * 检查 room
         *
         * @param propertyId  资产
         * @param year        年
         * @param roomFormula room
         * @author yyt
         * @date 2025/11/13
         **/
        private void checkRoomFormula(Integer propertyId, Integer year, List<Integer> roomFormula) {
            if (CollectionUtils.isEmpty(roomFormula)) return;

            // 检查 tab item 存在
            List<BudgetDatabaseConfigRoomTabItem> roomItems = budgetDatabaseConfigRoomTabItemService.listByIds(roomFormula);
            if (roomItems.size() != roomFormula.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("room"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the room not exist.");
            }
            if (roomItems.stream().anyMatch(roomItem -> !Objects.equals(roomItem.getPropertyId(), propertyId))) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the room do not belong to this property.");
            }
            // 检查 tab 存在
            List<Integer> tabIds = roomItems.stream().map(BudgetDatabaseConfigRoomTabItem::getRoomTabId).distinct().collect(Collectors.toList());
            List<BudgetDatabaseConfigRoomTab> tabs = budgetDatabaseConfigRoomTabService.list(tabIds, year);
            if (tabs.size() != tabIds.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("room tab"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the room tab not exist.");
            }
        }

        /**
         * 检查 fb
         *
         * @param propertyId 资产
         * @param year       年
         * @param fbFormula  fb
         * @author yyt
         * @date 2025/11/13
         **/
        private void checkFbFormula(Integer propertyId, int year, List<Integer> fbFormula) {
            if (CollectionUtils.isEmpty(fbFormula)) return;

            // 检查 tab item 存在
            List<BudgetDatabaseConfigFbTabItem> fbTabItems = budgetDatabaseConfigFbTabItemService.listByIds(fbFormula);
            if (fbFormula.size() != fbTabItems.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("fb"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the fb not exist.");
            }
            if (fbTabItems.stream().anyMatch(item -> !Objects.equals(item.getPropertyId(), propertyId))) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the fb not belong to this property.");
            }
            // 检查 tab 存在
            List<Integer> tabIds = fbTabItems.stream().map(BudgetDatabaseConfigFbTabItem::getFbTabId).distinct().collect(Collectors.toList());
            List<BudgetDatabaseConfigFbTab> tabs = budgetDatabaseConfigFbTabService.list(tabIds, year);
            if (tabs.size() != tabIds.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("fb tab"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the fb tab not exist.");
            }
        }

        /**
         * 检查 other
         *
         * @param propertyId   资产
         * @param year         年
         * @param otherFormula other
         * @author yyt
         * @date 2025/11/13
         **/
        private void checkOtherFormula(Integer propertyId, int year, List<Integer> otherFormula) {
            if (CollectionUtils.isEmpty(otherFormula)) return;

            // 检查 tab item 存在
            List<BudgetDatabaseConfigOtherTabItem> otherTabItems = budgetDatabaseConfigOtherTabItemService.listByIds(otherFormula);
            if (otherFormula.size() != otherTabItems.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("other"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the other not exist.");
            }
            if (otherTabItems.stream().anyMatch(item -> !Objects.equals(item.getPropertyId(), propertyId))) {
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), "the other not belong to this property.");
            }
            // 检查 tab 存在
            List<Integer> tabIds = otherTabItems.stream().map(BudgetDatabaseConfigOtherTabItem::getOtherTabId).distinct().collect(Collectors.toList());
            List<BudgetDatabaseConfigOtherTab> tabs = budgetDatabaseConfigOtherTabService.list(tabIds, year);
            if (tabs.size() != tabIds.size()) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("other tab"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the other tab not exist.");
            }
        }

    }