浏览代码

feat:计算公式

Roc 1 年之前
父节点
当前提交
3635d259bb

+ 7 - 7
controllers/data_manage/excel/excel_info.go

@@ -180,7 +180,7 @@ func (c *ExcelInfoController) Add() {
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result.CellRelation, result.Data)
+		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result)
 		if tmpErr != nil {
 			br.Msg = "获取失败"
 			if tmpErrMsg != `` {
@@ -598,7 +598,7 @@ func (c *ExcelInfoController) Edit() {
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result.CellRelation, result.Data)
+		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result)
 		if tmpErr != nil {
 			br.Msg = "获取失败"
 			if tmpErrMsg != `` {
@@ -1148,7 +1148,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 
 	var tableData excel.TableData
 	switch excelInfo.Source {
-	case 1:
+	case utils.EXCEL_DEFAULT:
 		luckySheetData, err := excel.GetLuckySheetData(excelInfo.Content)
 		if err != nil {
 			br.Msg = "获取失败"
@@ -1161,7 +1161,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
 			return
 		}
-	case 2:
+	case utils.TIME_TABLE:
 		var tableDataConfig excel2.TableDataConfig
 		err = json.Unmarshal([]byte(excelInfo.Content), &tableDataConfig)
 		if err != nil {
@@ -1181,7 +1181,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
 			return
 		}
-	case 3:
+	case utils.MIXED_TABLE:
 		var result request.MixedTableReq
 		err = json.Unmarshal([]byte(excelInfo.Content), &result)
 		if err != nil {
@@ -1189,7 +1189,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result.CellRelation, result.Data)
+		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result)
 		if tmpErr != nil {
 			br.Msg = "获取失败"
 			if tmpErrMsg != `` {
@@ -1917,7 +1917,7 @@ func (c *ExcelInfoController) Download() {
 			br.ErrMsg = "表格json转结构体失败,Err:" + err.Error()
 			return
 		}
-		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result.CellRelation, result.Data)
+		newResult, tmpErr, tmpErrMsg := excel2.GetMixedTableCellData(result)
 		if tmpErr != nil {
 			br.Msg = "获取失败"
 			if tmpErrMsg != `` {

+ 20 - 3
models/data_manage/excel/request/mixed_table.go

@@ -8,7 +8,7 @@ const (
 	InsertDataDT                        // 4 插值(插入指标值,表格上,自动判断日期和指标的交集位置,插入值)
 	PopInsertDataDT                     // 5 弹框插值(在表格上选择日期,然后空白单元格选择弹框并选择指标,插入该指标与该日期的值)
 	FormulateCalculateDataDT            // 6 公式计算(A+B这种)
-	InsertEdbCalculateDataDT            // 7 插入指标计算公式生成的值
+	InsertEdbCalculateDataDT            // 7 插入指标系统计算公式生成的值
 )
 
 // 单元格的日期类型类型
@@ -27,8 +27,9 @@ const (
 
 // MixedTableReq 混合表格保存请求参数
 type MixedTableReq struct {
-	CellRelation string                    `description:"单元格关系"`
-	Data         [][]MixedTableCellDataReq `description:"混合表格单元格参数"`
+	CellRelation       string                    `description:"单元格关系"`
+	Data               [][]MixedTableCellDataReq `description:"混合表格单元格参数"`
+	CalculateChainList []string                  `description:"计算公式链"`
 }
 
 // MixedTableCellDataReq 混合表格单元格参数
@@ -40,6 +41,7 @@ type MixedTableCellDataReq struct {
 	EdbInfoId    int    `description:"指标id"`
 	ShowValue    string `description:"展示值"`
 	Value        string `description:"实际值"`
+	Extra        string `description:"额外参数"`
 }
 
 // CellRelationConf
@@ -87,3 +89,18 @@ type CalculateConf struct {
 	MoveFrequency string      `description:"移动频度"`
 	Source        int         `description:"1:累计值转月;2:累计值转季;3:同比值;4:同差值;5:N数值移动平均数计算;6:环比值;7:环差值;8:升频;9:降频;10:时间移位;11:超季节性;12:年化;13:累计值;14:累计值年初至今;15:指数修匀;16:日均值"`
 }
+
+// BaseCalculateConf
+// @Description: 基础计算公式(A+B)
+type BaseCalculateConf struct {
+	Formula             string         `description:"计算公式,默认是string,实际上还需要转成其他样式"`
+	RelationEdbInfoList []RelationCell `description:"关联单元格(计算公式中关联的单元格,用于计算的时候去匹配)"`
+}
+
+// RelationCell
+// @Description: 关联单元格的信息
+type RelationCell struct {
+	Tag string `description:"指标标签"`
+	Row string `description:"第几行"`
+	Key string `json:"key" description:"单元格的唯一标识"`
+}

+ 1 - 1
services/data/excel/excel_info.go

@@ -73,7 +73,7 @@ func GetExcelDetailInfoByExcelInfoId(excelInfoId int) (excelDetail response.Exce
 			err = errors.New("表格json转结构体失败,Err:" + err.Error())
 			return
 		}
-		newData, tmpErr, tmpErrMsg := GetMixedTableCellData(result.CellRelation, result.Data)
+		newData, tmpErr, tmpErrMsg := GetMixedTableCellData(result)
 		if tmpErr != nil {
 			errMsg = "获取失败"
 			if tmpErrMsg != `` {

+ 243 - 13
services/data/excel/mixed_table.go

@@ -8,12 +8,40 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
+	"sort"
+	"strconv"
 	"strings"
 	"time"
 )
 
+// BaseCalculate
+// @Description: 指标数据计算请求
+type BaseCalculate struct {
+	DataList      []*data_manage.EdbDataList
+	Frequency     string `description:"需要转换的频度"`
+	Formula       interface{}
+	Calendar      string `description:"公历/农历"`
+	MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
+	MoveFrequency string `description:"移动频度"`
+	FromFrequency string `description:"来源的频度"`
+	Source        int    `description:"1:累计值转月;2:累计值转季;3:同比值;4:同差值;5:N数值移动平均数计算;6:环比值;7:环差值;8:升频;9:降频;10:时间移位;11:超季节性;12:年化;13:累计值;14:累计值年初至今;15:指数修匀;16:日均值"`
+}
+
+// Cell
+// @Description: 单元格位置
+type Cell struct {
+	Column   int                           `description:"行"`
+	Row      int                           `description:"列"`
+	CellInfo request.MixedTableCellDataReq `description:"对应的单元格信息"`
+}
+
 // GetMixedTableCellData 获取混合表格数据
-func GetMixedTableCellData(cellRelationConf string, config [][]request.MixedTableCellDataReq) (newMixedTableCellDataList [][]request.MixedTableCellDataReq, err error, errMsg string) {
+func GetMixedTableCellData(mixedTableReq request.MixedTableReq) (newMixedTableCellDataList [][]request.MixedTableCellDataReq, err error, errMsg string) {
+	cellRelationConf := mixedTableReq.CellRelation
+	config := mixedTableReq.Data
+
 	// 单元格关系配置x信息
 	cellRelationConfMap := make(map[string]request.CellRelationConf)
 	cellRelationConfList := make([]request.CellRelationConf, 0)
@@ -98,20 +126,17 @@ func GetMixedTableCellData(cellRelationConf string, config [][]request.MixedTabl
 		config[k] = row
 	}
 
-	type BaseCalculate struct {
-		DataList      []*data_manage.EdbDataList
-		Frequency     string `description:"需要转换的频度"`
-		Formula       interface{}
-		Calendar      string `description:"公历/农历"`
-		MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
-		MoveFrequency string `description:"移动频度"`
-		FromFrequency string `description:"来源的频度"`
-		Source        int    `description:"1:累计值转月;2:累计值转季;3:同比值;4:同差值;5:N数值移动平均数计算;6:环比值;7:环差值;8:升频;9:降频;10:时间移位;11:超季节性;12:年化;13:累计值;14:累计值年初至今;15:指数修匀;16:日均值"`
-	}
-
 	// 指标计算的结果map
 	edbSourceDataMap := make(map[string]map[string]float64)
 
+	// 单元格对应的key与他的值(只处理数据类型)
+	cellKeyVal := make(map[string]float64)
+
+	// 基础计算单元格的位置信息
+	calculateCellMap := make(map[string]Cell)
+	calculateChainList := make([]string, 0)
+
+	// 处理单元格中的数据类型(除去基础计算,因为这个是依赖于其他)
 	for k, row := range config {
 		for i, cell := range row {
 			switch cell.DataType {
@@ -125,6 +150,7 @@ func GetMixedTableCellData(cellRelationConf string, config [][]request.MixedTabl
 					if dateValList, ok := edbDataListMap[cell.EdbInfoId]; ok {
 						tmpLenData := len(dateValList)
 						if tmpLenData > 0 {
+							cellKeyVal[cell.Uid] = dateValList[tmpLenData-1].Value
 							cell.ShowValue = utils.FormatTableDataShowValue(dateValList[tmpLenData-1].Value)
 						}
 					}
@@ -145,11 +171,37 @@ func GetMixedTableCellData(cellRelationConf string, config [][]request.MixedTabl
 					}
 					if val, ok2 := tmpDateValMap[cell.DataTime]; ok2 {
 						//cell.ShowValue = fmt.Sprint(val)
+						cellKeyVal[cell.Uid] = val
 						cell.ShowValue = utils.FormatTableDataShowValue(val)
 					}
 				}
+			case request.CustomTextDT: //自定义文本
+				if cell.Value == `` {
+					continue
+				}
+				// 处理看下能否转成float,如果可以的话,说明这个也是可以参与计算的
+				tmpDeci, tmpErr := decimal.NewFromString(cell.Value)
+				if tmpErr == nil {
+					tmpVal, _ := tmpDeci.Float64()
+					cellKeyVal[cell.Uid] = tmpVal
+
+					calculateCellMap[cell.Uid] = Cell{
+						Column:   k,
+						Row:      i,
+						CellInfo: cell,
+					}
+				}
+
+			case request.FormulateCalculateDataDT: // 公式计算(A+B这种)
+				calculateCellMap[cell.Uid] = Cell{
+					Column:   k,
+					Row:      i,
+					CellInfo: cell,
+				}
 
-			case request.InsertEdbCalculateDataDT: // 指标类型
+				calculateChainList = append(calculateChainList, cell.Uid)
+
+			case request.InsertEdbCalculateDataDT: // 插入指标系统计算公式生成的值
 				// 日期
 				var cellDateTime string
 				// 日期关系配置不存在,则默认最新数据
@@ -228,6 +280,7 @@ func GetMixedTableCellData(cellRelationConf string, config [][]request.MixedTabl
 				}
 
 				val := tmpDataMap[cellDateTime]
+				cellKeyVal[cell.Uid] = val
 				cell.ShowValue = utils.FormatTableDataShowValue(val)
 			}
 
@@ -236,11 +289,109 @@ func GetMixedTableCellData(cellRelationConf string, config [][]request.MixedTabl
 		config[k] = row
 	}
 
+	// 公式链计算
+	if len(calculateChainList) > 0 {
+		for _, cellKey := range calculateChainList {
+			// 查找这个单元格的位置,直接map找了,而不是遍历整个单元格
+			cellPosition, ok := calculateCellMap[cellKey]
+			if !ok {
+				utils.FileLog.Error("找不到单元格位置:", cellKey)
+				continue
+			}
+
+			cell := config[cellPosition.Column][cellPosition.Row]
+			if cell.DataType != request.FormulateCalculateDataDT { // 判断公式计算(A+B这种)类型,不是的话也过滤了
+				continue
+			}
+
+			val, tmpErr, has := getCalculateValueByCell(calculateCellMap, cellKey, cellKeyVal)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			if !has {
+				continue
+			}
+
+			cellKeyVal[cell.Uid] = val
+			cell.ShowValue = utils.FormatTableDataShowValue(val)
+			config[cellPosition.Column][cellPosition.Row] = cell
+
+		}
+	}
+
 	newMixedTableCellDataList = config
 
 	return
 }
 
+// getCalculateValue 获取公式计算的结果
+func getCalculateValueByCell(calculateCellMap map[string]Cell, key string, cellKeyValMap map[string]float64) (val float64, err error, has bool) {
+	// 单元格的标签名
+	val, ok := cellKeyValMap[key]
+	if ok {
+		has = true
+		return
+	}
+
+	// 查找单元格数据
+	cell, ok := calculateCellMap[key]
+	if !ok {
+		err = errors.New("查找单元格" + key + "的数据失败")
+		return
+	}
+
+	colData := cell.CellInfo
+
+	// 如果不是基础计算单元格,直接返回
+	if colData.DataType != request.FormulateCalculateDataDT {
+		return
+	}
+
+	// 如果是计算单元格
+
+	tagList := make([]utils.CellPosition, 0)
+	// 计算单元格relationCellList
+	var relationCellList []request.RelationCell
+	err = json.Unmarshal([]byte(colData.Extra), &relationCellList)
+	if err != nil {
+		return
+	}
+
+	for _, relation := range relationCellList {
+		//relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row
+		tmpVal, tmpErr, _ := getCalculateValueByCell(calculateCellMap, relation.Key, cellKeyValMap)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		cellKeyValMap[relation.Key] = tmpVal
+
+		rowInt, tmpErr := strconv.Atoi(relation.Row)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		tagList = append(tagList, utils.CellPosition{
+			Tag:   relation.Tag,
+			Row:   rowInt,
+			Value: tmpVal,
+		})
+	}
+
+	// 计算
+	val, _, err = calculateByCellList(strings.ToUpper(colData.Value), tagList)
+	if err != nil {
+		return
+	}
+	// 重新赋值
+	has = true
+	cellKeyValMap[key] = val
+
+	return
+}
+
 // handleConfig
 // @Description: 处理混合表格配置
 // @author: Roc
@@ -499,3 +650,82 @@ func handleSystemAppointDateT(appointDay, frequency string) (date string, err er
 
 	return
 }
+
+// calculateByCellList
+// @Description: 根据单元格来进行公式计算
+// @author: Roc
+// @datetime2023-11-14 16:17:38
+// @param calculateFormula string
+// @param tagList []utils.CellPosition
+// @return calVal string
+// @return errMsg string
+// @return err error
+func calculateByCellList(calculateFormula string, tagList []utils.CellPosition) (calVal float64, errMsg string, err error) {
+	if calculateFormula == "" {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	calculateFormula = strings.TrimPrefix(calculateFormula, "=")
+	calculateFormula = strings.Replace(calculateFormula, "(", "(", -1)
+	calculateFormula = strings.Replace(calculateFormula, ")", ")", -1)
+	calculateFormula = strings.Replace(calculateFormula, ",", ",", -1)
+	calculateFormula = strings.Replace(calculateFormula, "。", ".", -1)
+	calculateFormula = strings.Replace(calculateFormula, "%", "*0.01", -1)
+
+	rowList := make([]int, 0)
+	rowListMap := make(map[int][]utils.CellPosition)
+	for _, v := range tagList {
+		tmpRowList, ok := rowListMap[v.Row]
+		if !ok {
+			rowList = append(rowList, v.Row)
+			tmpRowList = make([]utils.CellPosition, 0)
+		}
+		tmpRowList = append(tmpRowList, v)
+		rowListMap[v.Row] = tmpRowList
+	}
+
+	sort.Ints(rowList)
+
+	list := make([]utils.CellPosition, 0)
+	for _, row := range rowList {
+		list = append(list, rowListMap[row]...)
+	}
+
+	formulaFormStr := utils.ReplaceFormulaByCellList(list, calculateFormula)
+	//计算公式异常,那么就移除该指标
+	if formulaFormStr == `` {
+		errMsg = "公式异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	expression := formula.NewExpression(formulaFormStr)
+	calResult, err := expression.Evaluate()
+	if err != nil {
+		errMsg = "计算失败"
+		err = errors.New("计算失败:Err:" + err.Error() + ";formulaStr:" + formulaFormStr)
+		// 分母为0的报错
+		if strings.Contains(err.Error(), "divide by zero") {
+			errMsg = "分母不能为0"
+			err = errors.New("分母不能为空,计算公式:" + formulaFormStr)
+		}
+		return
+	}
+	// 如果计算结果是NAN,那么就提示报错
+	if calResult.IsNan() {
+		errMsg = "计算失败"
+		err = errors.New("计算失败:计算结果是:NAN;formulaStr:" + formulaFormStr)
+		return
+	}
+	calVal, err = calResult.Float64()
+	if err != nil {
+		return
+	}
+
+	// 转Decimal然后四舍五入
+	calVal, _ = decimal.NewFromFloat(calVal).Round(4).Float64()
+
+	return
+}

+ 31 - 0
utils/calculate.go

@@ -188,6 +188,37 @@ func ReplaceFormula(valArr map[string]float64, formulaStr string) string {
 	return formulaStr
 }
 
+type CellPosition struct {
+	Tag   string
+	Row   int
+	Value float64
+}
+
+// ReplaceFormulaByCellList
+// @Description: 根据单元格列表替换
+// @author: Roc
+// @datetime2023-11-14 16:16:12
+// @param cellList []CellPosition
+// @param formulaStr string
+// @return string
+func ReplaceFormulaByCellList(cellList []CellPosition, formulaStr string) string {
+	funMap := getFormulaMap()
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, k, v, -1)
+	}
+
+	replaceCount := 0
+	for _, cell := range cellList {
+		dvStr := fmt.Sprintf("%v", cell.Value)
+		formulaStr = strings.Replace(formulaStr, fmt.Sprint(cell.Tag, cell.Row), dvStr, -1)
+		replaceCount++
+	}
+	for k, v := range funMap {
+		formulaStr = strings.Replace(formulaStr, v, k, -1)
+	}
+	return formulaStr
+}
+
 func getFormulaMap() map[string]string {
 	funMap := make(map[string]string)
 	funMap["MAX"] = "[@@]"