# M3BudgetsServiceImpl.java

    package cloud.wconnect.finance.service.pl.impl;

    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.m3Config.PropertyM3ConfigDTO;
    import cloud.wconnect.commonlib.pojo.dto.m3Config.TenantM3ConfigDTO;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.finance.cons.pl.M3DataTypeEnum;
    import cloud.wconnect.finance.cons.pl.IndexTypeEnum;
    import cloud.wconnect.finance.cons.pl.RowCategoryEnum;
    import cloud.wconnect.finance.entity.pl.M3Budget;
    import cloud.wconnect.finance.entity.pl.M3Row;
    import cloud.wconnect.finance.entity.pl.M3Variable;
    import cloud.wconnect.finance.mapper.pl.M3BudgetsMapper;
    import cloud.wconnect.finance.pojo.dto.pl.index.IndexCalculationDTO;
    import cloud.wconnect.finance.pojo.dto.pl.index.RowIndexCalculationDTO;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.AccountBudgetDTO;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.BudgetDTO;
    import cloud.wconnect.finance.pojo.dto.pl.m3api.PeriodBudgetDTO;
    import cloud.wconnect.finance.service.pl.*;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.collections.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * M3系统budgets数据 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-09-25
     */
    @Service
    @DS("finance")
    public class M3BudgetsServiceImpl extends ServiceImpl<M3BudgetsMapper, M3Budget> implements IM3BudgetsService {

        @Autowired
        private M3ApiService m3ApiService;
        @Autowired
        private PlCommonService plCommonService;
        @Autowired
        private IM3VariableService m3VariableService;
        /**
         * 数据类型
         */
        public static final String DATA_TYPE = M3DataTypeEnum.BUDGET.getDataType();

        /**
         * 处理budget数据
         *
         * @param tenantM3ConfigDTO   租户M3配置
         * @param propertyM3ConfigDTO 资产M3配置
         * @param yearMonth           年月,yyyyMM
         * @param forcedRefresh       是否强制刷新m3数据
         * @author lichen
         * @date 2025/10/20
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void handleBudget(TenantM3ConfigDTO tenantM3ConfigDTO, PropertyM3ConfigDTO propertyM3ConfigDTO, Integer yearMonth, Boolean forcedRefresh) {
            Integer propertyId = propertyM3ConfigDTO.getPropertyId();
            boolean m3DataHandleStatus = plCommonService.getM3DataHandleStatus(DATA_TYPE, yearMonth.toString(), propertyId);
            if (forcedRefresh || !m3DataHandleStatus) {
                //删除旧数据
                remove(new LambdaQueryWrapper<M3Budget>().eq(M3Budget::getPropertyId, propertyId).eq(M3Budget::getYearMonth, yearMonth));

                //指定月份
                String monthValue = String.valueOf(YearMonthUtils.getYearMonth(yearMonth.toString()).getMonthValue());

                ArrayList<M3Budget> m3BudgetList = Lists.newArrayList();

                //获取budget数据
                List<BudgetDTO> budgetDTOS = m3ApiService.listBudget(tenantM3ConfigDTO, propertyM3ConfigDTO, yearMonth.toString().substring(0, 4));
                if (CollectionUtils.isNotEmpty(budgetDTOS)) {
                    //获取有效的budget数据
                    for (AccountBudgetDTO accountBudgetDTO : budgetDTOS.get(0).getAccountEntries()) {
                        if (StringUtils.isNotBlank(accountBudgetDTO.getGlAccountNumber())) {
                            //筛选出指定月份的budget数据
                            BigDecimal ptdAmount = accountBudgetDTO.getPeriodEntries().stream()
                                    .filter(item -> Objects.isNull(item.getDay()) && item.getPeriod().equals(monthValue))
                                    .map(PeriodBudgetDTO::getAmount).findFirst().orElse(BigDecimal.ZERO);
                            //计算ytd预算
                            BigDecimal ytdAmount = accountBudgetDTO.getPeriodEntries().stream()
                                    .filter(item -> {
                                        int period = Integer.parseInt(item.getPeriod());
                                        return Objects.isNull(item.getDay()) && period <= Integer.parseInt(monthValue);
                                    })
                                    .map(PeriodBudgetDTO::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

                            M3Budget m3Budget = new M3Budget();
                            m3Budget.setTenantId(tenantM3ConfigDTO.getTenantId());
                            m3Budget.setPropertyId(propertyM3ConfigDTO.getPropertyId());
                            m3Budget.setYearMonth(yearMonth);
                            m3Budget.setAccountNumber(accountBudgetDTO.getGlAccountNumber());
                            m3Budget.setPtdBudget(ptdAmount.setScale(BaseConstant.DATA_PRECISION, RoundingMode.HALF_UP));
                            m3Budget.setYtdBudget(ytdAmount.setScale(BaseConstant.DATA_PRECISION, RoundingMode.HALF_UP));
                            m3BudgetList.add(m3Budget);
                        }
                    }
                }

                if (CollectionUtils.isNotEmpty(m3BudgetList)) {
                    // 分批处理，每300条记录一批
                    int batchSize = 300;
                    for (int i = 0; i < m3BudgetList.size(); i += batchSize) {
                        int endIndex = Math.min(i + batchSize, m3BudgetList.size());
                        List<M3Budget> batch = m3BudgetList.subList(i, endIndex);
                        baseMapper.insertBatch(batch);
                    }
                }

                //缓存处理状态
                plCommonService.setM3DataAlreadyHandled(DATA_TYPE, yearMonth.toString(), propertyId);
            }
        }

        /**
         * 根据m3数据计算指标
         *
         * @param propertyId                        资产id
         * @param m3Row                             m3 Row
         * @param yearMonth                         计算的年月，yyyyMM
         * @param alreadyCalculationRowIndexDTOList 已经计算过的Row指标集合
         * @param m3DataMap                         m3数据集合
         * @param rooms                             房间数
         * @return java.util.List<cloud.wconnect.finance.pojo.dto.pl.index.IndexCalculationDTO>
         * @author lichen
         * @date 2025/9/26
         */
        @Override
        public List<IndexCalculationDTO> calculationIndex(Integer propertyId, M3Row m3Row, Integer yearMonth,
                                                          ArrayList<RowIndexCalculationDTO> alreadyCalculationRowIndexDTOList,
                                                          Map<String, Object> m3DataMap, Integer rooms) {
            String accountNumber = m3Row.getAccountNumber();
            List<IndexCalculationDTO> indexCalculationDTOS = Lists.newArrayListWithCapacity(2);
            ArrayList<String> needIndexList = Lists.newArrayList(IndexTypeEnum.PTD_BDGT.getIndexType(), IndexTypeEnum.YTD_BDGT.getIndexType());

            if (m3Row.getRowCategory().equals(RowCategoryEnum.ITEM.getValue())) {//非公式计算
                //指定资产指定月份的Budgets数据
                List<M3Budget> m3BudgetList = (List<M3Budget>) m3DataMap.getOrDefault(M3DataTypeEnum.BUDGET.getDataType(), new ArrayList<M3Budget>());

                Map<String, M3Budget> accountNumber2Amount = m3BudgetList.stream().collect(Collectors.toMap(M3Budget::getAccountNumber, Function.identity()));
                if (MapUtils.isNotEmpty(accountNumber2Amount)) {
                    //获取row对应的Budgets数据
                    M3Budget m3Budget = accountNumber2Amount.getOrDefault(accountNumber, null);
                    if (Objects.nonNull(m3Budget)) {
                        //PTD BDGT
                        IndexCalculationDTO ptdBdgtIndexCalculationDTO = new IndexCalculationDTO(IndexTypeEnum.PTD_BDGT.getIndexType(), m3Budget.getPtdBudget(), true);
                        indexCalculationDTOS.add(ptdBdgtIndexCalculationDTO);
                        //YTD BDGT
                        IndexCalculationDTO ytdBdgtIndexCalculationDTO = new IndexCalculationDTO(IndexTypeEnum.YTD_BDGT.getIndexType(), m3Budget.getYtdBudget(), true);
                        indexCalculationDTOS.add(ytdBdgtIndexCalculationDTO);
                    }
                }
            } else {//公式计算
                //特殊变量集合
                List<M3Variable> specialVariables = (List<M3Variable>) m3DataMap.getOrDefault(M3DataTypeEnum.SPECIAL_VARIABLES.getDataType(), new ArrayList<M3Variable>());
                for (String indexType : needIndexList) {
                    //替换公式中的特殊变量
                    String expandedFormula = m3VariableService.replaceSpecialVariable(m3Row.getFormula(), propertyId, indexType, yearMonth, rooms, specialVariables);

                    IndexCalculationDTO indexCalculationDTO = plCommonService.calculationIndex(m3Row.getRowNumber(), expandedFormula, indexType, alreadyCalculationRowIndexDTOList, "");
                    indexCalculationDTOS.add(indexCalculationDTO);
                }
            }

            //没有计算的指标默认按0处理
            List<String> alreadyCalculationIndexList = indexCalculationDTOS.stream().map(IndexCalculationDTO::getIndexType).toList();
            List<IndexCalculationDTO> defaultIndexCalculationDTOS = needIndexList.stream().filter(indexType -> !alreadyCalculationIndexList.contains(indexType))
                    .map(indexType -> new IndexCalculationDTO(indexType, new BigDecimal("0"), true))
                    .toList();
            if (CollectionUtils.isNotEmpty(defaultIndexCalculationDTOS)) {
                indexCalculationDTOS.addAll(defaultIndexCalculationDTOS);
            }

            return indexCalculationDTOS;
        }

        /**
         * 根据资产id和年月获取budget数据
         *
         * @param propertyId 资产id
         * @param yearMonth  年月，yyyyMM
         * @return java.util.List<cloud.wconnect.finance.entity.pl.M3Budgets>
         * @author lichen
         * @date 2025/9/26
         */
        @Override
        public List<M3Budget> listByYearMonth(Integer propertyId, Integer yearMonth) {
            return list(new LambdaQueryWrapper<M3Budget>().eq(M3Budget::getPropertyId, propertyId).eq(M3Budget::getYearMonth, yearMonth));
        }
    }