# BudgetDatabaseRoomRgiServiceImpl.java

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

    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.NumberUtil;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseErrorCons;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseStatusEnum;
    import cloud.wconnect.rateshop.cons.budgetdatabase.RoomLogAreaCons;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabase;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseConfigRoomTab;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabaseRoomRgi;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseRoomRgiMapper;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.UpdateMonthRgiDTO;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseDataLogRoomMonthStatisticsService;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseRoomRgiService;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseService;
    import cloud.wconnect.rateshop.service.budgetdatabase.IBudgetDatabaseStatusLogService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import jakarta.annotation.Nullable;
    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.time.YearMonth;
    import java.time.ZonedDateTime;
    import java.util.Comparator;
    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 BudgetDatabaseRoomRgiServiceImpl extends ServiceImpl<BudgetDatabaseRoomRgiMapper, BudgetDatabaseRoomRgi> implements IBudgetDatabaseRoomRgiService {

        @Lazy
        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Autowired
        private IBudgetDatabaseStatusLogService budgetDatabaseStatusLogService;
        @Autowired
        private IBudgetDatabaseDataLogRoomMonthStatisticsService budgetDatabaseDataLogRoomMonthStatisticsService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;

        /**
         * 查询 rgi
         *
         * @param tabId tab id
         * @return rgi
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public List<BudgetDatabaseRoomRgi> list(Integer tabId) {
            return list(tabId, null);
        }

        /**
         * 查询 rgi
         *
         * @param tabId      tab id
         * @param yearMonths 年月
         * @return rgi
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public List<BudgetDatabaseRoomRgi> list(Integer tabId, @Nullable List<Integer> yearMonths) {
            return list(new QueryWrapper<BudgetDatabaseRoomRgi>().lambda()
                    .eq(BudgetDatabaseRoomRgi::getTabId, tabId)
                    .in(CollectionUtils.isNotEmpty(yearMonths), BudgetDatabaseRoomRgi::getYearMonth, yearMonths));
        }

        /**
         * 检查是否可以修改预测信息
         *
         * @param tab                tab
         * @param updateMonthRgiDTOS 修改信息
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateBudgetForStatus(BudgetDatabaseConfigRoomTab tab, List<UpdateMonthRgiDTO> updateMonthRgiDTOS) {
            Integer propertyId = tab.getPropertyId();
            Integer budgetYear = tab.getBudgetYear();
            // Draft 状态下可以修改 Budget 数据，Completed 状态下 Budget 数据不可修改
            BudgetDatabase budgetDatabase = budgetDatabaseService.get(propertyId, budgetYear);
            if (Objects.isNull(budgetDatabase)) return;
            if (Objects.equals(budgetDatabase.getStatus(), BudgetDatabaseStatusEnum.DRAFT.getCode())) return;

            // 检查 Budget 数据不可修改
            // 查询数据库对应的 rgi 数据
            Map<Integer, BudgetDatabaseRoomRgi> existRgiByYearMonth = list(tab.getId(), updateMonthRgiDTOS.stream()
                    .map(item -> Integer.parseInt(item.getYearMonth())).distinct().collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseRoomRgi::getYearMonth, v -> v));

            for (UpdateMonthRgiDTO updateMonthRgiDTO : updateMonthRgiDTOS) {
                BudgetDatabaseRoomRgi existRgi = existRgiByYearMonth.getOrDefault(Integer.parseInt(updateMonthRgiDTO.getYearMonth()), new BudgetDatabaseRoomRgi());
                if (!NumberUtil.equals(existRgi.getBudgetRgi(), updateMonthRgiDTO.getBudgetRgi())) {
                    throw new BadRequestException(BudgetDatabaseErrorCons.EDITING_NOT_SUPPORTED_FOR_COMPLETED_STATUS,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the budget data is not allowed to be modified");
                }
            }
        }

        /**
         * 检查是否可以修改预测信息
         *
         * @param tab                tab
         * @param updateMonthRgiDTOS 修改信息
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateForReopen(BudgetDatabaseConfigRoomTab tab, List<UpdateMonthRgiDTO> updateMonthRgiDTOS) {
            Integer propertyId = tab.getPropertyId();
            Integer budgetYear = tab.getBudgetYear();
            // Reopen 后的数据不可以修改历史日期的数据（检查是否有 reopen 记录，若有，则不能修改历史月的 预测数据）
            boolean hasReopen = budgetDatabaseStatusLogService.checkHasReopen(propertyId, budgetYear);
            if (!hasReopen) return;

            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            ZonedDateTime currentDate = DateTimeUtils.dateNow(timezone);
            int currentYearMonth = Integer.parseInt(YearMonthUtils.DATE_TIME_FORMATTER.format(YearMonth.of(currentDate.getYear(), currentDate.getMonthValue())));

            Map<Integer, BudgetDatabaseRoomRgi> existRgiByYearMonth = list(tab.getId(), updateMonthRgiDTOS.stream()
                    .map(item -> Integer.parseInt(item.getYearMonth())).distinct().collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseRoomRgi::getYearMonth, v -> v));

            for (UpdateMonthRgiDTO updateMonthRgiDTO : updateMonthRgiDTOS) {
                BudgetDatabaseRoomRgi existRgi = existRgiByYearMonth.getOrDefault(Integer.parseInt(updateMonthRgiDTO.getYearMonth()), new BudgetDatabaseRoomRgi());
                if (Integer.parseInt(updateMonthRgiDTO.getYearMonth()) < currentYearMonth && !NumberUtil.equals(existRgi.getBudgetRgi(), updateMonthRgiDTO.getBudgetRgi())) {
                    throw new BadRequestException(BudgetDatabaseErrorCons.EDITING_NOT_SUPPORTED_FOR_REOPEN,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the budget data of the previous month cannot be modified after reopen");
                }
            }
        }

        /**
         * 修改 rgi
         *
         * @param propertyId         资产 id
         * @param tab                tab
         * @param updateMonthRgiDTOS 修改信息
         * @param userId             操作人
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateMonthRgi(int propertyId, BudgetDatabaseConfigRoomTab tab, List<UpdateMonthRgiDTO> updateMonthRgiDTOS, int userId) {
            Integer year = tab.getBudgetYear();
            Integer tabId = tab.getId();
            // 处理 budget database 状态数据
            budgetDatabaseService.handleSaveBudgetDatabaseStatus(propertyId, year);

            baseMapper.saveOrUpdateBatch(updateMonthRgiDTOS.stream().map(item ->
                    new BudgetDatabaseRoomRgi().setPropertyId(propertyId).setTabId(tabId)
                            .setYearMonth(Integer.parseInt(item.getYearMonth()))
                            .setBudgetRgi(item.getBudgetRgi()).setActualRgi(item.getActualRgi())
                            .setStrRevpar(item.getStrRevpar()).setHotelRevpar(item.getHotelRevpar())
                            .setCsRevpar(item.getCsRevpar())
            ).sorted(Comparator.comparing(BudgetDatabaseRoomRgi::getYearMonth)).toList());

            // 记录操作日志
            budgetDatabaseDataLogRoomMonthStatisticsService.saveLog(propertyId, tabId, userId, RoomLogAreaCons.MONTH_STATISTICS);
        }

        /**
         * 批量查询 rgi
         *
         * @param tabIds tab ids
         * @return rgi
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public List<BudgetDatabaseRoomRgi> listByTabIds(List<Integer> tabIds) {
            return list(new QueryWrapper<BudgetDatabaseRoomRgi>().lambda()
                    .in(BudgetDatabaseRoomRgi::getTabId, tabIds));
        }

    }