# BudgetDatabaseConfigFbTabServiceImpl.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.FbClassificationEnum;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigFbTab;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigFbTabItem;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseFbCovers;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseFbRevenue;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseConfigFbTabMapper;
    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 BudgetDatabaseConfigFbTabServiceImpl extends ServiceImpl<BudgetDatabaseConfigFbTabMapper, BudgetDatabaseConfigFbTab> implements IBudgetDatabaseConfigFbTabService {

        @Autowired
        private IBudgetDatabaseConfigVariableFbService budgetDatabaseConfigVariableFbService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigFbTabItemService budgetDatabaseConfigFbTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseFbRevenueService budgetDatabaseFbRevenueService;
        @Autowired
        private IBudgetDatabaseFbCoversService budgetDatabaseFbCoversService;
        @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<BudgetDatabaseConfigFbTab> list(int propertyId, int year) {
            return list(new QueryWrapper<BudgetDatabaseConfigFbTab>().lambda()
                    .eq(BudgetDatabaseConfigFbTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigFbTab::getBudgetYear, year)
                    .orderByAsc(BudgetDatabaseConfigFbTab::getId));
        }

        /**
         * 查询所有 tab
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return tab
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<TabDTO> listDTOs(int propertyId, int year) {
            List<BudgetDatabaseConfigFbTab> 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) {
            BudgetDatabaseConfigFbTab one = getOne(new QueryWrapper<BudgetDatabaseConfigFbTab>().lambda()
                    .eq(BudgetDatabaseConfigFbTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigFbTab::getBudgetYear, year)
                    .eq(BudgetDatabaseConfigFbTab::getName, name)
                    .ne(Objects.nonNull(id), BudgetDatabaseConfigFbTab::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) {
            BudgetDatabaseConfigFbTab tab = new BudgetDatabaseConfigFbTab()
                    .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 BudgetDatabaseConfigFbTab checkExist(int id) {
            BudgetDatabaseConfigFbTab 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(BudgetDatabaseConfigFbTab 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 budgetDatabaseConfigFbTab;propertyId:" + propertyId + ",budgetDatabaseConfigFbTabPropertyId:" +
                        tab.getPropertyId() + ",budgetDatabaseConfigFbTabId:" + 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 BudgetDatabaseConfigFbTab()
                    .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<BudgetDatabaseConfigFbTab>().lambda()
                    .eq(BudgetDatabaseConfigFbTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigFbTab::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 BudgetDatabaseConfigFbTab().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(BudgetDatabaseConfigFbTab tab) {
            // 当前 tab 下的指标被变量配置占用时，不可以删除。
            Integer tabId = tab.getId();
            if (budgetDatabaseConfigVariableFbService.checkTabBoundToTheVariable(tabId)) {
                return new ConfigDeleteFlagDTO(false, true);
            }

            List<BudgetDatabaseFbRevenue> revenues = Collections.emptyList();
            List<BudgetDatabaseFbCovers> covers = Collections.emptyList();
            // 当前 tab 下的指标有数据时，不能删除。
            List<BudgetDatabaseConfigFbTabItem> itemList = budgetDatabaseConfigFbTabItemService.listByTabId(tabId);
            if (CollectionUtils.isNotEmpty(itemList)) {
                Map<Integer, List<BudgetDatabaseConfigFbTabItem>> itemsByClassification = itemList.stream().collect(Collectors.groupingBy(BudgetDatabaseConfigFbTabItem::getClassification));
                List<BudgetDatabaseConfigFbTabItem> revenueItems = itemsByClassification.getOrDefault(FbClassificationEnum.REVENUE.getCode(), Collections.emptyList());
                if (CollectionUtils.isNotEmpty(revenueItems)) {
                    revenues = budgetDatabaseFbRevenueService.listByItemIds(revenueItems.stream().map(BudgetDatabaseConfigFbTabItem::getId).collect(Collectors.toList()));
                    if (CollectionUtils.isNotEmpty(revenues) && revenues.stream().anyMatch(BudgetDatabaseFbRevenue::hasData)) {
                        return new ConfigDeleteFlagDTO(true, false);
                    }
                }
                List<BudgetDatabaseConfigFbTabItem> coversItems = itemsByClassification.getOrDefault(FbClassificationEnum.COVERS.getCode(), Collections.emptyList());
                if (CollectionUtils.isNotEmpty(coversItems)) {
                    covers = budgetDatabaseFbCoversService.listByItemIds(coversItems.stream().map(BudgetDatabaseConfigFbTabItem::getId).collect(Collectors.toList()));
                    if (CollectionUtils.isNotEmpty(covers) && covers.stream().anyMatch(BudgetDatabaseFbCovers::hasData)) {
                        return new ConfigDeleteFlagDTO(true, false);
                    }
                }
            }

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

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

            // 删除 指标占用数据记录
            if (CollectionUtils.isNotEmpty(revenues)) {
                budgetDatabaseFbRevenueService.removeByIds(revenues.stream().map(BudgetDatabaseFbRevenue::getId).toList());
            }
            if (CollectionUtils.isNotEmpty(covers)) {
                budgetDatabaseFbCoversService.removeByIds(covers.stream().map(BudgetDatabaseFbCovers::getId).toList());
            }

            return new ConfigDeleteFlagDTO(false, false);
        }

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

        /**
         * 批量查询参与计算的 tab
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return tab
         * @author yyt
         * @date 2025/11/13
         **/
        @Override
        public List<Integer> listParticipateCalculateTabs(int propertyId, int year) {
            return list(new QueryWrapper<BudgetDatabaseConfigFbTab>().lambda()
                    .in(BudgetDatabaseConfigFbTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigFbTab::getBudgetYear, year)
                    .eq(BudgetDatabaseConfigFbTab::getParticipateCalculate, true))
                    .stream().map(BudgetDatabaseConfigFbTab::getId).toList();
        }

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

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

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

            List<BudgetDatabaseConfigFbTabItem> tabItems = budgetDatabaseConfigFbTabItemService.listByTabIds(tabs.stream().map(BudgetDatabaseConfigFbTab::getId).toList());
            Map<Integer, List<BudgetDatabaseConfigFbTabItem>> itemsByTabId = tabItems
                    .stream().collect(Collectors.groupingBy(BudgetDatabaseConfigFbTabItem::getFbTabId));

            long now = DateTimeUtils.nowTimestamp();

            List<BudgetDatabaseConfigFbTab> newTabs = tabs.stream().map(item -> {
                BudgetDatabaseConfigFbTab tab = beanMapper.map(item, BudgetDatabaseConfigFbTab.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(BudgetDatabaseConfigFbTab::getOldTabId, BudgetDatabaseConfigFbTab::getId));

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

                    Integer newTabId = newTabIdByOldTabId.get(tabId);

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

                        newItem.setOldTabId(tabId);
                        newItem.setOldItemId(item.getId());
                        newItem.setUpdatedBy(BaseConstant.ZERO);
                        newItem.setUpdatedTime(now);
                        return newItem;
                    }).toList());
                });
                budgetDatabaseConfigFbTabItemService.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<BudgetDatabaseConfigFbTab>().lambda()
                    .eq(BudgetDatabaseConfigFbTab::getPropertyId, propertyId)
                    .eq(BudgetDatabaseConfigFbTab::getBudgetYear, year)) > 0;
        }

    }