# M3PlReportServiceImpl.java

    package cloud.wconnect.finance.service.pl.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.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.dto.RoomsConfigDTO;
    import cloud.wconnect.commonlib.pojo.dto.internationalization.InternationalizationNumberDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.RoomsConfigInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.finance.cons.PrecisionCons;
    import cloud.wconnect.finance.cons.pl.*;
    import cloud.wconnect.finance.entity.pl.*;
    import cloud.wconnect.finance.mapper.pl.M3PlReportMapper;
    import cloud.wconnect.finance.pojo.dto.pl.*;
    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.report.IndexDTO;
    import cloud.wconnect.finance.pojo.dto.pl.report.IndexValueDTO;
    import cloud.wconnect.finance.pojo.dto.pl.report.RowDTO;
    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.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import com.google.common.collect.Sets;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang3.StringUtils;
    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.math.BigDecimal;
    import java.time.LocalDate;
    import java.time.YearMonth;
    import java.time.temporal.TemporalAdjusters;
    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-09-25
     */
    @Service
    @DS("finance")
    @Slf4j
    public class M3PlReportServiceImpl extends ServiceImpl<M3PlReportMapper, M3PlReport> implements IM3PlReportService {

        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        @Lazy
        private IM3RowService m3RowService;
        @Autowired
        private IM3VariableService m3VariableService;
        @Autowired
        private PlCommonService plCommonService;
        @Autowired
        private IM3TrialbalanceService m3TrialbalanceService;
        @Autowired
        private IM3BudgetsService m3BudgetsService;
        @Autowired
        private IM3ForecastsService m3ForecastsService;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private IM3PlReportLogService m3PlReportLogService;
        @Autowired
        private IM3AccountService m3AccountService;
        @Autowired
        private IM3PlLayoutService m3PlLayoutService;
        @Autowired
        private M3DataService m3DataService;
        @Autowired
        private RoomsConfigInternalService roomsConfigInternalService;

        /**
         * 根据资产id和月份查询
         *
         * @param propertyId 资产id
         * @param yearMonth  月份
         * @return cloud.wconnect.finance.entity.pl.M3PlReport
         * @author lichen
         * @date 2025/9/29
         */
        @Override
        public M3PlReport listByYearMonth(Integer propertyId, Integer yearMonth) {
            return getOne(new LambdaQueryWrapper<M3PlReport>().eq(M3PlReport::getPropertyId, propertyId)
                    .eq(M3PlReport::getYearMonth, yearMonth));
        }

        /**
         * 生成P&L报告
         *
         * @param propertyId 资产id
         * @param yearMonth  指定年月，yyyyMM
         * @param userId     创建人id
         * @author lichen
         * @date 2025/9/26
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void generatePLReport(Integer propertyId, Integer yearMonth, int userId, Integer logId) {
            boolean result = true;
            try {
                PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);
                //查询m3Row
                List<M3Row> m3RowList = m3RowService.listByPropertyIdOrderById(propertyId);
                //查询Layout
                List<M3PlLayout> m3PlLayoutList = m3PlLayoutService.listOrderByIdAsc(propertyId);
                //查询资产的房间数
                RoomsConfigDTO roomsConfig = roomsConfigInternalService.getRoomsConfig(propertyId);
                Integer rooms = roomsConfig.getRooms();

                //获取需要计算的Layout
                List<M3PlLayout> needCalculationLayouts = m3PlLayoutList.stream()
                        .filter(m3PlLayout -> m3PlLayout.getCategory() == M3PlLayout.CATEGORY_ROW)
                        .toList();
                //layout公式中依赖的Row
                List<String> rowNumbersByLayoutFormula = needCalculationLayouts.stream().map(item -> {
                    HashSet<String> set = Sets.newHashSet();
                    //POR公式依赖的Row
                    set.add(item.getPorFormulaDenominator());

                    //%Rev公式只有除法时才需要根据M3基础数据计算指标
                    RevFormulaDTO revFormulaDTO = JsonUtils.convertStringToObject(objectMapper, item.getRevFormula(), RevFormulaDTO.class);
                    if (revFormulaDTO.getType() == RevFormulaTypeEnum.DIVISION.getType()) {
                        List<String> rowNumberList = plCommonService.parseRevUsedRowNumber(item.getRevFormula());
                        set.addAll(rowNumberList);
                    }

                    return set;
                }).flatMap(Collection::stream).toList();

                //需要根据M3Data计算的Row集合
                HashSet<String> needCalculationRowNumbersByM3Data = Sets.newHashSet();
                needCalculationRowNumbersByM3Data.addAll(needCalculationLayouts.stream().map(M3PlLayout::getRowNumber).toList());
                needCalculationRowNumbersByM3Data.addAll(rowNumbersByLayoutFormula);

                //查询【公式】、【常量】类型的变量
                List<M3Variable> m3Variables = m3VariableService.listByTypes(propertyId, Lists.newArrayList(M3Variable.TYPE_CONSTANT, M3Variable.TYPE_FORMULA));
                List<M3VariableWithFormulaWrapperDTO> m3VariableWithFormulaWrapperDTOList = m3VariableService.convertToDTO(propertyId, m3Variables);

                /*
                 * 1.替换公式中的公有变量，再进行拓扑排序
                 */
                List<M3Row> orderM3RowList = plCommonService.topologySort(m3RowList, m3VariableWithFormulaWrapperDTOList);

                //Row指标计算的DTO
                ArrayList<RowIndexCalculationDTO> alreadyCalculationRowIndexDTOList = Lists.newArrayListWithCapacity(orderM3RowList.size());
                ArrayList<String> alreadyCalculationRowNumbers = Lists.newArrayListWithCapacity(orderM3RowList.size());

                //从数据库查询计算所需的M3数据
                Map<String, Object> m3DataMap = m3DataService.listExistM3Data(propertyId, yearMonth);

                for (M3Row m3Row : orderM3RowList) {
                    //2. 已经计算完毕时停止循环
                    if (alreadyCalculationRowNumbers.containsAll(needCalculationRowNumbersByM3Data)) {
                        break;
                    }

                    //3.根据row分类判断是否需要计算指标
                    if (!RowCategoryEnum.needCalculation(m3Row.getRowCategory())) {
                        continue;
                    }

                    //当前计算的Row
                    RowIndexCalculationDTO currentRowIndexCalculationDTO = new RowIndexCalculationDTO();
                    currentRowIndexCalculationDTO.setRowNumber(m3Row.getRowNumber());
                    currentRowIndexCalculationDTO.setRowName(m3Row.getRowName());
                    currentRowIndexCalculationDTO.setFormat(m3Row.getFormat());

                    //5. 根据m3数据计算指标
                    ArrayList<IndexCalculationDTO> indexCalculationDTOS = calculationIndexByM3Data(propertyId, m3Row, yearMonth, alreadyCalculationRowIndexDTOList, m3DataMap, rooms);
                    currentRowIndexCalculationDTO.setIndexCalculationList(indexCalculationDTOS);

                    alreadyCalculationRowIndexDTOList.add(currentRowIndexCalculationDTO);
                    alreadyCalculationRowNumbers.add(m3Row.getRowNumber());
                }

                Map<String, RowIndexCalculationDTO> rowNumber2RowIndexCalculationDTO = alreadyCalculationRowIndexDTOList.stream()
                        .collect(Collectors.toMap(RowIndexCalculationDTO::getRowNumber, Function.identity()));
                //6. layout拓扑排序后，获取有效的Row指标集合
                ArrayList<RowIndexCalculationDTO> effectiveRowIndexDTOList = Lists.newArrayListWithCapacity(alreadyCalculationRowIndexDTOList.size());
                for (M3PlLayout orderM3PlLayout : plCommonService.topologySortForLayout(needCalculationLayouts)) {
                    effectiveRowIndexDTOList.add(rowNumber2RowIndexCalculationDTO.get(orderM3PlLayout.getRowNumber()));
                }

                Map<String, M3PlLayout> rowNumber2M3PlLayout = needCalculationLayouts.stream().collect(Collectors.toMap(M3PlLayout::getRowNumber, Function.identity()));
                for (RowIndexCalculationDTO currentRowIndexCalculationDTO : effectiveRowIndexDTOList) {
                    M3PlLayout m3PlLayout = rowNumber2M3PlLayout.get(currentRowIndexCalculationDTO.getRowNumber());

                    //7.计算【本身的指标作为计算因子】计算的指标
                    calculationIndexBySelfIndex(m3PlLayout.getRowType(), currentRowIndexCalculationDTO);

                    //8.计算【POR】指标
                    calculationIndexByPor(m3PlLayout, currentRowIndexCalculationDTO, alreadyCalculationRowIndexDTOList);

                    //9.计算【%Rev】指标
                    calculationIndexByRev(m3PlLayout, currentRowIndexCalculationDTO, alreadyCalculationRowIndexDTOList);
                }

                //保存P&L报告
                saveReport(propertyDTO, yearMonth, effectiveRowIndexDTOList, m3PlLayoutList);
            } catch (Exception e) {
                result = false;
                log.error("P&L generate report error", e);
                throw e;
            } finally {
                if (result) {
                    //记录成功的日志
                    m3PlReportLogService.updateSuccess(logId);
                } else {
                    //记录错误日志
                    m3PlReportLogService.updateFailed(logId, M3PlReportLog.MESSAGE_FLAG_CALCULATE_FAILED);
                }
            }
        }

        /**
         * 校验是否可以生成P&L报告
         *
         * @param propertyId 资产id
         * @param yearMonth  目标年月，yyyyMM
         * @param userId     用户id
         * @author lichen
         * @date 2025/10/2
         */
        @Override
        public void checkCanGeneratePLReport(Integer propertyId, Integer yearMonth, int userId) {
            //校验资产是否正在生成报告
            if (m3PlReportLogService.checkIsBeingGenerated(propertyId, yearMonth)) {
                log.warn("generatePLReport, property is being generated, propertyId:{}, yearMonth:{}", propertyId, yearMonth);
                //正在生成报告
                throw new BadRequestException(PlErrorI18nConstant.REPORT_IS_BEING_GENERATED,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The report is being generated");
            }

            //系统生成报告时，才需要校验是否已经生成
            if (userId == 0) {
                //校验指定月份报告是否已存在
                M3PlReport m3PlReport = listByYearMonth(propertyId, yearMonth);
                if (Objects.nonNull(m3PlReport)) {
                    log.warn("generatePLReport, target month pl report exists, propertyId:" + propertyId);
                    //指定月份的报告已存在
                    throw new BadRequestException(PlErrorI18nConstant.REPORT_ALREADY_EXIST,
                            BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                            "The report for the specified month already exists");
                }
            }

            //校验account是否存在
            List<M3Account> m3Accounts = m3AccountService.listByPropertyId(propertyId);
            if (CollectionUtils.isEmpty(m3Accounts)) {
                log.error("generatePLReport, accounts not exist, propertyId:" + propertyId);
                //Account不存在
                throw new BadRequestException(PlErrorI18nConstant.M3_ACCOUNT_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The Account does not exist.");
            }

            //校验row是否存在
            List<M3Row> m3RowList = m3RowService.listByPropertyIdOrderById(propertyId);
            if (CollectionUtils.isEmpty(m3RowList)) {
                log.error("generatePLReport, row not exist, propertyId:" + propertyId);
                //Row不存在
                throw new BadRequestException(PlErrorI18nConstant.ROW_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The Row does not exist.");
            }

            //校验指定月份的layout配置是否存在
            List<M3PlLayoutAbnormalDTO> m3PlLayoutAbnormalDTOS = m3PlLayoutService.listLayoutAbnormal(propertyId, null);
            if (CollectionUtils.isEmpty(m3PlLayoutAbnormalDTOS)) {
                log.error("generatePLReport, layout not exist, propertyId:" + propertyId);
                //Layout配置不存在
                throw new BadRequestException(PlErrorI18nConstant.LAYOUT_CONFIG_NOT_EXIST,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "Target month Layout does not exist.");
            }
            List<M3RowAbnormalDTO> m3RowAbnormalDTOS = m3PlLayoutAbnormalDTOS.stream().flatMap(item -> item.getM3RowList()
                    .stream()).collect(Collectors.toList());
            if (m3RowAbnormalDTOS.stream().anyMatch(item -> !item.getRowExist() || !item.getRowTypeNormal())) {
                //Layout配置有异常
                throw new BadRequestException(PlErrorI18nConstant.LAYOUT_CONFIG_HAS_ERROR,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "There is an anomaly in the Layout configuration");
            }

            //校验变量是否有空的存在
            List<M3Variable> m3VariableList = m3VariableService.listByPropertyId(propertyId);
            if (m3VariableList.stream().anyMatch(item -> StringUtils.isBlank(item.getValue()))) {
                //Variable配置有异常
                throw new BadRequestException(PlErrorI18nConstant.VARIABLE_CONFIG_HAS_ERROR,
                        BaseErrorCodeEnum.DATA_VALIDATION_ERROR.getCode(),
                        "The Variable configuration has an exception.");
            }
        }

        /**
         * 计算一组Row PTD的和
         *
         * @param propertyId             资产id
         * @param yearMonth              年月
         * @param needCalculateM3RowList 需要计算的Row的集合
         * @param m3Accounts             Account数据集合
         * @param m3Trialbalances        Trialbalances数据集合
         * @return java.math.BigDecimal
         * @author lichen
         * @date 2026-03-16
         */
        @Override
        public BigDecimal calculateRowPTDSum(Integer propertyId, Integer yearMonth, List<M3Row> needCalculateM3RowList,
                                             List<M3Account> m3Accounts, List<M3Trialbalance> m3Trialbalances) {
            //查询m3Row
            List<M3Row> m3RowList = m3RowService.listByPropertyIdOrderById(propertyId);

            //查询资产的房间数
            RoomsConfigDTO roomsConfig = roomsConfigInternalService.getRoomsConfig(propertyId);
            Integer rooms = roomsConfig.getRooms();

            //查询【公式】、【常量】类型的变量
            List<M3Variable> m3Variables = m3VariableService.listByTypes(propertyId, Lists.newArrayList(M3Variable.TYPE_CONSTANT, M3Variable.TYPE_FORMULA));
            List<M3VariableWithFormulaWrapperDTO> m3VariableWithFormulaWrapperDTOList = m3VariableService.convertToDTO(propertyId, m3Variables);

            /*
             * 替换公式中的公有变量，再进行拓扑排序
             */
            List<M3Row> orderM3RowList = plCommonService.topologySort(m3RowList, m3VariableWithFormulaWrapperDTOList);

            //特殊变量，这里指{[Total Rooms Available]}
            List<M3Variable> specialM3Variables = m3VariableService.listByTypes(propertyId, Lists.newArrayList(M3Variable.TYPE_SPECIAL));

            //Row指标计算的DTO
            ArrayList<RowIndexCalculationDTO> alreadyCalculationRowIndexDTOList = Lists.newArrayListWithCapacity(orderM3RowList.size());
            ArrayList<String> alreadyCalculationRowNumbers = Lists.newArrayListWithCapacity(orderM3RowList.size());

            List<String> needCalculateM3RowNumberList = needCalculateM3RowList.stream().map(M3Row::getRowNumber).collect(Collectors.toList());

            for (M3Row m3Row : orderM3RowList) {
                //已经计算完毕时停止循环
                if (alreadyCalculationRowNumbers.containsAll(needCalculateM3RowNumberList)) {
                    break;
                }

                //当前计算的Row
                RowIndexCalculationDTO currentRowIndexCalculationDTO = new RowIndexCalculationDTO();
                currentRowIndexCalculationDTO.setRowNumber(m3Row.getRowNumber());
                currentRowIndexCalculationDTO.setRowName(m3Row.getRowName());
                currentRowIndexCalculationDTO.setFormat(m3Row.getFormat());

                //根据m3数据计算PTD指标
                IndexCalculationDTO indexCalculationDTO = m3TrialbalanceService.calculationPTD(propertyId,
                        m3Row, yearMonth, alreadyCalculationRowIndexDTOList, m3Trialbalances, m3Accounts, rooms, specialM3Variables);

                currentRowIndexCalculationDTO.setIndexCalculationList(Collections.singletonList(indexCalculationDTO));

                alreadyCalculationRowIndexDTOList.add(currentRowIndexCalculationDTO);
                alreadyCalculationRowNumbers.add(m3Row.getRowNumber());
            }

            // 求和
            return alreadyCalculationRowIndexDTOList.stream()
                    .filter(item -> needCalculateM3RowNumberList.contains(item.getRowNumber()))
                    .map(item -> item.getIndexCalculationList().get(0).getIndexValue())
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }

        /**
         * 生成导出的DTO
         *
         * @param propertyDTO    资产信息
         * @param m3PlReport     P&L报告
         * @param m3ForecastName m3 forecast name
         * @return cloud.wconnect.finance.pojo.dto.pl.ExportPLReportDTO
         * @author lichen
         * @date 2026/3/7
         */
        @Override
        public ExportPLReportDTO generateExportDTO(PropertyDTO propertyDTO, M3PlReport m3PlReport, M3ForecastName m3ForecastName) {
            YearMonth yearMonth = YearMonthUtils.getYearMonth(m3PlReport.getYearMonth().toString());

            // 获取当月的第一天
            LocalDate firstDayOfMonth = LocalDate.of(yearMonth.getYear(), yearMonth.getMonth(), 1);
            String firstDayOfMonthStr = DateTimeUtils.localDateToStr(firstDayOfMonth, BaseConstant.DATE_FORMAT_SLASH_MM_D);

            // 获取当月的最后一天
            LocalDate lastDayOfMonth = firstDayOfMonth.with(TemporalAdjusters.lastDayOfMonth());
            String lastDayOfMonthStr = DateTimeUtils.localDateToStr(lastDayOfMonth, BaseConstant.DATE_FORMAT_SLASH_MM_D);

            // 获取当年的第一天
            LocalDate firstDayOfYear = LocalDate.of(yearMonth.getYear(), 1, 1);
            String firstDayOfYearStr = DateTimeUtils.localDateToStr(firstDayOfYear, BaseConstant.DATE_FORMAT_SLASH_MM_D);

            ExportPLReportDTO exportPLReportDTO = new ExportPLReportDTO();
            exportPLReportDTO.setMonth(YearMonthUtils.parseYearMonth(yearMonth, BaseConstant.DATE_FORMAT_YYYY_MM));
            exportPLReportDTO.setForecastName(m3ForecastName.getName());
            exportPLReportDTO.setFirstDayOfMonth(firstDayOfMonthStr);
            exportPLReportDTO.setLastDayOfMonth(lastDayOfMonthStr);
            exportPLReportDTO.setFirstDayOfYear(firstDayOfYearStr);

            // PL报告转换成DTO
            List<RowDTO> rowDTOS = JsonUtils.convertStringToList(objectMapper, m3PlReport.getInfo(), RowDTO.class);
            // 需要设置底边框的索引
            ArrayList<Integer> descBorderBottomIndexList = new ArrayList<>();

            ArrayList<ExportDescIndexDTO> exportDescIndexDTOS = Lists.newArrayListWithCapacity(rowDTOS.size());
            for (int i = 0; i < rowDTOS.size(); i++) {
                RowDTO rowDTO = rowDTOS.get(i);
                Integer category = rowDTO.getCategory();

                ExportDescIndexDTO exportDescIndexDTO = new ExportDescIndexDTO();

                // 当前分类是分割线时，给上一行的数据设置底边框
                if (category == M3PlLayout.CATEGORY_LINE) {
                    if (i > 0) {
                        descBorderBottomIndexList.add(i - 1);
                    }
                    exportDescIndexDTOS.add(exportDescIndexDTO);
                } else if (category == M3PlLayout.CATEGORY_BLANK) {
                    exportDescIndexDTOS.add(exportDescIndexDTO);
                } else if (category == M3PlLayout.CATEGORY_LABEL) {
                    // 设置描述
                    exportDescIndexDTO.setDesc(rowDTO.getRowNumber());
                    // 设置Row的样式
                    setExportDTOStyle(rowDTO, exportDescIndexDTO);
                    exportDescIndexDTOS.add(exportDescIndexDTO);
                } else if (category == M3PlLayout.CATEGORY_ROW) {
                    // 设置描述
                    exportDescIndexDTO.setDesc(rowDTO.getRowName());
                    // 设置Row的样式
                    setExportDTOStyle(rowDTO, exportDescIndexDTO);
                    // 设置指标
                    setExportDTOIndex(propertyDTO, rowDTO, exportDescIndexDTO, m3ForecastName.getM3ForecastId());
                    exportDescIndexDTOS.add(exportDescIndexDTO);
                }
            }

            // 设置底边框
            descBorderBottomIndexList.forEach(index -> {
                exportDescIndexDTOS.get(index).setDescBorderBottom(true);
            });

            exportPLReportDTO.setIndexList(exportDescIndexDTOS);
            return exportPLReportDTO;
        }

        /**
         * 设置指标
         *
         * @param propertyDTO        资产DTO
         * @param rowDTO             rowDTO
         * @param exportDescIndexDTO 导出的DTO
         * @param m3ForecastId       M3 Forecast ID
         * @author lichen
         * @date 2026/3/17
         */
        private void setExportDTOIndex(PropertyDTO propertyDTO, RowDTO rowDTO, ExportDescIndexDTO exportDescIndexDTO, String m3ForecastId) {
            InternationalizationNumberDTO number = propertyDTO.getInternationalization().getNumber();

            //两个Forecast指标类型
            ArrayList<String> forecastIndexType = Lists.newArrayList(IndexTypeEnum.PTD_FORECAST.getIndexType(), IndexTypeEnum.YTD_FORECAST.getIndexType());

            // Row 的指标集合
            List<IndexDTO> indexs = rowDTO.getIndexs();
            // Row的格式类型
            String format = rowDTO.getFormat();

            for (IndexDTO index : indexs) {
                // 指标类型
                String indexType = index.getIndexType();
                IndexTypeEnum.SetterBiConsumer setter = IndexTypeEnum.getSetter(indexType);

                IndexValueDTO indexValueDTO = null;
                if (forecastIndexType.contains(indexType)) {
                    // 获取指定Forecast指标
                    indexValueDTO = index.getIndexValues().stream().filter(item -> item.getIndexNameId().equals(m3ForecastId))
                            .findFirst().orElse(null);
                } else {
                    indexValueDTO = index.getIndexValues().get(0);
                }

                if (Objects.nonNull(indexValueDTO) && Objects.nonNull(indexValueDTO.getIndexValue())) {
                    // 判断是否是计算成功
                    if (indexValueDTO.isCalculationResult()) {
                        BigDecimal indexValue = indexValueDTO.getIndexValue();
                        String unit = "";
                        if (format.equals(M3Row.FORMAT_PERCENT) || IndexTypeEnum.isPercent(indexType)) {
                            // Row本身是百分比指标，或者某个指标是百分比指标
                            unit = "%";
                            // 转换成百分比
                            indexValue = indexValue.multiply(new BigDecimal("100"));
                        }
                        String indexValueStr = InternationalizationUtils.formatViewValue(
                                indexValue, PrecisionCons.PL_PRECISION,
                                number.getCalculationRule(), unit);
                        setter.accept(exportDescIndexDTO, indexValueStr);
                    } else {
                        setter.accept(exportDescIndexDTO, "--");
                    }
                }
            }
        }

        /**
         * 设置导出的样式
         *
         * @param rowDTO             rowDTO
         * @param exportDescIndexDTO exportDescIndexDTO
         * @author lichen
         * @date 2025/9/29
         */
        private void setExportDTOStyle(RowDTO rowDTO, ExportDescIndexDTO exportDescIndexDTO) {
            String styleType = rowDTO.getStyleType();
            if (StringUtils.isNotBlank(styleType)) {
                List<Integer> styleTypeList = Arrays.stream(styleType.split(",")).map(Integer::parseInt).toList();
                if (styleTypeList.contains(M3PlLayout.STYLE_TYPE_BOLD)) {
                    exportDescIndexDTO.setDescBold(true);
                }
                if (styleTypeList.contains(M3PlLayout.STYLE_TYPE_ITALIC)) {
                    exportDescIndexDTO.setDescItalic(true);
                }
                if (styleTypeList.contains(M3PlLayout.STYLE_TYPE_UNDERLINE)) {
                    exportDescIndexDTO.setDescUnderline(true);
                }
                if (styleTypeList.contains(M3PlLayout.STYLE_TYPE_FONT_COLOR)) {
                    exportDescIndexDTO.setDescFontColor(Integer.parseInt(rowDTO.getStyleValue()));
                }
            }
        }

        /**
         * 保存P&L报告
         *
         * @param propertyDTO     资产DTO
         * @param yearMonth       年月
         * @param rowIndexDTOList row指标计算集合
         * @param m3PlLayoutList  Layout配置集合
         * @author lichen
         * @date 2025/9/29
         */
        private void saveReport(PropertyDTO propertyDTO, Integer yearMonth, ArrayList<RowIndexCalculationDTO> rowIndexDTOList, List<M3PlLayout> m3PlLayoutList) {
            M3PlReport m3PlReport = new M3PlReport();
            m3PlReport.setTenantId(propertyDTO.getTenantId());
            m3PlReport.setPropertyId(propertyDTO.getId());
            m3PlReport.setYearMonth(yearMonth);

            //两个Forecast指标类型
            ArrayList<String> forecastIndexType = Lists.newArrayList(IndexTypeEnum.PTD_FORECAST.getIndexType(), IndexTypeEnum.YTD_FORECAST.getIndexType());

            ArrayList<RowDTO> rowDTOS = Lists.newArrayListWithCapacity(m3PlLayoutList.size());
            Map<String, RowIndexCalculationDTO> rowNumber2RowIndexDTO = rowIndexDTOList.stream().collect(Collectors.toMap(RowIndexCalculationDTO::getRowNumber, Function.identity()));

            for (M3PlLayout m3PlLayout : m3PlLayoutList) {
                RowDTO rowDTO = beanMapper.map(m3PlLayout, RowDTO.class);
                if (m3PlLayout.getCategory() == M3PlLayout.CATEGORY_ROW) {
                    RowIndexCalculationDTO rowIndexCalculationDTO = rowNumber2RowIndexDTO.get(m3PlLayout.getRowNumber());
                    rowDTO.setRowName(rowIndexCalculationDTO.getRowName());
                    rowDTO.setFormat(rowIndexCalculationDTO.getFormat());

                    //设置指标数据
                    List<IndexCalculationDTO> indexCalculationList = rowIndexCalculationDTO.getIndexCalculationList();
                    ArrayList<IndexDTO> indexs = Lists.newArrayListWithCapacity(indexCalculationList.size());

                    //按照是否Forecast指标分组
                    Map<Boolean, List<IndexCalculationDTO>> isForecast2IndexDTO = indexCalculationList.stream().collect(
                            Collectors.partitioningBy(item -> forecastIndexType.contains(item.getIndexType())));

                    //forecast指标的处理方式
                    List<IndexCalculationDTO> indexCalculationDTOSByForecast = isForecast2IndexDTO.get(true);
                    //按照指标类型进行分组
                    Map<String, List<IndexCalculationDTO>> indexCalculationDTOSByIndexType = indexCalculationDTOSByForecast.stream()
                            .collect(Collectors.groupingBy(IndexCalculationDTO::getIndexType));
                    indexCalculationDTOSByIndexType.forEach((indexType, indexCalculationDTOS) -> {
                        IndexDTO indexDTO = new IndexDTO();
                        indexDTO.setIndexType(indexType);
                        indexDTO.setIndexValues(beanMapper.mapList(indexCalculationDTOS, IndexValueDTO.class));
                        indexs.add(indexDTO);
                    });

                    //非forecast指标的处理方式
                    List<IndexCalculationDTO> indexCalculationDTOSByElse = isForecast2IndexDTO.get(false);
                    for (IndexCalculationDTO indexCalculationDTO : indexCalculationDTOSByElse) {
                        IndexDTO indexDTO = new IndexDTO();
                        indexDTO.setIndexType(indexCalculationDTO.getIndexType());

                        IndexValueDTO indexValueDTO = new IndexValueDTO();
                        indexValueDTO.setIndexValue(indexCalculationDTO.getIndexValue());
                        indexValueDTO.setIndexNameId("");
                        indexValueDTO.setFailedFactors(indexCalculationDTO.getFailFactorList());
                        indexValueDTO.setCalculationResult(indexCalculationDTO.isCalculationResult());
                        indexDTO.setIndexValues(Lists.newArrayList(indexValueDTO));

                        indexs.add(indexDTO);
                    }

                    rowDTO.setIndexs(indexs);

                }
                rowDTOS.add(rowDTO);
            }

            if (CollectionUtils.isEmpty(rowDTOS)) {
                m3PlReport.setInfo("[]");
            } else {
                m3PlReport.setInfo(JsonUtils.convertObjListToString(objectMapper, rowDTOS));
            }
            //新增或修改P&L报告
            baseMapper.saveOrUpdateByUniqueKey(m3PlReport);
        }

        /**
         * 获取【%Rev】指标
         *
         * @param m3PlLayout                        M3 PL Layout
         * @param currentRowIndexCalculationDTO     Row指标计算DTO
         * @param alreadyCalculationRowIndexDTOList 根据m3数据计算过的Row指标计算DTO列表
         * @author lichen
         * @date 2025/9/26
         */
        private void calculationIndexByRev(M3PlLayout m3PlLayout, RowIndexCalculationDTO currentRowIndexCalculationDTO,
                                           ArrayList<RowIndexCalculationDTO> alreadyCalculationRowIndexDTOList) {
            List<IndexCalculationDTO> indexCalculationList = currentRowIndexCalculationDTO.getIndexCalculationList();

            String revFormula = m3PlLayout.getRevFormula();

            RevFormulaDTO revFormulaDTO = JsonUtils.convertStringToObject(objectMapper, revFormula, RevFormulaDTO.class);
            if (RevFormulaTypeEnum.SUBTRACTION.getType() == revFormulaDTO.getType()) {
                //Rev指标公式
                String formula = revFormulaDTO.getValue();
                //计算ptd rev
                IndexCalculationDTO rev = plCommonService.calculationIndex(m3PlLayout.getRowNumber(), formula, IndexTypeEnum.REV.getIndexType(), alreadyCalculationRowIndexDTOList, "");
                indexCalculationList.add(rev);
                //计算ytd rev
                IndexCalculationDTO ytdRev = plCommonService.calculationIndex(m3PlLayout.getRowNumber(), formula, IndexTypeEnum.YTD_REV.getIndexType(), alreadyCalculationRowIndexDTOList, "");
                indexCalculationList.add(ytdRev);
            } else if (RevFormulaTypeEnum.DIVISION.getType() == revFormulaDTO.getType()) {
                //Rev指标公式
                String formula = revFormulaDTO.getValue();
                //计算ptd rev
                IndexCalculationDTO rev = plCommonService.calculationIndex(m3PlLayout.getRowNumber(), formula, IndexTypeEnum.PTD.getIndexType(), alreadyCalculationRowIndexDTOList, "");
                rev.setIndexType(IndexTypeEnum.REV.getIndexType());
                indexCalculationList.add(rev);
                //计算ytd rev
                IndexCalculationDTO ytdRev = plCommonService.calculationIndex(m3PlLayout.getRowNumber(), formula, IndexTypeEnum.YTD.getIndexType(), alreadyCalculationRowIndexDTOList, "");
                ytdRev.setIndexType(IndexTypeEnum.YTD_REV.getIndexType());
                indexCalculationList.add(ytdRev);
            } else {
                String vlaue = revFormulaDTO.getValue();
                BigDecimal bigDecimal;
                if (StringUtils.isNotBlank(vlaue) && NumberUtil.isBigDecimal(vlaue)) {
                    bigDecimal = new BigDecimal(vlaue);
                } else {
                    bigDecimal = null;
                }
                indexCalculationList.add(new IndexCalculationDTO(IndexTypeEnum.REV.getIndexType(), bigDecimal, true));
                indexCalculationList.add(new IndexCalculationDTO(IndexTypeEnum.YTD_REV.getIndexType(), bigDecimal, true));
            }
        }

        /**
         * 计算【POR】指标
         *
         * @param m3PlLayout                        M3 PL Layout
         * @param currentRowIndexCalculationDTO     Row指标计算DTO
         * @param alreadyCalculationRowIndexDTOList 根据m3数据计算过的Row指标计算DTO列表
         * @author lichen
         * @date 2025/9/26
         */
        private void calculationIndexByPor(M3PlLayout m3PlLayout, RowIndexCalculationDTO currentRowIndexCalculationDTO,
                                           ArrayList<RowIndexCalculationDTO> alreadyCalculationRowIndexDTOList) {
            String rowNumber = currentRowIndexCalculationDTO.getRowNumber();

            HashMap<String, String> variable2RowNumber = new HashMap<>();
            variable2RowNumber.put(FormulaConstant.A, rowNumber);
            variable2RowNumber.put(FormulaConstant.B, m3PlLayout.getPorFormulaDenominator());

            ArrayList<IndexCalculationDTO> list = Lists.newArrayListWithCapacity(3);

            //当前Row的指标集合
            List<IndexCalculationDTO> indexCalculationList = currentRowIndexCalculationDTO.getIndexCalculationList();
            Map<String, IndexCalculationDTO> key2IndexCalculationDTO = indexCalculationList.stream()
                    .collect(Collectors.toMap(IndexCalculationDTO::generateKey, Function.identity()));
            //当前Row的PTD
            IndexCalculationDTO ptdIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.PTD.getIndexType());
            //当前Row的PTD BDGT
            IndexCalculationDTO ptdBdgtIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.PTD_BDGT.getIndexType());
            //当前Row的YTD
            IndexCalculationDTO ytdIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.YTD.getIndexType());

            //计算POR时用到的分母
            RowIndexCalculationDTO porFormulaDenominatorIndexDTOList = alreadyCalculationRowIndexDTOList.stream()
                    .filter(item -> item.getRowNumber().equals(m3PlLayout.getPorFormulaDenominator()))
                    .findFirst()
                    .orElse(new RowIndexCalculationDTO(m3PlLayout.getPorFormulaDenominator(),
                            "", "", Collections.emptyList()));
            Map<String, IndexCalculationDTO> key2porFormulaDenominatorCalculationDTO = porFormulaDenominatorIndexDTOList.getIndexCalculationList().stream()
                    .collect(Collectors.toMap(IndexCalculationDTO::generateKey, Function.identity()));

            //配置的分母，PTD
            IndexCalculationDTO ptdIndexDTOByDenominator = key2porFormulaDenominatorCalculationDTO.get(IndexTypeEnum.PTD.getIndexType());
            //配置的分母，PTD BDGT
            IndexCalculationDTO ptdBdgtIndexDTOByDenominator = key2porFormulaDenominatorCalculationDTO.get(IndexTypeEnum.PTD_BDGT.getIndexType());
            //配置的分母，YTD
            IndexCalculationDTO ytdIndexDTOByDenominator = key2porFormulaDenominatorCalculationDTO.get(IndexTypeEnum.YTD.getIndexType());

            /*
             * 1.计算POR = PTD / xxx PTD
             */
            HashMap<String, IndexCalculationDTO> mapByPor = new HashMap<>();
            mapByPor.put(FormulaConstant.A, ptdIndexDTO);
            mapByPor.put(FormulaConstant.B, ptdIndexDTOByDenominator);
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.POR.getIndexType(),
                    FormulaConstant.DIVISION_FORMULA, mapByPor, variable2RowNumber));

            /*
             * 2.计算PTD Budget POR = PTD Budget / xxx PTD Budget
             */
            HashMap<String, IndexCalculationDTO> mapByPtdBdgtPor = new HashMap<>();
            mapByPtdBdgtPor.put(FormulaConstant.A, ptdBdgtIndexDTO);
            mapByPtdBdgtPor.put(FormulaConstant.B, ptdBdgtIndexDTOByDenominator);
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.PTD_BUDGET_POR.getIndexType(),
                    FormulaConstant.DIVISION_FORMULA, mapByPtdBdgtPor, variable2RowNumber));

            /*
             * 3.计算YTD POR = YTD / xxx YTD
             */
            HashMap<String, IndexCalculationDTO> mapByYtdPor = new HashMap<>();
            mapByYtdPor.put(FormulaConstant.A, ytdIndexDTO);
            mapByYtdPor.put(FormulaConstant.B, ytdIndexDTOByDenominator);
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.YTD_POR.getIndexType(),
                    FormulaConstant.DIVISION_FORMULA, mapByYtdPor, variable2RowNumber));

            indexCalculationList.addAll(list);
        }

        /**
         * 计算【本身指标作为计算因子】的指标
         *
         * @param rowType                Row的类型
         * @param rowIndexCalculationDTO Row指标计算DTO
         * @author lichen
         * @date 2025/9/26
         */
        private void calculationIndexBySelfIndex(Integer rowType, RowIndexCalculationDTO rowIndexCalculationDTO) {
            String rowNumber = rowIndexCalculationDTO.getRowNumber();

            //根据唯一索引转换成Map, key=indexType + m3ForecastId
            List<IndexCalculationDTO> indexCalculationList = rowIndexCalculationDTO.getIndexCalculationList();
            Map<String, IndexCalculationDTO> key2IndexCalculationDTO = indexCalculationList.stream()
                    .collect(Collectors.toMap(IndexCalculationDTO::generateKey, Function.identity()));
            IndexCalculationDTO ptdIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.PTD.getIndexType());
            IndexCalculationDTO ptdBdgtIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.PTD_BDGT.getIndexType());
            IndexCalculationDTO ptdLyIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.PTD_LY.getIndexType());
            IndexCalculationDTO ytdIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.YTD.getIndexType());
            IndexCalculationDTO ytdBDGTIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.YTD_BDGT.getIndexType());
            IndexCalculationDTO ytdLyIndexDTO = key2IndexCalculationDTO.get(IndexTypeEnum.YTD_LY.getIndexType());

            HashMap<String, String> variable2RowNumber = new HashMap<>();
            variable2RowNumber.put(FormulaConstant.A, rowNumber);
            variable2RowNumber.put(FormulaConstant.B, rowNumber);
            variable2RowNumber.put(FormulaConstant.C, rowNumber);

            ArrayList<IndexCalculationDTO> list = Lists.newArrayListWithCapacity(5);

            /*
             * 1.计算VAR BDGT、VAR BDGT %
             */
            HashMap<String, IndexCalculationDTO> mapByVarBdgt = new HashMap<>();
            if (AccountTypeEnum.INCOME.getType() == rowType) {
                //VAR BDGT = PTD - PTD BDGT
                mapByVarBdgt.put(FormulaConstant.A, ptdIndexDTO);
                mapByVarBdgt.put(FormulaConstant.B, ptdBdgtIndexDTO);
            } else {
                //VAR BDGT = PTD BDGT - PTD
                mapByVarBdgt.put(FormulaConstant.A, ptdBdgtIndexDTO);
                mapByVarBdgt.put(FormulaConstant.B, ptdIndexDTO);
            }
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.VAR_BDGT.getIndexType(),
                    FormulaConstant.VAR_BDGT_FORMULA, mapByVarBdgt, variable2RowNumber));

            //VAR BDGT % = VAR BDGT / PTD BDGT
            mapByVarBdgt.put(FormulaConstant.C, ptdBdgtIndexDTO);
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.VAR_BDGT_PERCENTAGE.getIndexType(),
                    FormulaConstant.VAR_BDGT_PERCENTAGE_FORMULA, mapByVarBdgt, variable2RowNumber));

            /*
             * 2.计算VAR LY
             */
            HashMap<String, IndexCalculationDTO> mapByVarLy = new HashMap<>();
            if (AccountTypeEnum.INCOME.getType() == rowType) {
                //VAR LY = PTD - PTD LY
                mapByVarLy.put(FormulaConstant.A, ptdIndexDTO);
                mapByVarLy.put(FormulaConstant.B, ptdLyIndexDTO);
            } else {
                //VAR LY = PTD LY - PTD
                mapByVarLy.put(FormulaConstant.A, ptdLyIndexDTO);
                mapByVarLy.put(FormulaConstant.B, ptdIndexDTO);
            }
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.VAR_LY.getIndexType(),
                    FormulaConstant.VAR_LY_FORMULA, mapByVarLy, variable2RowNumber));

            /*
             * 3. 计算YTD VAR BDGT
             */
            HashMap<String, IndexCalculationDTO> mapByYtdVarBdgt = new HashMap<>();
            if (AccountTypeEnum.INCOME.getType() == rowType) {
                //YTD VAR BDGT = YTD - YTD BDGT
                mapByYtdVarBdgt.put(FormulaConstant.A, ytdIndexDTO);
                mapByYtdVarBdgt.put(FormulaConstant.B, ytdBDGTIndexDTO);
            } else {
                //YTD VAR BDGT = YTD BDGT - YTD
                mapByYtdVarBdgt.put(FormulaConstant.A, ytdBDGTIndexDTO);
                mapByYtdVarBdgt.put(FormulaConstant.B, ytdIndexDTO);
            }
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.YTD_VAR_BDGT.getIndexType(),
                    FormulaConstant.YTD_VAR_BDGT_FORMULA, mapByYtdVarBdgt, variable2RowNumber));

            /*
             * 4. 计算YTD VAR LY
             */
            HashMap<String, IndexCalculationDTO> mapByYtdVarLy = new HashMap<>();
            if (AccountTypeEnum.INCOME.getType() == rowType) {
                //YTD VAR LY = YTD - YTD LY
                mapByYtdVarLy.put(FormulaConstant.A, ytdIndexDTO);
                mapByYtdVarLy.put(FormulaConstant.B, ytdLyIndexDTO);
            } else {
                //YTD VAR LY = YTD LY - YTD
                mapByYtdVarLy.put(FormulaConstant.A, ytdLyIndexDTO);
                mapByYtdVarLy.put(FormulaConstant.B, ytdIndexDTO);
            }
            list.add(plCommonService.calculationIndex(rowNumber, IndexTypeEnum.YTD_VAR_LY.getIndexType(),
                    FormulaConstant.YTD_VAR_LY_FORMULA, mapByYtdVarLy, variable2RowNumber));

            indexCalculationList.addAll(list);
        }

        /**
         * 根据m3数据计算指标
         *
         * @param propertyId                        资产id
         * @param m3Row                             m3 Row
         * @param yearMonth                         计算的年月，yyyyMM
         * @param alreadyCalculationRowIndexDTOList 已经计算过的Row指标集合
         * @param m3DataMap                         M3 Data集合
         * @param rooms                             资产的房间数
         * @return java.util.ArrayList<cloud.wconnect.finance.pojo.dto.pl.index.IndexCalculationDTO>
         * @author lichen
         * @date 2025/9/26
         */
        private ArrayList<IndexCalculationDTO> calculationIndexByM3Data(Integer propertyId, M3Row m3Row, Integer yearMonth,
                                                                        ArrayList<RowIndexCalculationDTO> alreadyCalculationRowIndexDTOList,
                                                                        Map<String, Object> m3DataMap, Integer rooms) {
            List<IndexCalculationDTO> indexCalculationDTOSByTrialbalance = m3TrialbalanceService.calculationIndex(propertyId, m3Row, yearMonth, alreadyCalculationRowIndexDTOList, m3DataMap, rooms);

            //计算依赖Budgets数据的指标
            List<IndexCalculationDTO> indexCalculationDTOSByBudgets = m3BudgetsService.calculationIndex(propertyId, m3Row, yearMonth, alreadyCalculationRowIndexDTOList, m3DataMap, rooms);

            //计算依赖Forecasts数据的指标
            List<IndexCalculationDTO> indexCalculationDTOSByForecasts = m3ForecastsService.calculationIndex(propertyId, m3Row, yearMonth, alreadyCalculationRowIndexDTOList, m3DataMap, rooms);

            ArrayList<IndexCalculationDTO> indexCalculationDTOS = Lists.newArrayListWithExpectedSize(20);
            indexCalculationDTOS.addAll(indexCalculationDTOSByTrialbalance);
            indexCalculationDTOS.addAll(indexCalculationDTOSByBudgets);
            indexCalculationDTOS.addAll(indexCalculationDTOSByForecasts);
            return indexCalculationDTOS;
        }

    }