# M3VariableServiceImpl.java

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

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.util.YearMonthUtils;
    import cloud.wconnect.commonlib.cons.m3.DailyOperatingStatisticsIndexEnum;
    import cloud.wconnect.finance.cons.pl.IndexTypeEnum;
    import cloud.wconnect.finance.entity.m3.M3Mapping;
    import cloud.wconnect.finance.entity.pl.M3Row;
    import cloud.wconnect.finance.entity.pl.M3Variable;
    import cloud.wconnect.finance.mapper.pl.M3VariableMapper;
    import cloud.wconnect.finance.pojo.dto.pl.M3RowDTO;
    import cloud.wconnect.finance.pojo.dto.pl.M3VariableDTO;
    import cloud.wconnect.finance.pojo.dto.pl.M3VariableWithFormulaWrapperDTO;
    import cloud.wconnect.finance.service.m3.IM3MappingService;
    import cloud.wconnect.finance.service.pl.IM3RowService;
    import cloud.wconnect.finance.service.pl.IM3VariableService;
    import cloud.wconnect.finance.service.pl.PlCommonService;
    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.checkerframework.checker.nullness.qual.Nullable;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.stereotype.Service;

    import java.time.YearMonth;
    import java.util.*;
    import java.util.function.Function;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.stream.Collectors;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2025-09-25
     */
    @Service
    @DS("finance")
    public class M3VariableServiceImpl extends ServiceImpl<M3VariableMapper, M3Variable> implements IM3VariableService {

        @Autowired
        private IM3MappingService m3MappingService;
        @Autowired
        @Lazy
        private IM3RowService m3RowService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private PlCommonService plCommonService;

        /**
         * 查询的变量集合
         *
         * @param propertyId 资产id
         * @return java.util.List<cloud.wconnect.finance.entity.pl.M3Variable>
         * @author lichen
         * @date 2025/9/25
         */
        @Override
        public List<M3Variable> listByPropertyId(int propertyId) {
            return list(new LambdaQueryWrapper<M3Variable>()
                    .eq(M3Variable::getPropertyId, propertyId)
                    .orderByAsc(M3Variable::getId));
        }

        /**
         * 转换为DTO
         *
         * @param propertyId  资产id
         * @param m3Variables 变量集合
         * @return java.util.List<cloud.wconnect.finance.pojo.dto.pl.M3VariableDTO>
         * @author lichen
         * @date 2025/9/29
         */
        @Override
        public List<M3VariableWithFormulaWrapperDTO> convertToDTO(int propertyId, List<M3Variable> m3Variables) {
            if (CollectionUtils.isEmpty(m3Variables)) return Collections.emptyList();

            ArrayList<M3VariableWithFormulaWrapperDTO> list = Lists.newArrayListWithCapacity(m3Variables.size());
            for (M3Variable m3Variable : m3Variables) {
                M3VariableWithFormulaWrapperDTO m3VariableWithFormulaWrapperDTO = new M3VariableWithFormulaWrapperDTO();
                m3VariableWithFormulaWrapperDTO.setName(m3Variable.getName());
                if (m3Variable.getType() == M3Variable.TYPE_FORMULA) {
                    m3VariableWithFormulaWrapperDTO.setValue("(" + m3Variable.getValue() + ")");
                } else if (m3Variable.getType() == M3Variable.TYPE_CONSTANT) {
                    m3VariableWithFormulaWrapperDTO.setValue(m3Variable.getValue());
                }
                list.add(m3VariableWithFormulaWrapperDTO);
            }
            return list;
        }

        /**
         * 根据类型查询变量
         *
         * @param propertyId 资产id
         * @param typeList   类型集合
         * @return java.util.List<cloud.wconnect.finance.entity.pl.M3Variable>
         * @author lichen
         * @date 2025/9/29
         */
        @Override
        public List<M3Variable> listByTypes(Integer propertyId, List<Integer> typeList) {
            if (CollectionUtils.isEmpty(typeList)) return Collections.emptyList();
            return list(new LambdaQueryWrapper<M3Variable>()
                    .eq(M3Variable::getPropertyId, propertyId)
                    .in(M3Variable::getType, typeList)
                    .orderByAsc(M3Variable::getId));
        }

        /**
         * 替换特殊变量
         *
         * @param formula            公式
         * @param propertyId         资产id
         * @param indexType          指标类型
         * @param yearMonth          年月，格式yyyyMM
         * @param rooms              房间数
         * @param specialM3Variables 特殊变量集合
         * @return java.lang.String
         * @author lichen
         * @date 2025/9/29
         */
        @Override
        public String replaceSpecialVariable(String formula, Integer propertyId, String indexType,
                                             Integer yearMonth, Integer rooms, List<M3Variable> specialM3Variables) {
            if (CollectionUtils.isEmpty(specialM3Variables)) {
                return formula;
            }
            // 获取{[Total Rooms Available]}
            M3Variable m3Variable = specialM3Variables.stream()
                    .filter(item -> item.getName().equals(M3Variable.NAME_TOTAL_ROOMS_AVAILABLE))
                    .findFirst().orElse(null);
            if (Objects.isNull(m3Variable)) return formula;

            if (!formula.contains(m3Variable.getName())) {
                return formula;
            }

            YearMonth yearMonthObj = YearMonthUtils.getYearMonth(yearMonth.toString());

            int days = 0;
            if (IndexTypeEnum.isYtdIndex(indexType)) {
                // 计算从年初到该月月底的总天数
                for (int month = 1; month <= yearMonthObj.getMonthValue(); month++) {
                    YearMonth ym = YearMonth.of(yearMonthObj.getYear(), month);
                    days += ym.lengthOfMonth();
                }
            } else {
                //获取当月的总天数
                days = yearMonthObj.lengthOfMonth();
            }

            //总房间数
            int totalRoomsAvailable = rooms * days;

            // 转义特殊字符并构建精确匹配模式
            String escapedVariableName = Pattern.quote(m3Variable.getName());
            // 使用正则表达式进行精确替换
            return formula.replaceAll(escapedVariableName, totalRoomsAvailable + "");
        }

        /**
         * 更新{[Rooms Rented]}变量的值
         *
         * @param propertyId 资产id
         * @author lichen
         * @date 2025/9/29
         */
        @Override
        public void updateRoomsRentedValue(int propertyId) {
            //查询{[Rooms Rented]}变量
            M3Variable m3Variable = getOne(new LambdaQueryWrapper<M3Variable>().eq(M3Variable::getPropertyId, propertyId)
                    .eq(M3Variable::getName, M3Variable.NAME_TOTAL_ROOMS_RENTED));
            if (Objects.isNull(m3Variable)) return;

            M3Mapping m3Mapping = m3MappingService.listByIndexName(propertyId, DailyOperatingStatisticsIndexEnum.ROOMS_RENTED.getIndexName());
            if (Objects.isNull(m3Mapping)) return;

            //指标的公式
            String formula = m3Mapping.getFormula();
            if (formula.contains("-") || formula.contains("*") || formula.contains("/")) {
                //指标公式包含-*/时，不做更新处理，需要通过功能手动调整变量的值
                return;
            }

            //解析指标公式中使用的accountNumber
            List<String> accountNumberList = m3MappingService.parseFormulaUsedAccountNumber(formula);

            //查询accountNumber对应的m3Row
            List<M3Row> m3Rows = m3RowService.listByAccountNumber(propertyId, accountNumberList);
            if (CollectionUtils.isEmpty(m3Rows)) return;
            Map<String, String> accountNumber2RowNumber = m3Rows.stream().collect(Collectors.toMap(M3Row::getAccountNumber, M3Row::getRowNumber,
                    (v1, v2) -> v1));

            //把公式中的accountNumber替换成rowNumber
            String newFormula = replaceVariables(formula, accountNumber2RowNumber);

            //更新M3Variable的value字段
            m3Variable.setValue(newFormula);
            updateById(m3Variable);
        }

        /**
         * 获取变量列表
         *
         * @param propertyId 资产id
         * @return java.util.List<cloud.wconnect.finance.pojo.dto.pl.M3VariableDTO>
         * @author lichen
         * @date 2025/12/24
         */
        @Override
        public List<M3VariableDTO> listDTO(int propertyId) {
            List<M3Variable> m3Variables = listByPropertyId(propertyId);

            // 收集公式类型的变量中引用的rowNumber集合
            Set<String> rowNumbers = m3Variables.stream().filter(item -> item.getType() == M3Variable.TYPE_FORMULA)
                    .map(item -> plCommonService.parseFormulaUsedRowNumber(item.getValue()))
                    .flatMap(Collection::stream)
                    .collect(Collectors.toSet());
            // 查询rowNumber对应的m3Row
            Map<String, M3Row> rowNumber2M3Row = m3RowService.listByRowNumber(propertyId, Lists.newArrayList(rowNumbers))
                    .stream().collect(Collectors.toMap(M3Row::getRowNumber, Function.identity()));

            ArrayList<M3VariableDTO> dtoList = Lists.newArrayListWithCapacity(m3Variables.size());
            for (M3Variable m3Variable : m3Variables) {
                M3VariableDTO dto = beanMapper.map(m3Variable, M3VariableDTO.class);

                if (m3Variable.getType().equals(M3Variable.TYPE_FORMULA)) {
                    // 获取公式中的rowNumber
                    List<String> rowNumbersByVariable = plCommonService.parseFormulaUsedRowNumber(m3Variable.getValue());

                    // 获取rowNumber对应的m3Row，并转换成M3RowDTO
                    List<M3RowDTO> m3RowDTOS = rowNumbersByVariable.stream().map(rowNumber -> {
                        M3RowDTO m3RowDTO = new M3RowDTO();
                        m3RowDTO.setRowNumber(rowNumber);

                        M3Row m3Row = rowNumber2M3Row.get(rowNumber);
                        if (Objects.nonNull(m3Row)) {
                            m3RowDTO.setRowName(m3Row.getRowName());
                            m3RowDTO.setRowCategory(m3Row.getRowCategory());
                            m3RowDTO.setAccountNumber(m3Row.getAccountNumber());
                        }
                        return m3RowDTO;
                    }).collect(Collectors.toList());

                    dto.setFormulaFactorList(m3RowDTOS);
                }
                dtoList.add(dto);
            }
            return dtoList;
        }

        /**
         * 替换公式中的变量
         *
         * @param formula  包含变量的公式字符串，如 "{4216}+{4213}+{4210}+{4212}+{4211}"
         * @param valueMap 变量值映射表
         * @return java.lang.String
         * @author lichen
         * @date 2025/10/15
         */
        private String replaceVariables(String formula, Map<String, String> valueMap) {
            // 匹配 {数字} 格式的变量
            Matcher matcher = PlCommonService.CURLY_BRACES_PATTERN.matcher(formula);
            StringBuilder newFormula = new StringBuilder();

            while (matcher.find()) {
                //获取变量：{}中间的内容
                String variableContent = matcher.group(1);
                //获取变量对应的值
                String value = valueMap.get(variableContent);

                if (value != null) {
                    // 如果值存在，则替换
                    matcher.appendReplacement(newFormula, "{" + value + "}");
                } else {
                    // 如果值不存在，则移除该项（替换为空字符串）
                    matcher.appendReplacement(newFormula, "");
                }
            }
            matcher.appendTail(newFormula);

            //最终的公式
            String finalFormual = newFormula.toString();

            // 移除开头和结尾的运算符
            finalFormual = finalFormual.replaceAll("^[+]+", "");
            finalFormual = finalFormual.replaceAll("[+]+$", "");
            // 移除连续的运算符
            finalFormual = finalFormual.replaceAll("[+]{2,}", "+");

            return finalFormual;
        }
    }