# BudgetDatabaseOtherRevenueServiceImpl.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.exception.BadRequestException;
    import cloud.wconnect.commonlib.exception.InternalServerErrorException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.UserDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.NumberUtil;
    import cloud.wconnect.rateshop.cons.budgetdatabase.*;
    import cloud.wconnect.rateshop.entity.budgetdatabase.*;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseOtherRevenueMapper;
    import cloud.wconnect.rateshop.pojo.bo.budgetdatabase.OneYearRevenueBO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.OtherDTO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.OtherDetailDTO;
    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 com.google.common.collect.Lists;
    import jakarta.validation.Valid;
    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 BudgetDatabaseOtherRevenueServiceImpl extends ServiceImpl<BudgetDatabaseOtherRevenueMapper, BudgetDatabaseOtherRevenue> implements IBudgetDatabaseOtherRevenueService {

        @Lazy
        @Autowired
        private IBudgetDatabaseService budgetDatabaseService;
        @Autowired
        private IBudgetDatabaseStatusLogService budgetDatabaseStatusLogService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabItemService budgetDatabaseConfigOtherTabItemService;
        @Autowired
        private IBudgetDatabaseCommentOtherService budgetDatabaseCommentOtherService;
        @Autowired
        private IBudgetDatabaseDataLogOtherService budgetDatabaseDataLogOtherService;
        @Autowired
        private IBudgetDatabaseCalculateService budgetDatabaseCalculateService;
        @Autowired
        private BudgetDatabaseCalculateRevenueAndRoomService budgetDatabaseCalculateRevenueAndRoomService;
        @Autowired
        private IBudgetDatabaseConfigVariableOtherService budgetDatabaseConfigVariableOtherService;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private BeanMapper beanMapper;

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

        /**
         * 查询指标数据
         *
         * @param itemIds 指标 id
         * @return 指标数据
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<BudgetDatabaseOtherRevenue> listByItemIds(List<Integer> itemIds) {
            if (CollectionUtils.isEmpty(itemIds)) return Collections.emptyList();

            return list(new QueryWrapper<BudgetDatabaseOtherRevenue>().lambda().in(BudgetDatabaseOtherRevenue::getOtherId, itemIds));
        }

        /**
         * 检查是否可以更新预算
         *
         * @param tab               tab
         * @param updateRevenueDTOS 更新预算数据
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateBudgetForStatus(BudgetDatabaseConfigOtherTab tab, List<UpdateOneYearRevenueDTO> updateRevenueDTOS) {
            Integer propertyId = tab.getPropertyId();
            Integer year = tab.getBudgetYear();
            // 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, BudgetDatabaseOtherRevenue> existRevenueByFbId = listByItemIds(updateRevenueDTOS.stream()
                    .map(UpdateOneYearRevenueDTO::getItemId).distinct().collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseOtherRevenue::getOtherId, v -> v));
            for (UpdateOneYearRevenueDTO updateOneYearRevenueDTO : updateRevenueDTOS) {
                BudgetDatabaseOtherRevenue existRevenue = existRevenueByFbId.getOrDefault(updateOneYearRevenueDTO.getItemId(), new BudgetDatabaseOtherRevenue());
                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 tab               tab
         * @param updateRevenueDTOS 预算数据
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateForReopen(BudgetDatabaseConfigOtherTab tab, List<UpdateOneYearRevenueDTO> updateRevenueDTOS) {
            Integer propertyId = tab.getPropertyId();
            Integer year = tab.getBudgetYear();
            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, BudgetDatabaseOtherRevenue> existRevenueByFbId = listByItemIds(updateRevenueDTOS.stream()
                    .map(UpdateOneYearRevenueDTO::getItemId).distinct().collect(Collectors.toList()))
                    .stream().collect(Collectors.toMap(BudgetDatabaseOtherRevenue::getOtherId, v -> v));

            for (UpdateOneYearRevenueDTO updateOneYearRevenueDTO : updateRevenueDTOS) {
                BudgetDatabaseOtherRevenue existRevenue = existRevenueByFbId.getOrDefault(updateOneYearRevenueDTO.getItemId(), new BudgetDatabaseOtherRevenue());
                // 限制不能修改历史月份数据，比如：当前月是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, BudgetDatabaseOtherRevenue 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 = BudgetDatabaseOtherRevenue.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);
                }
            }
        }

        /**
         * 查询 other
         *
         * @param tab             tab
         * @param selectCommenter 是否查询评论人
         * @return other
         * @author yyt
         * @date 2025/8/14
         **/
        @Override
        public OtherDTO listOther(BudgetDatabaseConfigOtherTab tab, boolean selectCommenter) {
            int tabId = tab.getId();
            Integer propertyId = tab.getPropertyId();
            // 查询配置指标
            List<BudgetDatabaseConfigOtherTabItem> items = budgetDatabaseConfigOtherTabItemService.listByTabIdAndClassification(tabId, OtherClassificationEnum.REVENUE.getCode());
            if (CollectionUtils.isEmpty(items)) return new OtherDTO();

            // 查询 revenue
            Map<Integer, BudgetDatabaseOtherRevenue> revenueByItemId = listByItemIds(items.stream().map(BudgetDatabaseConfigOtherTabItem::getId).toList())
                    .stream().collect(Collectors.toMap(BudgetDatabaseOtherRevenue::getOtherId, v -> v));

            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // comment
            List<BudgetDatabaseCommentOther> comments = budgetDatabaseCommentOtherService.list(propertyId, tabId, OtherCommentAreaCons.OTHER);
            Map<Integer, BudgetDatabaseCommentOther> commentByRow = comments.stream().collect(Collectors.toMap(BudgetDatabaseCommentOther::getRow, v -> v));
            Map<Integer, UserDTO> userById;
            if (selectCommenter) {
                userById = userInternalService.getUsers(propertyDTO.getTenantId(), comments.stream().map(BudgetDatabaseCommentOther::getUpdatedBy).distinct().toList())
                        .stream().collect(Collectors.toMap(UserDTO::getId, v -> v));
            } else {
                userById = Collections.emptyMap();
            }

            // 所有指标
            List<OtherDetailDTO> otherDetailDTOS = Lists.newArrayListWithExpectedSize(items.size() + 1);
            // 汇总指标最下面的 total 时使用
            List<OneYearRevenueBO> totalBOs = Lists.newArrayListWithExpectedSize(2);

            for (BudgetDatabaseConfigOtherTabItem other : items) {
                OneYearRevenueBO oneYearRevenueBO = beanMapper.map(revenueByItemId.getOrDefault(other.getId(), new BudgetDatabaseOtherRevenue()), OneYearRevenueBO.class);
                totalBOs.add(oneYearRevenueBO);

                OtherDetailDTO otherDetailDTO = beanMapper.map(budgetDatabaseCalculateService.calculateRevenueTotal(propertyId, Lists.newArrayList(oneYearRevenueBO)), OtherDetailDTO.class);
                otherDetailDTO.setId(other.getId());
                otherDetailDTO.setName(other.getName());
                otherDetailDTO.setComment(budgetDatabaseCommentOtherService.packageCommentDTO(commentByRow.get(other.getId()), userById));
                otherDetailDTO.setRevenueParticipateCalculate(other.getEnableBudgetRevenue());
                otherDetailDTOS.add(otherDetailDTO);
            }

            // total
            OtherDetailDTO otherDetailForTotal = beanMapper.map(budgetDatabaseCalculateService.calculateRevenueTotal(propertyId, totalBOs), OtherDetailDTO.class);
            otherDetailForTotal.setId(BaseConstant.ZERO);
            otherDetailForTotal.setName(BudgetDatabaseTitleCons.TOTAL);
            otherDetailForTotal.setComment(budgetDatabaseCommentOtherService.packageCommentDTO(commentByRow.get(BaseConstant.ZERO), userById));
            otherDetailDTOS.add(otherDetailForTotal);

            OtherDTO otherDTO = new OtherDTO();
            // 最后修改人
            otherDTO.setUpdatedInfo(budgetDatabaseDataLogOtherService.getLastLogDTO(tabId, FbLogAreaCons.REVENUE));
            // other revenue
            otherDTO.setItems(otherDetailDTOS);
            return otherDTO;
        }

        /**
         * 检查是否可以修改预算
         *
         * @param tab               tab
         * @param updateRevenueDTOS 预算信息
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        public void checkCanUpdateForParticipateCalculate(BudgetDatabaseConfigOtherTab tab, List<@Valid UpdateOneYearRevenueDTO> updateRevenueDTOS) {
            // 检查若指标是参与计算的，则不能手动修改预算数据，但是能改实际数据
            List<Integer> roomItemIds = updateRevenueDTOS.stream().map(UpdateOneYearRevenueDTO::getItemId).toList();
            List<Integer> enableBudgetRevenueItemIds = budgetDatabaseConfigOtherTabItemService.listByIds(roomItemIds)
                    .stream().filter(BudgetDatabaseConfigOtherTabItem::getEnableBudgetRevenue).map(BudgetDatabaseConfigOtherTabItem::getId).toList();
            Map<Integer, BudgetDatabaseOtherRevenue> existRoomOtherById = listByItemIds(roomItemIds).stream().collect(Collectors.toMap(BudgetDatabaseOtherRevenue::getOtherId, v -> v));
            for (UpdateOneYearRevenueDTO updateRevenueDTO : updateRevenueDTOS) {
                BudgetDatabaseOtherRevenue existData = existRoomOtherById.getOrDefault(updateRevenueDTO.getItemId(), new BudgetDatabaseOtherRevenue());
                if (enableBudgetRevenueItemIds.contains(updateRevenueDTO.getItemId())) {
                    if (!NumberUtil.equals(updateRevenueDTO.getJanuary(), existData.getJanuary()) || !NumberUtil.equals(updateRevenueDTO.getFebruary(), existData.getFebruary())
                            || !NumberUtil.equals(updateRevenueDTO.getMarch(), existData.getMarch()) || !NumberUtil.equals(updateRevenueDTO.getApril(), existData.getApril())
                            || !NumberUtil.equals(updateRevenueDTO.getMay(), existData.getMay()) || !NumberUtil.equals(updateRevenueDTO.getJune(), existData.getJune())
                            || !NumberUtil.equals(updateRevenueDTO.getJuly(), existData.getJuly()) || !NumberUtil.equals(updateRevenueDTO.getAugust(), existData.getAugust())
                            || !NumberUtil.equals(updateRevenueDTO.getSeptember(), existData.getSeptember()) || !NumberUtil.equals(updateRevenueDTO.getOctober(), existData.getOctober())
                            || !NumberUtil.equals(updateRevenueDTO.getNovember(), existData.getNovember()) || !NumberUtil.equals(updateRevenueDTO.getDecember(), existData.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");
                    }
                }
            }
        }

        /**
         * 修改 other
         *
         * @param tab               tab
         * @param updateRevenueDTOS 预算信息
         * @param userId            用户 id
         * @author yyt
         * @date 2025/11/14
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateOther(BudgetDatabaseConfigOtherTab tab, List<UpdateOneYearRevenueDTO> updateRevenueDTOS, int userId) {
            Integer tabId = tab.getId();
            Integer propertyId = tab.getPropertyId();
            Integer year = tab.getBudgetYear();
            // 处理 budget database 状态数据
            budgetDatabaseService.handleSaveBudgetDatabaseStatus(propertyId, year);

            baseMapper.saveOrUpdateBatch(updateRevenueDTOS.stream().map(item -> {
                BudgetDatabaseOtherRevenue other = beanMapper.map(item, BudgetDatabaseOtherRevenue.class);
                other.setPropertyId(propertyId).setTabId(tabId).setOtherId(item.getItemId());
                return other;
            }).sorted(Comparator.comparing(BudgetDatabaseOtherRevenue::getOtherId)).toList());

            // 记录操作日志
            budgetDatabaseDataLogOtherService.saveLog(propertyId, tabId, userId, OtherLogAreaCons.REVENUE);

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

            if (CollectionUtils.isEmpty(notEnableBudgetRevenueItems)) return;

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

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

        /**
         * 查询指标数据
         *
         * @param tabIds tabIds
         * @return  other
         * @author yyt
         * @date 2025/11/17
         **/
        @Override
        public List<BudgetDatabaseOtherRevenue> listByTabIds(List<Integer> tabIds) {
            if (CollectionUtils.isEmpty(tabIds)) return Collections.emptyList();

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

        /**
         * 根据 property id 获取 other
         *
         * @param propertyId property id
         * @return other
         * @author yyt
         * @date 2025/8/14
         **/
        @Override
        public List<BudgetDatabaseOtherRevenue> listByPropertyId(Integer propertyId) {
            return list(new QueryWrapper<BudgetDatabaseOtherRevenue>().lambda().eq(BudgetDatabaseOtherRevenue::getPropertyId, propertyId));
        }

        /**
         * 批量更新 other
         *
         * @param updates other
         * @author yyt
         * @date 2025/8/14
         **/
        @Override
        public void updateBatchByIdRewrite(List<BudgetDatabaseOtherRevenue> updates) {
            if (CollectionUtils.isEmpty(updates)) return;

            baseMapper.updateBatchByIdRewrite(updates);
        }

    }