# BudgetDatabaseConfigRoomTabServiceImpl.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.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.I18nMessageUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.RoomClassificationEnum;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigRoomTab;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigRoomTabItem;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseRoom;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseRoomOther;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseConfigRoomTabMapper;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.ConfigDeleteFlagDTO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.SaveTabDTO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.TabDTO;
    import cloud.wconnect.rateshop.service.budgetdatabase.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    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
     */
    @Service
    @DS("rate_shop")
    public class BudgetDatabaseConfigRoomTabServiceImpl extends ServiceImpl<BudgetDatabaseConfigRoomTabMapper, BudgetDatabaseConfigRoomTab> implements IBudgetDatabaseConfigRoomTabService {

        @Lazy
        @Autowired
        private IBudgetDatabaseConfigVariableRoomService budgetDatabaseConfigVariableRoomService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabItemService budgetDatabaseConfigRoomTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseRoomService budgetDatabaseRoomService;
        @Autowired
        private IBudgetDatabaseRoomOtherService budgetDatabaseRoomOtherService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private I18nMessageUtils i18nMessageUtils;

        /**
         * 查询所有 tab
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return tab
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<BudgetDatabaseConfigRoomTab> list(int propertyId, int year) {
            return list(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .eq(BudgetDatabaseConfigRoomTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigRoomTab::getBudgetYear, year)
                    .orderByAsc(BudgetDatabaseConfigRoomTab::getId));
        }

        /**
         * 查询参与计算的所有 tab
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return tab
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<BudgetDatabaseConfigRoomTab> listParticipateCalculateTabs(int propertyId, int year) {
            return list(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .eq(BudgetDatabaseConfigRoomTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigRoomTab::getBudgetYear, year)
                    .eq(BudgetDatabaseConfigRoomTab::getParticipateCalculate, true));
        }

        /**
         * 查询所有 tab
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return tab
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<TabDTO> listDTOs(int propertyId, int year) {
            List<BudgetDatabaseConfigRoomTab> tabs = list(propertyId, year);
            return beanMapper.mapList(tabs, TabDTO.class);
        }

        /**
         * 验重
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param name       名称
         * @param id         排除自身
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public void checkRepeat(int propertyId, int year, String name, Integer id) {
            BudgetDatabaseConfigRoomTab one = getOne(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .eq(BudgetDatabaseConfigRoomTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigRoomTab::getBudgetYear, year)
                    .eq(BudgetDatabaseConfigRoomTab::getName, name)
                    .ne(Objects.nonNull(id), BudgetDatabaseConfigRoomTab::getId, id)
                    .last("limit 1"));
            if (Objects.nonNull(one)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_EXISTED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the tab is repeat");
            }
        }

        /**
         * 保存 tab
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param saveTabDTO tab 信息
         * @param userId     操作人
         * @return tab id
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public Integer save(int propertyId, int year, SaveTabDTO saveTabDTO, int userId) {
            BudgetDatabaseConfigRoomTab tab = new BudgetDatabaseConfigRoomTab()
                    .setPropertyId(propertyId)
                    .setBudgetYear(year)
                    .setName(saveTabDTO.getName())
                    .setParticipateCalculate(true)
                    .setUpdatedBy(userId)
                    .setUpdatedTime(DateTimeUtils.nowTimestamp());
            save(tab);
            return tab.getId();
        }

        /**
         * 验证存在
         *
         * @param id tab id
         * @return tab
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public BudgetDatabaseConfigRoomTab checkExist(int id) {
            BudgetDatabaseConfigRoomTab tab = getById(id);
            if (Objects.isNull(tab)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_FOUND,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the tab is not found");
            }
            return tab;
        }

        /**
         * 校验数据归属
         *
         * @param tab        tab
         * @param propertyId 资产id
         * @author yyt
         * @date 2025/11/10
         */
        @Override
        public void checkBelong(BudgetDatabaseConfigRoomTab tab, int propertyId) {
            if (!tab.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 budgetDatabaseConfigRoomTab;propertyId:" + propertyId + ",budgetDatabaseConfigRoomTabPropertyId:" +
                        tab.getPropertyId() + ",budgetDatabaseConfigRoomTabId:" + tab.getId());
            }
        }

        /**
         * 修改 tab
         *
         * @param tabId      tab id
         * @param saveTabDTO tab 信息
         * @param userId     操作人
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public void update(int tabId, SaveTabDTO saveTabDTO, int userId) {
            updateById(new BudgetDatabaseConfigRoomTab()
                    .setId(tabId)
                    .setName(saveTabDTO.getName())
                    .setUpdatedBy(userId)
                    .setUpdatedTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 验证 tab 数量
         *
         * @param propertyId 资产 id
         * @param year       年
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public void checkTabCount(int propertyId, int year) {
            // 验证 tab 数量最多不可超过5个
            long count = count(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .eq(BudgetDatabaseConfigRoomTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigRoomTab::getBudgetYear, year));
            if (count >= 5) {
                throw new BadRequestException(CommonErrorI18nConstant.CREATE_DATA_LIMIT,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "Creating data exceeds the quantity limit");
            }
        }

        /**
         * 参与计算 Total Revenue
         *
         * @param id                   tab id
         * @param participateCalculate 参与计算
         * @param userId               操作人
         * @author yyt
         * @date 2025/11/11
         **/
        @Override
        public void participateCalculate(int id, boolean participateCalculate, int userId) {
            updateById(new BudgetDatabaseConfigRoomTab().setId(id).setParticipateCalculate(participateCalculate).setUpdatedBy(userId).setUpdatedTime(DateTimeUtils.nowTimestamp()));
        }

        /**
         * 删除 tab
         *
         * @param tab room tab
         * @return 配置删除标志
         * @author yyt
         * @date 2025/11/11
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public ConfigDeleteFlagDTO handleDelete(BudgetDatabaseConfigRoomTab tab) {
            // 当前 tab 下的指标被变量配置占用时，不可以删除。
            Integer tabId = tab.getId();
            if (budgetDatabaseConfigVariableRoomService.checkTabBoundToTheVariable(tabId)) {
                return new ConfigDeleteFlagDTO(false, true);
            }

            List<BudgetDatabaseRoomOther> others = Collections.emptyList();
            List<BudgetDatabaseRoom> rooms = Collections.emptyList();
            // 当前 tab 下的指标有数据时，不能删除。
            List<BudgetDatabaseConfigRoomTabItem> itemList = budgetDatabaseConfigRoomTabItemService.listByTabId(tabId);
            if (CollectionUtils.isNotEmpty(itemList)) {
                Map<Integer, List<BudgetDatabaseConfigRoomTabItem>> itemsByClassification = itemList.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigRoomTabItem::getClassification));
                List<BudgetDatabaseConfigRoomTabItem> otherItems = itemsByClassification.getOrDefault(RoomClassificationEnum.OTHER.getCode(), Collections.emptyList());
                if (CollectionUtils.isNotEmpty(otherItems)) {
                    others = budgetDatabaseRoomOtherService.listByItemIds(otherItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()));
                    if (CollectionUtils.isNotEmpty(others) && others.stream().anyMatch(BudgetDatabaseRoomOther::hasData)) {
                        return new ConfigDeleteFlagDTO(true, false);
                    }
                }
                List<BudgetDatabaseConfigRoomTabItem> roomItems = itemsByClassification.getOrDefault(RoomClassificationEnum.ROOM.getCode(), Collections.emptyList());
                if (CollectionUtils.isNotEmpty(roomItems)) {
                    rooms = budgetDatabaseRoomService.listByItemIds(roomItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).collect(Collectors.toList()));
                    if (CollectionUtils.isNotEmpty(rooms) && rooms.stream().anyMatch(BudgetDatabaseRoom::hasData)) {
                        return new ConfigDeleteFlagDTO(true, false);
                    }
                }
            }

            // 删除 tab
            removeById(tab.getId());

            // 删除 tab 下的指标
            if (CollectionUtils.isNotEmpty(itemList)) {
                budgetDatabaseConfigRoomTabItemService.removeBatchByIds(itemList);
            }

            // 删除 指标占用数据记录
            if (CollectionUtils.isNotEmpty(others)) {
                budgetDatabaseRoomOtherService.removeByIds(others.stream().map(BudgetDatabaseRoomOther::getId).toList());
            }
            if (CollectionUtils.isNotEmpty(rooms)) {
                budgetDatabaseRoomService.removeByIds(rooms.stream().map(BudgetDatabaseRoom::getId).toList());
            }

            return new ConfigDeleteFlagDTO(false, false);
        }

        /**
         * 批量查询
         *
         * @param tabIds tab ids
         * @param year   年
         * @return tab
         * @author yyt
         * @date 2025/11/11
         **/
        @Override
        public List<BudgetDatabaseConfigRoomTab> list(List<Integer> tabIds, Integer year) {
            return list(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .in(BudgetDatabaseConfigRoomTab::getId, tabIds)
                    .eq(BudgetDatabaseConfigRoomTab::getBudgetYear, year));
        }

        /**
         * 检查 tab
         *
         * @param tabId      tab id
         * @param propertyId 资产 id
         * @return tab
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public BudgetDatabaseConfigRoomTab checkTab(int tabId, int propertyId) {
            BudgetDatabaseConfigRoomTab tab = checkExist(tabId);
            checkBelong(tab, propertyId);
            return tab;
        }

        /**
         * 检查 tab 存在
         *
         * @param propertyId 资产 id
         * @param tabId      tab id
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkExist(Integer propertyId, Integer tabId) {
            BudgetDatabaseConfigRoomTab tab = getOne(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .eq(BudgetDatabaseConfigRoomTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigRoomTab::getId, tabId));
            if (Objects.isNull(tab)) {
                throw new BadRequestException(CommonErrorI18nConstant.DATA_NOT_EXIST,
                        i18nMessageUtils.getErrorParamMapNotNeedTranslate("room tab"),
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the room tab is not found");
            }
        }

        /**
         * 复制上一年的配置至下一年
         *
         * @param propertyId   资产 id
         * @param previousYear 上一年
         * @param nextYear     下一年
         * @return 下一年的配置
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public List<BudgetDatabaseConfigRoomTabItem> copy(Integer propertyId, int previousYear, int nextYear) {
            // 查询上一年的配置
            List<BudgetDatabaseConfigRoomTab> tabs = list(propertyId, previousYear);
            if (CollectionUtils.isEmpty(tabs)) return Collections.emptyList();

            List<BudgetDatabaseConfigRoomTabItem> tabItems = budgetDatabaseConfigRoomTabItemService.listByTabIds(tabs.stream().map(BudgetDatabaseConfigRoomTab::getId).toList());
            Map<Integer, List<BudgetDatabaseConfigRoomTabItem>> itemsByTabId = tabItems
                    .stream().collect(Collectors.groupingBy(BudgetDatabaseConfigRoomTabItem::getRoomTabId));

            long now = DateTimeUtils.nowTimestamp();

            List<BudgetDatabaseConfigRoomTab> newTabs = tabs.stream().map(item -> {
                BudgetDatabaseConfigRoomTab tab = beanMapper.map(item, BudgetDatabaseConfigRoomTab.class);
                tab.setBudgetYear(nextYear);
                tab.setId(null);
                tab.setOldTabId(item.getId());
                tab.setUpdatedBy(BaseConstant.ZERO);
                tab.setUpdatedTime(now);
                return tab;
            }).toList();
            saveBatch(newTabs);

            Map<Integer, Integer> newTabIdByOldTabId = newTabs.stream().collect(Collectors.toMap(BudgetDatabaseConfigRoomTab::getOldTabId, BudgetDatabaseConfigRoomTab::getId));

            if (CollectionUtils.isNotEmpty(tabItems)) {
                List<BudgetDatabaseConfigRoomTabItem> newTabItems = Lists.newArrayListWithExpectedSize(tabItems.size());
                itemsByTabId.forEach((tabId, items) -> {

                    Integer newTabId = newTabIdByOldTabId.get(tabId);

                    newTabItems.addAll(items.stream().map(item -> {
                        BudgetDatabaseConfigRoomTabItem newItem = beanMapper.map(item, BudgetDatabaseConfigRoomTabItem.class);
                        newItem.setId(null);
                        newItem.setRoomTabId(newTabId);

                        newItem.setOldTabId(tabId);
                        newItem.setOldItemId(item.getId());
                        newItem.setUpdatedBy(BaseConstant.ZERO);
                        newItem.setUpdatedTime(now);
                        return newItem;
                    }).toList());
                });
                budgetDatabaseConfigRoomTabItemService.saveBatch(newTabItems);

                return newTabItems;
            }

            return Collections.emptyList();
        }

        /**
         * 检查是否有配置
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return 是否有配置
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public boolean checkHasConfig(int propertyId, int year) {
            return count(new QueryWrapper<BudgetDatabaseConfigRoomTab>().lambda()
                    .eq(BudgetDatabaseConfigRoomTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigRoomTab::getBudgetYear, year)) > 0;
        }

    }