package excel import ( "encoding/json" "errors" "eta/eta_api/models/data_manage" "eta/eta_api/models/data_manage/excel/request" "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(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) if cellRelationConf != `` { err = json.Unmarshal([]byte(cellRelationConf), &cellRelationConfList) if err != nil { return } for _, v := range cellRelationConfList { cellRelationConfMap[v.Key] = v } } // 找出所有的关联指标id config, edbInfoIdList, _, err, errMsg := handleConfig(config) if err != nil { return } // 查询所有关联的指标信息 edbInfoList, err := data_manage.GetEdbInfoByIdList(edbInfoIdList) if err != nil { return } // 指标信息map edbInfoMap := make(map[int]*data_manage.EdbInfo) // 日度指标数据map edbDayDataListMap := make(map[int]map[string]float64) // 月度指标数据map edbMonthDataListMap := make(map[int]map[string]float64) // 日度指标数据map edbDataListMap := make(map[int][]*data_manage.EdbDataList) for _, edbInfo := range edbInfoList { edbInfoMap[edbInfo.EdbInfoId] = edbInfo dataList := make([]*data_manage.EdbDataList, 0) switch edbInfo.EdbInfoType { case 0: dataList, _ = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``) case 1: _, dataList, _, _, _, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false) default: err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType)) } dateValMap := make(map[string]float64) monthValMap := make(map[string]float64) for _, tmpData := range dataList { // 日度数据 dateValMap[tmpData.DataTime] = tmpData.Value // 月度数据(取该月份的第一个数据) yearMonth := strings.Join(strings.Split(tmpData.DataTime, "-")[0:2], "-") if _, ok := monthValMap[yearMonth]; !ok { monthValMap[yearMonth] = tmpData.Value } } edbDayDataListMap[edbInfo.EdbInfoId] = dateValMap edbMonthDataListMap[edbInfo.EdbInfoId] = monthValMap edbDataListMap[edbInfo.EdbInfoId] = dataList } // 单元格实际绑定的信息map cellDataRelationMap := make(map[string]request.MixedTableCellDataReq, 0) // 处理指定指标的日期 for k, row := range config { for i, cell := range row { // 单元格是日期类型,且是日导入指标日期(指标库的最新日期) if cell.DataType == request.DateDT && cell.DataTimeType == request.EdbDateDT { // 指标id是在配置里面 var edbDateConfig request.EdbDateConf err = json.Unmarshal([]byte(cell.Value), &edbDateConfig) if err != nil { return } if edbInfo, ok := edbInfoMap[edbDateConfig.EdbInfoId]; ok { cell.ShowValue = edbInfo.EndDate cell.DataTime = edbInfo.EndDate config[k][i] = cell } } row[i] = cell cellDataRelationMap[cell.Uid] = cell } config[k] = row } // 指标计算的结果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 { case request.EdbDT: // 指标类型 if edbInfo, ok := edbInfoMap[cell.EdbInfoId]; ok { cell.ShowValue = edbInfo.EdbName } case request.InsertDataDT, request.PopInsertDataDT: // 数据类型 // 数值先清空 cell.ShowValue = `` //cell.Value = `` // 日期关系配置不存在,则默认最新数据 if relationConf, ok := cellRelationConfMap[cell.Uid]; ok { if relationConf.RelationDate.Key == `` { // 日期关系配置未绑定 continue } // 配置 relationCell, ok := cellDataRelationMap[relationConf.RelationDate.Key] if !ok { // 找不到对应日期的单元格 continue } // 确实是找到了这个关联日期的单元格,那么通过日期重新获取数据值 tmpDateValMap := make(map[string]float64) // 日度数据 if dateValMap, ok := edbDayDataListMap[cell.EdbInfoId]; ok { tmpDateValMap = dateValMap } if val, ok2 := tmpDateValMap[relationCell.DataTime]; ok2 { //cell.ShowValue = fmt.Sprint(val) cellKeyVal[cell.Uid] = val cell.ShowValue = utils.FormatTableDataShowValue(val) } } else { // 如果不是取得一个关联的日期,那么就是指定日期 // 如果没有指定日期,则默认最新数据 if cell.DataTime == `` { // 指标的最新日期 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) } } } else { tmpDateList := strings.Split(cell.DataTime, "-") tmpDateValMap := make(map[string]float64) if len(tmpDateList) == 2 { //月度数据 if dateValMap, ok := edbMonthDataListMap[cell.EdbInfoId]; ok { tmpDateValMap = dateValMap } } else { // 日度数据 if dateValMap, ok := edbDayDataListMap[cell.EdbInfoId]; ok { tmpDateValMap = dateValMap } } if val, ok2 := tmpDateValMap[cell.DataTime]; ok2 { //cell.ShowValue = fmt.Sprint(val) cellKeyVal[cell.Uid] = val cell.ShowValue = utils.FormatTableDataShowValue(val) } } } calculateCellMap[cell.Uid] = Cell{ Column: k, Row: i, CellInfo: cell, } 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, } calculateChainList = append(calculateChainList, cell.Uid) case request.InsertEdbCalculateDataDT: // 插入指标系统计算公式生成的值 // 日期 var cellDateTime string // 日期关系配置不存在,则默认最新数据 if relationConf, ok := cellRelationConfMap[cell.Uid]; ok { if relationConf.RelationDate.Key == `` { // 日期关系配置未绑定 continue } // 配置 relationCell, ok := cellDataRelationMap[relationConf.RelationDate.Key] if !ok { // 找不到对应日期的单元格 continue } cellDateTime = relationCell.DataTime } var tmpDataMap map[string]float64 key := utils.MD5(cell.Value) tmpDataMap, ok := edbSourceDataMap[key] if !ok { // 对应的配置值 var tmpConfig request.CalculateConf err = json.Unmarshal([]byte(cell.Value), &tmpConfig) if err != nil { return } tmpDataList, ok := edbDataListMap[tmpConfig.EdbInfoId] if !ok { continue } edbInfo, ok := edbInfoMap[tmpConfig.EdbInfoId] if !ok { continue } req2 := &BaseCalculate{ DataList: tmpDataList, Frequency: tmpConfig.Frequency, Formula: tmpConfig.Formula, Calendar: tmpConfig.Calendar, MoveType: tmpConfig.MoveType, MoveFrequency: tmpConfig.MoveFrequency, FromFrequency: edbInfo.Frequency, Source: tmpConfig.Source, } // 调用指标库去更新 reqJson, tmpErr := json.Marshal(req2) if tmpErr != nil { utils.FileLog.Error(fmt.Sprintf("计算失败1,配置信息;%s;错误原因:%s", cell.Value, tmpErr.Error())) err = tmpErr return } respItem, tmpErr := data.BaseCalculate(string(reqJson)) if tmpErr != nil { utils.FileLog.Error(fmt.Sprintf("计算失败2,配置信息;%s;错误原因:%s", cell.Value, tmpErr.Error())) err = tmpErr return } if respItem.Ret != 200 { utils.FileLog.Error(fmt.Sprintf("计算失败3,配置信息;%s;原因:%s;错误原因:%s", cell.Value, respItem.Msg, respItem.ErrMsg)) continue } tmpDataMap = respItem.Data.DataMap // 计算结果存一份,万一存在重复的计算方式,那么省的重新计算一下 edbSourceDataMap[key] = tmpDataMap lenDataList := len(respItem.Data.DateList) if cellDateTime == `` && lenDataList > 0 { cellDateTime = respItem.Data.DateList[lenDataList-1] } } val := tmpDataMap[cellDateTime] cellKeyVal[cell.Uid] = val cell.ShowValue = utils.FormatTableDataShowValue(val) } row[i] = cell } 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, has, tmpErr, tmpErrMsg := getCalculateValueByCell(calculateCellMap, cellKey, cellKeyVal) if tmpErr != nil { errMsg = tmpErrMsg 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, has bool, err error, errMsg string) { // 单元格的标签名 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 if colData.Extra == `` { err = errors.New(colData.Uid + "没有绑定关系") return } err = json.Unmarshal([]byte(colData.Extra), &relationCellList) if err != nil { return } for _, relation := range relationCellList { //relationCellTagName := strings.ToUpper(relation.Tag) + relation.Row tmpVal, _, tmpErr, tmpErrMsg := getCalculateValueByCell(calculateCellMap, relation.Key, cellKeyValMap) if tmpErr != nil { errMsg = tmpErrMsg 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, errMsg, err = calculateByCellList(strings.ToUpper(colData.Value), tagList) if err != nil { return } // 重新赋值 has = true cellKeyValMap[key] = val return } // handleConfig // @Description: 处理混合表格配置 // @author: Roc // @datetime2023-10-27 13:24:53 // @param configList [][]request.MixedTableCellDataReq // @return newConfig [][]request.MixedTableCellDataReq // @return edbInfoIdList []int // @return dataEdbInfoIdList []int // @return err error // @return errMsg string func handleConfig(configList [][]request.MixedTableCellDataReq) (newConfig [][]request.MixedTableCellDataReq, edbInfoIdList []int, dataEdbInfoIdList []int, err error, errMsg string) { edbInfoIdList = make([]int, 0) dataEdbInfoIdList = make([]int, 0) for ck, rowList := range configList { for rk, cell := range rowList { switch cell.DataType { case request.EdbDT: // 指标信息 edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId) case request.InsertDataDT, request.PopInsertDataDT: // 插值、弹框插值 dataEdbInfoIdList = append(dataEdbInfoIdList, cell.EdbInfoId) edbInfoIdList = append(edbInfoIdList, cell.EdbInfoId) case request.InsertEdbCalculateDataDT: // 插入指标计算公式生成的值 var config request.CalculateConf err = json.Unmarshal([]byte(cell.Value), &config) if err != nil { return } edbInfoIdList = append(edbInfoIdList, config.EdbInfoId) dataEdbInfoIdList = append(dataEdbInfoIdList, cell.EdbInfoId) case request.DateDT: // 日期类型 date, tmpErr, tmpErrMsg := handleDate(cell.DataTimeType, cell.Value) if tmpErr != nil { err = tmpErr errMsg = tmpErrMsg return } rowList[rk].DataTime = date rowList[rk].ShowValue = date // 指标日期类型的单元格需要额外将指标id取出来 if cell.DataTimeType == request.EdbDateDT { var config request.EdbDateConf err = json.Unmarshal([]byte(cell.Value), &config) if err != nil { return } edbInfoIdList = append(edbInfoIdList, config.EdbInfoId) } } } configList[ck] = rowList } newConfig = configList return } // HandleDate // @Description: 日期处理 // @author: Roc // @datetime2023-10-27 09:37:02 // @param dataTimeType int // @param val string // @return date string // @return err error // @return errMsg string func HandleDate(dataTimeType int, val string) (date string, err error, errMsg string) { return handleDate(dataTimeType, val) } // handleDate // @Description: 日期处理 // @author: Roc // @datetime2023-10-27 09:36:49 // @param dataTimeType int // @param val string // @return date string // @return err error // @return errMsg string func handleDate(dataTimeType int, val string) (date string, err error, errMsg string) { if val == `` { errMsg = "错误的日期数据" err = errors.New(errMsg) return } switch dataTimeType { case request.CustomDateT: //手动输入日期 date = val case request.SystemDateT: // 系统日期 date, err, errMsg = handleSystemDateT(val) case request.EdbDateDT: // 导入指标日期(指标库的最新日期) default: errMsg = "错误的日期类型" err = errors.New(errMsg) return } return } // handleSystemDateT // @Description: 处理导入系统日期 // @author: Roc // @datetime2023-10-27 09:36:21 // @param confStr string // @return date string // @return err error // @return errMsg string func handleSystemDateT(confStr string) (date string, err error, errMsg string) { var config request.SystemDateConf err = json.Unmarshal([]byte(confStr), &config) if err != nil { return } switch config.Source { case request.SystemCurrDateT: date = time.Now().Format(utils.FormatDate) case request.SystemCalculateDateT: date, err, errMsg = handleSystemCalculateDateT(config.CalculateNum, config.CalculateFrequency) case request.SystemFrequencyDateT: // 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天) date, err, errMsg = handleSystemAppointDateT(config.Day, config.Frequency) default: errMsg = "错误的日期日期导入方式" err = errors.New(fmt.Sprint("错误的日期日期导入方式:", config.Source)) return } return } // handleSystemCalculateDateT // @Description: 处理系统日期计算后的日期 // @author: Roc // @datetime2023-10-27 09:31:22 // @param num int // @param frequency string // @return date string // @return err error // @return errMsg string func handleSystemCalculateDateT(num int, frequency string) (date string, err error, errMsg string) { if err != nil { return } currDate := time.Now() switch frequency { case "", "日": date = currDate.AddDate(0, 0, num).Format(utils.FormatDate) default: errMsg = "错误的日期频度:" + frequency err = errors.New(errMsg) return } return } // handleSystemAppointDateT // @Description: 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天) // @author: Roc // @datetime2023-10-27 09:31:35 // @param Frequency string // @param Day string // @return date string // @return err error // @return errMsg string func handleSystemAppointDateT(appointDay, frequency string) (date string, err error, errMsg string) { currDate := time.Now() switch frequency { case "本周": day := int(currDate.Weekday()) if day == 0 { // 周日 day = 7 } num := 0 switch appointDay { case "周一": num = 1 case "周二": num = 2 case "周三": num = 3 case "周四": num = 4 case "周五": num = 5 case "周六": num = 6 case "周日": num = 7 } day = num - day date = currDate.AddDate(0, 0, day).Format(utils.FormatDate) case "本旬": day := currDate.Day() var tmpDate time.Time switch appointDay { case "第一天": if day <= 10 { tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location()) } else if day <= 20 { tmpDate = time.Date(currDate.Year(), currDate.Month(), 11, 0, 0, 0, 0, currDate.Location()) } else { tmpDate = time.Date(currDate.Year(), currDate.Month(), 21, 0, 0, 0, 0, currDate.Location()) } case "最后一天": if day <= 10 { tmpDate = time.Date(currDate.Year(), currDate.Month(), 10, 0, 0, 0, 0, currDate.Location()) } else if day <= 20 { tmpDate = time.Date(currDate.Year(), currDate.Month(), 20, 0, 0, 0, 0, currDate.Location()) } else { tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1) } } date = tmpDate.Format(utils.FormatDate) case "本月": var tmpDate time.Time switch appointDay { case "第一天": tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location()) case "最后一天": tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1) } date = tmpDate.Format(utils.FormatDate) case "本季": month := currDate.Month() var tmpDate time.Time switch appointDay { case "第一天": if month <= 3 { tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location()) } else if month <= 6 { tmpDate = time.Date(currDate.Year(), 4, 1, 0, 0, 0, 0, currDate.Location()) } else if month <= 9 { tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location()) } else { tmpDate = time.Date(currDate.Year(), 10, 1, 0, 0, 0, 0, currDate.Location()) } case "最后一天": if month <= 3 { tmpDate = time.Date(currDate.Year(), 3, 31, 0, 0, 0, 0, currDate.Location()) } else if month <= 6 { tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location()) } else if month <= 9 { tmpDate = time.Date(currDate.Year(), 9, 30, 0, 0, 0, 0, currDate.Location()) } else { tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location()) } } date = tmpDate.Format(utils.FormatDate) case "本半年": month := currDate.Month() var tmpDate time.Time switch appointDay { case "第一天": if month <= 6 { tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location()) } else { tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location()) } case "最后一天": if month <= 6 { tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location()) } else { tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location()) } } date = tmpDate.Format(utils.FormatDate) case "本年": var tmpDate time.Time switch appointDay { case "第一天": tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location()) case "最后一天": tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location()) } date = tmpDate.Format(utils.FormatDate) default: errMsg = "错误的日期频度:" + frequency err = errors.New(errMsg) return } 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 }