# BudgetDatabaseServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CommonErrorI18nConstant;
    import cloud.wconnect.commonlib.exception.BadRequestException;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.util.DateTimeUtils;
    import cloud.wconnect.commonlib.util.TransactionUtils;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseStatusEnum;
    import cloud.wconnect.rateshop.cons.budgetdatabase.BudgetDatabaseStatusTypeEnum;
    import cloud.wconnect.rateshop.entity.budgetdatabase.BudgetDatabase;
    import cloud.wconnect.rateshop.mapper.budgetdatabase.BudgetDatabaseMapper;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.CopyToDTO;
    import cloud.wconnect.rateshop.pojo.dto.budgetdatabase.PropertyCopyToDTO;
    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.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.time.ZonedDateTime;
    import java.util.Collections;
    import java.util.List;
    import java.util.Objects;
    import java.util.concurrent.Executor;

    /**
     * <p>
     *  服务实现类
     * </p>
     *
     * @author yyt
     * @since 2025-08-13
     */
    @Service
    @DS("rate_shop")
    public class BudgetDatabaseServiceImpl extends ServiceImpl<BudgetDatabaseMapper, BudgetDatabase> implements IBudgetDatabaseService {

        @Autowired
        private IBudgetDatabaseConfigRoomTabService budgetDatabaseConfigRoomTabService;
        @Autowired
        private IBudgetDatabaseConfigFbTabService budgetDatabaseConfigFbTabService;
        @Autowired
        private IBudgetDatabaseConfigOtherTabService budgetDatabaseConfigOtherTabService;
        @Autowired
        private IBudgetDatabaseStatusLogService budgetDatabaseStatusLogService;
        @Autowired
        private IBudgetDatabaseSendEventService budgetDatabaseSendEventService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private Executor executor;
        @Autowired
        private BeanMapper beanMapper;

        /**
         * 检查是否配置过
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return true: 已配置 false: 未配置
         * @author yyt
         * @date 2025/8/14
         **/
        @Override
        public boolean checkHasConfig(int propertyId, int year) {
            boolean hasConfig = budgetDatabaseConfigRoomTabService.checkHasConfig(propertyId, year);
            if (!hasConfig) {
                hasConfig = budgetDatabaseConfigFbTabService.checkHasConfig(propertyId, year);
                if (!hasConfig) {
                    hasConfig = budgetDatabaseConfigOtherTabService.checkHasConfig(propertyId, year);
                }
            }
            return hasConfig;
        }

        /**
         * 获取 BudgetDatabase
         *
         * @param propertyId 资产 id
         * @param year       年
         * @return BudgetDatabase
         * @author yyt
         **/
        @Override
        public BudgetDatabase get(int propertyId, int year) {
            return getOne(new QueryWrapper<BudgetDatabase>().lambda()
                    .eq(BudgetDatabase::getPropertyId, propertyId)
                    .eq(BudgetDatabase::getBudgetYear, year));
        }

        /**
         * 处理 budget database 状态数据
         *
         * @param propertyId 资产 id
         * @param year       年
         * @author yyt
         * @date 2025/8/15
         **/
        @Override
        public void handleSaveBudgetDatabaseStatus(int propertyId, int year) {
            BudgetDatabase budgetDatabase = get(propertyId, year);
            if (Objects.nonNull(budgetDatabase)) return;

            budgetDatabase = new BudgetDatabase();
            budgetDatabase.setPropertyId(propertyId);
            budgetDatabase.setBudgetYear(year);
            budgetDatabase.setStatus(BudgetDatabaseStatusEnum.DRAFT.getCode());
            save(budgetDatabase);
        }

        /**
         * 修改状态
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param newStatus  1.complete，2.reopen
         * @param userId     操作人
         * @author yyt
         * @date 2025/8/15
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void updateStatus(int propertyId, int year, Integer newStatus, int userId) {
            // 修改状态
            boolean complete = Objects.equals(newStatus, BudgetDatabaseStatusTypeEnum.COMPLETE.getCode());
            baseMapper.saveOrUpdate(new BudgetDatabase().setPropertyId(propertyId).setBudgetYear(year)
                    .setStatus(complete ? BudgetDatabaseStatusEnum.COMPLETE.getCode() : BudgetDatabaseStatusEnum.DRAFT.getCode()));

            // 记录操作日志
            budgetDatabaseStatusLogService.saveLog(propertyId, year, userId, newStatus);

            if (complete) {
                // 完成后发送事件
                TransactionUtils.afterCommitAsyncExecute(executor, () -> budgetDatabaseSendEventService.sendBudgetEvent(propertyId, year));
            }
        }

        /**
         * Copy To
         *
         * @param propertyId 资产 id
         * @param year       年
         * @param copyToDTO  copy to
         * @author yyt
         * @date 2025/8/15
         **/
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void copyTo(int propertyId, int year, CopyToDTO copyToDTO) {

        }

        /**
         * budget database 完成后，则不支持任何操作
         *
         * @param propertyId 资产 id
         * @param year       年
         * @author yyt
         * @date 2025/9/6
         **/
        @Override
        public void checkComplete(Integer propertyId, int year) {
            BudgetDatabase budgetDatabase = get(propertyId, year);
            if (Objects.isNull(budgetDatabase)) return;

            if (Objects.equals(budgetDatabase.getStatus(), BudgetDatabaseStatusEnum.COMPLETE.getCode())) {
                throw new BadRequestException(CommonErrorI18nConstant.FINISH_CANNOT_HANDLE,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(), "After completion, no operation is supported.");
            }
        }

        /**
         * 检查是否有已完成的 budget database
         *
         * @param propertyIds 资产 id
         * @param year        年
         * @return  true: 有完成的 false: 没有完成的
         * @author yyt
         * @date 2025/9/6
         **/
        @Override
        public boolean checkComplete(List<Integer> propertyIds, int year) {
            return count(new QueryWrapper<BudgetDatabase>().lambda()
                    .eq(BudgetDatabase::getBudgetYear, year)
                    .in(BudgetDatabase::getPropertyId, propertyIds)
                    .eq(BudgetDatabase::getStatus, BudgetDatabaseStatusEnum.COMPLETE.getCode())) > 0;
        }

        /**
         * 查询可 copy to 的资产
         *
         * @param year        年
         * @param propertyIds 目标资产
         * @return 资产信息
         * @author yyt
         * @date 2025/9/6
         **/
        @Override
        public List<PropertyCopyToDTO> listCopyToProperty(int year, List<Integer> propertyIds) {
            // 目标资产下对应年份的 budget database 若是已完成，则表示不可 copy to（无效）
            List<Integer> completePropertyIds = list(new QueryWrapper<BudgetDatabase>().lambda()
                    .eq(BudgetDatabase::getBudgetYear, year)
                    .in(BudgetDatabase::getPropertyId, propertyIds)
                    .eq(BudgetDatabase::getStatus, BudgetDatabaseStatusEnum.COMPLETE.getCode()))
                    .stream().map(BudgetDatabase::getPropertyId).toList();

            return propertyI18nRedisService.list(propertyIds).stream()
                    .map(item ->
                            beanMapper.map(item, PropertyCopyToDTO.class, dto ->
                                    dto.setEffective(!completePropertyIds.contains(item.getId()))))
                    .toList();
        }

        /**
         * 获取可以修改预算数据的月份
         *
         * @param propertyId 资产 id
         * @param budgetYear 预算年
         * @return 月份
         * @author yyt
         * @date 2025/11/10
         **/
        @Override
        public List<Integer> listCanUpdateBudgetDataMonths(Integer propertyId, Integer budgetYear) {
            List<Integer> allMonths = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);

            // 若预算年是未来年，则可以修改所有月的预算数据【1-12月】
            String timezone = propertyI18nRedisService.get(propertyId).getTimezone();
            ZonedDateTime currentDate = DateTimeUtils.dateNow(timezone);
            if (budgetYear > currentDate.getYear()) return allMonths;

            // 若预算年 <= 当前年 且 没有 reopen 过，则可以修改所有月的预算数据【1-12月】
            boolean hasReopen = budgetDatabaseStatusLogService.checkHasReopen(propertyId, budgetYear);
            if (!hasReopen) return allMonths;

            // 若 预算年 < 当前年 且 有 reopen 过，则不可以修改所有月份的预算数据
            if (budgetYear < currentDate.getYear()) {
                return Collections.emptyList();
            }

            // 若 预算年 = 当前年 且 有 reopen 过，则只可以修改当前月+未来月的预算数据
            int currentMonth = currentDate.getMonthValue();
            return allMonths.stream()
                    .filter(month -> month >= currentMonth)
                    .toList();
        }

    }