# BudgetDatabaseRoomOtherServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.NumberUtil;
    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.*;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseRoomOtherMapper;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.UpdateOneYearRevenueDTO;
    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 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.lang.reflect.Field;
    import java.math.BigDecimal;
    import java.time.Month;
    import java.time.ZonedDateTime;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2025-11-10
     */
    @Service
    @DS("rate_shop")
    public class BudgetDatabaseRoomOtherServiceImpl extends ServiceImpl<BudgetDatabaseRoomOtherMapper, BudgetDatabaseRoomOther> implements IBudgetDatabaseRoomOtherService {

        @Lazy
        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private IBudgetDatabaseStatusLogService budgetDatabaseStatusLogService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigRoomTabItemService budgetDatabaseConfigRoomTabItemService;
        @Lazy
        @Autowired
        private IBudgetDatabaseCalculateRevenueAndRoomService budgetDatabaseCalculateRevenueAndRoomService;
        @Autowired
        private IBudgetDatabaseDataLogRoomMonthStatisticsService budgetDatabaseDataLogRoomMonthStatisticsService;
        @Lazy
        @Autowired
        private IBudgetDatabaseConfigVariableRoomService budgetDatabaseConfigVariableRoomService;
        @Autowired
        private BeanMapper beanMapper;

        /**
         * 查询指标数据
         *
         * @param itemIds 指标 id
         * @return 指标数据
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<BudgetDatabaseRoomOther> listByItemIds(List<Integer> itemIds) {
            return list(new QueryWrapper<BudgetDatabaseRoomOther>().lambda().in(BudgetDatabaseRoomOther::getRoomId, itemIds));
        }

        /**
         * 查询指标数据
         *
         * @param itemId 指标 id
         * @return 指标数据
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public BudgetDatabaseRoomOther getByItemId(Integer itemId) {
            return getOne(new QueryWrapper<BudgetDatabaseRoomOther>().lambda().eq(BudgetDatabaseRoomOther::getRoomId, itemId));
        }

        /**
         * 批量查询指标数据
         *
         * @param tabIds tab id
         * @return 批量查询指标数据
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<BudgetDatabaseRoomOther> listByTabIds(List<Integer> tabIds) {
            if (CollectionUtils.isEmpty(tabIds)) return Collections.emptyList();

            return list(new QueryWrapper<BudgetDatabaseRoomOther>().lambda()
                    .in(BudgetDatabaseRoomOther::getTabId, tabIds));
        }

        /**
         * 检查是否可以更新预算
         *
         * @param propertyId        资产 id
         * @param year              年份
         * @param updateRevenueDTOS 更新预算数据
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateBudgetForStatus(int propertyId, Integer year, List<UpdateOneYearRevenueDTO> updateRevenueDTOS) {
            // Draft 状态下可以修改 Budget 数据，Completed 状态下 Budget 数据不可修改
            BudgetDatabase budgetDatabase = budgetDatabaseService.get(propertyId, year);
            if (Objects.isNull(budgetDatabase)) return;
            if (Objects.equals(budgetDatabase.getStatus(), BudgetDatabaseStatusEnum.DRAFT.getCode())) return;

            // 检查 Budget 数据不可修改
            // 查询数据库对应的 revenue 数据
            Map<Integer, BudgetDatabaseRoomOther> existRevenueByFbId = listByItemIds(updateRevenueDTOS.stream()
                    .map(UpdateOneYearRevenueDTO::getItemId).distinct().collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseRoomOther::getRoomId, v -> v));
            for (UpdateOneYearRevenueDTO updateOneYearRevenueDTO : updateRevenueDTOS) {
                BudgetDatabaseRoomOther existRevenue = existRevenueByFbId.getOrDefault(updateOneYearRevenueDTO.getItemId(), new BudgetDatabaseRoomOther());
                if (!NumberUtil.equals(existRevenue.getJanuary(), updateOneYearRevenueDTO.getJanuary())
                        || !NumberUtil.equals(existRevenue.getFebruary(), updateOneYearRevenueDTO.getFebruary())
                        || !NumberUtil.equals(existRevenue.getMarch(), updateOneYearRevenueDTO.getMarch())
                        || !NumberUtil.equals(existRevenue.getApril(), updateOneYearRevenueDTO.getApril())
                        || !NumberUtil.equals(existRevenue.getMay(), updateOneYearRevenueDTO.getMay())
                        || !NumberUtil.equals(existRevenue.getJune(), updateOneYearRevenueDTO.getJune())
                        || !NumberUtil.equals(existRevenue.getJuly(), updateOneYearRevenueDTO.getJuly())
                        || !NumberUtil.equals(existRevenue.getAugust(), updateOneYearRevenueDTO.getAugust())
                        || !NumberUtil.equals(existRevenue.getSeptember(), updateOneYearRevenueDTO.getSeptember())
                        || !NumberUtil.equals(existRevenue.getOctober(), updateOneYearRevenueDTO.getOctober())
                        || !NumberUtil.equals(existRevenue.getNovember(), updateOneYearRevenueDTO.getNovember())
                        || !NumberUtil.equals(existRevenue.getDecember(), updateOneYearRevenueDTO.getDecember())) {
                    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 propertyId        资产 id
         * @param year              年份
         * @param updateRevenueDTOS 预算数据
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateForReopen(int propertyId, Integer year, List<UpdateOneYearRevenueDTO> updateRevenueDTOS) {
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            ZonedDateTime currentDate = DateTimeUtils.dateNow(timezone);
            if (year > currentDate.getYear()) return;

            // Reopen 后的数据不可以修改历史日期的数据（检查是否有 reopen 记录，若有，则不能修改历史月的 预测数据）
            boolean hasReopen = budgetDatabaseStatusLogService.checkHasReopen(propertyId, year);
            if (!hasReopen) return;

            // 历史年
            if (year < currentDate.getYear()) {
                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");
            }

            // 1月时，不限制，可以修改任一月份的值
            Month currentMonth = currentDate.getMonth();
            if (currentMonth == Month.JANUARY) return;

            Map<Integer, BudgetDatabaseRoomOther> existRevenueByFbId = listByItemIds(updateRevenueDTOS.stream()
                    .map(UpdateOneYearRevenueDTO::getItemId).distinct().collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseRoomOther::getRoomId, v -> v));

            for (UpdateOneYearRevenueDTO updateOneYearRevenueDTO : updateRevenueDTOS) {
                BudgetDatabaseRoomOther existRevenue = existRevenueByFbId.getOrDefault(updateOneYearRevenueDTO.getItemId(), new BudgetDatabaseRoomOther());
                // 限制不能修改历史月份数据，比如：当前月是2月，则不能修改1月（january）的数据
                validateHistoricalMonthUpdates(currentMonth, existRevenue, updateOneYearRevenueDTO);
            }
        }

        /**
         * 检查历史月份数据不能修改
         *
         * @param currentMonth 当前月
         * @param existRevenue 已存在 revenue 数据
         * @param updateDTO    修改 revenue 数据
         * @author yyt
         * @date 2025/11/14
         **/
        private void validateHistoricalMonthUpdates(Month currentMonth, BudgetDatabaseRoomOther existRevenue, UpdateOneYearRevenueDTO updateDTO) {
            // 获取当前月份的数字表示(1-12)
            int currentMonthValue = currentMonth.getValue();

            // 遍历所有月份字段
            for (int month = 1; month < currentMonthValue; month++) {
                Month monthEnum = Month.of(month);
                String fieldName = monthEnum.name().toLowerCase();

                try {
                    // 使用反射获取字段值进行比较
                    Field existField = BudgetDatabaseRoomOther.class.getDeclaredField(fieldName);
                    Field updateField = UpdateOneYearRevenueDTO.class.getDeclaredField(fieldName);

                    existField.setAccessible(true);
                    updateField.setAccessible(true);

                    BigDecimal existValue = (BigDecimal) existField.get(existRevenue);
                    BigDecimal updateValue = (BigDecimal) updateField.get(updateDTO);

                    if (!NumberUtil.equals(existValue, updateValue)) {
                        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");
                    }
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    throw new InternalServerErrorException(BaseErrorCodeEnum.SYSTEM_ERROR.getI18nCode(),
                            BaseErrorCodeEnum.SYSTEM_ERROR.getCode(), "Failed to verify the monthly data", e);
                }
            }
        }

        /**
         * 检查是否可以修改预算
         *
         * @param propertyId        资产 id
         * @param tabId             tab id
         * @param updateRevenueDTOS 预算信息
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateForParticipateCalculate(int propertyId, int tabId, List<UpdateOneYearRevenueDTO> updateRevenueDTOS) {
            // 检查若指标是参与计算的，则不能手动修改预算数据，但是能改实际数据
            List<Integer> roomItemIds = updateRevenueDTOS.stream().map(UpdateOneYearRevenueDTO::getItemId).toList();
            List<Integer> enableBudgetRevenueItemIds = budgetDatabaseConfigRoomTabItemService.listByIds(roomItemIds)
                    .stream().filter(BudgetDatabaseConfigRoomTabItem::getEnableBudgetRevenue).map(BudgetDatabaseConfigRoomTabItem::getId).toList();
            Map<Integer, BudgetDatabaseRoomOther> existRoomOtherById = listByItemIds(roomItemIds).stream().collect(Collectors.toMap(BudgetDatabaseRoomOther::getRoomId, v -> v));
            for (UpdateOneYearRevenueDTO updateRevenueDTO : updateRevenueDTOS) {
                BudgetDatabaseRoomOther existRoomOther = existRoomOtherById.getOrDefault(updateRevenueDTO.getItemId(), new BudgetDatabaseRoomOther());
                if (enableBudgetRevenueItemIds.contains(updateRevenueDTO.getItemId())) {
                    if (!NumberUtil.equals(updateRevenueDTO.getJanuary(), existRoomOther.getJanuary()) || !NumberUtil.equals(updateRevenueDTO.getFebruary(), existRoomOther.getFebruary())
                            || !NumberUtil.equals(updateRevenueDTO.getMarch(), existRoomOther.getMarch()) || !NumberUtil.equals(updateRevenueDTO.getApril(), existRoomOther.getApril())
                            || !NumberUtil.equals(updateRevenueDTO.getMay(), existRoomOther.getMay()) || !NumberUtil.equals(updateRevenueDTO.getJune(), existRoomOther.getJune())
                            || !NumberUtil.equals(updateRevenueDTO.getJuly(), existRoomOther.getJuly()) || !NumberUtil.equals(updateRevenueDTO.getAugust(), existRoomOther.getAugust())
                            || !NumberUtil.equals(updateRevenueDTO.getSeptember(), existRoomOther.getSeptember()) || !NumberUtil.equals(updateRevenueDTO.getOctober(), existRoomOther.getOctober())
                            || !NumberUtil.equals(updateRevenueDTO.getNovember(), existRoomOther.getNovember()) || !NumberUtil.equals(updateRevenueDTO.getDecember(), existRoomOther.getDecember())) {
                        throw new BadRequestException(BudgetDatabaseErrorCons.CANNOT_EDIT_BUDGET_DATA_FOR_CALCULATED_INDICATORS,
                                BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "the budget data is not allowed to be modified");
                    }
                }
            }
        }

        /**
         * 编辑 Room 分类下的 Total
         *
         * @param propertyId        资产 id
         * @param year              年份
         * @param tabId             tab id
         * @param updateRevenueDTOS 预算数据
         * @param userId            操作人
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void editRoomTotal(int propertyId, Integer year, int tabId, List<UpdateOneYearRevenueDTO> updateRevenueDTOS, int userId) {
            baseMapper.saveOrUpdateBatch(updateRevenueDTOS.stream().map(item -> {
                BudgetDatabaseRoomOther other = beanMapper.map(item, BudgetDatabaseRoomOther.class);
                other.setPropertyId(propertyId).setTabId(tabId).setRoomId(item.getItemId());
                return other;
            }).sorted(Comparator.comparing(BudgetDatabaseRoomOther::getRoomId)).toList());

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

            // 过滤出没有参与计算的指标，看看这些指标是否配置在变配置中，若有，则重置依赖变量配置的相关指标数据
            List<Integer> roomItemIds = updateRevenueDTOS.stream().map(UpdateOneYearRevenueDTO::getItemId).toList();
            List<BudgetDatabaseConfigRoomTabItem> roomTabItems = budgetDatabaseConfigRoomTabItemService.listByIds(roomItemIds);
            List<BudgetDatabaseConfigRoomTabItem> notEnableBudgetRevenueItems = roomTabItems.stream().filter(item -> !item.getEnableBudgetRevenue()).toList();

            if (CollectionUtils.isEmpty(notEnableBudgetRevenueItems)) return;

            // 查询引用当前指标的变量配置
            List<BudgetDatabaseConfigVariableRoom> variableRooms = budgetDatabaseConfigVariableRoomService.listByItemIds(notEnableBudgetRevenueItems.stream().map(BudgetDatabaseConfigRoomTabItem::getId).toList());
            if (CollectionUtils.isEmpty(variableRooms)) {
                return;
            }

            budgetDatabaseCalculateRevenueAndRoomService.updateBudgetRevenue(propertyId, year, variableRooms.stream().map(BudgetDatabaseConfigVariableRoom::getVariableId).distinct().toList());
        }

        /**
         * 查询某资产下的 room other 数据
         *
         * @param propertyId 资产 id
         * @return room other 数据
         * @author yyt
         * @date 2025/11/26
         **/
        @Override
        public List<BudgetDatabaseRoomOther> listByPropertyId(Integer propertyId) {
            return list(new QueryWrapper<BudgetDatabaseRoomOther>().lambda().eq(BudgetDatabaseRoomOther::getPropertyId, propertyId));
        }

        /**
         * 批量更新
         *
         * @param updates 更新数据
         * @author yyt
         * @date 2025/11/26
         **/
        @Override
        public void updateBatchByIdRewrite(List<BudgetDatabaseRoomOther> updates) {
            if(CollectionUtils.isEmpty(updates)) return;

            baseMapper.updateBatchByIdRewrite(updates);
        }

    }