Przeglądaj źródła

Merge branch 'master' of http://8.136.199.33:3000/eta_server/eta_api into bzq1/record

zqbao 7 miesięcy temu
rodzic
commit
25bf0f124e
83 zmienionych plików z 10836 dodań i 558 usunięć
  1. 2 0
      cache/replace_edb_info.go
  2. 151 0
      controllers/business_conf.go
  3. 2 0
      controllers/data_manage/chart_classify.go
  4. 1 0
      controllers/data_manage/chart_common.go
  5. 302 46
      controllers/data_manage/chart_info.go
  6. 266 139
      controllers/data_manage/correlation/correlation_chart_classify.go
  7. 1304 136
      controllers/data_manage/correlation/correlation_chart_info.go
  8. 27 25
      controllers/data_manage/cross_variety/chart_info.go
  9. 90 10
      controllers/data_manage/edb_info.go
  10. 32 6
      controllers/data_manage/edb_info_calculate.go
  11. 232 2
      controllers/data_manage/edb_info_refresh.go
  12. 298 0
      controllers/data_manage/edb_info_relation.go
  13. 1 1
      controllers/data_manage/excel/balance_table.go
  14. 8 3
      controllers/data_manage/excel/excel_info.go
  15. 816 0
      controllers/data_manage/factor_edb_series.go
  16. 92 25
      controllers/data_manage/future_good/future_good_chart_info.go
  17. 40 26
      controllers/data_manage/future_good/future_good_profit_chart_info.go
  18. 27 25
      controllers/data_manage/line_equation/line_chart_info.go
  19. 27 25
      controllers/data_manage/line_feature/chart_info.go
  20. 35 6
      controllers/data_manage/multiple_graph_config.go
  21. 78 0
      controllers/data_manage/predict_edb_info.go
  22. 331 0
      controllers/data_manage/wind_data.go
  23. 2 0
      controllers/fe_calendar/fe_calendar_matter.go
  24. 9 1
      controllers/sandbox/sandbox.go
  25. 33 0
      models/business_conf.go
  26. 231 0
      models/data_manage/base_from_business_data.go
  27. 47 0
      models/data_manage/chart_classify.go
  28. 11 0
      models/data_manage/chart_edb_mapping.go
  29. 249 7
      models/data_manage/chart_info.go
  30. 218 0
      models/data_manage/chart_info_correlation.go
  31. 22 0
      models/data_manage/correlation/request/chart.go
  32. 8 0
      models/data_manage/edb_classify.go
  33. 1 1
      models/data_manage/edb_data_base.go
  34. 1 1
      models/data_manage/edb_data_insert_config.go
  35. 8 0
      models/data_manage/edb_data_mysteel_chemical.go
  36. 41 0
      models/data_manage/edb_data_wind.go
  37. 58 5
      models/data_manage/edb_info.go
  38. 9 0
      models/data_manage/edb_info_calculate.go
  39. 11 0
      models/data_manage/edb_info_calculate_mapping.go
  40. 518 0
      models/data_manage/edb_info_relation.go
  41. 22 0
      models/data_manage/edb_refresh/request/edb_info_refresh.go
  42. 9 0
      models/data_manage/excel/excel_edb_mapping.go
  43. 10 0
      models/data_manage/excel/request/mixed_table.go
  44. 351 0
      models/data_manage/factor_edb_series.go
  45. 190 0
      models/data_manage/factor_edb_series_calculate_data.go
  46. 153 0
      models/data_manage/factor_edb_series_calculate_func.go
  47. 211 0
      models/data_manage/factor_edb_series_chart_mapping.go
  48. 167 0
      models/data_manage/factor_edb_series_mapping.go
  49. 85 0
      models/data_manage/mysteel_chemical_index.go
  50. 25 0
      models/data_manage/request/factor_edb_series.go
  51. 12 10
      models/data_manage/request/multiple_graph_config.go
  52. 6 0
      models/data_manage/response/edb_info.go
  53. 7 3
      models/data_manage/response/multiple_graph_config.go
  54. 27 1
      models/db.go
  55. 36 0
      models/sandbox/sandbox.go
  56. 198 0
      routers/commentsRouter.go
  57. 2 0
      routers/router.go
  58. 11 0
      services/data/base_edb_lib.go
  59. 66 0
      services/data/base_from_business.go
  60. 589 1
      services/data/chart_classify.go
  61. 876 6
      services/data/chart_info.go
  62. 77 22
      services/data/chart_info_excel_balance.go
  63. 7 0
      services/data/chart_theme.go
  64. 370 8
      services/data/correlation/chart_info.go
  65. 9 3
      services/data/cross_variety/chart.go
  66. 65 0
      services/data/edb_classify.go
  67. 107 1
      services/data/edb_data.go
  68. 16 3
      services/data/edb_info.go
  69. 140 0
      services/data/edb_info_calculate.go
  70. 680 0
      services/data/edb_info_relation.go
  71. 3 3
      services/data/excel/balance_table.go
  72. 4 1
      services/data/excel/excel_op.go
  73. 4 3
      services/data/excel/mixed_table.go
  74. 191 0
      services/data/factor_edb_series.go
  75. 5 0
      services/data/line_equation/chart_info.go
  76. 5 1
      services/data/line_feature/chart_info.go
  77. 274 0
      services/edb_info_replace.go
  78. 1 1
      services/eta_forum/eta_forum_hub.go
  79. 12 1
      services/excel/lucky_sheet.go
  80. 99 0
      services/file.go
  81. 41 0
      services/sandbox/sandbox.go
  82. 4 0
      utils/config.go
  83. 30 0
      utils/constants.go

+ 2 - 0
cache/replace_edb_info.go

@@ -13,6 +13,8 @@ func AddReplaceEdbInfo(oldEdbInfo, newEdbInfo *data_manage.EdbInfo) bool {
 	record.NewEdbInfo = newEdbInfo
 	if utils.Re == nil {
 		err := utils.Rc.LPush(utils.CACHE_KEY_REPLACE_EDB, record)
+		
+		utils.FileLog.Info(fmt.Sprintf("指标替换 加入缓存 AddReplaceEdbInfo LPush: 旧指标ID:%d,新指标ID:%d", oldEdbInfo.EdbInfoId, newEdbInfo.EdbInfoId))
 		if err != nil {
 			fmt.Println("AddReplaceEdbInfo LPush Err:" + err.Error())
 		}

+ 151 - 0
controllers/business_conf.go

@@ -279,3 +279,154 @@ func (this *BusinessConfOpenController) CodeEncrypt() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// SingleSave
+// @Title 保存单项配置
+// @Description 保存配置
+// @Param	request	body map[string]interface{} true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /single/save [post]
+func (this *BusinessConfController) SingleSave() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.BusinessConfSingleSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.ConfKey == "" {
+		br.Msg = "参数异常!"
+		return
+	}
+
+	// 设置白名单,只有白名单里的配置才允许保存
+	writeList := []string{models.BusinessConfEdbStopRefreshRule}
+	if !utils.InArrayByStr(writeList, req.ConfKey) {
+		br.Msg = "不支持该项配置"
+		return
+	}
+	// 获取配置信息
+	confOb, e := models.GetBusinessConfByKey(req.ConfKey)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "配置不存在"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = "获取配置失败, Err: " + e.Error()
+		return
+	}
+
+	switch confOb.ValType {
+	case 1: // 字符串
+		req.ConfVal = strings.TrimSpace(req.ConfVal)
+		if confOb.Necessary == 1 && req.ConfVal == "" {
+			br.Msg = confOb.Remark + "不可为空"
+			return
+		}
+	}
+	if req.ConfKey == models.BusinessConfEdbStopRefreshRule {
+		//将json转为结构体
+		rule := new(models.EdbStopRefreshRule)
+		err := json.Unmarshal([]byte(req.ConfVal), rule)
+		if err != nil {
+			br.Msg = confOb.Remark + "格式错误"
+			return
+		}
+		if rule.IsOpen == 1 && (rule.EdbStopDays == 0 || rule.BaseIndexStopDays == 0) {
+			br.Msg = confOb.Remark + "天数不可设置为0"
+			return
+		}
+	}
+	if confOb.ConfVal != req.ConfVal {
+		confOb.ConfVal = req.ConfVal
+		if e = confOb.Update([]string{"ConfVal"}); e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存商家配置失败, Err: " + e.Error()
+			return
+		}
+		// 操作日志
+		go func() {
+			b, e := json.Marshal(req)
+			if e != nil {
+				return
+			}
+			recordOb := new(models.BusinessConfOperationRecord)
+			recordOb.SysUserId = sysUser.AdminId
+			recordOb.SysRealName = sysUser.RealName
+			recordOb.Content = string(b)
+			recordOb.CreateTime = time.Now().Local()
+			_ = recordOb.Create()
+		}()
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// GetSingle
+// @Title 获取单项配置
+// @Description 保存配置
+// @Param	request	body map[string]interface{} true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /single [get]
+func (this *BusinessConfController) GetSingle() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	confKey := this.GetString("ConfKey")
+	if confKey == "" {
+		br.Msg = "参数异常!"
+		return
+	}
+
+	// 设置白名单,只有白名单里的配置才允许保存
+	writeList := []string{models.BusinessConfEdbStopRefreshRule}
+	if !utils.InArrayByStr(writeList, confKey) {
+		br.Msg = "不支持该项配置"
+		return
+	}
+	// 获取配置信息
+	confOb, e := models.GetBusinessConfByKey(confKey)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "配置不存在"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = "获取配置失败, Err: " + e.Error()
+		return
+	}
+	resp := models.BusinessConfSingleResp{ConfVal: confOb.ConfVal}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.Data = resp
+}

+ 2 - 0
controllers/data_manage/chart_classify.go

@@ -709,6 +709,8 @@ func (this *ChartClassifyController) DeleteChartClassify() {
 				}
 			}
 		}
+		// 删除图表中的指标引用
+		_ = data_manage.DeleteEdbRelationByObjectId(req.ChartInfoId, utils.EDB_RELATION_CHART)
 		//新增操作日志
 		{
 			chartLog := new(data_manage.ChartInfoLog)

+ 1 - 0
controllers/data_manage/chart_common.go

@@ -176,6 +176,7 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 
 func getBalanceChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool, sysUser *system.Admin) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
 	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+
 	resp, isOk, msg, errMsg = data.CheckBalanceChartCacheAndPermission(chartInfo, isCache, sysUser)
 	if isOk {
 		return

+ 302 - 46
controllers/data_manage/chart_info.go

@@ -1423,6 +1423,62 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
 	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 
+	// 标识线计算
+	if req.MarkersLines != "" {
+		markerLines := make([]data_manage.MarkersLine, 0)
+		err = json.Unmarshal([]byte(req.MarkersLines), &markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+		for i := range markerLines {
+			if markerLines[i].EdbType == 1 {
+				// 指标计算
+				edbInfo, err := data_manage.GetEdbInfoById(markerLines[i].EdbInfoId)
+				if err != nil {
+					br.Msg = "指标计算标识线获取指标信息异常"
+					br.ErrMsg = "指标计算标识线获取指标信息异常" + err.Error()
+					return
+				}
+				dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, markerLines[i].StartDate.Date, markerLines[i].EndDate.Date)
+				if err != nil {
+					br.Msg = "指标计算标识线获取指标数据异常"
+					br.ErrMsg = "指标计算标识线获取指标数据异常" + err.Error()
+					return
+				}
+				value, err := data.MarkerLineCalculate(markerLines[i], dataList, chartInfo)
+				if err != nil {
+					br.Msg = "标识线配置异常"
+					br.ErrMsg = "标识线配置异常" + err.Error()
+					return
+				}
+				markerLines[i].Value = value
+
+			} else {
+				// 跟随图表
+				if edbList[0].IsAxis == 1 {
+					value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
+					if err != nil {
+						br.Msg = "标识线配置异常"
+						br.ErrMsg = "标识线配置异常" + err.Error()
+						return
+					}
+					markerLines[i].Value = value
+				}
+			}
+		}
+
+		markerLineStr, err := json.Marshal(markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+
+		chartInfo.MarkersLines = string(markerLineStr)
+	}
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -2481,7 +2537,6 @@ func (this *ChartInfoController) ChartInfoDetailFromUniqueCode() {
 
 func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool, sysUser *system.Admin) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
 	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
-
 	msg = `获取失败`
 	adminId := sysUser.AdminId
 
@@ -2504,6 +2559,28 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
 
 			// 图表权限校验
 			{
@@ -2599,50 +2676,6 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 				if err != nil || resp == nil {
 					return
 				}
-				// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-				var myCond string
-				var myPars []interface{}
-				myCond += ` AND a.admin_id=? `
-				myPars = append(myPars, adminId)
-				myCond += ` AND a.chart_info_id=? `
-				myPars = append(myPars, chartInfo.ChartInfoId)
-				myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-				if err != nil && err.Error() != utils.ErrNoRow() {
-					msg = "获取失败"
-					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-					return
-				}
-				resp.ChartInfo.IsAdd = false
-				resp.ChartInfo.MyChartId = 0
-				resp.ChartInfo.MyChartClassifyId = ""
-				if myList != nil && len(myList) > 0 {
-					resp.ChartInfo.IsAdd = true
-					resp.ChartInfo.MyChartId = myList[0].MyChartId
-					resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-				}
-
-				//判断是否加入我的图库
-				{
-					var myChartCondition string
-					var myChartPars []interface{}
-					myChartCondition += ` AND a.admin_id=? `
-					myChartPars = append(myChartPars, adminId)
-					myChartCondition += ` AND a.chart_info_id=? `
-					myChartPars = append(myChartPars, chartInfo.ChartInfoId)
-
-					myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					if myChartList != nil && len(myChartList) > 0 {
-						chartInfo.IsAdd = true
-						chartInfo.MyChartId = myChartList[0].MyChartId
-						chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
-					}
-				}
-
 				isOk = true
 				fmt.Println("source redis")
 				return
@@ -3345,6 +3378,62 @@ func (this *ChartInfoController) PreviewBarChartInfo() {
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 标识线计算
+	if req.MarkersLines != "" {
+		markerLines := make([]data_manage.MarkersLine, 0)
+		err = json.Unmarshal([]byte(req.MarkersLines), &markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+		for i := range markerLines {
+			if markerLines[i].EdbType == 1 {
+				// 指标计算
+				edbInfo, err := data_manage.GetEdbInfoById(markerLines[i].EdbInfoId)
+				if err != nil {
+					br.Msg = "指标计算标识线获取指标信息异常"
+					br.ErrMsg = "指标计算标识线获取指标信息异常" + err.Error()
+					return
+				}
+				dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, markerLines[i].StartDate.Date, markerLines[i].EndDate.Date)
+				if err != nil {
+					br.Msg = "指标计算标识线获取指标数据异常"
+					br.ErrMsg = "指标计算标识线获取指标数据异常" + err.Error()
+					return
+				}
+				value, err := data.MarkerLineCalculate(markerLines[i], dataList, chartInfo)
+				if err != nil {
+					br.Msg = "标识线配置异常"
+					br.ErrMsg = "标识线配置异常" + err.Error()
+					return
+				}
+				markerLines[i].Value = value
+
+			} else {
+				// 跟随图表
+				if edbList[0].IsAxis == 1 {
+					value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
+					if err != nil {
+						br.Msg = "标识线配置异常"
+						br.ErrMsg = "标识线配置异常" + err.Error()
+						return
+					}
+					markerLines[i].Value = value
+				}
+			}
+		}
+
+		markerLineStr, err := json.Marshal(markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+
+		chartInfo.MarkersLines = string(markerLineStr)
+	}
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -3909,6 +3998,7 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 
 	edbInfoId := this.GetString("EdbInfoId")
 	chartType, _ := this.GetInt("ChartType")
+	isAxis, _ := this.GetInt("IsAxis")
 
 	calendar := this.GetString("Calendar")
 	if calendar == "" {
@@ -4052,7 +4142,7 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 		startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
 
 		// 获取图表中的指标数据
-		edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartConvertEdbData(chartInfoId, chartType, calendar, startDate, endDate, mappingList, extraConfigStr, chartInfo.SeasonExtraConfig)
+		edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartConvertEdbData(chartInfoId, chartType, calendar, startDate, endDate, mappingList, extraConfigStr, chartInfo.SeasonExtraConfig, isAxis)
 		if err != nil {
 			br.Msg = "获取失败"
 			if errMsg != `` {
@@ -4266,3 +4356,169 @@ func (this *ChartInfoController) ForumDelete() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// PreviewSeasonChartInfo
+// @Title 图表-获取预览的季节性图
+// @Description 图表-获取预览的季节性图
+// @Param	ExtraConfig	body data_manage.PreviewRadarChartReq true "type json string"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview/season [post]
+func (this *ChartInfoController) PreviewSeasonChartInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req data_manage.PreviewSeasonChartReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	chartType := utils.CHART_TYPE_SEASON
+	if len(req.ChartEdbInfoList) <= 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	edbInfoIdArr := make([]int, 0)
+	edbInfoNameMap := make(map[int]string)
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+		edbInfoNameMap[v.EdbInfoId] = v.EdbAliasName
+	}
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdArr)
+	for _, v := range mappingList {
+		if name, ok := edbInfoNameMap[v.EdbInfoId]; ok {
+			v.EdbAliasName = name
+		}
+	}
+
+	var seasonExtraConfig string
+	seasonExtra, tErr := json.Marshal(req.SeasonExtraConfig)
+	if tErr != nil {
+		br.Msg = "季节性图表配置信息异常"
+		br.ErrMsg = "季节性图表配置信息异常,Err:" + tErr.Error()
+		return
+	}
+	seasonExtraConfig = string(seasonExtra)
+	// 获取图表中的指标数据
+	edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartEdbData(0, chartType, "", "", "", mappingList, "", seasonExtraConfig)
+	if err != nil {
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	warnEdbList := make([]string, 0)
+	for _, v := range edbList {
+		if v.IsNullData {
+			warnEdbList = append(warnEdbList, v.EdbName+"("+v.EdbCode+")")
+		}
+	}
+	if len(warnEdbList) > 0 {
+		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, utils.CHART_SOURCE_DEFAULT, chartType)
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
+	}
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+
+// ChartInfoImgSetBySvg
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_svg [post]
+func (this *ChartInfoController) ChartInfoImgSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,ChartInfoId Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 266 - 139
controllers/data_manage/correlation/correlation_chart_classify.go

@@ -7,7 +7,10 @@ import (
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/utils"
+	"fmt"
+	"sort"
 	"time"
 )
 
@@ -19,93 +22,120 @@ type CorrelationChartClassifyController struct {
 // ChartClassifyList
 // @Title 相关性图表分类列表
 // @Description 相关性图表分类列表接口
-// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
-// @Param   Source   query   int  true       "图表类型,3:相关性,4:滚动相关性"
+// @Param   IsShowMe   query   bool  false       "是否只看我的,true、false"
+// @Param   ParentId   query   bool  false       "父级ID"
+// @Param   Source   query   int  false       "图表类型,3:相关性,4:滚动相关性"
 // @Success 200 {object} data_manage.ChartClassifyListResp
 // @router /chart_classify/list [get]
 func (this *CorrelationChartClassifyController) ChartClassifyList() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
 	resp := new(data_manage.ChartClassifyListResp)
 
 	// 获取当前账号的不可见指标
-	noPermissionChartIdMap := make(map[int]bool)
-	{
-		obj := data_manage.EdbInfoNoPermissionAdmin{}
-		confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
-		if err != nil && err.Error() != utils.ErrNoRow() {
-			br.Msg = "获取失败"
-			br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
-			return
-		}
-		for _, v := range confList {
-			noPermissionChartIdMap[v.ChartInfoId] = true
-		}
-	}
-
-	isShowMe, _ := this.GetBool("IsShowMe")
-	//if isShowMe {
-	//	errMsg, err := getChartClassifyListForMe(*this.SysUser, resp)
-	//	if err != nil {
-	//		br.Msg = errMsg
-	//		br.ErrMsg = err.Error()
+	//noPermissionChartIdMap := make(map[int]bool)
+	//{
+	//	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	//	confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+	//	if err != nil && err.Error() != utils.ErrNoRow() {
+	//		br.Msg = "获取失败"
+	//		br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
 	//		return
 	//	}
-	//	br.Ret = 200
-	//	br.Success = true
-	//	br.Msg = "获取成功"
-	//	br.Data = resp
-	//	return
+	//	for _, v := range confList {
+	//		noPermissionChartIdMap[v.ChartInfoId] = true
+	//	}
 	//}
 
-	source, _ := this.GetInt("Source")
-	if source <= 0 {
-		source = utils.CHART_SOURCE_CORRELATION
-	}
+	isShowMe, _ := this.GetBool("IsShowMe")
+	parentId, _ := this.GetInt("ParentId")
+	source, _ := this.GetInt("Source", utils.CHART_SOURCE_CORRELATION)
 
-	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_CORRELATION)
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	// 查询分类节点
+	rootList, err := data_manage.GetChartClassifyByParentId(parentId, utils.CHART_SOURCE_CORRELATION)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
 
-	allChartInfo, err := data_manage.GetChartInfoAll([]int{source})
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
-		return
+	if len(rootList) > 0 {
+		permissionClassifyIdList, e := data_manage_permission.GetUserChartClassifyPermissionList(this.SysUser.AdminId, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取已授权分类id数据失败,Err:" + e.Error()
+			return
+		}
+
+		for _, v := range rootList {
+			// 操作按钮权限
+			v.HaveOperaAuth = data_manage_permission.CheckChartClassifyPermissionByPermissionIdList(v.IsJoinPermission, v.ChartClassifyId, permissionClassifyIdList)
+			button := data.GetChartClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			v.Button = button
+			v.ParentId = parentId
+			v.Children = make([]*data_manage.ChartClassifyItems, 0)
+
+			nodeAll = append(nodeAll, v)
+		}
 	}
 
-	chartInfoMap := make(map[int][]*data_manage.ChartClassifyItems)
-	for _, v := range allChartInfo {
-		if !isShowMe {
-			chartInfoMap[v.ChartClassifyId] = append(chartInfoMap[v.ChartClassifyId], v)
-			continue
+	// 查询图表节点, ParentId=0时说明仅查询一级目录节点
+	if parentId > 0 {
+		// 查询当前分类信息
+		currClassify, e := data_manage.GetChartClassifyById(parentId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取当前分类信息失败,Err:" + e.Error()
+			return
 		}
-		if v.SysUserId != this.SysUser.AdminId {
-			continue
+
+		// 获取所有有权限的指标和分类
+		permissionEdbIdList, permissionClassifyIdList, e := data_manage_permission.GetUserChartAndClassifyPermissionList(this.SysUser.AdminId, 0, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + e.Error()
+			return
 		}
-		chartInfoMap[v.ChartClassifyId] = append(chartInfoMap[v.ChartClassifyId], v)
-	}
-	rootChildMap := make(map[int][]*data_manage.ChartClassifyItems)
-	for _, v := range rootList {
-		rootChildMap[v.ParentId] = append(rootChildMap[v.ParentId], v)
-		if existItems, ok := chartInfoMap[v.ChartClassifyId]; ok {
-			v.Children = existItems
-		} else {
-			items := make([]*data_manage.ChartClassifyItems, 0)
-			v.Children = items
+
+		var adminId int
+		if isShowMe {
+			adminId = this.SysUser.AdminId
+		}
+
+		charts, e := data_manage.GetChartInfoBySourceAndParentId(source, parentId, adminId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图表信息失败, Err: %v", e)
+			return
+		}
+		for _, v := range charts {
+			// 操作按钮权限
+			v.HaveOperaAuth = data_manage_permission.CheckChartPermissionByPermissionIdList(v.IsJoinPermission, currClassify.IsJoinPermission, v.ChartInfoId, v.ChartClassifyId, permissionEdbIdList, permissionClassifyIdList)
+			button := data.GetChartOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			button.AddButton = false //不管有没有权限,图表都是没有添加按钮的
+			v.Button = button
+			v.ParentId = parentId
+			v.Children = make([]*data_manage.ChartClassifyItems, 0)
+
+			nodeAll = append(nodeAll, v)
 		}
 	}
 
-	// 移除没有权限的图表
-	allNodes := data.HandleNoPermissionChart(rootList, noPermissionChartIdMap, this.SysUser.AdminId)
-	resp.AllNodes = allNodes
+	// 整体排序
+	if len(nodeAll) > 0 {
+		sort.Slice(nodeAll, func(i, j int) bool {
+			return nodeAll[i].Sort < nodeAll[j].Sort
+		})
+	}
 
+	resp.AllNodes = nodeAll
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -545,10 +575,12 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 func (this *CorrelationChartClassifyController) ChartClassifyMove() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
 	sysUser := this.SysUser
 	if sysUser == nil {
 		br.Msg = "请登录"
@@ -556,7 +588,6 @@ func (this *CorrelationChartClassifyController) ChartClassifyMove() {
 		br.Ret = 408
 		return
 	}
-
 	var req data_manage.MoveChartClassifyReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -564,107 +595,203 @@ func (this *CorrelationChartClassifyController) ChartClassifyMove() {
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
-
-	if req.ClassifyId <= 0 {
+	if req.ClassifyId <= 0 && req.ChartInfoId <= 0 {
 		br.Msg = "参数错误"
-		br.ErrMsg = "分类id小于等于0"
-		return
-	}
-	//判断分类是否存在
-	chartClassifyInfo, err := data_manage.GetChartClassifyById(req.ClassifyId)
-	if err != nil {
-		br.Msg = "移动失败"
-		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
-		return
-	}
-	if chartClassifyInfo.Source != utils.CHART_SOURCE_CORRELATION {
-		br.Msg = "分类异常"
-		br.ErrMsg = "分类异常,不是相关性图表的分类"
+		br.ErrMsg = "请选择拖动目标,分类目录或者指标"
 		return
 	}
-	updateCol := make([]string, 0)
 
-	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
-	if chartClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
-		parentChartClassifyInfo, err := data_manage.GetChartClassifyById(req.ParentClassifyId)
+	err, errMsg := data.MoveChartClassify(req, sysUser, utils.CHART_SOURCE_CORRELATION)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
 		if err != nil {
-			br.Msg = "移动失败"
-			br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
-			return
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
 		}
-		chartClassifyInfo.ParentId = parentChartClassifyInfo.ChartClassifyId
-		chartClassifyInfo.Level = parentChartClassifyInfo.Level + 1
-		chartClassifyInfo.ModifyTime = time.Now()
-		updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+		return
 	}
 
-	//如果有传入 上一个兄弟节点分类id
-	if req.PrevClassifyId > 0 {
-		//上一个兄弟节点
-		prevClassify, err := data_manage.GetChartClassifyById(req.PrevClassifyId)
-		if err != nil {
-			br.Msg = "移动失败"
-			br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
-			return
-		}
+	//if req.ClassifyId <= 0 {
+	//	br.Msg = "参数错误"
+	//	br.ErrMsg = "分类id小于等于0"
+	//	return
+	//}
+	//
+	////判断分类是否存在
+	//chartClassifyInfo, err := data_manage.GetChartClassifyById(req.ClassifyId)
+	//if err != nil {
+	//	br.Msg = "移动失败"
+	//	br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+	//	return
+	//}
+	//if chartClassifyInfo.Source != utils.CHART_SOURCE_CORRELATION {
+	//	br.Msg = "分类异常"
+	//	br.ErrMsg = "分类异常,不是相关性图表的分类"
+	//	return
+	//}
+	//updateCol := make([]string, 0)
+	//
+	////判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	//if chartClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+	//	parentChartClassifyInfo, err := data_manage.GetChartClassifyById(req.ParentClassifyId)
+	//	if err != nil {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+	//		return
+	//	}
+	//	chartClassifyInfo.ParentId = parentChartClassifyInfo.ChartClassifyId
+	//	chartClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+	//	chartClassifyInfo.ModifyTime = time.Now()
+	//	updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+	//}
+	//
+	////如果有传入 上一个兄弟节点分类id
+	//if req.PrevClassifyId > 0 {
+	//	//上一个兄弟节点
+	//	prevClassify, err := data_manage.GetChartClassifyById(req.PrevClassifyId)
+	//	if err != nil {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+	//		return
+	//	}
+	//
+	//	//如果是移动在两个兄弟节点之间
+	//	if req.NextClassifyId > 0 {
+	//		//下一个兄弟节点
+	//		nextClassify, err := data_manage.GetChartClassifyById(req.NextClassifyId)
+	//		if err != nil {
+	//			br.Msg = "移动失败"
+	//			br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+	//			return
+	//		}
+	//		//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+	//		if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == chartClassifyInfo.Sort {
+	//			//变更兄弟节点的排序
+	//			updateSortStr := `sort + 2`
+	//			_ = data_manage.UpdateChartClassifySortByParentId(prevClassify.ParentId, prevClassify.ChartClassifyId, prevClassify.Sort, updateSortStr)
+	//		} else {
+	//			//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+	//			if nextClassify.Sort-prevClassify.Sort == 1 {
+	//				//变更兄弟节点的排序
+	//				updateSortStr := `sort + 1`
+	//				_ = data_manage.UpdateChartClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+	//			}
+	//		}
+	//	}
+	//
+	//	chartClassifyInfo.Sort = prevClassify.Sort + 1
+	//	chartClassifyInfo.ModifyTime = time.Now()
+	//	updateCol = append(updateCol, "Sort", "ModifyTime")
+	//
+	//} else {
+	//	firstClassify, err := data_manage.GetFirstChartClassifyByParentId(chartClassifyInfo.ParentId)
+	//	if err != nil && err.Error() != utils.ErrNoRow() {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+	//		return
+	//	}
+	//
+	//	//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+	//	if firstClassify != nil && firstClassify.Sort == 0 {
+	//		updateSortStr := ` sort + 1 `
+	//		_ = data_manage.UpdateChartClassifySortByParentId(firstClassify.ParentId, firstClassify.ChartClassifyId-1, 0, updateSortStr)
+	//	}
+	//
+	//	chartClassifyInfo.Sort = 0 //那就是排在第一位
+	//	chartClassifyInfo.ModifyTime = time.Now()
+	//	updateCol = append(updateCol, "Sort", "ModifyTime")
+	//}
+	//
+	////更新
+	//if len(updateCol) > 0 {
+	//	err = chartClassifyInfo.Update(updateCol)
+	//	if err != nil {
+	//		br.Msg = "移动失败"
+	//		br.ErrMsg = "修改失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
 
-		//如果是移动在两个兄弟节点之间
-		if req.NextClassifyId > 0 {
-			//下一个兄弟节点
-			nextClassify, err := data_manage.GetChartClassifyById(req.NextClassifyId)
-			if err != nil {
-				br.Msg = "移动失败"
-				br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
-				return
-			}
-			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
-			if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == chartClassifyInfo.Sort {
-				//变更兄弟节点的排序
-				updateSortStr := `sort + 2`
-				_ = data_manage.UpdateChartClassifySortByParentId(prevClassify.ParentId, prevClassify.ChartClassifyId, prevClassify.Sort, updateSortStr)
-			} else {
-				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
-				if nextClassify.Sort-prevClassify.Sort == 1 {
-					//变更兄弟节点的排序
-					updateSortStr := `sort + 1`
-					_ = data_manage.UpdateChartClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
-				}
-			}
+// ClassifyTree
+// @Title 多层分类列表树
+// @Description 多层分类列表树
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /chart_classify/tree [get]
+func (this *CorrelationChartClassifyController) ClassifyTree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
 		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
 
-		chartClassifyInfo.Sort = prevClassify.Sort + 1
-		chartClassifyInfo.ModifyTime = time.Now()
-		updateCol = append(updateCol, "Sort", "ModifyTime")
+	allList, err := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_CORRELATION)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取所有分类失败, Err:" + err.Error()
+		return
+	}
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
 
-	} else {
-		firstClassify, err := data_manage.GetFirstChartClassifyByParentId(chartClassifyInfo.ParentId)
-		if err != nil && err.Error() != utils.ErrNoRow() {
-			br.Msg = "移动失败"
-			br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+	if len(allList) > 0 {
+		// 已授权分类id
+		permissionClassifyIdList, e := data_manage_permission.GetUserChartClassifyPermissionList(this.SysUser.AdminId, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取已授权分类id数据失败,Err:" + e.Error()
 			return
 		}
 
-		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-		if firstClassify != nil && firstClassify.Sort == 0 {
-			updateSortStr := ` sort + 1 `
-			_ = data_manage.UpdateChartClassifySortByParentId(firstClassify.ParentId, firstClassify.ChartClassifyId-1, 0, updateSortStr)
+		for k, v := range allList {
+			// 数据权限
+			v.HaveOperaAuth = data_manage_permission.CheckChartClassifyPermissionByPermissionIdList(v.IsJoinPermission, v.ChartClassifyId, permissionClassifyIdList)
+			// 按钮权限
+			button := data.GetChartClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			allList[k].Button = button
 		}
 
-		chartClassifyInfo.Sort = 0 //那就是排在第一位
-		chartClassifyInfo.ModifyTime = time.Now()
-		updateCol = append(updateCol, "Sort", "ModifyTime")
+		nodeAll = data.GetChartClassifyTreeRecursive(allList, 0)
+		//根据sort值排序
+		sort.Slice(nodeAll, func(i, j int) bool {
+			return nodeAll[i].Sort < nodeAll[j].Sort
+		})
 	}
 
-	//更新
-	if len(updateCol) > 0 {
-		err = chartClassifyInfo.Update(updateCol)
-		if err != nil {
-			br.Msg = "移动失败"
-			br.ErrMsg = "修改失败,Err:" + err.Error()
-			return
+	language := `CN`
+	// 显示的语言
+	{
+		configDetail, _ := system.GetConfigDetailByCode(this.SysUser.AdminId, system.ChartLanguageVar)
+		if configDetail != nil {
+			language = configDetail.ConfigValue
+		} else {
+			configDetail, _ = system.GetDefaultConfigDetailByCode(system.ChartLanguageVar)
+			if configDetail != nil {
+				language = configDetail.ConfigValue
+			}
 		}
 	}
+
+	// 是否允许添加一级分类
+	canOpClassify := true
+	button := data.GetChartClassifyOpButton(this.SysUser, 0, true)
+	if !button.AddButton {
+		canOpClassify = false
+	}
+
+	resp := new(data_manage.ChartClassifyListResp)
+	resp.AllNodes = nodeAll
+	resp.Language = language
+	resp.CanOpClassify = canOpClassify
 	br.Ret = 200
 	br.Success = true
-	br.Msg = "移动成功"
+	br.Msg = "获取成功"
+	br.Data = resp
 }

+ 1304 - 136
controllers/data_manage/correlation/correlation_chart_info.go

@@ -13,6 +13,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -90,7 +91,7 @@ func (this *CorrelationChartInfoController) Preview() {
 
 	//chartInfo.CorrelationLeadUnit = req.LeadUnit
 	// 获取图表x轴y轴
-	xEdbIdValue, yDataList, e := correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, req.LeadValue, req.LeadUnit, req.StartDate, req.EndDate)
+	xEdbIdValue, yDataList, e := correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, req.LeadValue, req.LeadUnit, req.StartDate, req.EndDate, "")
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
@@ -698,49 +699,72 @@ func (this *CorrelationChartInfoController) Detail() {
 		br.ErrMsg = "获取相关性图表, A指标mapping信息失败, Err:" + e.Error()
 		return
 	}
-	edbInfoMappingB, e := data_manage.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
-		return
+	edbInfoMappingB := new(data_manage.ChartEdbInfoMapping)
+	// 非多因子
+	if correlationChart.AnalysisMode != 1 {
+		edbInfoMappingB, e = data_manage.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
+			return
+		}
 	}
 
 	var dataResp interface{} // 绘图数据返回(目前是滚动相关性的图)
 	var xEdbIdValue []int
 	var yDataList []data_manage.YData
-	switch chartInfo.Source {
-	case utils.CHART_SOURCE_CORRELATION: // 相关性图
-		moveUnitDays, ok := utils.FrequencyDaysMap[correlationChart.CalculateUnit]
-		if !ok {
-			br.Msg = "错误的分析周期"
-			br.IsSendEmail = false
-			return
-		}
-		startDate := time.Now().AddDate(0, 0, -correlationChart.CalculateValue*moveUnitDays).Format(utils.FormatDate)
-		endDate := time.Now().Format(utils.FormatDate)
+	if correlationChart.AnalysisMode != 1 {
+		switch chartInfo.Source {
+		case utils.CHART_SOURCE_CORRELATION: // 相关性图
+			moveUnitDays, ok := utils.FrequencyDaysMap[correlationChart.CalculateUnit]
+			if !ok {
+				br.Msg = "错误的分析周期"
+				br.IsSendEmail = false
+				return
+			}
+			startDate := time.Now().AddDate(0, 0, -correlationChart.CalculateValue*moveUnitDays).Format(utils.FormatDate)
+			endDate := time.Now().Format(utils.FormatDate)
 
-		xEdbIdValue, yDataList, e = correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, startDate, endDate)
+			xEdbIdValue, yDataList, e = correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, startDate, endDate, chartInfo.ExtraConfig)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
+				return
+			}
+		case utils.CHART_SOURCE_ROLLING_CORRELATION: // 滚动相关性图
+			startDate, endDate := utils.GetDateByDateType(correlationChart.DateType, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
+			dataResp, e = correlationServ.GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.CalculateValue, correlationChart.CalculateUnit, startDate, endDate, chartInfo.ChartName, chartInfo.ChartNameEn)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取滚动相关性图表, 图表计算值失败, Err:" + e.Error()
+				return
+			}
+		}
+	} else {
+		xEdbIdValue, yDataList, e = correlationServ.GetFactorChartDataByChartId(chartInfoId, chartInfo.ExtraConfig)
 		if e != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
 			return
 		}
-	case utils.CHART_SOURCE_ROLLING_CORRELATION: // 滚动相关性图
-		startDate, endDate := utils.GetDateByDateType(correlationChart.DateType, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
-		dataResp, e = correlationServ.GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.CalculateValue, correlationChart.CalculateUnit, startDate, endDate, chartInfo.ChartName, chartInfo.ChartNameEn)
+	}
+
+	// 完善指标信息
+	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	if correlationChart.AnalysisMode != 1 {
+		edbList, e = correlationServ.GetChartEdbInfoFormat(chartInfo.ChartInfoId, edbInfoMappingA, edbInfoMappingB)
 		if e != nil {
 			br.Msg = "获取失败"
-			br.ErrMsg = "获取滚动相关性图表, 图表计算值失败, Err:" + e.Error()
+			br.ErrMsg = "获取相关性图表, 完善指标信息失败, Err:" + e.Error()
+			return
+		}
+	} else {
+		// 多因子指标, 获取图表引用到的系列指标(去重后)
+		edbList, e = data_manage.GetChartEdbMappingListByIdList([]int{chartInfoId})
+		if e != nil {
+			err = fmt.Errorf("获取图表引用系列指标失败, err: %v", e)
 			return
 		}
-	}
-
-	// 完善指标信息
-	edbList, e := correlationServ.GetChartEdbInfoFormat(chartInfo.ChartInfoId, edbInfoMappingA, edbInfoMappingB)
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取相关性图表, 完善指标信息失败, Err:" + e.Error()
-		return
 	}
 	correlationInfo := new(data_manage.CorrelationInfo)
 	correlationInfo.LeadValue = correlationChart.LeadValue
@@ -752,6 +776,7 @@ func (this *CorrelationChartInfoController) Detail() {
 	correlationInfo.LeadValue = correlationChart.LeadValue
 	correlationInfo.EdbInfoIdFirst = correlationChart.EdbInfoIdFirst
 	correlationInfo.EdbInfoIdSecond = correlationChart.EdbInfoIdSecond
+	correlationInfo.AnalysisMode = correlationChart.AnalysisMode
 
 	// 判断是否加入我的图库
 	if chartInfoId > 0 && chartInfo != nil {
@@ -783,7 +808,6 @@ func (this *CorrelationChartInfoController) Detail() {
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
 	chartInfo.UnitEn = edbInfoMappingA.UnitEn
 
-	// 图表的指标来源
 	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
 	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
 	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
@@ -852,6 +876,24 @@ func (this *CorrelationChartInfoController) Detail() {
 		}
 	}
 
+	// 图表当前分类的分类树
+	classifyLevels := make([]string, 0)
+	{
+		list, e := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_CORRELATION)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", e)
+			return
+		}
+		parents := data.GetChartClassifyParentRecursive(list, chartInfo.ChartClassifyId)
+		sort.Slice(parents, func(i, j int) bool {
+			return parents[i].Level < parents[i].Level
+		})
+		for _, v := range parents {
+			classifyLevels = append(classifyLevels, v.UniqueCode)
+		}
+	}
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -859,6 +901,7 @@ func (this *CorrelationChartInfoController) Detail() {
 	resp.YDataList = yDataList
 	resp.CorrelationChartInfo = correlationInfo
 	resp.DataResp = dataResp
+	resp.ClassifyLevels = classifyLevels
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -1027,11 +1070,19 @@ func (this *CorrelationChartInfoController) Refresh() {
 	}
 
 	// 刷新相关性图表
-	if e := correlationServ.ChartInfoRefresh(chartInfo.ChartInfoId); e != nil {
+	isAsync, e := correlationServ.ChartInfoRefresh(chartInfo.ChartInfoId, chartInfo.UniqueCode)
+	if e != nil {
 		br.Msg = "刷新失败"
 		br.ErrMsg = "刷新相关性图表失败, Err:" + err.Error()
 		return
 	}
+	// 多因子相关性异步刷新, 前端提示
+	if isAsync {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "刷新时间较长, 请10分钟后查看"
+		return
+	}
 
 	//清除图表缓存
 	{
@@ -1053,10 +1104,12 @@ func (this *CorrelationChartInfoController) Refresh() {
 func (this *CorrelationChartInfoController) Copy() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
 	sysUser := this.SysUser
 	if sysUser == nil {
 		br.Msg = "请登录"
@@ -1064,6 +1117,28 @@ func (this *CorrelationChartInfoController) Copy() {
 		br.Ret = 408
 		return
 	}
+	var req data_manage.CopyAddChartInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartInfoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartInfoId: %d", req.ChartInfoId)
+		return
+	}
+	req.ChartName = strings.TrimSpace(req.ChartName)
+	if req.ChartName == "" {
+		br.Msg = "请输入图表名称"
+		return
+	}
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "请选择图表分类"
+		return
+	}
+
 	deleteCache := true
 	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
 	defer func() {
@@ -1077,76 +1152,282 @@ func (this *CorrelationChartInfoController) Copy() {
 		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
 		return
 	}
-	var req data_manage.CopyAddChartInfoReq
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
+
+	chartSource := utils.CHART_SOURCE_CORRELATION
+	// 校验分类、图表名称
+	{
+		var cond string
+		var pars []interface{}
+		switch this.Lang {
+		case utils.EnLangVersion:
+			cond += " AND chart_name_en = ? AND source = ? "
+		default:
+			cond += " AND chart_name = ? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, chartSource)
+		count, e := data_manage.GetChartInfoCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取同名图表失败, Err: %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "图表名称已存在, 请重新填写"
+			return
+		}
+
+		_, e = data_manage.GetChartClassifyById(req.ChartClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", e)
+			return
+		}
 	}
 
-	// 获取原图表信息
-	oldChartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
-	if err != nil {
-		br.Msg = "获取原图表信息失败"
-		br.ErrMsg = "获取原图表信息失败,Err:" + err.Error()
+	// 图表信息
+	originChart, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "原图表不存在"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("获取原图表信息失败, Err: %v", e)
 		return
 	}
-	if oldChartInfo.Source == utils.CHART_SOURCE_ROLLING_CORRELATION {
+	if originChart.Source == utils.CHART_SOURCE_ROLLING_CORRELATION {
 		br.Msg = `滚动相关性图不支持另存为`
 		br.IsSendEmail = false
 		return
 	}
 
-	multipleGraphConfigChartMapping, err := data_manage.GetMultipleGraphConfigChartMappingByChartId(req.ChartInfoId)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = `保存失败`
-		br.ErrMsg = "获取配置与图表的关联关系失败,ERR:" + err.Error()
+	// 相关性图
+	originCorrelate := new(data_manage.ChartInfoCorrelation)
+	if e = originCorrelate.GetItemById(req.ChartInfoId); e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "原相关性图表不存在"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("获取原相关性图表信息失败, Err: %v", e)
 		return
 	}
 
-	correlationChart := new(data_manage.ChartInfoCorrelation)
-	if e := correlationChart.GetItemById(req.ChartInfoId); e != nil {
-		br.Msg = "另存为失败"
-		br.ErrMsg = "获取原图表相关性信息失败, Err:" + e.Error()
-		return
-	}
-
-	correlationChartInfoReq := data_manage.CorrelationChartInfoReq{
-		LeadValue:          correlationChart.LeadValue,
-		LeadUnit:           correlationChart.LeadUnit,
-		CalculateValue:     correlationChart.CalculateValue,
-		CalculateUnit:      correlationChart.CalculateUnit,
-		BaseCalculateUnit:  correlationChart.BaseCalculateUnit,
-		BaseCalculateValue: correlationChart.BaseCalculateValue,
-		StartDate:          correlationChart.StartDate.Format(utils.FormatDate),
-		EndDate:            correlationChart.EndDate.Format(utils.FormatDate),
-		EdbInfoIdList: []data_manage.CorrelationChartInfoEdbItemReq{
-			{
-				EdbInfoId: correlationChart.EdbInfoIdFirst,
-				Name:      "",
-				NameEn:    "",
-			}, {
-				EdbInfoId: correlationChart.EdbInfoIdSecond,
-				Name:      "",
-				NameEn:    "",
+	// 普通相关性图表
+	chartInfo := new(data_manage.ChartInfo)
+	if originCorrelate.AnalysisMode != 1 {
+		multipleGraphConfigChartMapping, err := data_manage.GetMultipleGraphConfigChartMappingByChartId(req.ChartInfoId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = `保存失败`
+			br.ErrMsg = "获取配置与图表的关联关系失败,ERR:" + err.Error()
+			return
+		}
+
+		correlationChartInfoReq := data_manage.CorrelationChartInfoReq{
+			LeadValue:          originCorrelate.LeadValue,
+			LeadUnit:           originCorrelate.LeadUnit,
+			CalculateValue:     originCorrelate.CalculateValue,
+			CalculateUnit:      originCorrelate.CalculateUnit,
+			BaseCalculateUnit:  originCorrelate.BaseCalculateUnit,
+			BaseCalculateValue: originCorrelate.BaseCalculateValue,
+			StartDate:          originCorrelate.StartDate.Format(utils.FormatDate),
+			EndDate:            originCorrelate.EndDate.Format(utils.FormatDate),
+			EdbInfoIdList: []data_manage.CorrelationChartInfoEdbItemReq{
+				{
+					EdbInfoId: originCorrelate.EdbInfoIdFirst,
+					Name:      "",
+					NameEn:    "",
+				}, {
+					EdbInfoId: originCorrelate.EdbInfoIdSecond,
+					Name:      "",
+					NameEn:    "",
+				},
 			},
-		},
+		}
+
+		newChart, err, errMsg, isSendEmail := correlationServ.CopyChartInfo(multipleGraphConfigChartMapping.MultipleGraphConfigId, req.ChartClassifyId, req.ChartName, correlationChartInfoReq, originChart, sysUser, this.Lang)
+		chartInfo = newChart
+
+		if err != nil {
+			br.Msg = "保存失败"
+			if errMsg != `` {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = err.Error()
+			br.IsSendEmail = isSendEmail
+			return
+		}
 	}
 
-	chartInfo, err, errMsg, isSendEmail := correlationServ.CopyChartInfo(multipleGraphConfigChartMapping.MultipleGraphConfigId, req.ChartClassifyId, req.ChartName, correlationChartInfoReq, oldChartInfo, sysUser, this.Lang)
+	// 多因子相关性图表
+	if originCorrelate.AnalysisMode == 1 {
+		chartInfo.ChartName = req.ChartName
+		chartInfo.ChartNameEn = req.ChartName
+		chartInfo.ChartClassifyId = req.ChartClassifyId
+		chartInfo.SysUserId = sysUser.AdminId
+		chartInfo.SysUserRealName = sysUser.RealName
+		chartInfo.CreateTime = time.Now()
+		chartInfo.ModifyTime = time.Now()
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		chartInfo.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp)
+		chartInfo.ChartType = originChart.ChartType
+		chartInfo.Calendar = originChart.Calendar
+		chartInfo.DateType = originChart.DateType
+		chartInfo.StartDate = originChart.StartDate
+		chartInfo.EndDate = originChart.EndDate
+		chartInfo.SeasonStartDate = originChart.StartDate
+		chartInfo.SeasonEndDate = originChart.EndDate
+		chartInfo.Disabled = originChart.Disabled
+		chartInfo.Source = originChart.Source
+		chartInfo.ChartThemeId = originChart.ChartThemeId
+		chartInfo.ExtraConfig = originChart.ExtraConfig
+		chartInfo.SourcesFrom = originChart.SourcesFrom
+		chartInfo.Instructions = originChart.Instructions
+		chartInfo.MarkersLines = originChart.MarkersLines
+		chartInfo.MarkersAreas = originChart.MarkersAreas
+
+		// 相关性图
+		chartCorrelate := new(data_manage.ChartInfoCorrelation)
+		chartCorrelate.LeadValue = originCorrelate.LeadValue
+		chartCorrelate.LeadUnit = originCorrelate.LeadUnit
+		chartCorrelate.CalculateValue = originCorrelate.CalculateValue
+		chartCorrelate.CalculateUnit = originCorrelate.CalculateUnit
+		chartCorrelate.EdbInfoIdFirst = originCorrelate.EdbInfoIdFirst
+		chartCorrelate.AnalysisMode = 1
+		chartCorrelate.CreateTime = time.Now().Local()
+		chartCorrelate.ModifyTime = time.Now().Local()
+
+		// 图表指标关联
+		edbMappings := make([]*data_manage.ChartEdbMapping, 0)
+		{
+			mappings, e := data_manage.GetChartMappingList(req.ChartInfoId)
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取图表指标关联失败, Err: %v", e)
+				return
+			}
+			for _, v := range mappings {
+				m := new(data_manage.ChartEdbMapping)
+				m.EdbInfoId = v.EdbInfoId
+				edbTimestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+				m.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + edbTimestamp + "_" + strconv.Itoa(v.EdbInfoId))
+				m.IsOrder = true
+				m.IsAxis = 1
+				m.EdbInfoType = 1
+				m.Source = utils.CHART_SOURCE_CORRELATION
+				m.CreateTime = time.Now()
+				m.ModifyTime = time.Now()
+				edbMappings = append(edbMappings, m)
+			}
+		}
 
-	if err != nil {
-		br.Msg = "保存失败"
-		if errMsg != `` {
-			br.Msg = errMsg
+		// 指标系列-图表关联
+		seriesIds := make([]int, 0)
+		originChartMappings := make([]*data_manage.FactorEdbSeriesChartMapping, 0)
+		{
+			ob := new(data_manage.FactorEdbSeriesChartMapping)
+			cond := fmt.Sprintf(" AND %s = ?", ob.Cols().ChartInfoId)
+			pars := make([]interface{}, 0)
+			pars = append(pars, req.ChartInfoId)
+			list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取指标系列-图表关联失败, Err: %v", e)
+				return
+			}
+			for _, v := range list {
+				//seriesMappings = append(seriesMappings, &data_manage.FactorEdbSeriesChartMapping{
+				//	FactorEdbSeriesId: v.FactorEdbSeriesId,
+				//	ChartInfoId:       req.ChartInfoId,
+				//	EdbInfoId:         v.EdbInfoId,
+				//	Source:            v.Source,
+				//	CreateTime:        time.Now().Local(),
+				//	ModifyTime:        time.Now().Local(),
+				//})
+				if !utils.InArrayByInt(seriesIds, v.FactorEdbSeriesId) {
+					seriesIds = append(seriesIds, v.FactorEdbSeriesId)
+				}
+			}
+			originChartMappings = list
 		}
-		br.ErrMsg = err.Error()
-		br.IsSendEmail = isSendEmail
-		return
+		if len(seriesIds) == 0 {
+			br.Msg = "保存失败"
+			br.ErrMsg = "源图表系列有误"
+			return
+		}
+
+		// 注意此处要复制一份系列及系列关联指标, 不可共用被复制图表的系列
+		originSeries := make([]*data_manage.FactorEdbSeries, 0)
+		{
+			ob := new(data_manage.FactorEdbSeries)
+			cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().PrimaryId, utils.GetOrmInReplace(len(seriesIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, seriesIds)
+			list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取源图表系列失败, %v", e)
+				return
+			}
+			originSeries = list
+		}
+		originEdbSeriesMapping := make([]*data_manage.FactorEdbSeriesMapping, 0)
+		{
+			ob := new(data_manage.FactorEdbSeriesMapping)
+			cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, seriesIds)
+			list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取源图表系列指标失败, %v", e)
+				return
+			}
+			originEdbSeriesMapping = list
+		}
+
+		// 新增图表/相关性图表/图表指标关联/指标系列图表关联
+		chartInfoId, seriesIdMap, e := data_manage.CreateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, originChartMappings, true, originSeries, originEdbSeriesMapping)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("新增多因子相关性图表失败, Err: %v", e)
+			return
+		}
+
+		// 如果原图表有设置图例-那么替换图例设置中的系列ID
+		if originChart.ExtraConfig != "" {
+			conf := new(data_manage.CorrelationChartInfoExtraConfig)
+			e = json.Unmarshal([]byte(originChart.ExtraConfig), &conf)
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("解析图表额外配置失败, Err: %v", e)
+				return
+			}
+			for _, v := range conf.LegendConfig {
+				v.SeriesId = seriesIdMap[v.SeriesId]
+			}
+			b, e := json.Marshal(conf)
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+				return
+			}
+			chartInfo.ExtraConfig = string(b)
+			if e = chartInfo.Update([]string{"ExtraConfig"}); e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("更新图例信息失败, %v", e)
+				return
+			}
+		}
+
+		go data.EsAddOrEditChartInfo(chartInfoId)
 	}
 
-	//新增操作日志
+	// 新增操作日志
 	{
 		chartLog := new(data_manage.ChartInfoLog)
 		chartLog.ChartInfoId = chartInfo.ChartInfoId
@@ -1183,6 +1464,29 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1252,33 +1556,12 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}
@@ -1310,50 +1593,74 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		errMsg = "获取相关性图表, A指标mapping信息失败, Err:" + e.Error()
 		return
 	}
-	edbInfoMappingB, e := data_manage.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
-	if e != nil {
-		msg = "获取失败"
-		errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
-		return
+	edbInfoMappingB := new(data_manage.ChartEdbInfoMapping)
+	// 非多因子
+	if correlationChart.AnalysisMode != 1 {
+		edbInfoMappingB, e = data_manage.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
+		if e != nil {
+			msg = "获取失败"
+			errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
+			return
+		}
 	}
 
 	var dataResp interface{} // 绘图数据返回(目前是滚动相关性的图)
 	var xEdbIdValue []int
 	var yDataList []data_manage.YData
-	switch chartInfo.Source {
-	case utils.CHART_SOURCE_CORRELATION: // 相关性图
-		moveUnitDays, ok := utils.FrequencyDaysMap[correlationChart.CalculateUnit]
-		if !ok {
-			msg = "错误的分析周期"
-			errMsg = "相关性图表数据有误"
-			return
-		}
-		startDate := time.Now().AddDate(0, 0, -correlationChart.CalculateValue*moveUnitDays).Format(utils.FormatDate)
-		endDate := time.Now().Format(utils.FormatDate)
+	if correlationChart.AnalysisMode != 1 {
+		switch chartInfo.Source {
+		case utils.CHART_SOURCE_CORRELATION: // 相关性图
+			moveUnitDays, ok := utils.FrequencyDaysMap[correlationChart.CalculateUnit]
+			if !ok {
+				msg = "错误的分析周期"
+				errMsg = "相关性图表数据有误"
+				return
+			}
+			startDate := time.Now().AddDate(0, 0, -correlationChart.CalculateValue*moveUnitDays).Format(utils.FormatDate)
+			endDate := time.Now().Format(utils.FormatDate)
 
-		xEdbIdValue, yDataList, e = correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, startDate, endDate)
+			xEdbIdValue, yDataList, e = correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, startDate, endDate, chartInfo.ExtraConfig)
+			if e != nil {
+				msg = "获取失败"
+				errMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
+				return
+			}
+		case utils.CHART_SOURCE_ROLLING_CORRELATION: // 滚动相关性图
+			startDate, endDate := utils.GetDateByDateType(correlationChart.DateType, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
+
+			dataResp, e = correlationServ.GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.CalculateValue, correlationChart.CalculateUnit, startDate, endDate, chartInfo.ChartName, chartInfo.ChartNameEn)
+			if e != nil {
+				msg = "获取失败"
+				errMsg = "获取滚动相关性图表, 图表计算值失败, Err:" + e.Error()
+				return
+			}
+		}
+	} else {
+		xEdbIdValue, yDataList, e = correlationServ.GetFactorChartDataByChartId(chartInfoId, chartInfo.ExtraConfig)
 		if e != nil {
 			msg = "获取失败"
 			errMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
 			return
 		}
-	case utils.CHART_SOURCE_ROLLING_CORRELATION: // 滚动相关性图
-		startDate, endDate := utils.GetDateByDateType(correlationChart.DateType, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
+	}
 
-		dataResp, e = correlationServ.GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.CalculateValue, correlationChart.CalculateUnit, startDate, endDate, chartInfo.ChartName, chartInfo.ChartNameEn)
+	// 完善指标信息
+	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	if correlationChart.AnalysisMode != 1 {
+		edbList, e = correlationServ.GetChartEdbInfoFormat(chartInfo.ChartInfoId, edbInfoMappingA, edbInfoMappingB)
 		if e != nil {
 			msg = "获取失败"
-			errMsg = "获取滚动相关性图表, 图表计算值失败, Err:" + e.Error()
+			errMsg = "获取相关性图表, 完善指标信息失败, Err:" + e.Error()
+			return
+		}
+	} else {
+		// 多因子指标, 获取图表引用到的系列指标(去重后)
+		edbList, e = data_manage.GetChartEdbMappingListByIdList([]int{chartInfoId})
+		if e != nil {
+			msg = "获取失败"
+			errMsg = "获取图表引用系列指标失败, Err:" + e.Error()
 			return
 		}
-	}
-
-	// 完善指标信息
-	edbList, e := correlationServ.GetChartEdbInfoFormat(chartInfo.ChartInfoId, edbInfoMappingA, edbInfoMappingB)
-	if e != nil {
-		msg = "获取失败"
-		errMsg = "获取相关性图表, 完善指标信息失败, Err:" + e.Error()
-		return
 	}
 	correlationInfo := new(data_manage.CorrelationInfo)
 	correlationInfo.LeadValue = correlationChart.LeadValue
@@ -1365,6 +1672,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	correlationInfo.LeadValue = correlationChart.LeadValue
 	correlationInfo.EdbInfoIdFirst = correlationChart.EdbInfoIdFirst
 	correlationInfo.EdbInfoIdSecond = correlationChart.EdbInfoIdSecond
+	correlationInfo.AnalysisMode = correlationChart.AnalysisMode
 
 	if chartInfoId > 0 && chartInfo != nil {
 		//判断是否加入我的图库
@@ -1821,3 +2129,863 @@ func (this *CorrelationChartInfoController) BaseInfoEdit() {
 	br.Msg = "编辑成功"
 	br.IsAddLog = true
 }
+
+// MultiFactorAdd
+// @Title 多因子图表-新增
+// @Description 多因子图表-新增
+// @Param	request	body request.CorrelationChartMultiFactorSaveReq true "type json string"
+// @Success Ret=200 返回图表id
+// @router /chart_info/multi_factor/add [post]
+func (this *CorrelationChartInfoController) MultiFactorAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.CorrelationChartMultiFactorSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, Err: %v", e)
+		return
+	}
+	// 此方法只加多因子图表
+	if req.AnalysisMode != 1 {
+		br.Msg = "分析模式有误"
+		br.ErrMsg = fmt.Sprintf("分析模式有误, Mode: %d", req.AnalysisMode)
+		return
+	}
+	if req.BaseEdbInfoId <= 0 {
+		br.Msg = "请选择标的指标"
+		return
+	}
+	if len(req.FactorCorrelation.SeriesIds) == 0 {
+		br.Msg = "请选择因子指标系列"
+		return
+	}
+	if len(req.FactorCorrelation.SeriesEdb) == 0 {
+		br.Msg = "请选择因子指标"
+		return
+	}
+	if req.FactorCorrelation.LeadValue <= 0 {
+		br.Msg = "分析周期不允许设置为负数或0"
+		return
+	}
+	if req.FactorCorrelation.LeadUnit == "" {
+		br.Msg = "请选择分析周期频度"
+		return
+	}
+	leadUnitDays, ok := utils.FrequencyDaysMap[req.FactorCorrelation.LeadUnit]
+	if !ok {
+		br.Msg = "错误的分析周期频度"
+		br.ErrMsg = fmt.Sprintf("分析周期频度有误: %s", req.FactorCorrelation.LeadUnit)
+		return
+	}
+	if req.FactorCorrelation.CalculateUnit == "" {
+		br.Msg = "请选择计算窗口频度"
+		return
+	}
+	calculateUnitDays, ok := utils.FrequencyDaysMap[req.FactorCorrelation.CalculateUnit]
+	if !ok {
+		br.Msg = "错误的计算窗口频度"
+		br.ErrMsg = fmt.Sprintf("计算窗口频度有误: %s", req.FactorCorrelation.CalculateUnit)
+		return
+	}
+	leadDays := 2 * req.FactorCorrelation.LeadValue * leadUnitDays
+	calculateDays := req.FactorCorrelation.CalculateValue * calculateUnitDays
+	if calculateDays < leadDays {
+		br.Msg = "计算窗口必须≥2*分析周期"
+		return
+	}
+	req.ChartName = strings.TrimSpace(req.ChartName)
+	if req.ChartName == "" {
+		br.Msg = "请输入图表名称"
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择图表分类"
+		return
+	}
+
+	cacheKey := "CACHE_CORRELATION_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中, 请稍后重试"
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
+	chartType := 9
+	chartSource := utils.CHART_SOURCE_CORRELATION
+	startDate := time.Now().AddDate(0, 0, -calculateDays).Format(utils.FormatDate)
+	endDate := time.Now().Format(utils.FormatDate)
+	mappingEdbIds := make([]int, 0) // 该图表关联的指标(去除系列中重复指标之后的)
+	{
+		exists := make(map[int]bool)
+		for _, v := range req.FactorCorrelation.SeriesEdb {
+			if !exists[v.EdbInfoId] {
+				mappingEdbIds = append(mappingEdbIds, v.EdbInfoId)
+			}
+		}
+	}
+
+	// 校验分类、图表名称
+	{
+		var cond string
+		var pars []interface{}
+		switch this.Lang {
+		case utils.EnLangVersion:
+			cond += " AND chart_name_en = ? AND source = ? "
+		default:
+			cond += " AND chart_name = ? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, chartSource)
+		count, e := data_manage.GetChartInfoCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取同名图表失败, Err: %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "图表名称已存在, 请重新填写"
+			return
+		}
+
+		_, e = data_manage.GetChartClassifyById(req.ClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", e)
+			return
+		}
+	}
+
+	// 图表来源、图例设置
+	var sourceFrom, extraConfig string
+	if req.SourcesFrom != nil {
+		b, e := json.Marshal(req.SourcesFrom)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+			return
+		}
+		sourceFrom = string(b)
+	}
+	if req.ExtraConfig != nil {
+		b, e := json.Marshal(req.ExtraConfig)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+			return
+		}
+		extraConfig = string(b)
+	}
+
+	// 图表信息
+	chartInfo := new(data_manage.ChartInfo)
+	chartInfo.ChartName = req.ChartName
+	chartInfo.ChartNameEn = req.ChartName
+	chartInfo.ChartClassifyId = req.ClassifyId
+	chartInfo.SysUserId = sysUser.AdminId
+	chartInfo.SysUserRealName = sysUser.RealName
+	chartInfo.CreateTime = time.Now()
+	chartInfo.ModifyTime = time.Now()
+	chartInfo.IsSetName = 0
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	chartInfo.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp)
+	chartInfo.ChartType = chartType
+	chartInfo.Calendar = "公历"
+	chartInfo.DateType = 6
+	chartInfo.StartDate = startDate
+	chartInfo.EndDate = endDate
+	chartInfo.SeasonStartDate = startDate
+	chartInfo.SeasonEndDate = endDate
+	chartInfo.Disabled = data.CheckIsDisableChart(mappingEdbIds)
+	chartInfo.Source = chartSource
+	chartInfo.ExtraConfig = extraConfig
+	chartInfo.SourcesFrom = sourceFrom
+
+	// 相关性图
+	chartCorrelate := new(data_manage.ChartInfoCorrelation)
+	chartCorrelate.LeadValue = req.FactorCorrelation.LeadValue
+	chartCorrelate.LeadUnit = req.FactorCorrelation.LeadUnit
+	chartCorrelate.CalculateValue = req.FactorCorrelation.CalculateValue
+	chartCorrelate.CalculateUnit = req.FactorCorrelation.CalculateUnit
+	chartCorrelate.EdbInfoIdFirst = req.BaseEdbInfoId
+	chartCorrelate.AnalysisMode = 1
+	chartCorrelate.CreateTime = time.Now().Local()
+	chartCorrelate.ModifyTime = time.Now().Local()
+
+	// 图表指标关联
+	edbMappings := make([]*data_manage.ChartEdbMapping, 0)
+	for _, v := range mappingEdbIds {
+		m := new(data_manage.ChartEdbMapping)
+		m.EdbInfoId = v
+		m.CreateTime = time.Now()
+		m.ModifyTime = time.Now()
+		edbTimestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		m.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + edbTimestamp + "_" + strconv.Itoa(v))
+		m.IsOrder = true
+		m.IsAxis = 1
+		m.EdbInfoType = 1
+		m.Source = utils.CHART_SOURCE_CORRELATION
+		edbMappings = append(edbMappings, m)
+	}
+
+	// 标记引用的指标
+	seriesIds := req.FactorCorrelation.SeriesIds
+	edbUsed := make(map[string]bool)
+	for _, v := range req.FactorCorrelation.SeriesEdb {
+		k := fmt.Sprintf("%d-%d", v.SeriesId, v.EdbInfoId)
+		edbUsed[k] = true
+	}
+
+	// 指标系列-图表关联
+	chartMappings := make([]*data_manage.FactorEdbSeriesChartMapping, 0)
+	{
+		ob := new(data_manage.FactorEdbSeriesChartMapping)
+		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, seriesIds)
+		items, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取指标系列图表关联失败, Err: %v", e)
+			return
+		}
+		chartMappings = items
+	}
+	for _, v := range chartMappings {
+		v.Source = chartSource
+		k := fmt.Sprintf("%d-%d", v.FactorEdbSeriesId, v.EdbInfoId)
+		v.EdbUsed = 0 // 先重置一下
+		if edbUsed[k] {
+			v.EdbUsed = 1
+		}
+		v.ModifyTime = time.Now().Local()
+	}
+
+	// 另存为
+	originSeries := make([]*data_manage.FactorEdbSeries, 0)
+	originEdbSeriesMapping := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	if req.SaveAs {
+		// 注意此处要复制一份系列及系列关联指标, 不可共用被复制图表的系列
+		{
+			ob := new(data_manage.FactorEdbSeries)
+			cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().PrimaryId, utils.GetOrmInReplace(len(seriesIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, seriesIds)
+			list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取源图表系列失败, %v", e)
+				return
+			}
+			originSeries = list
+		}
+		{
+			ob := new(data_manage.FactorEdbSeriesMapping)
+			cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, seriesIds)
+			list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取源图表系列指标失败, %v", e)
+				return
+			}
+			originEdbSeriesMapping = list
+		}
+	}
+
+	// 新增图表/相关性图表/图表指标关联/指标系列图表关联
+	chartInfoId, seriesIdMap, e := data_manage.CreateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, chartMappings, req.SaveAs, originSeries, originEdbSeriesMapping)
+	if e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("新增多因子相关性图表失败, Err: %v", e)
+		return
+	}
+
+	// 另存为-替换图例设置中的系列ID
+	if req.SaveAs {
+		if req.ExtraConfig != nil {
+			for _, v := range req.ExtraConfig.LegendConfig {
+				v.SeriesId = seriesIdMap[v.SeriesId]
+			}
+			b, e := json.Marshal(req.ExtraConfig)
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+				return
+			}
+			chartInfo.ExtraConfig = string(b)
+			if e = chartInfo.Update([]string{"ExtraConfig"}); e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("更新图例信息失败, %v", e)
+				return
+			}
+		}
+	}
+
+	go data.EsAddOrEditChartInfo(chartInfoId)
+
+	// 操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartInfo.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "新增多因子相关性图表"
+		chartLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartInfo.ChartInfoId
+	resp.UniqueCode = chartInfo.UniqueCode
+	resp.ChartType = chartInfo.ChartType
+	resp.ClassifyId = req.ClassifyId
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.IsAddLog = true
+}
+
+// MultiFactorEdit
+// @Title 多因子图表-编辑
+// @Description 多因子图表-编辑
+// @Param	request	body request.CorrelationChartMultiFactorSaveReq true "type json string"
+// @Success Ret=200 返回图表id
+// @router /chart_info/multi_factor/edit [post]
+func (this *CorrelationChartInfoController) MultiFactorEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.CorrelationChartMultiFactorSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, Err: %v", e)
+		return
+	}
+	if req.ChartInfoId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ChartInfoId: %d", req.ChartInfoId)
+		return
+	}
+	// 此方法只加多因子图表
+	if req.AnalysisMode != 1 {
+		br.Msg = "分析模式有误"
+		br.ErrMsg = fmt.Sprintf("分析模式有误, Mode: %d", req.AnalysisMode)
+		return
+	}
+	if req.BaseEdbInfoId <= 0 {
+		br.Msg = "请选择标的指标"
+		return
+	}
+	if len(req.FactorCorrelation.SeriesIds) == 0 {
+		br.Msg = "请选择因子指标系列"
+		return
+	}
+	if len(req.FactorCorrelation.SeriesEdb) == 0 {
+		br.Msg = "请选择因子指标"
+		return
+	}
+	if req.FactorCorrelation.LeadValue <= 0 {
+		br.Msg = "分析周期不允许设置为负数或0"
+		return
+	}
+	if req.FactorCorrelation.LeadUnit == "" {
+		br.Msg = "请选择分析周期频度"
+		return
+	}
+	leadUnitDays, ok := utils.FrequencyDaysMap[req.FactorCorrelation.LeadUnit]
+	if !ok {
+		br.Msg = "错误的分析周期频度"
+		br.ErrMsg = fmt.Sprintf("分析周期频度有误: %s", req.FactorCorrelation.LeadUnit)
+		return
+	}
+	if req.FactorCorrelation.CalculateUnit == "" {
+		br.Msg = "请选择计算窗口频度"
+		return
+	}
+	calculateUnitDays, ok := utils.FrequencyDaysMap[req.FactorCorrelation.CalculateUnit]
+	if !ok {
+		br.Msg = "错误的计算窗口频度"
+		br.ErrMsg = fmt.Sprintf("计算窗口频度有误: %s", req.FactorCorrelation.CalculateUnit)
+		return
+	}
+	leadDays := 2 * req.FactorCorrelation.LeadValue * leadUnitDays
+	calculateDays := req.FactorCorrelation.CalculateValue * calculateUnitDays
+	if calculateDays < leadDays {
+		br.Msg = "计算窗口必须≥2*分析周期"
+		return
+	}
+	req.ChartName = strings.TrimSpace(req.ChartName)
+	if req.ChartName == "" {
+		br.Msg = "请输入图表名称"
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择图表分类"
+		return
+	}
+
+	cacheKey := "CACHE_CORRELATION_CHART_INFO_EDIT_" + strconv.Itoa(sysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中, 请稍后重试"
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
+	chartSource := utils.CHART_SOURCE_CORRELATION
+	startDate := time.Now().AddDate(0, 0, -calculateDays).Format(utils.FormatDate)
+	endDate := time.Now().Format(utils.FormatDate)
+	mappingEdbIds := make([]int, 0) // 该图表关联的指标(去除系列中重复指标之后的)
+	{
+		exists := make(map[int]bool)
+		for _, v := range req.FactorCorrelation.SeriesEdb {
+			if !exists[v.EdbInfoId] {
+				mappingEdbIds = append(mappingEdbIds, v.EdbInfoId)
+			}
+		}
+	}
+
+	chartInfo, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("获取图表信息失败, Err: %v", e)
+		return
+	}
+	chartCorrelate := new(data_manage.ChartInfoCorrelation)
+	if e = chartCorrelate.GetItemById(chartInfo.ChartInfoId); e != nil {
+		br.Msg = "图表相关性信息不存在"
+		br.ErrMsg = fmt.Sprintf("获取图表相关性信息失败, Err: %v", e)
+		return
+	}
+
+	// 图表检验
+	{
+		if chartInfo.Source != chartSource {
+			br.Msg = "该图表不是相关性图表"
+			return
+		}
+
+		// 图表操作权限
+		authOk := data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+		if !authOk {
+			br.Msg = "没有该图表的操作权限"
+			return
+		}
+
+		// 图表名称、分类
+		var cond string
+		var pars []interface{}
+		switch this.Lang {
+		case utils.EnLangVersion:
+			cond += " AND chart_name_en = ? AND source = ? AND chart_info_id <> ? "
+		default:
+			cond += " AND chart_name = ? AND source = ? AND chart_info_id <> ? "
+		}
+		pars = append(pars, req.ChartName, chartSource, chartInfo.ChartInfoId)
+		count, e := data_manage.GetChartInfoCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取同名图表失败, Err: %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "图表名称已存在, 请重新填写"
+			return
+		}
+
+		_, e = data_manage.GetChartClassifyById(req.ClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取图表分类失败, Err: %v", e)
+			return
+		}
+	}
+
+	// 图表来源、图例设置
+	var sourceFrom, extraConfig string
+	if req.SourcesFrom != nil {
+		b, e := json.Marshal(req.SourcesFrom)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+			return
+		}
+		sourceFrom = string(b)
+	}
+	if req.ExtraConfig != nil {
+		b, e := json.Marshal(req.ExtraConfig)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+			return
+		}
+		extraConfig = string(b)
+	}
+
+	// 图表信息
+	chartInfo.ChartName = req.ChartName
+	chartInfo.ChartNameEn = req.ChartName
+	chartInfo.ChartClassifyId = req.ClassifyId
+	chartInfo.ModifyTime = time.Now()
+	chartInfo.StartDate = startDate
+	chartInfo.EndDate = endDate
+	chartInfo.SeasonStartDate = startDate
+	chartInfo.SeasonEndDate = endDate
+	chartInfo.ExtraConfig = extraConfig
+	chartInfo.SourcesFrom = sourceFrom
+	chartInfo.Disabled = data.CheckIsDisableChart(mappingEdbIds)
+	chartUpdateCols := []string{"ChartName", "ChartNameEn", "ChartClassifyId", "ModifyTime", "StartDate", "EndDate", "SeasonStartDate", "SeasonEndDate", "ExtraConfig", "SourcesFrom", "Disabled"}
+
+	// 相关性图
+	chartCorrelate.LeadValue = req.FactorCorrelation.LeadValue
+	chartCorrelate.LeadUnit = req.FactorCorrelation.LeadUnit
+	chartCorrelate.CalculateValue = req.FactorCorrelation.CalculateValue
+	chartCorrelate.CalculateUnit = req.FactorCorrelation.CalculateUnit
+	chartCorrelate.EdbInfoIdFirst = req.BaseEdbInfoId
+	chartCorrelate.ModifyTime = time.Now().Local()
+	correlateUpdateCols := []string{"LeadValue", "LeadUnit", "CalculateValue", "CalculateUnit", "EdbInfoIdFirst", "ModifyTime"}
+
+	// 图表指标关联
+	edbMappings := make([]*data_manage.ChartEdbMapping, 0)
+	for _, v := range mappingEdbIds {
+		m := new(data_manage.ChartEdbMapping)
+		m.EdbInfoId = v
+		m.CreateTime = time.Now()
+		m.ModifyTime = time.Now()
+		edbTimestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		m.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + edbTimestamp + "_" + strconv.Itoa(v))
+		m.IsOrder = true
+		m.IsAxis = 1
+		m.EdbInfoType = 1
+		m.Source = utils.CHART_SOURCE_CORRELATION
+		edbMappings = append(edbMappings, m)
+	}
+
+	seriesIds := req.FactorCorrelation.SeriesIds
+	edbUsed := make(map[string]bool)
+	for _, v := range req.FactorCorrelation.SeriesEdb {
+		k := fmt.Sprintf("%d-%d", v.SeriesId, v.EdbInfoId)
+		edbUsed[k] = true
+	}
+	// 指标系列-图表关联
+	chartMappings := make([]*data_manage.FactorEdbSeriesChartMapping, 0)
+	{
+		ob := new(data_manage.FactorEdbSeriesChartMapping)
+		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, seriesIds)
+		items, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("获取指标系列图表关联失败, Err: %v", e)
+			return
+		}
+		chartMappings = items
+	}
+	for _, v := range chartMappings {
+		k := fmt.Sprintf("%d-%d", v.FactorEdbSeriesId, v.EdbInfoId)
+		v.EdbUsed = 0
+		if edbUsed[k] {
+			v.EdbUsed = 1
+		}
+		v.ModifyTime = time.Now().Local()
+	}
+
+	// 更新图表/相关性图表/图表指标关联/指标系列图表关联
+	e = data_manage.UpdateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, chartMappings, chartUpdateCols, correlateUpdateCols)
+	if e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("新增多因子相关性图表失败, Err: %v", e)
+		return
+	}
+
+	// ES
+	go func() {
+		data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
+		data.EsAddOrEditMyChartInfoByChartInfoId(chartInfo.ChartInfoId)
+	}()
+
+	// 操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartInfo.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑多因子相关性图表"
+		chartLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartInfo.ChartInfoId
+	resp.UniqueCode = chartInfo.UniqueCode
+	resp.ChartType = chartInfo.ChartType
+	resp.ClassifyId = req.ClassifyId
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.IsAddLog = true
+}
+
+// MultiFactorDetail
+// @Title 多因子图表-编辑页详情
+// @Description 多因子图表-编辑页详情
+// @Param   UniqueCode  query  string  true  "图表唯一编码"
+// @Success Ret=200 返回图表id
+// @router /chart_info/multi_factor/detail [get]
+func (this *CorrelationChartInfoController) MultiFactorDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == "" {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, UniqueCode: %s", uniqueCode)
+		return
+	}
+
+	chartInfo, e := data_manage.GetChartInfoByUniqueCode(uniqueCode)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取图表信息失败, Err: %v", e)
+		return
+	}
+	chartCorrelate := new(data_manage.ChartInfoCorrelation)
+	if e = chartCorrelate.GetItemById(chartInfo.ChartInfoId); e != nil {
+		br.Msg = "图表相关性信息不存在"
+		br.ErrMsg = fmt.Sprintf("获取图表相关性信息失败, Err: %v", e)
+		return
+	}
+	if chartCorrelate.AnalysisMode != 1 {
+		br.Msg = "图表分析模式有误"
+		br.ErrMsg = fmt.Sprintf("图表分析模式有误, Err: %v", e)
+		return
+	}
+	if chartCorrelate.EdbInfoIdFirst <= 0 {
+		br.Msg = "标的指标异常"
+		br.ErrMsg = fmt.Sprintf("标的指标ID异常, Err: %v", e)
+		return
+	}
+	resp := new(data_manage.FactorCorrelationEditDetail)
+
+	// 标的指标
+	baseEdbMapping := new(data_manage.ChartEdbInfoMapping)
+	{
+		mappings, e := data_manage.GetChartEdbMappingListByEdbInfoIdList([]int{chartCorrelate.EdbInfoIdFirst})
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取标的指标信息异常, Err: %v", e)
+			return
+		}
+		if len(mappings) > 0 {
+			baseEdbMapping = mappings[0]
+		}
+	}
+
+	// 获取图表系列
+	chartMappings := make([]*data_manage.FactorEdbSeriesChartMapping, 0)
+	{
+		ob := new(data_manage.FactorEdbSeriesChartMapping)
+		cond := fmt.Sprintf(" AND %s = ?", ob.Cols().ChartInfoId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, chartInfo.ChartInfoId)
+		list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图表关联系列列表失败, Err: %v", e)
+			return
+		}
+		chartMappings = list
+	}
+	if len(chartMappings) == 0 {
+		br.Msg = "因子指标系列异常"
+		br.ErrMsg = fmt.Sprintf("因子指标系列引用异常")
+		return
+	}
+
+	seriesIds := make([]int, 0)
+	seriesIdExist := make(map[int]bool)
+	edbInfoIds := make([]int, 0)
+	for _, v := range chartMappings {
+		if !seriesIdExist[v.FactorEdbSeriesId] {
+			seriesIds = append(seriesIds, v.FactorEdbSeriesId)
+		}
+		edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+	}
+	if len(seriesIds) == 0 {
+		br.Msg = "因子指标系列异常"
+		br.ErrMsg = fmt.Sprintf("因子指标系列引用异常")
+		return
+	}
+
+	// 系列信息
+	seriesDetails := make([]*data_manage.FactorEdbSeriesDetail, 0)
+	{
+		seriesOb := new(data_manage.FactorEdbSeries)
+		cond := fmt.Sprintf(" AND %s IN (%s)", seriesOb.Cols().PrimaryId, utils.GetOrmInReplace(len(seriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, seriesIds)
+		list, e := seriesOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", seriesOb.Cols().PrimaryId))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取关联系列失败, Err: %v", e)
+			return
+		}
+		for _, v := range list {
+			seriesDetails = append(seriesDetails, &data_manage.FactorEdbSeriesDetail{
+				FactorEdbSeriesItem: v.Format2Item(),
+				EdbMappings:         make([]*data_manage.FactorEdbSeriesMappingItem, 0),
+			})
+		}
+	}
+
+	// 相关性配置
+	var correlateConf data_manage.CorrelationConfig
+	correlateConf.LeadUnit = chartCorrelate.LeadUnit
+	correlateConf.LeadValue = chartCorrelate.LeadValue
+	correlateConf.CalculateUnit = chartCorrelate.CalculateUnit
+	correlateConf.CalculateValue = chartCorrelate.CalculateValue
+
+	edbIdItem := make(map[int]*data_manage.EdbInfo)
+	edbItems, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取因子指标失败, Err: " + e.Error()
+		return
+	}
+	for _, v := range edbItems {
+		edbIdItem[v.EdbInfoId] = v
+	}
+
+	// 相关性矩阵
+	seriesEdb := make(map[int][]*data_manage.FactorEdbSeriesMappingItem)
+	var matrixItems []data_manage.FactorEdbSeriesCorrelationMatrixItem
+	for _, v := range chartMappings {
+		edbItem := edbIdItem[v.EdbInfoId]
+		if edbItem == nil {
+			continue
+		}
+
+		var item data_manage.FactorEdbSeriesCorrelationMatrixItem
+		item.SeriesId = v.FactorEdbSeriesId
+		item.EdbInfoId = edbItem.EdbInfoId
+		item.EdbCode = edbItem.EdbCode
+		item.EdbName = edbItem.EdbName
+		var values []data_manage.FactorEdbSeriesCorrelationMatrixValues
+		if v.CalculateData != "" {
+			if e = json.Unmarshal([]byte(v.CalculateData), &values); e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("解析因子指标计算结果失败, Err: %v", e)
+				return
+			}
+		}
+		sort.Sort(data_manage.FactorEdbSeriesCorrelationMatrixOrder(values))
+		item.Values = values
+		if v.EdbUsed == 1 {
+			item.Used = true
+		}
+		item.SourceName = edbItem.SourceName
+		matrixItems = append(matrixItems, item)
+
+		// 系列关联的指标详情
+		if seriesEdb[v.FactorEdbSeriesId] == nil {
+			seriesEdb[v.FactorEdbSeriesId] = make([]*data_manage.FactorEdbSeriesMappingItem, 0)
+		}
+		seriesEdb[v.FactorEdbSeriesId] = append(seriesEdb[v.FactorEdbSeriesId], &data_manage.FactorEdbSeriesMappingItem{
+			SeriesId:  v.FactorEdbSeriesId,
+			EdbInfoId: v.EdbInfoId,
+			EdbCode:   edbItem.EdbCode,
+			EdbName:   edbItem.EdbName,
+			EdbNameEn: edbItem.EdbNameEn,
+		})
+	}
+
+	// 系列关联的指标详情
+	for _, v := range seriesDetails {
+		v.EdbMappings = seriesEdb[v.SeriesId]
+	}
+
+	resp.ChartInfoId = chartInfo.ChartInfoId
+	resp.UniqueCode = chartInfo.UniqueCode
+	resp.BaseEdbInfo = baseEdbMapping
+	resp.EdbSeries = seriesDetails
+	resp.CorrelationConfig = correlateConf
+	resp.CorrelationMatrix = matrixItems
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 27 - 25
controllers/data_manage/cross_variety/chart_info.go

@@ -1525,6 +1525,29 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1594,33 +1617,12 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}

+ 90 - 10
controllers/data_manage/edb_info.go

@@ -2,7 +2,6 @@ package data_manage
 
 import (
 	"encoding/json"
-	"eta/eta_api/cache"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/company"
@@ -11,8 +10,8 @@ import (
 	request2 "eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/models/data_manage/request"
 	"eta/eta_api/models/data_manage/response"
-	"eta/eta_api/models/mgo"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
 	"eta/eta_api/services/data/data_manage_permission"
@@ -27,8 +26,6 @@ import (
 	"sync"
 	"time"
 
-	"go.mongodb.org/mongo-driver/bson"
-
 	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
@@ -1737,8 +1734,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				return
 			}
 
-			obj := mgo.BaseFromBusinessData{}
-			tmpDataList, err := obj.GetLimitDataList(bson.M{"index_code": edbCode}, utils.EDB_DATA_LIMIT, []string{"-data_time"})
+			_, tmpDataList, err := data.GetPageBaseBusinessIndexData(edbCode, 1, utils.EDB_DATA_LIMIT)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "获取失败"
 				br.ErrMsg = "获取自有数据已存在信息失败,Err:" + err.Error()
@@ -1747,7 +1743,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 			dataItems := make([]*data_manage.EdbInfoSearchData, 0)
 			for _, v := range tmpDataList {
 				dataItems = append(dataItems, &data_manage.EdbInfoSearchData{
-					DataTime: v.DataTime.Format(utils.FormatDate),
+					DataTime: v.DataTime,
 					Value:    v.Value,
 				})
 			}
@@ -3401,6 +3397,10 @@ func (this *EdbInfoController) EdbInfoFilterByEs() {
 			pars = append(pars, frequency)
 		}
 
+		if source > 0 && filterSource != 5 {
+			condition += ` AND source = ? `
+			pars = append(pars, source)
+		}
 		// 查询只允许添加预测指标的搜索
 		if isAddPredictEdb {
 			condition += ` AND frequency in ("日度","周度","月度") `
@@ -3910,8 +3910,6 @@ func (this *ChartInfoController) EdbInfoReplace() {
 		return
 	}
 
-	//加入到缓存队列中处理
-	go cache.AddReplaceEdbInfo(oldEdbInfo, newEdbInfo)
 	br.Msg = "替换成功"
 	br.ErrMsg = "替换成功"
 	br.Ret = 200
@@ -5794,11 +5792,14 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 			edbInfoItem.SourceName = "ths"
 			edbInfoItem.EdbCode = utils.ThsDs + v.StockCode + v.EdbCode
 		}
-		edbInfoItem.SubSource = 1
+		edbInfoItem.SubSource = utils.DATA_SUB_SOURCE_DATE
 		edbInfoItem.SubSourceName = "日期序列"
 		edbInfoItem.EdbName = v.EdbName
+		edbInfoItem.EdbNameSource = v.EdbName
+		edbInfoItem.EdbNameEn = v.EdbName
 		edbInfoItem.Frequency = v.Frequency
 		edbInfoItem.Unit = v.Unit
+		edbInfoItem.UnitEn = v.Unit
 		edbInfoItem.ClassifyId = v.ClassifyId
 		edbInfoItem.SysUserId = sysUser.AdminId
 		edbInfoItem.SysUserRealName = sysUser.RealName
@@ -6383,8 +6384,11 @@ func (this *EdbInfoController) SmmEdbInfoBatchAdd() {
 		edbInfoItem.SubSource = 0
 		edbInfoItem.SubSourceName = ""
 		edbInfoItem.EdbName = v.EdbName
+		edbInfoItem.EdbNameSource = v.EdbName
+		edbInfoItem.EdbNameEn = v.EdbName
 		edbInfoItem.Frequency = v.Frequency
 		edbInfoItem.Unit = v.Unit
+		edbInfoItem.UnitEn = v.Unit
 		edbInfoItem.ClassifyId = v.ClassifyId
 		edbInfoItem.SysUserId = sysUser.AdminId
 		edbInfoItem.SysUserRealName = sysUser.RealName
@@ -6432,3 +6436,79 @@ func (this *EdbInfoController) SmmEdbInfoBatchAdd() {
 	br.Data = resp
 	br.IsAddLog = true
 }
+
+// ChartImageSetBySvg
+// @Title 设置指标的图表图片
+// @Description 设置指标的图表图片接口
+// @Param	request	body data_manage.SetChartInfoImageReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /edb_info/image/set_by_svg [post]
+func (this *EdbInfoController) ChartImageSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	edbInfoId, _ := this.GetInt("EdbInfoId", 0)
+	if edbInfoId <= 0 {
+		br.Msg = "指标参数错误"
+		br.ErrMsg = "指标参数错误,EdbInfoId Is Empty"
+		return
+	}
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if edbInfoId <= 0 || resourceUrl == "" {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "保存成功"
+		return
+	}
+
+	edbInfo, e := data_manage.GetEdbInfoById(edbInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "找不到该指标"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = "获取指标失败, Err:" + e.Error()
+		return
+	}
+	edbInfo.ChartImage = resourceUrl
+	if e = edbInfo.Update([]string{"ChartImage"}); e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "更新指标缩略图失败, Err:" + e.Error()
+		return
+	}
+
+	// 修改es数据
+	go data.AddOrEditEdbInfoToEs(edbInfoId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}

+ 32 - 6
controllers/data_manage/edb_info_calculate.go

@@ -1471,7 +1471,7 @@ func (this *EdbInfoController) QueryEdbDataTable() {
 	tableName := data_manage.GetEdbDataTableName(edbInfo.Source, edbInfo.SubSource)
 
 	var templateStr string
-	if edbInfo.Source == utils.DATA_SOURCE_BUSINESS {
+	if edbInfo.Source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		templateStr = fmt.Sprintf("# 查询条件\nquery = {\"edb_code\": \"%s\"}\n# 排序\nsort = [(\"data_time\", -1)]  # -1 表示降序排列,对应 SQL 的 DESC\nprojection = {\"data_time\": 1, \"value\": 1, \"_id\": 0}  # 只选择data_time和value字段,忽略_id字段\n# 使用 find() 方法获取数据,然后使用 aggregate() 转换为列表\nraw_data = list(collection.find(query, projection).sort(sort))\n# 将结果转换为 DataFrame\nraw = pd.DataFrame(raw_data)\n# 转换data_time字段为本地时区时间\nraw['data_time'] = raw['data_time'].apply(lambda x: x.replace(tzinfo=utc_tz)).dt.tz_convert(local_tz).dt.strftime('%s')", edbInfo.EdbCode, "%Y-%m-%d")
 	} else {
 		templateStr = fmt.Sprintf("sql1 = f\"\"\"SELECT data_time,`value` FROM %s WHERE edb_code = '%s' ORDER BY data_time DESC;\"\"\"\nraw = pandas_fetch_all(sql1, db)", tableName, edbInfo.EdbCode)
@@ -2124,6 +2124,7 @@ func (this *ChartInfoController) CalculateComputeCorrelation() {
 // @Param   SysUserIds   query   int  true       "创建人"
 // @Param   Keyword   query   string  false       "关键词搜索"
 // @Param   Frequency   query   string  false       "频度"
+// @Param   EdbInfoType  query  int  false  "指标类型: 0-普通指标; 1-预测指标"
 // @Success 200 {object} data_manage.EdbInfoSearchResp
 // @router /edb_info/calculate/multi/choice [get]
 func (this *ChartInfoController) CalculateMultiChoice() {
@@ -2141,13 +2142,28 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 	edbInfoIds := this.GetString("EdbInfoIds")
 	selectAll, _ := this.GetBool("SelectAll")
 	notFrequency := this.GetString("NotFrequency")
+	edbInfoType, _ := this.GetInt("EdbInfoType", 0)
 	var edbIdArr []int
+	var filterEdbIds []int
+	if edbInfoIds != "" {
+		arr := strings.Split(edbInfoIds, ",")
+		for _, v := range arr {
+			t, e := strconv.Atoi(v)
+			if e != nil {
+				br.Msg = "参数有误"
+				br.ErrMsg = fmt.Sprintf("指标ID有误, %s", v)
+				return
+			}
+			filterEdbIds = append(filterEdbIds, t)
+		}
+	}
 
 	if selectAll {
 		// 如果勾了列表全选,那么EdbCode传的就是排除的code
 
-		var condition string
-		var pars []interface{}
+		condition := ` AND edb_info_type = ? `
+		pars := make([]interface{}, 0)
+		pars = append(pars, edbInfoType)
 
 		if classifyIds != "" {
 			classifyIdsArr := strings.Split(classifyIds, ",")
@@ -2182,14 +2198,19 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 				}
 			}
 		}
-		edbList, e := data_manage.GetEdbInfoFilter(condition, pars)
+		edbList, e := data_manage.GetEdbInfoListByCond(condition, pars)
 		if e != nil {
 			br.Msg = "获取指标列表失败"
 			br.ErrMsg = "获取指标列表失败,Err:" + e.Error()
 			return
 		}
 
+		filterLen := len(filterEdbIds)
 		for _, v := range edbList {
+			// 全选-排除传入的指标
+			if filterLen > 0 && utils.InArrayByInt(filterEdbIds, v.EdbInfoId) {
+				continue
+			}
 			edbIdArr = append(edbIdArr, v.EdbInfoId)
 		}
 	} else {
@@ -2270,6 +2291,7 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 				EdbInfoId:     info.EdbInfoId,
 				ClassifyId:    info.ClassifyId,
 				HaveOperaAuth: haveOperaAuth,
+				EdbInfoType:   info.EdbInfoType,
 			}
 			searchItemList = append(searchItemList, searchItem)
 		}
@@ -2290,6 +2312,7 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 // @Param   SysUserIds   query   int  true       "创建人"
 // @Param   Keyword   query   string  false       "关键词搜索"
 // @Param   Frequency   query   string  false       "频度"
+// @Param   EdbInfoType  query  int  false  "指标类型: 0-普通指标; 1-预测指标"
 // @Success 200 {object} data_manage.EdbInfoSearchResp
 // @router /edb_info/calculate/multi/search [get]
 func (this *ChartInfoController) CalculateMultiSearch() {
@@ -2308,6 +2331,7 @@ func (this *ChartInfoController) CalculateMultiSearch() {
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
 	notFrequency := this.GetString("NotFrequency")
+	edbInfoType, _ := this.GetInt("EdbInfoType", 0)
 
 	var startSize int
 	if pageSize <= 0 {
@@ -2320,8 +2344,9 @@ func (this *ChartInfoController) CalculateMultiSearch() {
 
 	var edbIdArr []int
 
-	condition := ` AND edb_info_type = 0 `
-	var pars []interface{}
+	condition := ` AND edb_info_type = ? `
+	pars := make([]interface{}, 0)
+	pars = append(pars, edbInfoType)
 
 	if classifyIds != "" {
 		classifyIdsArr := strings.Split(classifyIds, ",")
@@ -2429,6 +2454,7 @@ func (this *ChartInfoController) CalculateMultiSearch() {
 				EndDate:         info.EndDate,
 				EndValue:        info.EndValue,
 				HaveOperaAuth:   haveOperaAuth,
+				EdbInfoType:     info.EdbInfoType,
 			}
 			searchItemList = append(searchItemList, searchItem)
 		}

+ 232 - 2
controllers/data_manage/edb_info_refresh.go

@@ -474,7 +474,7 @@ func (c *EdbInfoController) SaveEdbRefreshStatus() {
 		br.IsSendEmail = false
 		return
 	}
-
+	// todo 批量设置刷新状态修改
 	edbIdList := make([]int, 0)
 	edbCodeList := make([]string, 0)
 	// 指标id列表
@@ -519,13 +519,243 @@ func (c *EdbInfoController) SaveEdbRefreshStatus() {
 		isStop = 1
 	}
 
+	if len(edbIdList) <= 0 {
+		br.Msg = "指标不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	//查询计算指标信息
+	calculateEdbIdList := make([]int, 0)
+	if isStop == 1 {
+		fromEdbIdList := make([]int, 0)
+		edbList, e := data_manage.GetEdbInfoByIdList(edbIdList)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + e.Error()
+			return
+		}
+		for _, v := range edbList {
+			if v.EdbInfoType == 0 {
+				fromEdbIdList = append(fromEdbIdList, v.EdbInfoId)
+			}
+		}
+
+		hasFind := make(map[int]struct{})
+		calculateEdbIdList, err = data.GetCalculateEdbByFromEdbInfo(fromEdbIdList, calculateEdbIdList, hasFind)
+		if err != nil {
+			br.Msg = "查询计算指标信息失败"
+			br.ErrMsg = "查询计算指标信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
 	switch req.Source {
 	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL: // 钢联
 		err = data_manage.ModifyMysteelChemicalUpdateStatus(edbIdList, edbCodeList, isStop)
 	case utils.DATA_SOURCE_YS: // 有色
 		err = data_manage.ModifySmmUpdateStatus(edbIdList, edbCodeList, isStop)
 	default:
-		err = data_manage.ModifyEdbInfoUpdateStatus(edbIdList, isStop)
+		err = data_manage.ModifyEdbInfoUpdateStatus(edbIdList, isStop, calculateEdbIdList)
+	}
+	if err != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// SaveEdbRefreshStatusSingle
+// @Title 设置单个指标刷新状态接口
+// @Description 设置单个指标刷新状态接口
+// @Param	request	body data_manage.SaveEdbRefreshStatusReq true "type json string"
+// @Success 200 {object} data_manage.RefreshBaseEdbInfoResp
+// @router /edb_info/single_refresh/status/save [post]
+func (c *EdbInfoController) SaveEdbRefreshStatusSingle() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	var req request.SaveEdbRefreshStatusSingleReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.EdbInfoId <= 0 {
+		br.Msg = "请选择指标"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 查询指标
+	edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "指标不存在"
+			return
+		}
+		br.Msg = "查询指标失败"
+		br.ErrMsg = "查询指标失败,Err:" + err.Error()
+		return
+	}
+
+	// 查询指标的指标代码
+	calculateEdbIdList := make([]int, 0)
+
+	isStop := 0
+	if req.ModifyStatus == `暂停` {
+		isStop = 1
+	}
+
+	if isStop == 1 && edbInfo.EdbInfoType == 0 { //基础指标, 只有在停用的情况下才需要查询计算指标
+		hasFind := make(map[int]struct{})
+		calculateEdbIdList, err = data.GetCalculateEdbByFromEdbInfo([]int{edbInfo.EdbInfoId}, calculateEdbIdList, hasFind)
+		if err != nil {
+			br.Msg = "查询计算指标信息失败"
+			br.ErrMsg = "查询计算指标信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	switch edbInfo.Source {
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL: // 钢联
+		err = data_manage.ModifyMysteelChemicalUpdateStatusByEdbInfoId(edbInfo.EdbInfoId, isStop, edbInfo.EdbCode, calculateEdbIdList)
+	default: // wind
+		err = data_manage.EdbInfoUpdateStatusByEdbInfoId([]int{edbInfo.EdbInfoId}, isStop, calculateEdbIdList)
+	}
+	if err != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// SaveRelationEdbRefreshStatus
+// @Title 批量设置被引用的指标刷新状态接口
+// @Description 批量设置被引用的指标刷新状态接口
+// @Param	request	body data_manage.SaveEdbRefreshStatusReq true "type json string"
+// @Success 200 {object} data_manage.RefreshBaseEdbInfoResp
+// @router /edb_info/relation/refresh/save [post]
+func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	var req request.SaveRelationEdbRefreshStatusReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.Source <= 0 {
+		br.Msg = "来源不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	edbType := 1 //基础指标
+	switch req.Source {
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL, utils.DATA_SOURCE_WIND: // wind
+	case -1:
+		req.Source = 0
+		edbType = 2
+	default:
+		br.Msg = "暂不支持设置其他来源的指标"
+		return
+	}
+	// todo 批量设置刷新状态修改
+	edbIdList := make([]int, 0)
+	edbCodeList := make([]string, 0)
+	// 指标id列表
+	if req.IsSelectAll {
+		// 如果是列表全选
+		_, edbList, err := data.GetEdbRelationList(req.Source, edbType, req.ClassifyId, req.SysUserId, req.Frequency, req.Keyword, req.Status, 0, 100000, "", "")
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+
+		// 不配置的指标id
+		notIdMap := make(map[int]int, 0)
+		for _, v := range req.EdbSelectIdList {
+			notIdMap[v] = v
+		}
+
+		for _, v := range edbList {
+			_, ok := notIdMap[v.EdbInfoId]
+			// 在不配置的指标id列表内的话,那就过滤
+			if ok {
+				continue
+			}
+
+			// 加入到待配置的指标列表id
+			edbIdList = append(edbIdList, v.EdbInfoId)
+		}
+	} else {
+		edbIdList = req.EdbSelectIdList
+	}
+
+	if len(edbIdList) <= 0 {
+		br.Msg = "指标不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	//查询指标信息
+	edbList, e := data_manage.GetEdbInfoByIdList(edbIdList)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + e.Error()
+		return
+	}
+	fromEdbIdList := make([]int, 0)
+	for _, v := range edbList {
+		if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			edbCodeList = append(edbCodeList, v.EdbCode)
+		}
+		if v.EdbInfoType == 0 {
+			fromEdbIdList = append(fromEdbIdList, v.EdbInfoId)
+		}
+	}
+
+	isStop := 0
+	if req.ModifyStatus == `暂停` {
+		isStop = 1
+	}
+
+	// 查询计算指标ID
+	// 查询相关的计算指标
+	calculateEdbIdList := make([]int, 0)
+	if isStop == 1 {
+		hasFind := make(map[int]struct{})
+		calculateEdbIdList, err = data.GetCalculateEdbByFromEdbInfo(fromEdbIdList, calculateEdbIdList, hasFind)
+		if err != nil {
+			br.Msg = "查询计算指标信息失败"
+			br.ErrMsg = "查询计算指标信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	switch req.Source {
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL: // 钢联化工
+		err = data_manage.ModifyMysteelChemicalUpdateStatusByEdbInfoIds(edbIdList, isStop, edbCodeList, calculateEdbIdList)
+	default:
+		err = data_manage.EdbInfoUpdateStatusByEdbInfoId(edbIdList, isStop, calculateEdbIdList)
 	}
 	if err != nil {
 		br.Msg = `保存失败`

+ 298 - 0
controllers/data_manage/edb_info_relation.go

@@ -0,0 +1,298 @@
+package data_manage
+
+import (
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/fe_calendar"
+	"eta/eta_api/models/sandbox"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// EdbInfoRelationController 指标引用管理
+type EdbInfoRelationController struct {
+	controllers.BaseAuthController
+}
+
+// RelationEdbList
+// @Title 获取被引用的指标列表接口
+// @Description 获取被引用的指标列表接口
+// @Param   Source   query   int  true       "来源:2:wind,34:钢联化工"
+// @Param   ClassifyId   query   string  false             "分类ID,支持多选,用英文,隔开"
+// @Param   SysUserId   query   string  false       "创建人,支持多选,用英文,隔开"
+// @Param   Frequency   query   string  false       "频度,支持多选,用英文,隔开"
+// @Param   Keyword   query   string  false       "关键词"
+// @Param   SortParam   query   string  false       "排序字段参数,用来排序的字段, 枚举值:'RelationTime':引用日期 'RelationNum' 引用次数"
+// @Param   SortType   query   string  true       "如何排序,是正序还是倒序,枚举值:`asc 正序`,`desc 倒叙`"
+// @Success 200 {object} data_manage.BaseRelationEdbInfoResp
+// @router /edb_info/relation/edb_list [get]
+func (c *EdbInfoRelationController) RelationEdbList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	source, _ := c.GetInt("Source")
+	classifyId := c.GetString("ClassifyId")
+	sysUserId := c.GetString("SysUserId")
+	frequency := c.GetString("Frequency")
+	keyword := c.GetString("Keyword")
+	status := c.GetString("Status")
+
+	sortParam := c.GetString("SortParam")
+	sortType := c.GetString("SortType")
+
+	switch sortParam {
+	case "RelationTime":
+		sortParam = " r.relation_time "
+	case "RelationNum":
+		sortParam = " r.relation_num "
+	default:
+		sortParam = " r.relation_time "
+	}
+	switch sortType {
+	case "desc", "asc":
+	default:
+		sortType = "desc"
+	}
+
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	edbType := 1      //基础指标
+	if source == -1 { //计算指标
+		edbType = 2
+		source = 0
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	total, list, err := data.GetEdbRelationList(source, edbType, classifyId, sysUserId, frequency, keyword, status, startSize, pageSize, sortParam, sortType)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resp := data_manage.BaseRelationEdbInfoResp{
+		Paging: page,
+		List:   list,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// RelationEdbListDetail
+// @Title 获取引用详情接口
+// @Description 获取引用详情接口
+// @Param   EdbInfoId   query   int  true       "指标ID"
+// @Success 200 {object} data_manage.RefreshBaseEdbInfoResp
+// @router /edb_info/relation/detail [get]
+func (c *EdbInfoRelationController) RelationEdbListDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	edbInfoId, _ := c.GetInt("EdbInfoId")
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	total, relationList, err := data_manage.GetEdbInfoRelationDetailList(edbInfoId, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	list := make([]*data_manage.EdbInfoRelationDetail, 0)
+
+	// 查询逻辑图
+	sandboxIds := make([]int, 0)
+	// 查询图库
+	chartInfoIds := make([]int, 0)
+	// 查询表格
+	tableInfoIds := make([]int, 0)
+	// 查询事件日历
+	eventInfoIds := make([]int, 0)
+
+	for _, v := range relationList {
+		switch v.ReferObjectType {
+		case utils.EDB_RELATION_SANDBOX:
+			sandboxIds = append(sandboxIds, v.ReferObjectId)
+		case utils.EDB_RELATION_CALENDAR:
+			eventInfoIds = append(eventInfoIds, v.ReferObjectId)
+		case utils.EDB_RELATION_CHART:
+			chartInfoIds = append(chartInfoIds, v.ReferObjectId)
+		case utils.EDB_RELATION_TABLE:
+			tableInfoIds = append(tableInfoIds, v.ReferObjectId)
+		}
+	}
+	objectNameMap := make(map[int]string)
+	if len(sandboxIds) > 0 {
+		sandboxList, err := sandbox.GetSandboxNameByIds(sandboxIds)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range sandboxList {
+			objectNameMap[v.SandboxId] = v.Name
+		}
+	}
+
+	// 查询事件日历
+	if len(eventInfoIds) > 0 {
+		matterOb := new(fe_calendar.FeCalendarMatter)
+		cond := " AND fe_calendar_matter_id in (" + utils.GetOrmInReplace(len(eventInfoIds)) + ")"
+		pars := make([]interface{}, 0)
+		pars = append(pars, eventInfoIds)
+		eventList, err := matterOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取事件日历数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range eventList {
+			objectNameMap[v.FeCalendarMatterId] = fmt.Sprintf("%s, %s", v.MatterDate, v.ChartPermissionName)
+		}
+	}
+
+	// 查询图表
+	if len(chartInfoIds) > 0 {
+		chartList, err := data_manage.GetChartInfoByIdList(chartInfoIds)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range chartList {
+			objectNameMap[v.ChartInfoId] = v.ChartName
+		}
+	}
+
+	// 查询表格名称
+	if len(tableInfoIds) > 0 {
+		cond := " AND excel_info_id in (" + utils.GetOrmInReplace(len(tableInfoIds)) + ")"
+		pars := make([]interface{}, 0)
+		pars = append(pars, tableInfoIds)
+		tableList, err := excel.GetNoContentExcelInfoListByConditionNoPage(cond, pars)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取表格数据失败,Err:" + err.Error()
+			return
+		}
+		// 查询父级名称
+		excelParentName := make(map[int]string)
+		parentIds := make([]int, 0)
+		for _, v := range tableList {
+			if v.ParentId > 0 {
+				parentIds = append(parentIds, v.ParentId)
+			}
+		}
+		if len(parentIds) > 0 {
+			cond = " AND excel_info_id in (" + utils.GetOrmInReplace(len(parentIds)) + ")"
+			pars = make([]interface{}, 0)
+			pars = append(pars, parentIds)
+			parentList, err := excel.GetNoContentExcelInfoListByConditionNoPage(cond, pars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取表格数据失败,Err:" + err.Error()
+				return
+			}
+			for _, v := range parentList {
+				excelParentName[v.ExcelInfoId] = v.ExcelName
+			}
+		}
+		for _, v := range tableList {
+			if v.ParentId > 0 {
+				parentName := excelParentName[v.ParentId]
+				objectNameMap[v.ExcelInfoId] = fmt.Sprintf("%s_%s", parentName, v.ExcelName)
+			} else {
+				objectNameMap[v.ExcelInfoId] = v.ExcelName
+			}
+		}
+	}
+	for _, v := range relationList {
+		referObjectName, _ := objectNameMap[v.ReferObjectId]
+		tmp := &data_manage.EdbInfoRelationDetail{
+			EdbInfoRelationId:  v.EdbInfoRelationId,
+			EdbInfoId:          v.EdbInfoId,
+			ReferObjectId:      v.ReferObjectId,
+			ReferObjectType:    v.ReferObjectType,
+			ReferObjectSubType: v.ReferObjectSubType,
+			RelationTime:       v.RelationTime.Format(utils.FormatDateTime),
+			ReferObjectName:    referObjectName,
+		}
+		switch v.ReferObjectType {
+		case utils.EDB_RELATION_SANDBOX:
+			tmp.ReferObjectTypeName = "逻辑图"
+		case utils.EDB_RELATION_CALENDAR:
+			tmp.ReferObjectTypeName = "事件日历"
+		case utils.EDB_RELATION_CHART:
+			switch v.ReferObjectSubType {
+			case utils.CHART_SOURCE_DEFAULT:
+				tmp.ReferObjectTypeName = "图库"
+			case utils.CHART_SOURCE_CORRELATION, utils.CHART_SOURCE_ROLLING_CORRELATION:
+				tmp.ReferObjectTypeName = "相关性分析"
+			case utils.CHART_SOURCE_LINE_EQUATION:
+				tmp.ReferObjectTypeName = "拟合方程曲线"
+			case utils.CHART_SOURCE_LINE_FEATURE_STANDARD_DEVIATION, utils.CHART_SOURCE_LINE_FEATURE_PERCENTILE, utils.CHART_SOURCE_LINE_FEATURE_FREQUENCY:
+				tmp.ReferObjectTypeName = "统计特征"
+			case utils.CHART_SOURCE_CROSS_HEDGING:
+				tmp.ReferObjectTypeName = "跨品种分析"
+			case utils.CHART_SOURCE_FUTURE_GOOD, utils.CHART_SOURCE_FUTURE_GOOD_PROFIT:
+				tmp.ReferObjectTypeName = "商品价格曲线"
+			}
+		case utils.EDB_RELATION_TABLE:
+			switch v.ReferObjectSubType {
+			case utils.TIME_TABLE:
+				tmp.ReferObjectTypeName = "时间序列表格"
+			case utils.MIXED_TABLE:
+				tmp.ReferObjectTypeName = "混合表格"
+			case utils.BALANCE_TABLE:
+				tmp.ReferObjectTypeName = "平衡表"
+			}
+		}
+		list = append(list, tmp)
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resp := data_manage.BaseRelationEdbInfoDetailResp{
+		Paging: page,
+		List:   list,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 1 - 1
controllers/data_manage/excel/balance_table.go

@@ -1413,7 +1413,7 @@ func downloadBalanceTable(excelInfo *excel.ExcelInfo, lang string) (savePath, zi
 				errMsg = msg
 				return
 			}
-			tableData, er := excel2.GetTableDataByMixedTableData(newResult)
+			tableData, er := excel2.GetTableDataByMixedTableData(newResult, false)
 			if er != nil {
 				errMsg = "获取失败"
 				err = fmt.Errorf("转换成table失败,Err:" + err.Error())

+ 8 - 3
controllers/data_manage/excel/excel_info.go

@@ -308,6 +308,8 @@ func (c *ExcelInfoController) Add() {
 	resp.ExcelInfoId = excelInfo.ExcelInfoId
 	resp.UniqueCode = excelInfo.UniqueCode
 
+	//新增指标引用记录
+	_ = data.SaveExcelEdbInfoRelation(excelInfo.ExcelInfoId, excelInfo.Source, false)
 	//新增操作日志
 	//{
 	//	excelLog := &data_manage.ExcelInfoLog{
@@ -1064,7 +1066,8 @@ func (c *ExcelInfoController) Edit() {
 		ExcelInfoId: excelInfo.ExcelInfoId,
 		UniqueCode:  excelInfo.UniqueCode,
 	}
-
+	//新增指标引用记录
+	_ = data.SaveExcelEdbInfoRelation(excelInfo.ExcelInfoId, excelInfo.Source, false)
 	//删除公共图库那边的缓存
 	_ = utils.Rc.Delete(utils.HZ_CHART_LIB_EXCEL_TABLE_DETAIL + ":" + excelInfo.UniqueCode)
 
@@ -1606,7 +1609,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, true)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
@@ -2464,7 +2467,7 @@ func (c *ExcelInfoController) Download() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, false)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
@@ -2589,6 +2592,8 @@ func (c *ExcelInfoController) Copy() {
 		br.IsSendEmail = isSendEmail
 		return
 	}
+	//新增指标引用记录
+	_ = data.SaveExcelEdbInfoRelation(excelInfo.ExcelInfoId, excelInfo.Source, true)
 
 	resp := new(response.AddExcelInfoResp)
 	resp.ExcelInfoId = excelInfo.ExcelInfoId

+ 816 - 0
controllers/data_manage/factor_edb_series.go

@@ -0,0 +1,816 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/request"
+	"eta/eta_api/services/data"
+	correlationServ "eta/eta_api/services/data/correlation"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+// FactorEdbSeriesController 因子指标系列
+type FactorEdbSeriesController struct {
+	controllers.BaseAuthController
+}
+
+// CalculateFuncList
+// @Title 计算方式列表
+// @Description 计算方式列表
+// @Param   EdbInfoType  query  int  false "指标计算类型: 0-普通指标; 1-预测指标"
+// @Success Ret=200 操作成功
+// @router /factor_edb_series/calculate_func/list [get]
+func (this *FactorEdbSeriesController) CalculateFuncList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	edbInfoType, _ := this.GetInt("EdbInfoType", 0)
+
+	funcOb := new(data_manage.FactorEdbSeriesCalculateFunc)
+	cond := fmt.Sprintf(` AND %s = ?`, funcOb.Cols().EdbInfoType)
+	pars := make([]interface{}, 0)
+	pars = append(pars, edbInfoType)
+	list, e := funcOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", funcOb.Cols().PrimaryId))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取计算方式列表失败, Err: %v", e)
+		return
+	}
+	resp := make([]*data_manage.FactorEdbSeriesCalculateFuncItem, 0)
+	for _, v := range list {
+		resp = append(resp, v.Format2Item())
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增
+// @Description 新增
+// @Param	request	body request.AddFactorEdbSeriesReq true "type json string"
+// @Success Ret=200 操作成功
+// @router /factor_edb_series/add [post]
+func (this *FactorEdbSeriesController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.AddFactorEdbSeriesReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, Err: %v", e)
+		return
+	}
+	req.SeriesName = strings.TrimSpace(req.SeriesName)
+	if req.SeriesName == "" {
+		br.Msg = "请输入指标系列名称"
+		return
+	}
+	if len(req.EdbInfoIds) <= 0 {
+		br.Msg = "请选择因子指标系列"
+		return
+	}
+	if len(req.EdbInfoIds) > 100 {
+		br.Msg = "添加指标总数量不得超过100"
+		return
+	}
+	calculateLen := len(req.Calculates)
+	if calculateLen > 5 {
+		br.Msg = "计算公式不可超过5个"
+		return
+	}
+	var calculatesJson string
+	if calculateLen > 0 {
+		b, e := json.Marshal(req.Calculates)
+		if e != nil {
+			br.Msg = "计算方式格式有误"
+			br.ErrMsg = "解析计算方式参数失败, Err: " + e.Error()
+			return
+		}
+		calculatesJson = string(b)
+		for _, v := range req.Calculates {
+			switch v.Source {
+			case utils.EdbBaseCalculateNszydpjjs, utils.EdbBaseCalculateHbz, utils.EdbBaseCalculateHcz, utils.EdbBaseCalculateCjjx:
+				if v.Formula == nil {
+					br.Msg = "请输入N值"
+					return
+				}
+				formula, ok := v.Formula.(string)
+				if !ok {
+					br.Msg = "N值格式有误"
+					return
+				}
+				formulaInt, _ := strconv.Atoi(formula)
+				if formulaInt <= 0 {
+					br.Msg = "N值不可小于0, 重新输入"
+					return
+				}
+			case utils.EdbBaseCalculateExponentialSmoothing:
+				if v.Formula == nil {
+					br.Msg = "请填写alpha值"
+					return
+				}
+				formula, ok := v.Formula.(string)
+				if !ok {
+					br.Msg = "alpha值格式有误"
+					return
+				}
+				alpha, _ := strconv.ParseFloat(formula, 64)
+				if alpha <= 0 || alpha >= 1 {
+					br.Msg = "alpha值应在0-1之间, 请重新输入"
+					return
+				}
+			}
+		}
+	}
+	edbArr, e := data_manage.GetEdbInfoByIdList(req.EdbInfoIds)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取指标列表失败, Err: " + e.Error()
+		return
+	}
+	if len(edbArr) == 0 {
+		br.Msg = "因子指标系列有误"
+		br.ErrMsg = "因子指标系列长度为0"
+		return
+	}
+
+	// 新增指标系列
+	seriesItem := new(data_manage.FactorEdbSeries)
+	seriesItem.SeriesName = req.SeriesName
+	seriesItem.EdbInfoType = req.EdbInfoType
+	seriesItem.CreateTime = time.Now().Local()
+	seriesItem.ModifyTime = time.Now().Local()
+	if calculateLen > 0 {
+		seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculating
+		seriesItem.CalculateStep = calculatesJson
+	}
+	mappings := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	for _, v := range edbArr {
+		mappings = append(mappings, &data_manage.FactorEdbSeriesMapping{
+			EdbInfoId:  v.EdbInfoId,
+			EdbCode:    v.EdbCode,
+			CreateTime: time.Now().Local(),
+			ModifyTime: time.Now().Local(),
+		})
+	}
+	seriesId, e := seriesItem.CreateSeriesAndMapping(seriesItem, mappings)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增因子指标系列失败, Err: " + e.Error()
+		return
+	}
+
+	// 计算指标数据
+	var calculateResp data_manage.FactorEdbSeriesStepCalculateResp
+	if calculateLen > 0 {
+		calculateResp, e = data.FactorEdbStepCalculate(seriesId, edbArr, req.Calculates, this.Lang, false)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "计算因子指标失败, Err: " + e.Error()
+			return
+		}
+
+		// 更新系列计算状态
+		cols := []string{seriesItem.Cols().CalculateState, seriesItem.Cols().ModifyTime}
+		seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculated
+		seriesItem.ModifyTime = time.Now().Local()
+		if e = seriesItem.Update(cols); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新因子指标系列计算状态失败, Err: " + e.Error()
+			return
+		}
+	} else {
+		for _, v := range edbArr {
+			calculateResp.Success = append(calculateResp.Success, data_manage.FactorEdbSeriesStepCalculateResult{
+				EdbInfoId: v.EdbInfoId,
+				EdbCode:   v.EdbCode,
+				Msg:       "保存成功",
+			})
+		}
+	}
+	calculateResp.SeriesId = seriesId
+
+	br.Data = calculateResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.IsAddLog = true
+}
+
+// Edit
+// @Title 编辑
+// @Description 编辑
+// @Param	request	body request.EditFactorEdbSeriesReq true "type json string"
+// @Success Ret=200 操作成功
+// @router /factor_edb_series/edit [post]
+func (this *FactorEdbSeriesController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.EditFactorEdbSeriesReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, Err: %v", e)
+		return
+	}
+	if req.SeriesId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SeriesId: %d", req.SeriesId)
+		return
+	}
+	req.SeriesName = strings.TrimSpace(req.SeriesName)
+	if req.SeriesName == "" {
+		br.Msg = "请输入指标系列名称"
+		return
+	}
+	if len(req.EdbInfoIds) <= 0 {
+		br.Msg = "请选择因子指标系列"
+		return
+	}
+	if len(req.EdbInfoIds) > 100 {
+		br.Msg = "添加指标总数量不得超过100"
+		return
+	}
+	calculateLen := len(req.Calculates)
+	if calculateLen > 5 {
+		br.Msg = "计算公式不可超过5个"
+		return
+	}
+	var calculatesJson string
+	if calculateLen > 0 {
+		b, e := json.Marshal(req.Calculates)
+		if e != nil {
+			br.Msg = "计算方式格式有误"
+			br.ErrMsg = "解析计算方式参数失败, Err: " + e.Error()
+			return
+		}
+		calculatesJson = string(b)
+		for _, v := range req.Calculates {
+			switch v.Source {
+			case utils.EdbBaseCalculateNszydpjjs, utils.EdbBaseCalculateHbz, utils.EdbBaseCalculateHcz, utils.EdbBaseCalculateCjjx:
+				if v.Formula == nil {
+					br.Msg = "请输入N值"
+					return
+				}
+				formula, ok := v.Formula.(string)
+				if !ok {
+					br.Msg = "N值格式有误"
+					return
+				}
+				formulaInt, _ := strconv.Atoi(formula)
+				if formulaInt <= 0 {
+					br.Msg = "N值不可小于0, 重新输入"
+					return
+				}
+			case utils.EdbBaseCalculateExponentialSmoothing:
+				if v.Formula == nil {
+					br.Msg = "请填写alpha值"
+					return
+				}
+				formula, ok := v.Formula.(string)
+				if !ok {
+					br.Msg = "alpha值格式有误"
+					return
+				}
+				alpha, _ := strconv.ParseFloat(formula, 64)
+				if alpha <= 0 || alpha >= 1 {
+					br.Msg = "alpha值应在0-1之间, 请重新输入"
+					return
+				}
+			}
+		}
+	}
+	seriesOb := new(data_manage.FactorEdbSeries)
+	seriesItem, e := seriesOb.GetItemById(req.SeriesId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "该因子指标系列不存在"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取因子指标系列失败, Err: " + e.Error()
+		return
+	}
+
+	edbArr, e := data_manage.GetEdbInfoByIdList(req.EdbInfoIds)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取指标列表失败, Err: " + e.Error()
+		return
+	}
+	if len(edbArr) == 0 {
+		br.Msg = "因子指标系列有误"
+		br.ErrMsg = "因子指标系列长度为0"
+		return
+	}
+	var calculateResp data_manage.FactorEdbSeriesStepCalculateResp
+	calculateResp.SeriesId = seriesItem.FactorEdbSeriesId
+
+	// 如果不需要进行重新计算(比如只改了系列名称)那么只更新指标系列
+	seriesItem.SeriesName = req.SeriesName
+	seriesItem.EdbInfoType = req.EdbInfoType
+	seriesItem.ModifyTime = time.Now().Local()
+	updateCols := []string{seriesOb.Cols().SeriesName, seriesOb.Cols().EdbInfoType, seriesOb.Cols().ModifyTime}
+	if !req.Recalculate {
+		if e = seriesItem.Update(updateCols); e != nil {
+			br.Msg = "操作成功"
+			br.ErrMsg = "更新因子指标系列信息失败, Err: " + e.Error()
+			return
+		}
+		for _, v := range edbArr {
+			calculateResp.Success = append(calculateResp.Success, data_manage.FactorEdbSeriesStepCalculateResult{
+				EdbInfoId: v.EdbInfoId,
+				EdbCode:   v.EdbCode,
+				Msg:       "保存成功",
+			})
+		}
+		br.Data = calculateResp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 更新系列信息和指标关联
+	if calculateLen > 0 {
+		seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculating
+		seriesItem.CalculateStep = calculatesJson
+		updateCols = append(updateCols, seriesOb.Cols().CalculateState, seriesOb.Cols().CalculateStep)
+	}
+	mappings := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	for _, v := range edbArr {
+		mappings = append(mappings, &data_manage.FactorEdbSeriesMapping{
+			EdbInfoId:  v.EdbInfoId,
+			EdbCode:    v.EdbCode,
+			CreateTime: time.Now().Local(),
+			ModifyTime: time.Now().Local(),
+		})
+	}
+	if e = seriesItem.EditSeriesAndMapping(seriesItem, mappings, updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "编辑因子指标系列失败, Err: " + e.Error()
+		return
+	}
+
+	// 重新计算
+	calculateResp, e = data.FactorEdbStepCalculate(seriesItem.FactorEdbSeriesId, edbArr, req.Calculates, this.Lang, req.Recalculate)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "计算因子指标失败, Err: " + e.Error()
+		return
+	}
+
+	// 更新系列计算状态
+	cols := []string{seriesItem.Cols().CalculateState, seriesItem.Cols().ModifyTime}
+	seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculated
+	seriesItem.ModifyTime = time.Now().Local()
+	if e = seriesItem.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新因子指标系列计算状态失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = calculateResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	br.IsAddLog = true
+}
+
+// Detail
+// @Title 详情
+// @Description 详情
+// @Param   SeriesId  query  int  false  "多因子指标系列ID"
+// @Success 200 {object} data_manage.FactorEdbSeriesDetail
+// @router /factor_edb_series/detail [get]
+func (this *FactorEdbSeriesController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	seriesId, _ := this.GetInt("SeriesId", 0)
+	if seriesId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SeriesId: %d", seriesId)
+		return
+	}
+
+	seriesOb := new(data_manage.FactorEdbSeries)
+	series, e := seriesOb.GetItemById(seriesId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "该因子指标系列不存在"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取因子指标系列失败, Err: " + e.Error()
+		return
+	}
+
+	mappingOb := new(data_manage.FactorEdbSeriesMapping)
+	cond := fmt.Sprintf(" AND %s = ?", mappingOb.Cols().FactorEdbSeriesId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, seriesId)
+	mappings, e := mappingOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", mappingOb.Cols().CreateTime))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取因子指标系列关联失败, Err: " + e.Error()
+		return
+	}
+
+	resp := new(data_manage.FactorEdbSeriesDetail)
+	resp.FactorEdbSeriesItem = series.Format2Item()
+	for _, m := range mappings {
+		resp.EdbMappings = append(resp.EdbMappings, m.Format2Item())
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// CorrelationMatrix
+// @Title 因子指标系列-相关性矩阵
+// @Description 因子指标系列-相关性矩阵
+// @Param	request	body request.FactorEdbSeriesCorrelationMatrixReq true "type json string"
+// @Success 200 {object} data_manage.FactorEdbSeriesCorrelationMatrixItem
+// @router /factor_edb_series/correlation/matrix [post]
+func (this *FactorEdbSeriesController) CorrelationMatrix() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.FactorEdbSeriesCorrelationMatrixReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, Err: %v", e)
+		return
+	}
+	if req.BaseEdbInfoId <= 0 {
+		br.Msg = "请选择标的指标"
+		return
+	}
+	if len(req.SeriesIds) == 0 {
+		br.Msg = "请选择因子指标系列"
+		return
+	}
+	if req.Correlation.LeadValue <= 0 {
+		br.Msg = "分析周期不允许设置为负数或0"
+		return
+	}
+	if req.Correlation.LeadUnit == "" {
+		br.Msg = "请选择分析周期频度"
+		return
+	}
+	leadUnitDays, ok := utils.FrequencyDaysMap[req.Correlation.LeadUnit]
+	if !ok {
+		br.Msg = "错误的分析周期频度"
+		br.ErrMsg = fmt.Sprintf("分析周期频度有误: %s", req.Correlation.LeadUnit)
+		return
+	}
+
+	if req.Correlation.CalculateUnit == "" {
+		br.Msg = "请选择计算窗口频度"
+		return
+	}
+	calculateUnitDays, ok := utils.FrequencyDaysMap[req.Correlation.CalculateUnit]
+	if !ok {
+		br.Msg = "错误的计算窗口频度"
+		br.ErrMsg = fmt.Sprintf("计算窗口频度有误: %s", req.Correlation.CalculateUnit)
+		return
+	}
+	leadDays := 2 * req.Correlation.LeadValue * leadUnitDays
+	calculateDays := req.Correlation.CalculateValue * calculateUnitDays
+	if calculateDays < leadDays {
+		br.Msg = "计算窗口必须≥2*分析周期"
+		return
+	}
+
+	// 获取标的指标信息及数据
+	baseEdb, e := data_manage.GetEdbInfoById(req.BaseEdbInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的指标不存在"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取标的指标信息失败, Err: " + e.Error()
+		return
+	}
+	dataListA := make([]*data_manage.EdbDataList, 0)
+	{
+		// 标的指标数据日期区间
+		startDate := time.Now().AddDate(0, 0, -calculateDays).Format(utils.FormatDate)
+		endDate := time.Now().Format(utils.FormatDate)
+		startDateTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		startDate = startDateTime.AddDate(0, 0, 1).Format(utils.FormatDate) // 不包含第一天
+		switch baseEdb.EdbInfoType {
+		case 0:
+			dataListA, e = data_manage.GetEdbDataList(baseEdb.Source, baseEdb.SubSource, baseEdb.EdbInfoId, startDate, endDate)
+		case 1:
+			_, dataListA, _, _, e, _ = data.GetPredictDataListByPredictEdbInfoId(baseEdb.EdbInfoId, startDate, endDate, false)
+		default:
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("标的指标类型异常: %d", baseEdb.EdbInfoType)
+			return
+		}
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取标的指标数据失败, Err:" + e.Error()
+			return
+		}
+	}
+
+	// 获取因子系列
+	seriesIdItem := make(map[int]*data_manage.FactorEdbSeries)
+	{
+		ob := new(data_manage.FactorEdbSeries)
+		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().PrimaryId, utils.GetOrmInReplace(len(req.SeriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.SeriesIds)
+		items, e := ob.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", ob.Cols().PrimaryId))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取因子指标系列失败, Err: " + e.Error()
+			return
+		}
+		if len(items) != len(req.SeriesIds) {
+			br.Msg = "获取失败"
+			br.ErrMsg = "因子指标系列数量有误"
+			return
+		}
+		for _, v := range items {
+			seriesIdItem[v.FactorEdbSeriesId] = v
+		}
+	}
+
+	// 获取因子指标
+	edbMappings := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	edbInfoIds := make([]int, 0)
+	{
+		ob := new(data_manage.FactorEdbSeriesMapping)
+		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(req.SeriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.SeriesIds)
+		order := fmt.Sprintf("%s ASC, %s ASC", ob.Cols().FactorEdbSeriesId, ob.Cols().EdbInfoId)
+		items, e := ob.GetItemsByCondition(cond, pars, []string{}, order)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取因子指标关联失败, Err: " + e.Error()
+			return
+		}
+		for _, v := range items {
+			edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+		}
+		edbMappings = items
+	}
+	edbIdItem := make(map[int]*data_manage.EdbInfo)
+	edbItems, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取因子指标失败, Err: " + e.Error()
+		return
+	}
+	for _, v := range edbItems {
+		edbIdItem[v.EdbInfoId] = v
+	}
+
+	// 获取因子指标数据, 计算相关性
+	resp := new(data_manage.FactorEdbSeriesCorrelationMatrixResp)
+	calculateDataOb := new(data_manage.FactorEdbSeriesCalculateData)
+
+	calculateWorkers := make(chan struct{}, 10)
+	wg := sync.WaitGroup{}
+	edbExists := make(map[string]bool)
+	chartKeyMap := make(map[string]*data_manage.FactorEdbSeriesChartMapping)
+	for _, v := range edbMappings {
+		existsKey := fmt.Sprintf("%d-%d", v.FactorEdbSeriesId, v.EdbInfoId)
+		if edbExists[existsKey] {
+			continue
+		}
+		edbExists[existsKey] = true
+
+		edbItem := edbIdItem[v.EdbInfoId]
+		if edbItem == nil {
+			continue
+		}
+		seriesItem := seriesIdItem[v.FactorEdbSeriesId]
+		if seriesItem == nil {
+			continue
+		}
+
+		wg.Add(1)
+		go func(mapping *data_manage.FactorEdbSeriesMapping, edb *data_manage.EdbInfo, series *data_manage.FactorEdbSeries) {
+			defer func() {
+				wg.Done()
+				<-calculateWorkers
+			}()
+			calculateWorkers <- struct{}{}
+
+			var item data_manage.FactorEdbSeriesCorrelationMatrixItem
+			item.SeriesId = series.FactorEdbSeriesId
+			item.EdbInfoId = edb.EdbInfoId
+			item.EdbCode = edb.EdbCode
+			item.EdbName = edb.EdbName
+
+			// 指标来源
+			edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+			edbList = append(edbList, &data_manage.ChartEdbInfoMapping{
+				EdbInfoId:           edb.EdbInfoId,
+				EdbInfoCategoryType: edb.EdbInfoType,
+				EdbType:             edb.EdbType,
+				Source:              edb.Source,
+				SourceName:          edb.SourceName,
+			})
+			sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+			item.SourceName = strings.Join(sourceNameList, ",")
+			item.SourceNameEn = strings.Join(sourceNameEnList, ",")
+
+			// 获取指标数据
+			dataListB := make([]*data_manage.EdbDataList, 0)
+			if series.CalculateState == data_manage.FactorEdbSeriesCalculated {
+				cond := fmt.Sprintf(" AND %s = ? AND %s = ?", calculateDataOb.Cols().FactorEdbSeriesId, calculateDataOb.Cols().EdbInfoId)
+				pars := make([]interface{}, 0)
+				pars = append(pars, mapping.FactorEdbSeriesId, mapping.EdbInfoId)
+				dataItems, e := calculateDataOb.GetItemsByCondition(cond, pars, []string{calculateDataOb.Cols().DataTime, calculateDataOb.Cols().Value}, fmt.Sprintf("%s ASC", calculateDataOb.Cols().DataTime))
+				if e != nil {
+					item.Msg = fmt.Sprintf("计算失败")
+					item.ErrMsg = fmt.Sprintf("获取计算数据失败, err: %v", e)
+					resp.Fail = append(resp.Fail, item)
+					return
+				}
+				dataListB = data_manage.TransEdbSeriesCalculateData2EdbDataList(dataItems)
+			} else {
+				switch edb.EdbInfoType {
+				case 0:
+					dataListB, e = data_manage.GetEdbDataList(edb.Source, edb.SubSource, edb.EdbInfoId, "", "")
+				case 1:
+					_, dataListB, _, _, e, _ = data.GetPredictDataListByPredictEdbInfoId(edb.EdbInfoId, "", "", false)
+				default:
+					item.Msg = fmt.Sprintf("计算失败")
+					item.ErrMsg = fmt.Sprintf("指标类型异常, edbType: %d", edb.EdbInfoType)
+					resp.Fail = append(resp.Fail, item)
+					return
+				}
+			}
+
+			// 计算相关性
+			xEdbIdValue, yDataList, e := correlationServ.CalculateCorrelation(req.Correlation.LeadValue, req.Correlation.LeadUnit, baseEdb.Frequency, edb.Frequency, dataListA, dataListB)
+			if e != nil {
+				item.Msg = fmt.Sprintf("计算失败")
+				item.ErrMsg = fmt.Sprintf("相关性计算失败, err: %v", e)
+				resp.Fail = append(resp.Fail, item)
+				return
+			}
+
+			// X及Y轴数据
+			yData := yDataList[0].Value
+			yLen := len(yData)
+			values := make([]data_manage.FactorEdbSeriesCorrelationMatrixValues, len(xEdbIdValue))
+			for k, x := range xEdbIdValue {
+				var y float64
+				if k >= 0 && k < yLen {
+					y = yData[k]
+				}
+				y = utils.SubFloatToFloat(y, 2)
+				values[k] = data_manage.FactorEdbSeriesCorrelationMatrixValues{
+					XData: x, YData: y,
+				}
+			}
+
+			// 图表关联-此处添加的chart_info_id=0
+			newMapping := new(data_manage.FactorEdbSeriesChartMapping)
+			newMapping.CalculateType = data_manage.FactorEdbSeriesChartCalculateTypeCorrelation
+
+			// 计算参数
+			var calculatePars data_manage.FactorEdbSeriesChartCalculateCorrelationReq
+			calculatePars.BaseEdbInfoId = req.BaseEdbInfoId
+			calculatePars.LeadValue = req.Correlation.LeadValue
+			calculatePars.LeadUnit = req.Correlation.LeadUnit
+			calculatePars.CalculateValue = req.Correlation.CalculateValue
+			calculatePars.CalculateUnit = req.Correlation.CalculateUnit
+			bc, e := json.Marshal(calculatePars)
+			if e != nil {
+				item.Msg = fmt.Sprintf("计算失败")
+				item.ErrMsg = fmt.Sprintf("计算参数JSON格式化失败, err: %v", e)
+				resp.Fail = append(resp.Fail, item)
+				return
+			}
+			newMapping.CalculatePars = string(bc)
+
+			// 计算结果, 注此处保存的是排序前的顺序
+			bv, e := json.Marshal(values)
+			if e != nil {
+				item.Msg = fmt.Sprintf("计算失败")
+				item.ErrMsg = fmt.Sprintf("计算结果JSON格式化失败, err: %v", e)
+				resp.Fail = append(resp.Fail, item)
+				return
+			}
+			newMapping.CalculateData = string(bv)
+			newMapping.FactorEdbSeriesId = mapping.FactorEdbSeriesId
+			newMapping.EdbInfoId = mapping.EdbInfoId
+			newMapping.CreateTime = time.Now().Local()
+			newMapping.ModifyTime = time.Now().Local()
+			chartKeyMap[existsKey] = newMapping
+
+			// 按照固定规则排期数[0 1 2 3 -1 -2 -3], 仅矩阵展示为此顺序
+			sort.Sort(data_manage.FactorEdbSeriesCorrelationMatrixOrder(values))
+			item.Msg = "计算成功"
+			item.Values = values
+			resp.Success = append(resp.Success, item)
+		}(v, edbItem, seriesItem)
+	}
+	wg.Wait()
+
+	// 新增图表关联, 此处按照顺序添加
+	chartMappings := make([]*data_manage.FactorEdbSeriesChartMapping, 0)
+	for _, v := range edbMappings {
+		k := fmt.Sprintf("%d-%d", v.FactorEdbSeriesId, v.EdbInfoId)
+		item := chartKeyMap[k]
+		if item == nil {
+			continue
+		}
+		chartMappings = append(chartMappings, item)
+	}
+	chartMappingOb := new(data_manage.FactorEdbSeriesChartMapping)
+	if e = chartMappingOb.ClearAndCreateMapping(req.SeriesIds, chartMappings); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("新增图表关联失败, Err: %v", e)
+		return
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 92 - 25
controllers/data_manage/future_good/future_good_chart_info.go

@@ -544,6 +544,9 @@ func (this *FutureGoodChartInfoController) ChartInfoAdd() {
 	resp.UniqueCode = chartInfo.UniqueCode
 	resp.ChartType = req.ChartType
 
+	chartInfo.ChartInfoId = chartInfoId
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfoId)
 
@@ -767,6 +770,9 @@ func (this *FutureGoodChartInfoController) ChartInfoEdit() {
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
+
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据
@@ -1500,6 +1506,29 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1569,33 +1598,12 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}
@@ -3219,3 +3227,62 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 	br.Msg = "编辑成功"
 	br.IsAddLog = true
 }
+
+// ChartInfoImgSetBySvg
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_svg [post]
+func (this *FutureGoodChartInfoController) ChartInfoImgSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,ChartInfoId Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 40 - 26
controllers/data_manage/future_good/future_good_profit_chart_info.go

@@ -318,6 +318,8 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 	resp.UniqueCode = chartInfo.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 
@@ -639,6 +641,8 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据
@@ -819,7 +823,15 @@ func copyProfitChartInfo(oldChartInfo *data_manage.ChartInfo, chartClassifyId in
 		br.ErrMsg = "保存商品利润图表失败,Err:" + err.Error()
 		return
 	}
-
+	// 添加指标引用记录
+	edbInfoIdArr := make([]int, 0)
+	edbInfoIdArrStr := strings.Split(oldChartInfo.EdbInfoIds, ",")
+	for _, v := range edbInfoIdArrStr {
+		i, _ := strconv.Atoi(v)
+		edbInfoIdArr = append(edbInfoIdArr, i)
+	}
+	//保存图表与指标的关系
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 	//修改my eta es数据
@@ -1113,6 +1125,29 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1182,33 +1217,12 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}

+ 27 - 25
controllers/data_manage/line_equation/line_chart_info.go

@@ -1207,6 +1207,29 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1276,33 +1299,12 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}

+ 27 - 25
controllers/data_manage/line_feature/chart_info.go

@@ -2304,6 +2304,29 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -2373,33 +2396,12 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}

+ 35 - 6
controllers/data_manage/multiple_graph_config.go

@@ -318,7 +318,7 @@ func (this *ChartInfoController) MultipleGraphPreview() {
 			}
 			startDate := time.Now().AddDate(0, 0, -correlationConf.CalculateValue*moveUnitDays).Format(utils.FormatDate)
 			endDate := time.Now().Format(utils.FormatDate)
-			xEdbIdValue, yDataList, e := correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationConf.LeadValue, correlationConf.LeadUnit, startDate, endDate)
+			xEdbIdValue, yDataList, e := correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationConf.LeadValue, correlationConf.LeadUnit, startDate, endDate, "")
 			if e != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
@@ -828,6 +828,27 @@ func (this *ChartInfoController) MultipleGraphConfigSaveChart() {
 				startDate, endDate = utils.GetDateByDateType(req.Curve.DateType, req.Curve.StartDate, req.Curve.EndDate)
 			}
 
+			// 图例, 图表来源
+			var sourceFrom, extraConfig string
+			if req.CorrelationExtraConfig != nil {
+				b, e := json.Marshal(req.CorrelationExtraConfig)
+				if e != nil {
+					br.Msg = "保存失败"
+					br.ErrMsg = fmt.Sprintf("相关性图表图例信息JSON格式化失败, Err: %v", e)
+					return
+				}
+				extraConfig = string(b)
+			}
+			if req.SourcesFrom != nil {
+				b, e := json.Marshal(req.SourcesFrom)
+				if e != nil {
+					br.Msg = "保存失败"
+					br.ErrMsg = fmt.Sprintf("图表来源JSON格式化失败, Err: %v", e)
+					return
+				}
+				sourceFrom = string(b)
+			}
+
 			correlationChartInfoReq := data_manage.CorrelationChartInfoReq{
 				LeadValue:          leadValue,
 				LeadUnit:           leadUnit,
@@ -858,6 +879,8 @@ func (this *ChartInfoController) MultipleGraphConfigSaveChart() {
 					ChartType:            utils.CHART_TYPE_CURVE,
 					Calendar:             "公历",
 					CorrelationChartInfo: correlationChartInfoReq,
+					ExtraConfig:          extraConfig,
+					SourcesFrom:          sourceFrom,
 				}
 				chartInfo, err, errMsg, isSendEmail = correlationServ.AddChartInfo(addChartReq, chartSource, sysUser, this.Lang)
 			} else {
@@ -868,6 +891,8 @@ func (this *ChartInfoController) MultipleGraphConfigSaveChart() {
 					ChartType:            utils.CHART_TYPE_CURVE,
 					Calendar:             "公历",
 					CorrelationChartInfo: correlationChartInfoReq,
+					ExtraConfig:          extraConfig,
+					SourcesFrom:          sourceFrom,
 				}
 				chartInfo, err, errMsg, isSendEmail = correlationServ.EditChartInfo(editChartReq, sysUser, this.Lang)
 				if err != nil {
@@ -1495,12 +1520,16 @@ func (this *ChartInfoController) GetMultipleGraphConfig() {
 			return
 		}
 		for _, v := range chartList {
+			var mapping response.MultipleGraphConfigChartMapping
 			if tmpInfo, ok := chartMappingMap[v.ChartInfoId]; ok {
-				multipleGraphConfigChartMappingList = append(multipleGraphConfigChartMappingList, response.MultipleGraphConfigChartMapping{
-					ChartInfoId:            tmpInfo.ChartInfoId,
-					Source:                 tmpInfo.Source,
-					MultipleLocationSource: tmpInfo.Source,
-				})
+				mapping.ChartInfoId = tmpInfo.ChartInfoId
+				mapping.Source = tmpInfo.Source
+				mapping.MultipleLocationSource = tmpInfo.Source
+				if v.ChartInfoId == chartInfo.ChartInfoId {
+					mapping.CorrelationExtraConfig = chartInfo.ExtraConfig
+					mapping.SourcesFrom = chartInfo.SourcesFrom
+				}
+				multipleGraphConfigChartMappingList = append(multipleGraphConfigChartMappingList, mapping)
 			}
 		}
 	}

+ 78 - 0
controllers/data_manage/predict_edb_info.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/data_manage/request"
 	"eta/eta_api/models/data_manage/response"
+	"eta/eta_api/services"
 	"eta/eta_api/services/data"
 	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/services/elastic"
@@ -2106,3 +2107,80 @@ func (this *PredictEdbInfoController) ClassifyEdbInfoItems() {
 	br.Msg = "获取成功"
 	br.Data = resp
 }
+
+// ChartImageSetBySvg
+// @Title 设置指标的图表图片
+// @Description 设置指标的图表图片接口
+// @Param	request	body data_manage.SetChartInfoImageReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /predict_edb_info/image/set_by_svg [post]
+func (this *PredictEdbInfoController) ChartImageSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	edbInfoId, _ := this.GetInt("EdbInfoId", 0)
+	if edbInfoId <= 0 {
+		br.Msg = "指标参数错误"
+		br.ErrMsg = "指标参数错误,EdbInfoId Is Empty"
+		return
+	}
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if edbInfoId <= 0 || resourceUrl == "" {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "保存成功"
+		return
+	}
+
+	edbInfo, err := data_manage.GetEdbInfoById(edbInfoId)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "找不到该指标"
+			br.ErrMsg = "找不到该指标"
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	edbInfo.ChartImage = resourceUrl
+	err = edbInfo.Update([]string{"ChartImage"})
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	//修改es数据
+	go data.AddOrEditEdbInfoToEs(edbInfoId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}

+ 331 - 0
controllers/data_manage/wind_data.go

@@ -0,0 +1,331 @@
+package data_manage
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/response"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
+)
+
+// WindClassify
+// @Title wind指标分类
+// @Description wind指标分类
+// @Success 200 {object} data_manage.BaseFromYongyiClassify
+// @router /wind/classify [get]
+func (this *EdbInfoController) WindClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	// 默认查一级分类和一级分类下的指标信息,
+	// 如果是 子级分类,查询该子级分类的下一级分类和指标信息
+	// 增加标识判断是文件夹还是指标列表
+	parentId, _ := this.GetInt("ParentId")
+	// 特殊处理顶级分类
+	rootList, err := data_manage.GetEdbClassifyByParentId(parentId, 0)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	realRootMap := make(map[int]struct{})
+	if parentId == 0 {
+		// 查询wind指标的所有一级分类
+		classifyIdList, e := data_manage.GetEdbClassifyIdListBySource(utils.DATA_SOURCE_WIND)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取分类列表失败,Err:" + e.Error()
+			return
+		}
+		if len(classifyIdList) == 0 {
+			resp := new(data_manage.EdbClassifyListResp)
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			br.Data = resp
+			return
+		}
+		// 查询wind指标的所有一级分类下的指标信息
+		rootIds, e := data_manage.GetEdbClassifyRootIdsByClassifyIds(classifyIdList)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取分类列表失败,Err:" + e.Error()
+			return
+		}
+		for _, v := range rootIds {
+			realRootMap[v] = struct{}{}
+		}
+	}
+	nodeAll := make([]*data_manage.EdbClassifyItems, 0)
+
+	var sortList data_manage.EdbClassifyItemList
+	if parentId > 0 {
+		// 查询挂在当前分类上的指标列表
+		// 获取当前账号的不可见指标
+		obj := data_manage.EdbInfoNoPermissionAdmin{}
+		confList, err := obj.GetAllListByAdminId(this.SysUser.AdminId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+			return
+		}
+		noPermissionEdbInfoIdMap := make(map[int]bool)
+		for _, v := range confList {
+			noPermissionEdbInfoIdMap[v.EdbInfoId] = true
+		}
+		allEdbInfo, err := data_manage.GetEdbInfoByClassifyIdAndSource(parentId, 0, utils.DATA_SOURCE_WIND)
+		if err != nil {
+			br.Msg = "获取指标数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+
+		if len(allEdbInfo) > 0 {
+			// 查询当前子分类信息
+			/*currClassify, err := data_manage.GetEdbClassifyById(parentId)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取当前分类信息失败,Err:" + err.Error()
+				return
+			}
+			// 获取所有有权限的指标和分类
+			permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserEdbAndClassifyPermissionList(this.SysUser.AdminId, 0, 0)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+				return
+			}*/
+			for _, v := range allEdbInfo {
+				// 如果指标不可见,那么就不返回该指标
+				if _, ok := noPermissionEdbInfoIdMap[v.EdbInfoId]; ok {
+					continue
+				}
+				//v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(v.IsJoinPermission, currClassify.IsJoinPermission, v.EdbInfoId, v.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
+				/*button := data.GetEdbOpButton(this.SysUser, v.SysUserId, v.EdbType, utils.EDB_INFO_TYPE, v.HaveOperaAuth)
+				button.AddButton = false //不管有没有权限,指标都是没有添加按钮的
+				v.Button = button*/
+				v.Children = make([]*data_manage.EdbClassifyItems, 0)
+				v.ParentId = parentId
+				nodeAll = append(nodeAll, v)
+			}
+		}
+
+	}
+	if len(rootList) > 0 {
+		// 已授权分类id
+		/*permissionClassifyIdList, err := data_manage_permission.GetUserEdbClassifyPermissionList(this.SysUser.AdminId, 0)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取已授权分类id数据失败,Err:" + err.Error()
+			return
+		}*/
+
+		for _, v := range rootList {
+			// 数据权限
+			/*v.HaveOperaAuth = data_manage_permission.CheckEdbClassifyPermissionByPermissionIdList(v.IsJoinPermission, v.ClassifyId, permissionClassifyIdList)
+			// 按钮权限
+			button := data.GetEdbClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
+			v.Button = button*/
+			if _, ok := realRootMap[v.ClassifyId]; !ok && parentId == 0 { //查询一级分类时,过滤
+				continue
+			}
+			v.Children = make([]*data_manage.EdbClassifyItems, 0)
+			nodeAll = append(nodeAll, v)
+		}
+	}
+	if len(nodeAll) > 0 {
+		//根据sort值排序
+		sortList = nodeAll
+		sort.Sort(sortList)
+	}
+
+	language := `CN`
+	// 指标显示的语言
+	{
+		configDetail, _ := system.GetConfigDetailByCode(this.SysUser.AdminId, system.EdbLanguageVar)
+		if configDetail != nil {
+			language = configDetail.ConfigValue
+		} else {
+			configDetail, _ = system.GetDefaultConfigDetailByCode(system.EdbLanguageVar)
+			if configDetail != nil {
+				language = configDetail.ConfigValue
+			}
+		}
+	}
+
+	resp := new(data_manage.EdbClassifyListResp)
+	resp.AllNodes = sortList
+	resp.Language = language
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// WindEdbInfoList
+// @Title wind指标列表接口
+// @Description wind指标列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   int  true       "分类id"
+// @Success 200 {object} response.EdbInfoChartListResp
+// @router /wind/index [get]
+func (this *EdbInfoController) WindEdbInfoList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	// 分页
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var total int
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	// wind基础指标
+	var condition string
+	var pars []interface{}
+	condition += ` AND edb_info_type = ? AND source =? AND edb_type=?`
+	pars = append(pars, 0, utils.DATA_SOURCE_WIND, 1)
+
+	// 分类筛选
+	classifyId, _ := this.GetInt("ClassifyId")
+	edbInfoId, _ := this.GetInt("EdbInfoId")
+	if classifyId > 0 {
+		childClassify, e, _ := data.GetChildClassifyByClassifyId(classifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取分类信息失败, GetEdbClassify,Err:" + e.Error()
+			return
+		}
+		var classifyIds []int
+		for _, v := range childClassify {
+			classifyIds = append(classifyIds, v.ClassifyId)
+		}
+		condition += fmt.Sprintf(` AND classify_id IN (%s) `, utils.GetOrmInReplace(len(classifyIds)))
+		pars = append(pars, classifyIds)
+	}
+	if edbInfoId > 0 {
+		condition += ` AND edb_info_id = ?`
+		pars = append(pars, edbInfoId)
+	}
+
+	// 获取当前账号的不可见指标
+	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	confList, e := obj.GetAllListByAdminId(this.SysUser.AdminId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取不可见指标配置数据失败,Err:" + e.Error()
+		return
+	}
+	noPermissionEdbInfoIdList := make([]int, 0)
+	for _, v := range confList {
+		noPermissionEdbInfoIdList = append(noPermissionEdbInfoIdList, v.EdbInfoId)
+	}
+	noPermissionEdbInfoIdNum := len(noPermissionEdbInfoIdList)
+	if noPermissionEdbInfoIdNum > 0 {
+		condition += ` AND edb_info_id NOT IN (` + utils.GetOrmInReplace(noPermissionEdbInfoIdNum) + `) `
+		pars = append(pars, noPermissionEdbInfoIdList)
+	}
+	list := make([]*data_manage.WindEdbInfoList, 0)
+	// 获取指标信息
+	dataCount, edbList, e := data_manage.GetEdbInfoFilterList(condition, pars, startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取普通指标列表失败, Err:" + e.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, int(dataCount))
+	if len(edbList) > 0 {
+		classifyMap := make(map[int]*data_manage.EdbClassify)
+		targetClassifyList := make([]*data_manage.EdbClassifyItems, 0)
+		if edbInfoId > 0 && classifyId == 0 {
+			classifyId = edbList[0].ClassifyId
+		}
+		if classifyId > 0 { // todo 当没有传入分类ID时,如何处理 同一个分类ID,顶级分类是一样的
+			targetClassify, err := data_manage.GetEdbClassifyById(classifyId)
+			if err != nil {
+				if err.Error() == utils.ErrNoRow() {
+					br.Msg = "当前分类不存在"
+					return
+				}
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+				return
+			}
+			targetClassifyList, err = data_manage.GetEdbClassifyByRootIdLevel(targetClassify.RootId, targetClassify.ClassifyType, "")
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+				return
+			}
+			classifyIds := make([]int, 0)
+			for _, v := range edbList {
+				classifyIds = append(classifyIds, v.ClassifyId)
+			}
+			//查询分类信息
+			classifyList, err := data_manage.GetEdbClassifyByIdList(classifyIds)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+				return
+			}
+			for _, v := range classifyList {
+				classifyMap[v.ClassifyId] = v
+			}
+		}
+		for _, v := range edbList {
+			//查询目录
+			targetClassify, ok := classifyMap[v.ClassifyId]
+			if !ok {
+				br.Msg = "当前分类不存在"
+				return
+			}
+			classifyList, err, errMsg := data.GetFullClassifyByRootId(targetClassify, targetClassifyList)
+			if err != nil {
+				br.Msg = err.Error()
+				br.ErrMsg = errMsg
+				return
+			}
+			tmp := &data_manage.WindEdbInfoList{
+				EdbInfoList:  v,
+				ClassifyList: classifyList,
+			}
+			list = append(list, tmp)
+		}
+	}
+
+	resp := response.WindEdbInfoListResp{
+		Paging: page,
+		List:   list,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 2 - 0
controllers/fe_calendar/fe_calendar_matter.go

@@ -5,6 +5,7 @@ import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/fe_calendar"
+	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
 	"strings"
@@ -323,6 +324,7 @@ func (this *FeCalendarMatterController) Save() {
 		return
 	}
 
+	_ = data.SaveCalendarEdbInfoRelation(req.ChartPermissionId, req.MatterDate, editMatters, removeMatters)
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"

+ 9 - 1
controllers/sandbox/sandbox.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/sandbox/request"
 	"eta/eta_api/models/sandbox/response"
+	"eta/eta_api/services/data"
 	sandboxService "eta/eta_api/services/sandbox"
 	"eta/eta_api/utils"
 	"fmt"
@@ -636,7 +637,8 @@ func (this *SandboxController) Delete() {
 		br.Msg = err.Error()
 		return
 	}
-
+	// 删除图表中的指标引用
+	_ = data_manage.DeleteEdbRelationByObjectId(req.SandboxId, utils.EDB_RELATION_SANDBOX)
 	msg := "删除成功"
 	br.Ret = 200
 	br.Success = true
@@ -1802,6 +1804,7 @@ func (this *SandboxController) SaveV2() {
 
 	var errMsg string
 
+	var sandBoxData *sandbox.Sandbox
 	if req.SandboxId <= 0 {
 		//新增沙盘
 		sandboxResp, err = sandboxService.AddSandboxV2(req, sysUser.AdminId, sysUser.RealName)
@@ -1813,6 +1816,7 @@ func (this *SandboxController) SaveV2() {
 			br.ErrMsg = "保存失败,Err:" + err.Error()
 			return
 		}
+		sandBoxData = sandboxResp.Sandbox
 	} else {
 		//编辑沙盘
 		sandboxInfo := &sandbox.Sandbox{
@@ -1841,8 +1845,12 @@ func (this *SandboxController) SaveV2() {
 			br.ErrMsg = "保存失败,Err:" + err.Error()
 			return
 		}
+		sandBoxData = sandboxInfo
 	}
 
+	//解析逻辑图的指标
+	_ = data.SaveSandBoxEdbInfoRelation(sandBoxData.SandboxId, sandBoxData.Content)
+
 	msg := "保存成功"
 	br.Ret = 200
 	br.Success = true

+ 33 - 0
models/business_conf.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"html"
@@ -47,6 +48,8 @@ const (
 	BusinessConfTencentApiSecretKey          = "TencentApiSecretKey"          // 腾讯云API-密钥对
 	BusinessConfTencentApiRecTaskCallbackUrl = "TencentApiRecTaskCallbackUrl" // 腾讯云API-语音识别回调地址
 	BusinessConfSmsJhgjVariable              = "SmsJhgjVariable"              // 聚合国际短信变量
+
+	BusinessConfEdbStopRefreshRule = "EdbStopRefreshRule" // 是否停止指标刷新规则
 )
 
 const (
@@ -220,3 +223,33 @@ func GetBusinessConfByKey(key string) (item *BusinessConf, err error) {
 	err = o.Raw(sql, key).QueryRow(&item)
 	return
 }
+
+type BusinessConfSingleSaveReq struct {
+	ConfKey string `description:"配置Key"`
+	ConfVal string `description:"配置值"`
+}
+
+type EdbStopRefreshRule struct {
+	IsOpen            int `description:"是否开启自动禁用1,开启,0未开启"`
+	BaseIndexStopDays int `description:"数据源间隔天数未加入指标库则停用"`
+	EdbStopDays       int `description:"指标库间隔天数未引用则停用"`
+}
+
+type BusinessConfSingleResp struct {
+	ConfVal string
+}
+
+// InitUseMongoConf
+// @Description:
+// @author: Roc
+// @datetime 2024-07-01 13:49:09
+func InitUseMongoConf() {
+	useMongo, e := GetBusinessConfByKey("UseMongo")
+	if e != nil {
+		return
+	}
+
+	if useMongo.ConfVal == `true` {
+		utils.UseMongo = true
+	}
+}

+ 231 - 0
models/data_manage/base_from_business_data.go

@@ -0,0 +1,231 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromBusinessData
+// @Description: 外部指标(商家系统)原始数据表
+type BaseFromBusinessData struct {
+	BusinessDataId          int       `orm:"column(business_data_id);pk" json:"business_data_id"`
+	BaseFromBusinessIndexId int       `json:"base_from_business_index_id"` // 指标id
+	IndexCode               string    `json:"index_code"`                  // 指标编码
+	DataTime                time.Time `json:"data_time"`                   // 数据日期
+	Value                   float64   `json:"value"`                       // 数据值
+	CreateTime              time.Time `json:"create_time"`                 // 创建时间
+	ModifyTime              time.Time `json:"modify_time"`                 // 修改时间
+}
+
+// TableName
+// @Description:  获取表名
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:36
+// @return string
+func (m *BaseFromBusinessData) TableName() string {
+	return "base_from_business_data"
+}
+
+// CollectionName
+// @Description:  获取集合名称
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:36
+// @return string
+func (m *BaseFromBusinessData) CollectionName() string {
+	return "base_from_business_data"
+}
+
+// DataBaseName
+// @Description: 获取数据库名称
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:33
+// @return string
+func (m *BaseFromBusinessData) DataBaseName() string {
+	return utils.MgoDataDbName
+}
+
+type WhereParams struct {
+	Condition string
+	Pars      []interface{}
+	Order     string `description:"排序字段"`
+}
+
+// GetAllDataList
+// @Description: 根据条件获取所有数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:42:19
+// @param sort []string
+// @param whereParams interface{}
+// @return result []BaseFromBusinessData
+// @return err error
+func (m *BaseFromBusinessData) GetAllDataList(condition string, pars []interface{}, order string) (result []*BaseFromBusinessData, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := `SELECT * FROM base_from_business_data WHERE 1=1 `
+	if condition != `` {
+		sql += ` ` + condition
+	}
+
+	if order != `` {
+		sql += ` ORDER BY ` + order
+	}
+
+	_, err = o.Raw(sql, pars).QueryRows(&result)
+
+	return
+}
+
+// GetLimitDataList
+// @Description: 根据条件获取指定数量数据列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-06 17:08:32
+// @param whereParams interface{}
+// @param size int64
+// @return result []*BaseFromBusinessData
+// @return err error
+func (m *BaseFromBusinessData) GetLimitDataList(condition string, pars []interface{}, order string, size int64) (result []*BaseFromBusinessData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_business_data WHERE 1=1 `
+	if condition != `` {
+		sql += ` ` + condition
+	}
+
+	if order != `` {
+		sql += ` ORDER BY ` + order
+	}
+
+	sql += fmt.Sprintf(` LIMIT %d`, size)
+
+	_, err = o.Raw(sql, pars).QueryRows(&result)
+
+	return
+}
+
+// GetPageDataList
+// @Description: 根据条件获取分页数据列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-07 10:21:07
+// @param whereParams interface{}
+// @param startSize int64
+// @param size int64
+// @param sort []string
+// @return result []*BaseFromBusinessData
+// @return err error
+func (m *BaseFromBusinessData) GetPageDataList(condition []string, pars []interface{}, order string, startSize, size int64) (result []*BaseFromBusinessData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_business_data `
+	if len(condition) > 0 {
+		sql += ` WHERE ` + strings.Join(condition, " AND ")
+	}
+
+	if order != `` {
+		sql += ` ORDER BY ` + order
+	}
+
+	sql += fmt.Sprintf(` LIMIT %d,%d`, startSize, size)
+
+	_, err = o.Raw(sql, pars).QueryRows(&result)
+
+	return
+}
+
+// GetCountDataList
+// @Description:  根据条件获取数据列表总数
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-07 10:29:00
+// @param whereParams interface{}
+// @return count int64
+// @return err error
+func (m *BaseFromBusinessData) GetCountDataList(condition []string, pars []interface{}) (count int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) FROM base_from_business_data `
+	if len(condition) > 0 {
+		sql += ` WHERE ` + strings.Join(condition, " AND ")
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+
+	return
+}
+
+// InsertDataByColl
+// @Description: 写入单条数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param addData interface{}
+// @return err error
+func (m *BaseFromBusinessData) InsertDataByColl(addData interface{}) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Insert(addData)
+
+	return
+}
+
+// BatchInsertData
+// @Description: 批量写入数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param bulk int 每次请求保存的数据量
+// @param dataList []interface{}
+// @return err error
+func (m *BaseFromBusinessData) BatchInsertData(bulk int, dataList []interface{}) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(bulk, dataList)
+
+	return
+}
+
+// UpdateData
+// @Description: 单条数据修改
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 15:01:51
+// @param whereParams interface{}
+// @param updateParams interface{}
+// @return err error
+func (m *BaseFromBusinessData) UpdateData(updateCols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, updateCols...)
+	if err != nil {
+		fmt.Println("UpdateDataByColl:Err:" + err.Error())
+		return
+	}
+
+	return
+}
+
+// GetEdbInfoMaxAndMinInfo
+// @Description: 获取当前指标的最大最小值
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 17:15:39
+// @param whereParams interface{}
+// @return result EdbInfoMaxAndMinInfo
+// @return err error
+func (m *BaseFromBusinessData) GetEdbInfoMaxAndMinInfo(indexCode string) (result EdbInfoMaxAndMinInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ``
+	sql = ` SELECT MIN(data_time) AS min_date,MAX(data_time) AS max_date,MIN(value) AS min_value,MAX(value) AS max_value FROM base_from_business_data WHERE index_code = ? `
+	err = o.Raw(sql, indexCode).QueryRow(&result)
+	if err != nil {
+		return
+	}
+
+	var latestValue float64
+	sql = ` SELECT value AS latest_value FROM base_from_business_data WHERE index_code = ? ORDER BY data_time DESC LIMIT 1 `
+	err = o.Raw(sql, indexCode).QueryRow(&latestValue)
+	result.LatestValue = latestValue
+
+	return
+}

+ 47 - 0
models/data_manage/chart_classify.go

@@ -22,6 +22,7 @@ type ChartClassify struct {
 	Source              int       `description:"1:ETA图库;2:商品价格曲线"`
 	IsJoinPermission    int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	ChartClassifyNameEn string    `description:"英文分类名称"`
+	RootId              int       `description:"顶级ID"`
 }
 
 func AddChartClassify(item *ChartClassify) (lastId int64, err error) {
@@ -262,6 +263,9 @@ type MoveChartClassifyReq struct {
 	ParentClassifyId int `description:"父级分类id"`
 	PrevClassifyId   int `description:"上一个兄弟节点分类id"`
 	NextClassifyId   int `description:"下一个兄弟节点分类id"`
+	ChartInfoId      int `description:"图表ID, 如果图表ID有值,则移动对象为图表,否则认为移动对象分类"`
+	PrevChartInfoId  int `description:"上一个图表ID"`
+	NextChartInfoId  int `description:"下一个图表ID"`
 }
 
 // GetFirstChartClassifyByParentId 获取当前父级图表分类下的排序第一条的数据
@@ -416,3 +420,46 @@ func GetChartClassifyBySourceAndIsJoinPermission(source, isJoinPermission int) (
 	_, err = o.Raw(sql, source, isJoinPermission).QueryRows(&items)
 	return
 }
+
+type ChartClassifyIdItems struct {
+	ChartClassifyId   int    `description:"分类id"`
+	ChartClassifyName string `description:"分类名称"`
+	UniqueCode        string `description:"唯一编码"`
+	ParentId          int    `description:"父级分类id"`
+	Level             int    `description:"层级"`
+	RootId            int    `description:"顶级分类id"`
+	IsJoinPermission  int    `description:"是否加入权限管控,0:不加入;1:加入;默认:0" json:"-"`
+	HaveOperaAuth     bool   `description:"是否有该数据权限,默认:false"`
+}
+
+func GetChartClassifyByRootIdLevel(rootId, source int, orderStr string) (items []*ChartClassifyIdItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE root_id = ? AND source = ? `
+	if orderStr != "" {
+		sql += orderStr
+	} else {
+		sql += ` order by level desc, sort asc, chart_classify_id asc`
+	}
+
+	_, err = o.Raw(sql, rootId, source).QueryRows(&items)
+	return
+}
+
+// UpdateChartClassifySortByParentIdAndSource 根据图表父类id更新排序
+func UpdateChartClassifySortByParentIdAndSource(parentId, classifyId, nowSort int, updateSort string, source int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update chart_classify set sort = ` + updateSort + ` WHERE parent_id=? and sort > ? AND source = ? `
+	if classifyId > 0 {
+		sql += ` or ( chart_classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort, source).Exec()
+	return
+}
+
+// GetChartClassifyAllBySource 根据来源获取所有分类
+func GetChartClassifyAllBySource(source int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE source = ? ORDER BY parent_id ASC, sort ASC, chart_classify_id ASC`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}

+ 11 - 0
models/data_manage/chart_edb_mapping.go

@@ -326,3 +326,14 @@ func ModifyChartEdbMapping(chartInfoId int, edbInfoList []*EdbInfo) (err error)
 
 	return
 }
+
+func GetRelationEdbInfoListMappingByCondition(condition string, pars []interface{}) (item []*ChartEdbInfoMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT a.* FROM edb_info AS a 
+	JOIN edb_info_calculate_mapping AS b on a.edb_info_id = b.edb_info_id WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}

+ 249 - 7
models/data_manage/chart_info.go

@@ -263,6 +263,54 @@ type EditChartInfoReq struct {
 	UnitEn               string                  `description:"英文单位名称"`
 }
 
+type MarkersLine struct {
+	Axis             int             `json:"axis" description:"1左轴 2右轴 3横轴"`
+	AxisName         string          `json:"axisName" description:"轴的名称,例如'左轴'"`
+	MarkerType       string          `json:"markerType" description:"标识线或标识区"`
+	MarkLineType     int             `json:"markLineType" description:"1:固定 2:指标计算"`
+	Value            string          `json:"value" description:"连线指向的数值,例如'4000'"`
+	FromValue        string          `json:"fromValue" description:"连线的起始点,可以为空"`
+	ToValue          string          `json:"toValue" description:"连线的结束点,可以为空"`
+	LineWidth        float64         `json:"lineWidth" description:"连线的宽度"`
+	DashStyle        string          `json:"dashStyle" description:"连线的虚线样式,例如'ShortDashDot'"`
+	Color            string          `json:"color" description:"连线的颜色"`
+	Text             string          `json:"text" description:"连线旁边显示的文本"`
+	TextPosition     string          `json:"textPosition" description:"文本的显示位置,例如'bottom'"`
+	TextColor        string          `json:"textColor" description:"文本颜色"`
+	TextFontSize     int             `json:"textFontSize" description:"文本的字号大小"`
+	IsShow           bool            `json:"isShow" description:"是否显示连线及文本"`
+	EdbType          int             `json:"edbType" description:"指标类型 0图中第一个指标 1其他指标 前端回显用"`
+	EdbInfoId        int             `json:"edbInfoId" description:"指标id"`
+	Calculation      int             `json:"calculation" description:"计算方式 1区间均值 2区间均值加N倍标准差 3区间个数分位 4区间数值分位"`
+	CalculationValue float64         `json:"calculationValue" description:"计算方式对应的值 2就是几倍标准差 3就是分位值 4就是数值值·"`
+	TimeIntervalType int             `json:"timeInterval" description:"时间区间 0跟随图表 1自定义"`
+	StartDate        MarkersLineTime `json:"startTime" description:"开始时间"`
+	EndDate          MarkersLineTime `json:"endTime" description:"结束时间"`
+}
+
+type MarkersLineTime struct {
+	TimeType int               `json:"timeType" description:"时间类型 1固定 2动态 3至今(仅结束时间)"`
+	Date     string            `json:"date" description:"日期"`
+	Conf     EdbDateChangeConf `json:"conf" description:"动态时间配置"`
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `description:"前移的期数"`
+	BaseDate    int `description:"基准日期 0系统日期 1指标最新日期"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+type EdbDateConfDateChange struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
 type EditChartEnInfoReq struct {
 	ChartInfoId      int                       `description:"图表ID"`
 	ChartNameEn      string                    `description:"英文图表名称"`
@@ -395,7 +443,7 @@ type EdbDataList struct {
 // @return err error
 func GetEdbDataList(source, subSource, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getEdbDataListByMongo(source, subSource, edbInfoId, startDate, endDate)
 	}
 
@@ -518,7 +566,7 @@ func GetEdbDataLunarList(endInfoId int, startDate, endDate string) (list []*EdbD
 // @return err error
 func GetEdbDataListMinAndMax(source, subSource, edbInfoId int, startDate, endDate string) (min_data, max_data float64, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getEdbDataListMinAndMaxByMongo(source, subSource, edbInfoId, startDate, endDate)
 	}
 
@@ -700,7 +748,9 @@ type ChartInfoDetailResp struct {
 	BarChartInfo         BarChartInfoReq  `description:"柱方图的配置"`
 	CorrelationChartInfo *CorrelationInfo `description:"相关性图表信息"`
 	DataResp             interface{}      `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
+	ClassifyLevels       []string         `description:"图表分类的UniqueCode-从最顶级到当前分类(给前端回显定位用的=_=!)"`
 }
+
 type BalanceTableChartListResp struct {
 	List []*BalanceChartInfoDetailResp
 }
@@ -739,6 +789,10 @@ type YData struct {
 	M              []int           `description:"对应开始日期的间隔值" json:"-"`
 	Unit           string          `description:"中文单位名称"`
 	UnitEn         string          `description:"英文单位名称"`
+	SeriesEdb      struct {
+		SeriesId  int `description:"因子指标系列ID"`
+		EdbInfoId int `description:"指标ID"`
+	} `description:"对应的系列指标"`
 }
 
 func ModifyChartInfoAndMapping(edbInfoIdStr string, req *SaveChartInfoReq, chartType int) (err error) {
@@ -1417,23 +1471,134 @@ type PreviewChartInfoReq struct {
 	SeasonExtraConfig SeasonExtraItem  `description:"季节性图表中的配置,json数据"`
 	StartYear         int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
 	ChartSource       int
+	MarkersLines      string `description:"标识线"`
 }
 
 type SeasonExtraItem struct {
-	ChartLegend []SeasonChartLegend `description:"自定义的图例名称"`
-	XStartDate  string              `description:"横坐标显示的起始日"`
-	XEndDate    string              `description:"横坐标显示的截止日"`
-	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	ChartLegend                 []SeasonChartLegend         `description:"自定义的图例名称"`
+	XStartDate                  string                      `description:"横坐标显示的起始日"`
+	XEndDate                    string                      `description:"横坐标显示的截止日"`
+	JumpYear                    int                         `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
 }
 
 type SeasonChartLegend struct {
 	Name  string
 	Value string
 }
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int     `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string  `description:"生成样式"`
+	Shape         string  `description:"形状"`
+	ChartColor    string  `description:"图表颜色"`
+	Size          float64 `description:"大小"`
+	Legend        string  `description:"图例名称"`
+	NumFormat     int     `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int     `description:"是否连接 0不连接 1连接"`
+	LineColor     string  `description:"线条颜色"`
+	LineWidth     float64 `description:"线条宽度"`
+	LineStyle     string  `description:"线条样式"`
+	IsShow        bool    `description:"是否显示"`
+	IsAdd         bool    `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string  `description:"颜色"`
+	Year      int     `description:"均线取值范围"`
+	Legend    string  `description:"图例名称"`
+	LineType  string  `description:"线型"`
+	LineWidth float64 `description:"线宽"`
+	IsShow    bool    `description:"是否显示"`
+	IsAdd     bool    `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth float64                  `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*ChartEdbInfoMapping
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
+}
+
 type AddChartInfoResp struct {
 	ChartInfoId int    `description:"图表id"`
 	UniqueCode  string `description:"图表唯一编码"`
 	ChartType   int    `description:"生成样式:1:曲线图,2:季节性图"`
+	ClassifyId  int    `description:"分类ID"`
 }
 
 type QuarterDataList []*QuarterData
@@ -1927,6 +2092,7 @@ type BarChartInfoReq struct {
 	YEdbList      []BarChartInfoEdbItemReq `description:"Y轴选择的指标列表"`
 	Unit          string                   `description:"中文单位"`
 	UnitEn        string                   `description:"英文单位"`
+	MarkersLines  string                   `description:"标识线"`
 }
 
 // BarChartInfoEdbItemReq 柱方图预览请求数据(指标相关)
@@ -2000,6 +2166,8 @@ func EditCorrelationChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr stri
 	pars = append(pars, req.ChartClassifyId)
 	pars = append(pars, disabled)
 	pars = append(pars, barChartConf)
+	pars = append(pars, req.ExtraConfig)
+	pars = append(pars, req.SourcesFrom)
 
 	sql := ` UPDATE  chart_info
 			SET
@@ -2009,7 +2177,9 @@ func EditCorrelationChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr stri
 			  chart_classify_id = ?,
 			  modify_time = NOW(),
               disabled = ?,
-              bar_config = ?
+              bar_config = ?,
+			  extra_config = ?,
+			  sources_from = ?
 			`
 	if calendar != "" {
 		sql += `,calendar = ? `
@@ -2352,3 +2522,75 @@ type RadarYData struct {
 	Name  string    `description:"别名"`
 	Value []float64 `description:"每个指标的值"`
 }
+
+// ChartInfoSourcesFrom 图表来源
+type ChartInfoSourcesFrom struct {
+	IsShow   bool   `description:"是否展示" json:"isShow"`
+	Text     string `description:"来源文本" json:"text"`
+	Color    string `description:"来源颜色" json:"color"`
+	FontSize int    `description:"来源字号" json:"fontSize"`
+}
+
+// UpdateChartInfoSortByClassifyIdV2 根据分类id更新排序
+func UpdateChartInfoSortByClassifyIdV2(classifyId, nowSort int, prevChartInfoId int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update chart_info set sort = ` + updateSort + ` WHERE chart_classify_id = ?`
+	if prevChartInfoId > 0 {
+		sql += ` AND ( sort > ? or ( chart_info_id > ` + fmt.Sprint(prevChartInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	} else {
+		sql += ` AND ( sort > ? )`
+	}
+	_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// GetChartInfoMaxSortByClassifyId 获取分类下指标的最大的排序数
+func GetChartInfoMaxSortByClassifyId(classifyId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT Max(sort) AS sort FROM chart_info WHERE chart_classify_id = ? `
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+// GetChartInfoBySourceAndParentId 根据图表来源及父级ID获取图表
+func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT chart_info_id,chart_classify_id,chart_name AS chart_classify_name,chart_name_en AS chart_classify_name_en,
+             unique_code,sys_user_id,sys_user_real_name,date_type,start_date,end_date,chart_type,calendar,season_start_date,season_end_date,source
+            FROM chart_info WHERE source = ? AND chart_classify_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, source, parentId)
+	if adminId > 0 {
+		sql += ` AND sys_user_id = ?`
+		pars = append(pars, adminId)
+	}
+	sql += ` ORDER BY sort asc,chart_info_id ASC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// PreviewSeasonChartReq 预览季节性图的请求入参
+type PreviewSeasonChartReq struct {
+	ChartEdbInfoList  []*ChartSaveItem `description:"指标及配置信息"`
+	SeasonExtraConfig SeasonExtraItem  `description:"季节图额外配置信息,json字符串"`
+}
+
+// EditChartInfoImageV2
+// @Description: 修改图表的缩略图
+// @author: Roc
+// @datetime 2024-07-09 10:52:34
+// @param chartInfoId int
+// @param imageUrl string
+// @return err error
+func EditChartInfoImageV2(chartInfoId int, imageUrl string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := ` UPDATE  chart_info SET chart_image=?, modify_time = NOW() WHERE chart_info_id = ? `
+	_, err = o.Raw(sql, imageUrl, chartInfoId).Exec()
+	if err != nil {
+		fmt.Println("EditChartInfoImageV2 Err:", err.Error())
+		return err
+	}
+
+	return
+}

+ 218 - 0
models/data_manage/chart_info_correlation.go

@@ -25,6 +25,7 @@ type ChartInfoCorrelation struct {
 	CorrelationData        string    `description:"Y轴-相关性系数"`
 	CreateTime             time.Time `description:"创建时间"`
 	ModifyTime             time.Time `description:"更新时间"`
+	AnalysisMode           int       `description:"分析模式: 0-单因子; 1-多因子"`
 }
 
 type CorrelationInfo struct {
@@ -38,6 +39,7 @@ type CorrelationInfo struct {
 	EdbInfoIdSecond int    `description:"B指标ID"`
 	PeriodData      string `description:"X轴-期数数据"`
 	CorrelationData string `description:"Y轴-相关性系数"`
+	AnalysisMode    int    `description:"分析模式: 0-单因子; 1-多因子"`
 }
 
 func (m *ChartInfoCorrelation) TableName() string {
@@ -135,3 +137,219 @@ func CreateCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingList []*ChartE
 	}
 	return
 }
+
+// FactorCorrelationConfig 因子指标系列-相关性配置
+type FactorCorrelationConfig struct {
+	LeadValue      int                       `description:"领先期数"`
+	LeadUnit       string                    `description:"频度"`
+	CalculateValue int                       `description:"计算窗口"`
+	CalculateUnit  string                    `description:"计算频度"`
+	SeriesEdb      []CorrelationSeriesEdbReq `description:"关联系列指标"`
+	SeriesIds      []int                     `description:"所有因子系列"`
+}
+
+// CorrelationChartLegend 相关性图表图例
+type CorrelationChartLegend struct {
+	LegendName string `description:"图例名称"`
+	Color      string `description:"图例颜色"`
+	EdbInfoId  int    `description:"指标ID"`
+	SeriesId   int    `description:"因子指标系列ID"`
+}
+
+// CorrelationSeriesEdbReq 指标系列
+type CorrelationSeriesEdbReq struct {
+	EdbInfoId int `description:"指标ID"`
+	SeriesId  int `description:"因子指标系列ID"`
+}
+
+// CorrelationChartInfoExtraConfig 相关性图表额外设置
+type CorrelationChartInfoExtraConfig struct {
+	LegendConfig []*CorrelationChartLegend `description:"图例设置"`
+}
+
+// CreateMultiFactorCorrelationChartAndEdb 新增多因子相关性图表
+func CreateMultiFactorCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingList []*ChartEdbMapping, correlationInfo *ChartInfoCorrelation, chartMappings []*FactorEdbSeriesChartMapping, saveAs bool, copySeries []*FactorEdbSeries, copySeriesEdb []*FactorEdbSeriesMapping) (chartInfoId int, seriesIdMap map[int]int, err error) {
+	seriesIdMap = make(map[int]int) // 此处做一个原ID与新ID的映射, 另存为才用的到
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 新增图表信息
+	chartId, e := tx.Insert(chartInfo)
+	if e != nil {
+		err = fmt.Errorf("chart insert err: %v", e)
+		return
+	}
+	chartInfo.ChartInfoId = int(chartId)
+	chartInfoId = chartInfo.ChartInfoId
+
+	// 指标mapping
+	if len(edbMappingList) > 0 {
+		for i := range edbMappingList {
+			edbMappingList[i].ChartInfoId = chartInfoId
+		}
+		_, e = tx.InsertMulti(200, edbMappingList)
+		if e != nil {
+			err = fmt.Errorf("edb mappings insert err: %v", e)
+			return
+		}
+	}
+
+	// 相关性信息
+	correlationInfo.CorrelationChartInfoId = chartInfoId
+	if _, e = tx.Insert(correlationInfo); e != nil {
+		err = fmt.Errorf("correlate chart insert err: %v", e)
+		return
+	}
+
+	// 新增
+	if !saveAs {
+		// 指标系列-图表关联
+		if len(chartMappings) > 0 {
+			chartMappingOb := new(FactorEdbSeriesChartMapping)
+			sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = ?, %s = ?, %s = ? WHERE %s = ?`, chartMappingOb.TableName(), chartMappingOb.Cols().ChartInfoId, chartMappingOb.Cols().Source, chartMappingOb.Cols().EdbUsed, chartMappingOb.Cols().ModifyTime, chartMappingOb.Cols().PrimaryId)
+			p, e := o.Raw(sql).Prepare()
+			if e != nil {
+				err = fmt.Errorf("sql prepare err: %v", e)
+				return
+			}
+			defer func() {
+				_ = p.Close()
+			}()
+			for _, v := range chartMappings {
+				v.ChartInfoId = chartInfoId
+				_, e = p.Exec(v.ChartInfoId, v.Source, v.EdbUsed, v.ModifyTime, v.FactorEdbSeriesChartMappingId)
+				if e != nil {
+					err = fmt.Errorf("update exec err: %v", e)
+					return
+				}
+			}
+		}
+		return
+	}
+
+	// 另存为
+	for _, v := range copySeries {
+		id := v.FactorEdbSeriesId
+		v.FactorEdbSeriesId = 0
+		newId, e := tx.Insert(v)
+		if e != nil {
+			err = fmt.Errorf("copy series err: %v", e)
+			return
+		}
+		seriesIdMap[id] = int(newId)
+	}
+	for _, v := range copySeriesEdb {
+		v.FactorEdbSeriesMappingId = 0
+		v.FactorEdbSeriesId = seriesIdMap[v.FactorEdbSeriesId]
+	}
+	_, e = tx.InsertMulti(200, copySeriesEdb)
+	if e != nil {
+		err = fmt.Errorf("copy series edb mappings err: %v", e)
+		return
+	}
+	if len(chartMappings) > 0 {
+		for _, v := range chartMappings {
+			v.FactorEdbSeriesChartMappingId = 0
+			v.ChartInfoId = chartInfoId
+			v.FactorEdbSeriesId = seriesIdMap[v.FactorEdbSeriesId]
+		}
+		_, e = tx.InsertMulti(200, chartMappings)
+		if e != nil {
+			err = fmt.Errorf("copy series chart mappings err: %v", e)
+			return
+		}
+	}
+	return
+}
+
+// UpdateMultiFactorCorrelationChartAndEdb 编辑多因子相关性图表
+func UpdateMultiFactorCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingList []*ChartEdbMapping, correlationInfo *ChartInfoCorrelation, chartMappings []*FactorEdbSeriesChartMapping, chartUpdateCols, correlateUpdateCols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 更新图表信息
+	_, e = tx.Update(chartInfo, chartUpdateCols...)
+	if e != nil {
+		err = fmt.Errorf("chart update err: %v", e)
+		return
+	}
+
+	// 指标mapping
+	sql := `DELETE FROM chart_edb_mapping WHERE chart_info_id = ?`
+	_, e = tx.Raw(sql, chartInfo.ChartInfoId).Exec()
+	if e != nil {
+		err = fmt.Errorf("clear chart edb mapping err: %v", e)
+		return
+	}
+	if len(edbMappingList) > 0 {
+		for i := range edbMappingList {
+			edbMappingList[i].ChartInfoId = chartInfo.ChartInfoId
+		}
+		_, e = tx.InsertMulti(200, edbMappingList)
+		if e != nil {
+			err = fmt.Errorf("edb mappings insert err: %v", e)
+			return
+		}
+	}
+
+	// 相关性信息
+	_, e = tx.Update(correlationInfo, correlateUpdateCols...)
+	if e != nil {
+		err = fmt.Errorf("correlate chart update err: %v", e)
+		return
+	}
+
+	// 指标系列-图表关联
+	if len(chartMappings) > 0 {
+		chartMappingOb := new(FactorEdbSeriesChartMapping)
+		sql = fmt.Sprintf(`UPDATE %s SET %s = ?, %s = ? WHERE %s = ?`, chartMappingOb.TableName(), chartMappingOb.Cols().EdbUsed, chartMappingOb.Cols().ModifyTime, chartMappingOb.Cols().PrimaryId)
+		p, e := o.Raw(sql).Prepare()
+		if e != nil {
+			err = fmt.Errorf("sql prepare err: %v", e)
+			return
+		}
+		defer func() {
+			_ = p.Close()
+		}()
+		for _, v := range chartMappings {
+			_, e = p.Exec(v.EdbUsed, v.ModifyTime, v.FactorEdbSeriesChartMappingId)
+			if e != nil {
+				err = fmt.Errorf("update exec err: %v", e)
+				return
+			}
+		}
+	}
+	return
+}
+
+// FactorCorrelationEditDetail 编辑页详情
+type FactorCorrelationEditDetail struct {
+	ChartInfoId       int                                    `description:"图表ID"`
+	UniqueCode        string                                 `description:"图表唯一编码"`
+	BaseEdbInfo       *ChartEdbInfoMapping                   `description:"标的指标信息"`
+	EdbSeries         []*FactorEdbSeriesDetail               `description:"指标系列"`
+	CorrelationConfig CorrelationConfig                      `description:"相关性基础配置"`
+	CorrelationMatrix []FactorEdbSeriesCorrelationMatrixItem `description:"相关性矩阵"`
+}

+ 22 - 0
models/data_manage/correlation/request/chart.go

@@ -1,5 +1,7 @@
 package request
 
+import "eta/eta_api/models/data_manage"
+
 // EditChartEnInfoReq
 // @Description: 编辑图表英文信息请求
 type EditChartEnInfoReq struct {
@@ -13,3 +15,23 @@ type EditChartInfoBaseReq struct {
 	ChartInfoId int    `description:"图表ID"`
 	ChartName   string `description:"英文图表名称"`
 }
+
+// CorrelationChartMultiFactorSaveReq 多因子相关性图表-保存请求
+type CorrelationChartMultiFactorSaveReq struct {
+	ChartInfoId       int                                          `description:"图表ID"`
+	ChartName         string                                       `description:"图表名称"`
+	ClassifyId        int                                          `description:"分类id"`
+	AnalysisMode      int                                          `description:"分析模式: 0-单因子; 1-多因子"`
+	BaseEdbInfoId     int                                          `description:"标的指标ID"`
+	FactorCorrelation *data_manage.FactorCorrelationConfig         `description:"多因子指标系列-相关性配置"`
+	SourcesFrom       *data_manage.ChartInfoSourcesFrom            `description:"图表来源"`
+	ExtraConfig       *data_manage.CorrelationChartInfoExtraConfig `description:"额外设置"`
+	SaveAs            bool                                         `description:"是否另存为: true-是"`
+}
+
+// CorrelationChartMultiFactorSaveAsReq 多因子相关性图表-另存为请求
+type CorrelationChartMultiFactorSaveAsReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	ChartName   string `description:"图表名称"`
+	ClassifyId  int    `description:"分类id"`
+}

+ 8 - 0
models/data_manage/edb_classify.go

@@ -653,3 +653,11 @@ func GetEdbClassifyByClassifyTypeAndIsJoinPermission(classifyType, isJoinPermiss
 	_, err = o.Raw(sql, classifyType, isJoinPermission).QueryRows(&items)
 	return
 }
+
+// GetEdbClassifyRootIdsByClassifyIds 获取普通指标的顶级分类列表
+func GetEdbClassifyRootIdsByClassifyIds(classifyIds []int) (items []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT distinct root_id FROM edb_classify WHERE classify_type=0 and classify_id in (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	_, err = o.Raw(sql, classifyIds).QueryRows(&items)
+	return
+}

+ 1 - 1
models/data_manage/edb_data_base.go

@@ -222,7 +222,7 @@ type EdbDataBase struct {
 
 func GetEdbDataAllByEdbCode(edbCode string, source, subSource, limit int) (items []*EdbInfoSearchData, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return GetEdbDataAllByEdbCodeByMongo(edbCode, source, subSource, limit)
 	}
 

+ 1 - 1
models/data_manage/edb_data_insert_config.go

@@ -133,7 +133,7 @@ func CreateEdbDataInsertConfigAndData(edbInfo *EdbInfo, date time.Time, value st
 	}
 
 	// 指标明细数据更新
-	if edbInfo.Source == utils.DATA_SOURCE_BUSINESS {
+	if edbInfo.Source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		dateStr, err = updateInsertConfigValueByMongo(to, edbInfo, oldConfigDate, date, value)
 	} else {
 		dateStr, err = updateInsertConfigValueByMysql(to, edbInfo, oldConfigDate, date, value)

+ 8 - 0
models/data_manage/edb_data_mysteel_chemical.go

@@ -11,3 +11,11 @@ func GetEdbDataMysteelChemicalMaxOrMinDate(edbCode string) (minDate, maxDate str
 	err = o.Raw(sql, edbCode).QueryRow(&minDate, &maxDate)
 	return
 }
+
+// 更新钢联化工指标的刷新状态
+func UpdateMysteelChemicalRefreshStatus(edbCode string, isStop int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = ? WHERE index_code =? and is_stop=1`
+	_, err = o.Raw(sql, isStop, edbCode).Exec()
+	return
+}

+ 41 - 0
models/data_manage/edb_data_wind.go

@@ -1,6 +1,7 @@
 package data_manage
 
 import (
+	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -63,3 +64,43 @@ type EdbDataFromWind struct {
 	Dt     map[string]int64   `json:"DT"`
 	ErrMsg string
 }
+
+func EdbInfoUpdateStatusByEdbInfoId(edbInfoIds []int, isStop int, calculateEdbInfoIds []int) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+
+	// 更改指标的更新状态
+	if len(edbInfoIds) == 1 {
+		sql := ` UPDATE edb_info SET no_update = ? WHERE edb_info_id=? `
+		_, err = o.Raw(sql, isStop, edbInfoIds[0]).Exec()
+		if err != nil {
+			return
+		}
+	} else {
+		sql := ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, edbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	if len(calculateEdbInfoIds) > 0 {
+		// 批量更新相关联的指标ID
+		sql := ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 58 - 5
models/data_manage/edb_info.go

@@ -232,7 +232,7 @@ func DeleteEdbInfoAndData(edbInfoId, source, subSource int) (err error) {
 	}
 
 	// 删除指标的明细数据
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		err = deleteAllEdbDataByMongo(to, edbInfoId, source, subSource)
 	} else {
 		err = deleteAllEdbDataByMysql(to, edbInfoId, source, subSource)
@@ -432,6 +432,11 @@ type EdbInfoListResp struct {
 	ClassifyList []*EdbClassifyIdItems
 }
 
+type WindEdbInfoList struct {
+	*EdbInfoList
+	ClassifyList []*EdbClassifyIdItems
+}
+
 func GetEdbInfoByCondition(condition string, pars []interface{}) (item *EdbInfoList, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM edb_info WHERE 1=1 `
@@ -592,7 +597,7 @@ type EdbInfoMaxAndMinInfo struct {
 // @return err error
 func GetEdbInfoMaxAndMinInfo(source, subSource int, edbCode string) (item *EdbInfoMaxAndMinInfo, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return GetEdbInfoMaxAndMinInfoByMongo(source, subSource, edbCode)
 	}
 
@@ -1135,6 +1140,17 @@ func GetAllCalculateByEdbInfoId(edbInfoId int) (mappingList []*EdbInfoCalculateM
 	return
 }
 
+// 获取指标的所有计算指标,以及计算指标所依赖计算指标
+func GetAllCalculateByEdbInfoIds(edbInfoIds []int) (mappingList []*EdbInfoCalculateMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	msql := ` SELECT * FROM edb_info_calculate_mapping WHERE from_edb_info_id in (` + utils.GetOrmInReplace(len(edbInfoIds)) + `)  GROUP BY edb_info_id `
+	_, err = o.Raw(msql, edbInfoIds).QueryRows(&mappingList)
+	if err != nil {
+		return
+	}
+	return
+}
+
 func GetAllCalculate(edbInfoId int, edbInfoMap map[int]int) (mappingList []*EdbInfoCalculateMapping, err error) {
 	calculateList, err := GetAllCalculateByEdbInfoId(edbInfoId)
 	if err != nil && err.Error() != utils.ErrNoRow() {
@@ -1634,7 +1650,7 @@ func GetEdbBaseInfoList(condition string, pars []interface{}, orderBy string, st
 // @param indexCodeList []string
 // @param isStop int
 // @return err error
-func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int) (err error) {
+func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int, calculateEdbInfoIds []int) (err error) {
 	idNum := len(edbIdList)
 	if idNum <= 0 {
 		return
@@ -1657,7 +1673,14 @@ func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int) (err error) {
 	if err != nil {
 		return
 	}
-
+	if len(calculateEdbInfoIds) > 0 {
+		// 批量更新相关联的指标ID
+		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
 	return
 }
 
@@ -1700,7 +1723,7 @@ func GetEdbCodesBySource(source int) (items []*EdbInfo, err error) {
 // @return err error
 func GetAllEdbDataListData(edbInfoId, source, subSource int, startDataTime string) (dataList []*EdbData, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getAllDataByMongo(edbInfoId, source, subSource, startDataTime)
 	}
 
@@ -1787,6 +1810,25 @@ func getAllDataByMongo(edbInfoId, source, subSource int, startDataTime string) (
 	return
 }
 
+// GetEdbClassifyIdListBySource 获取普通指标的分类列表
+func GetEdbClassifyIdListBySource(source int) (items []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT distinct classify_id FROM edb_info WHERE source = ? and edb_info_type=0`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+
+// GetEdbInfoByClassifyIdAndSource 用于分类展示
+func GetEdbInfoByClassifyIdAndSource(classifyId, edbInfoType, source int) (items []*EdbClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT edb_info_id,classify_id,edb_name AS classify_name,edb_name_en AS classify_name_en,unique_code,source_name,source,sys_user_id,sys_user_real_name,start_date,edb_code,edb_type, sort,is_join_permission FROM edb_info WHERE classify_id = ? AND edb_info_type = ? AND source =?`
+
+	pars := []interface{}{classifyId, edbInfoType, source}
+	sql += ` order by sort asc,edb_info_id asc `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
 type ReplaceEdbInfoItem struct {
 	OldEdbInfo *EdbInfo
 	NewEdbInfo *EdbInfo
@@ -1810,3 +1852,14 @@ func ModifyEdbInfoBaseTimeById(edbInfoId int, cTime time.Time) (err error) {
 	_, err = o.Raw(sql, cTime, edbInfoId).Exec()
 	return
 }
+
+func GetEdbInfoListByCond(condition string, pars []interface{}) (list []*EdbInfoList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM edb_info WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY create_time DESC `
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}

+ 9 - 0
models/data_manage/edb_info_calculate.go

@@ -169,6 +169,7 @@ type EdbInfoBase struct {
 	UnitEn        string `description:"英文单位"`
 	ClassifyId    int    `description:"分类id"`
 	HaveOperaAuth bool   `description:"是否有数据权限,默认:false"`
+	EdbInfoType   int    `description:"指标类型: 0-普通指标; 1-预测指标"`
 }
 
 type CalculateEdbInfoItem struct {
@@ -515,6 +516,7 @@ type CalculateMultiEdbSearchResp struct {
 	SearchItem []CalculateMultiEdbSearchItem `description:"查询结果"`
 	Paging     *paging.PagingItem
 }
+
 type CalculateMultiEdbSearchItem struct {
 	EdbInfoId       int    `description:"指标id"`
 	EdbName         string `description:"指标名称"`
@@ -528,4 +530,11 @@ type CalculateMultiEdbSearchItem struct {
 	EndValue        float64 `description:"数据的最新值(预测日期的最新值)"`
 	EndDate         string  `description:"终止日期"`
 	HaveOperaAuth   bool    `description:"是否有数据权限,默认:false"`
+	EdbInfoType     int     `description:"指标类型: 0-普通指标; 1-预测指标"`
+}
+
+// BaseStepCalculateReq 指标库-基础分步骤计算请求
+type BaseStepCalculateReq struct {
+	DataList   []*EdbInfoSearchData
+	Calculates []FactorEdbSeriesCalculatePars
 }

+ 11 - 0
models/data_manage/edb_info_calculate_mapping.go

@@ -1,6 +1,7 @@
 package data_manage
 
 import (
+	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -101,3 +102,13 @@ func GetEdbInfoCalculateMappingListByEdbInfoId(edbInfoId int) (items []*EdbInfoC
 	_, err = o.Raw(sql, edbInfoId).QueryRows(&items)
 	return
 }
+
+// GetEdbInfoCalculateMappingListByEdbInfoIds 根据生成的指标id获取来源的指标id列表
+func GetEdbInfoCalculateMappingListByEdbInfoIds(edbInfoIds []int) (items []*EdbInfoCalculateMappingInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT a.*,b.edb_type as from_edb_type,b.edb_info_type as from_edb_info_type, b.unique_code AS from_unique_code, b.classify_id AS from_classify_id,b.no_update FROM edb_info_calculate_mapping AS a
+			INNER JOIN edb_info AS b ON a.from_edb_info_id=b.edb_info_id
+			WHERE a.edb_info_id in (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
+	_, err = o.Raw(sql, edbInfoIds).QueryRows(&items)
+	return
+}

+ 518 - 0
models/data_manage/edb_info_relation.go

@@ -0,0 +1,518 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type EdbInfoRelation struct {
+	EdbInfoRelationId  int       `orm:"column(edb_info_relation_id);pk"`
+	EdbInfoId          int       `description:"指标id"`
+	Source             int       `description:"来源:1:同花顺,2:wind,3:彭博,4:指标运算,5:累计值转月,6:同比值,7:同差值,8:N数值移动平均计算,9:手工指标,10:隆众"`
+	EdbName            string    `description:"指标名称"`
+	EdbCode            string    `description:"指标编码"`
+	ReferObjectId      int       `description:"引用对象ID(图表ID,ETA逻辑ID等)"`
+	ReferObjectType    int       `description:"引用对象ID类型(1.图表,2.ETA逻辑)"`
+	ReferObjectSubType int       `description:"引用对象子类"`
+	CreateTime         time.Time `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	RelationTime       time.Time `description:"引用时间"`
+	RelationType       int       `description:"引用类型,0:直接饮用,1间接引用"`
+	RootEdbInfoId      int       `description:"间接引用时,关联的直接引用的指标ID"`
+	ChildEdbInfoId     int       `description:"间接引用时,关联的计算指标ID"`
+	RelationCode       string    `description:"引用标识"`
+	ParentRelationId   int       `description:"间接引用关联的直接引用的ID"`
+}
+
+func (e *EdbInfoRelation) TableName() string {
+	return "edb_info_relation"
+}
+
+// GetEdbInfoRelationByRelationIds 查询引用的指标ID
+func GetEdbInfoRelationByRelationIds(ids []int) (items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	msql := ` SELECT * FROM edb_info_relation WHERE edb_info_relation_id in (` + utils.GetOrmInReplace(len(ids)) + `) `
+	_, err = o.Raw(msql, ids).QueryRows(&items)
+	return
+}
+
+// GetEdbInfoRelationByReferObjectId 查询直接引用的指标ID
+func GetEdbInfoRelationByReferObjectId(referObjectId int, referObjectType int) (items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	msql := ` SELECT * FROM edb_info_relation WHERE refer_object_id =? and relation_type=0 AND refer_object_type=?  GROUP BY edb_info_id `
+	_, err = o.Raw(msql, referObjectId, referObjectType).QueryRows(&items)
+	return
+}
+
+// GetEdbInfoRelationByReferObjectIds 查询引用的指标ID
+func GetEdbInfoRelationByReferObjectIds(referObjectIds []int, referObjectType int) (items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	msql := ` SELECT * FROM edb_info_relation WHERE refer_object_id in (` + utils.GetOrmInReplace(len(referObjectIds)) + `) AND refer_object_type=? and relation_type=0`
+	_, err = o.Raw(msql, referObjectIds, referObjectType).QueryRows(&items)
+	return
+}
+
+// GetEdbInfoRelationAllByReferObjectIds 查询引用的指标ID
+func GetEdbInfoRelationAllByReferObjectIds(referObjectIds []int, referObjectType int) (items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	msql := ` SELECT * FROM edb_info_relation WHERE refer_object_id in (` + utils.GetOrmInReplace(len(referObjectIds)) + `) AND refer_object_type=?`
+	_, err = o.Raw(msql, referObjectIds, referObjectType).QueryRows(&items)
+	return
+}
+
+// 新增记录
+func AddOrUpdateEdbInfoRelation(objectId, objectType int, relationList []*EdbInfoRelation, deleteEdbInfoIds []int, refreshEdbInfoIds []int, indexCodeList []string) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+
+	if len(deleteEdbInfoIds) > 0 {
+		sql := ` DELETE FROM edb_info_relation WHERE refer_object_id = ? AND refer_object_type=? AND edb_info_id in (` + utils.GetOrmInReplace(len(deleteEdbInfoIds)) + `) AND relation_type=0`
+		_, err = o.Raw(sql, objectId, objectType, deleteEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+		// 同时删除相关连的间接引用的指标ID
+		sql = ` DELETE FROM edb_info_relation WHERE refer_object_id = ? AND refer_object_type=? AND root_edb_info_id in (` + utils.GetOrmInReplace(len(deleteEdbInfoIds)) + `) AND relation_type=1 `
+		_, err = o.Raw(sql, objectId, objectType, deleteEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+	relationCodesMap := make(map[string]struct{}, 0)
+	if len(relationList) > 0 {
+		for _, relation := range relationList {
+			if relation.RelationType == 1 {
+				relationCodesMap[relation.RelationCode] = struct{}{}
+			}
+		}
+		_, err = o.InsertMulti(len(relationList), relationList)
+		if err != nil {
+			return
+		}
+	}
+
+	if len(refreshEdbInfoIds) > 0 {
+		//todo 是否需要所有指标的刷新状态
+		sql := ` UPDATE edb_info SET no_update = 0 WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND  no_update = 1`
+		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	//更新数据源钢联化工指标
+	if len(indexCodeList) > 0 {
+		// 更改数据源的更新状态
+		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
+		_, err = o.Raw(sql, indexCodeList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	if len(relationList) > 0 {
+		// 更新间接引用指标的关联ID
+		relationCodes := make([]string, 0)
+		for relationCode := range relationCodesMap {
+			relationCodes = append(relationCodes, relationCode)
+		}
+		if len(relationCodes) > 0 {
+			sql := ` UPDATE edb_info_relation e1  
+JOIN edb_info_relation e2 ON e1.relation_code = e2.relation_code   
+SET e1.parent_relation_id = e2.edb_info_relation_id  
+WHERE  
+    e1.relation_type = 1   
+    AND e2.relation_type = 0 AND e1.parent_relation_id !=e2.edb_info_relation_id AND e1.relation_code in (` + utils.GetOrmInReplace(len(relationCodes)) + `)`
+			_, err = o.Raw(sql, relationCodes).Exec()
+			if err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+// 新增记录
+func AddOrUpdateEdbInfoRelationMulti(relationList []*EdbInfoRelation, refreshEdbInfoIds []int, indexCodeList []string) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+
+	relationCodesMap := make(map[string]struct{}, 0)
+	if len(relationList) > 0 {
+		for _, relation := range relationList {
+			if relation.RelationType == 1 {
+				relationCodesMap[relation.RelationCode] = struct{}{}
+			}
+		}
+		_, err = o.InsertMulti(len(relationList), relationList)
+		if err != nil {
+			return
+		}
+	}
+
+	if len(refreshEdbInfoIds) > 0 {
+		// todo 更新指标的刷新状态
+		sql := ` UPDATE edb_info SET no_update = 0 WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND no_update = 1`
+		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	//更新数据源钢联化工指标
+	if len(indexCodeList) > 0 {
+		// 更改数据源的更新状态
+		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
+		_, err = o.Raw(sql, indexCodeList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	if len(relationList) > 0 {
+		// 更新间接引用指标的关联ID
+		relationCodes := make([]string, 0)
+		for relationCode := range relationCodesMap {
+			relationCodes = append(relationCodes, relationCode)
+		}
+		if len(relationCodes) > 0 {
+			sql := ` UPDATE edb_info_relation e1  
+JOIN edb_info_relation e2 ON e1.relation_code = e2.relation_code   
+SET e1.parent_relation_id = e2.edb_info_relation_id  
+WHERE  
+    e1.relation_type = 1   
+    AND e2.relation_type = 0 AND e1.parent_relation_id !=e2.edb_info_relation_id AND e1.relation_code in (` + utils.GetOrmInReplace(len(relationCodes)) + `)`
+			_, err = o.Raw(sql, relationCodes).Exec()
+			if err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+// 删除指标引用内容
+func DeleteEdbRelationByObjectIds(referObjectIds []int, referObjectType int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` DELETE FROM edb_info_relation WHERE refer_object_id in (` + utils.GetOrmInReplace(len(referObjectIds)) + `) AND refer_object_type=?`
+	_, err = o.Raw(sql, referObjectIds, referObjectType).Exec()
+	return
+}
+
+// DeleteEdbRelationByObjectId 删除指标引用内容
+func DeleteEdbRelationByObjectId(referObjectId int, referObjectType int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` DELETE FROM edb_info_relation WHERE refer_object_id =? AND refer_object_type=?`
+	_, err = o.Raw(sql, referObjectId, referObjectType).Exec()
+	return
+}
+
+type BaseRelationEdbInfo struct {
+	EdbInfoId       int
+	ClassifyId      int    `description:"指标分类id"`
+	EdbName         string `description:"指标名称"`
+	EdbCode         string `description:"指标编码"`
+	SysUserId       int    `description:"创建人id"`
+	SysUserRealName string `description:"创建人姓名"`
+	Frequency       string `description:"频度"`
+	IsStop          int    `description:"是否停更:1:停更,0:未停更"`
+	RelationNum     int    `description:"引用次数"`
+	RelationTime    string `description:"引用时间"`
+}
+
+type BaseRelationEdbInfoResp struct {
+	Paging *paging.PagingItem
+	List   []*BaseRelationEdbInfo
+}
+
+type EdbInfoRelationDetail struct {
+	EdbInfoRelationId   int    `orm:"column(edb_info_relation_id);pk"`
+	EdbInfoId           int    `description:"指标id"`
+	ReferObjectId       int    `description:"引用对象ID(图表ID,ETA逻辑ID等)"`
+	ReferObjectTypeName string `description:"引用对象类型"`
+	ReferObjectType     int    `description:"引用对象ID类型(1.图表,2.ETA逻辑)"`
+	ReferObjectSubType  int    `description:"引用对象子类"`
+	RelationTime        string `description:"引用时间"`
+	ReferObjectName     string `description:"引用对象名称"`
+}
+type BaseRelationEdbInfoDetailResp struct {
+	Paging *paging.PagingItem
+	List   []*EdbInfoRelationDetail
+}
+
+// 查询指标引用列表
+func GetEdbInfoRelationList(condition string, pars []interface{}, orderBy string, startSize, pageSize int) (total int, items []*BaseRelationEdbInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 数量汇总
+	totalSql := ` SELECT count(1) FROM edb_info e LEFT JOIN (
+SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as relation_time FROM edb_info_relation GROUP BY edb_info_id) r on e.edb_info_id=r.edb_info_id WHERE 1=1 `
+	if condition != "" {
+		totalSql += condition
+	}
+	err = o.Raw(totalSql, pars).QueryRow(&total)
+	if err != nil {
+		return
+	}
+
+	// 列表数据
+	sql := ` SELECT e.edb_info_id, e.classify_id,e.edb_code,e.edb_name,e.sys_user_id,e.sys_user_real_name,e.frequency,e.no_update as is_stop, r.relation_num, r.relation_time from edb_info e LEFT JOIN (
+SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as relation_time FROM edb_info_relation GROUP BY edb_info_id) r on e.edb_info_id=r.edb_info_id WHERE 1=1
+ `
+	if condition != "" {
+		sql += condition
+	}
+
+	if orderBy != "" {
+		sql += ` ORDER BY ` + orderBy
+	} else {
+		sql += ` ORDER BY edb_info_id ASC `
+	}
+	sql += `  LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+
+	return
+}
+
+// GetEdbInfoRelationDetailList 查询指标引用详情列表
+func GetEdbInfoRelationDetailList(edbInfoId int, startSize, pageSize int) (total int, items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 数量汇总
+	totalSql := ` SELECT count(*) FROM edb_info_relation where edb_info_id=?`
+	err = o.Raw(totalSql, edbInfoId).QueryRow(&total)
+	if err != nil {
+		return
+	}
+
+	// 列表数据
+	sql := ` SELECT *FROM edb_info_relation where edb_info_id=? ORDER BY relation_time desc, edb_info_id ASC `
+	sql += `  LIMIT ?,? `
+	_, err = o.Raw(sql, edbInfoId, startSize, pageSize).QueryRows(&items)
+
+	return
+}
+
+// 查询相关的指标记录总数
+func GetReplaceChildEdbInfoRelationTotal(edbInfoId int) (total int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 数量汇总
+	totalSql := ` SELECT count(*) FROM edb_info_relation where  relation_type=1 and (child_edb_info_id=? or edb_info_id=? ) group by parent_relation_id`
+	err = o.Raw(totalSql, edbInfoId, edbInfoId).QueryRow(&total)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// 查询相关的指标记录列表
+func GetReplaceChildEdbInfoRelationList(edbInfoId int, startSize, pageSize int) (items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 列表数据
+	sql := ` SELECT * FROM edb_info_relation where  relation_type=1 and (child_edb_info_id=? or edb_info_id=? )  group by parent_relation_id ORDER BY edb_info_relation_id ASC  LIMIT ?,? `
+	_, err = o.Raw(sql, edbInfoId, edbInfoId, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// 查询相关的指标记录总数
+func GetReplaceEdbInfoRelationTotal(edbInfoId int) (total int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 数量汇总
+	totalSql := ` SELECT count(*) FROM edb_info_relation where edb_info_id=? and relation_type = 0`
+	err = o.Raw(totalSql, edbInfoId).QueryRow(&total)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// 查询相关的指标记录列表
+func GetReplaceEdbInfoRelationList(edbInfoId int, startSize, pageSize int) (items []*EdbInfoRelation, err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 列表数据
+	sql := ` SELECT * FROM edb_info_relation where edb_info_id=? and relation_type = 0 ORDER BY edb_info_relation_id ASC LIMIT ?,? `
+	_, err = o.Raw(sql, edbInfoId, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// 替换指标引用表中直接引用的指标
+func ReplaceRelationEdbInfoId(oldEdbInfo, newEdbInfo *EdbInfo, edbRelationIds []int, relationList []*EdbInfoRelation, refreshEdbInfoIds []int, indexCodeList []string) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+
+	now := time.Now()
+	// 删除相关的间接引用
+	sql := ` DELETE FROM edb_info_relation WHERE root_edb_info_id=?  and relation_type=1 and parent_relation_id in (` + utils.GetOrmInReplace(len(edbRelationIds)) + `)`
+	_, err = o.Raw(sql, oldEdbInfo.EdbInfoId, edbRelationIds).Exec()
+	if err != nil {
+		return
+	}
+
+	sourceWhere := ` and (refer_object_type in (1,2 ) or (refer_object_type=4 and refer_object_sub_type !=5) )` //平衡表和事件日历中的直接引用无需替换,
+	// 替换edb_info_id
+	sql = ` UPDATE edb_info_relation SET edb_info_id=?, source=?, edb_name=?, edb_code=?, modify_time=?, relation_time=?  WHERE edb_info_id=? ` + sourceWhere + ` and relation_type=0 and edb_info_relation_id in (` + utils.GetOrmInReplace(len(edbRelationIds)) + `)`
+	_, err = o.Raw(sql, newEdbInfo.EdbInfoId, newEdbInfo.Source, newEdbInfo.EdbName, newEdbInfo.EdbCode, now, now, oldEdbInfo.EdbInfoId, edbRelationIds).Exec()
+	if err != nil {
+		return
+	}
+
+	// 更新code值
+	sql = ` UPDATE edb_info_relation SET relation_code=CONCAT_WS("_", edb_info_id,refer_object_id,refer_object_type,refer_object_sub_type)  WHERE relation_type=0 ` + sourceWhere + `  and edb_info_relation_id in (` + utils.GetOrmInReplace(len(edbRelationIds)) + `)`
+	_, err = o.Raw(sql, edbRelationIds).Exec()
+	if err != nil {
+		return
+	}
+	// 新增间接引用
+	relationCodesMap := make(map[string]struct{}, 0)
+	if len(relationList) > 0 {
+		for _, relation := range relationList {
+			if relation.RelationType == 1 {
+				relationCodesMap[relation.RelationCode] = struct{}{}
+			}
+		}
+		_, err = o.InsertMulti(len(relationList), relationList)
+		if err != nil {
+			return
+		}
+	}
+
+	if len(refreshEdbInfoIds) > 0 {
+		// todo 更新指标的刷新状态
+		sql := ` UPDATE edb_info SET no_update = 0 WHERE  edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND no_update = 1`
+		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	//更新数据源钢联化工指标
+	if len(indexCodeList) > 0 {
+		// 更改数据源的更新状态
+		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
+		_, err = o.Raw(sql, indexCodeList).Exec()
+		if err != nil {
+			return
+		}
+	}
+	if len(relationList) > 0 {
+		// 更新间接引用指标的关联ID
+		relationCodes := make([]string, 0)
+		for relationCode := range relationCodesMap {
+			relationCodes = append(relationCodes, relationCode)
+		}
+		if len(relationCodes) > 0 {
+			sql := ` UPDATE edb_info_relation e1  
+JOIN edb_info_relation e2 ON e1.relation_code = e2.relation_code   
+SET e1.parent_relation_id = e2.edb_info_relation_id  
+WHERE  
+    e1.relation_type = 1   
+    AND e2.relation_type = 0 AND e1.parent_relation_id !=e2.edb_info_relation_id AND e1.relation_code in (` + utils.GetOrmInReplace(len(relationCodes)) + `)`
+			_, err = o.Raw(sql, relationCodes).Exec()
+			if err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+// UpdateSecondRelationEdbInfoId 更新指标替换后的间接引用记录
+func UpdateSecondRelationEdbInfoId(edbRelationIds []int, relationList []*EdbInfoRelation, refreshEdbInfoIds []int, indexCodeList []string) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+	// 删除相关的间接引用
+	sql := ` DELETE FROM edb_info_relation WHERE relation_type=1 and parent_relation_id in (` + utils.GetOrmInReplace(len(edbRelationIds)) + `)`
+	_, err = o.Raw(sql, edbRelationIds).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增间接引用
+	relationCodesMap := make(map[string]struct{}, 0)
+	if len(relationList) > 0 {
+		for _, relation := range relationList {
+			if relation.RelationType == 1 {
+				relationCodesMap[relation.RelationCode] = struct{}{}
+			}
+		}
+		_, err = o.InsertMulti(len(relationList), relationList)
+		if err != nil {
+			return
+		}
+	}
+
+	if len(refreshEdbInfoIds) > 0 {
+		// todo 更新指标的刷新状态
+		sql := ` UPDATE edb_info SET no_update = 0 WHERE  edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND no_update = 1`
+		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	//更新数据源钢联化工指标
+	if len(indexCodeList) > 0 {
+		// 更改数据源的更新状态
+		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
+		_, err = o.Raw(sql, indexCodeList).Exec()
+		if err != nil {
+			return
+		}
+	}
+	if len(relationList) > 0 {
+		// 更新间接引用指标的关联ID
+		relationCodes := make([]string, 0)
+		for relationCode := range relationCodesMap {
+			relationCodes = append(relationCodes, relationCode)
+		}
+		if len(relationCodes) > 0 {
+			sql := ` UPDATE edb_info_relation e1  
+JOIN edb_info_relation e2 ON e1.relation_code = e2.relation_code   
+SET e1.parent_relation_id = e2.edb_info_relation_id  
+WHERE  
+    e1.relation_type = 1   
+    AND e2.relation_type = 0 AND e1.parent_relation_id !=e2.edb_info_relation_id AND e1.relation_code in (` + utils.GetOrmInReplace(len(relationCodes)) + `)`
+			_, err = o.Raw(sql, relationCodes).Exec()
+			if err != nil {
+				return
+			}
+		}
+	}
+	return
+}

+ 22 - 0
models/data_manage/edb_refresh/request/edb_info_refresh.go

@@ -50,3 +50,25 @@ type SaveEdbRefreshStatusReq struct {
 	EdbSelectIdList []int  `description:"选择的指标id列表"`
 	ModifyStatus    string `description:"需要更改的状态,枚举值:启用、暂停"`
 }
+
+// SaveEdbRefreshStatusSingleReq
+// @Description: 设置单个指标的刷新状态
+type SaveEdbRefreshStatusSingleReq struct {
+	EdbInfoId    int
+	ModifyStatus string `description:"需要更改的状态,枚举值:启用、暂停"`
+}
+
+// SaveRelationEdbRefreshStatusReq
+// @Description: 设置被引用的指标的刷新状态
+type SaveRelationEdbRefreshStatusReq struct {
+	Source          int    `description:"来源"`
+	ClassifyId      string `description:"分类id,支持多选,用英文,隔开"`
+	SysUserId       string `description:"操作人id,支持多选,用英文,隔开"`
+	Frequency       string `description:"频度,支持多选,用英文,隔开"`
+	Status          string `description:"状态,枚举值:启用、暂停"`
+	EdbInfoType     int    `description:"1计算指标,2预测指标"`
+	Keyword         string `description:"关键字"`
+	IsSelectAll     bool   `description:"是否选择所有指标"`
+	EdbSelectIdList []int  `description:"选择的指标id列表"`
+	ModifyStatus    string `description:"需要更改的状态,枚举值:启用、暂停"`
+}

+ 9 - 0
models/data_manage/excel/excel_edb_mapping.go

@@ -119,6 +119,15 @@ func GetAllExcelEdbMappingByExcelInfoId(excelInfoId int) (items []*ExcelEdbMappi
 	return
 }
 
+// GetAllExcelEdbMappingByExcelInfoIds 根据excel的id获取所有的指标
+func GetAllExcelEdbMappingByExcelInfoIds(excelInfoIds []int) (items []*ExcelEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.* FROM  excel_edb_mapping a
+         WHERE a.excel_info_id in (` + utils.GetOrmInReplace(len(excelInfoIds)) + `) ORDER BY a.excel_edb_mapping_id ASC `
+	_, err = o.Raw(sql, excelInfoIds).QueryRows(&items)
+	return
+}
+
 // GetExcelEdbMappingByEdbInfoIdAndSource 根据指标id获取配置关系
 func GetExcelEdbMappingByEdbInfoIdAndSource(edbInfoId int, sources []int) (items []*ExcelEdbMapping, err error) {
 	o := orm.NewOrmUsingDB("data")

+ 10 - 0
models/data_manage/excel/request/mixed_table.go

@@ -45,6 +45,16 @@ type MixedTableCellDataReq struct {
 	Extra           string      `description:"额外参数"`
 	ShowStyle       string      `description:"展示的样式配置"`
 	ShowFormatValue interface{} `description:"样式处理后的值"`
+	MerData         *struct {
+		Type string `json:"type"`
+		Mer  struct {
+			SKey    string `json:"sKey"`
+			Rowspan int    `json:"rowspan"`
+			Colspan int    `json:"colspan"`
+			Row     int    `json:"row"`
+			Col     int    `json:"col"`
+		} `json:"mer"`
+	} `json:"merData" description:"合并单元格"`
 }
 
 // CellRelationConf

+ 351 - 0
models/data_manage/factor_edb_series.go

@@ -0,0 +1,351 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	FactorEdbSeriesCalculateNone = 0
+	FactorEdbSeriesCalculating   = 1
+	FactorEdbSeriesCalculated    = 2
+)
+
+// FactorEdbSeries 因子指标系列表
+type FactorEdbSeries struct {
+	FactorEdbSeriesId int       `orm:"column(factor_edb_series_id);pk"`
+	SeriesName        string    `description:"系列名称"`
+	EdbInfoType       int       `description:"关联指标类型:0-普通指标;1-预测指标"`
+	CalculateStep     string    `description:"计算步骤-JSON"`
+	CalculateState    int       `description:"计算状态: 0-无计算; 1-计算中; 2-计算完成"`
+	CreateTime        time.Time `description:"创建时间"`
+	ModifyTime        time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeries) TableName() string {
+	return "factor_edb_series"
+}
+
+type FactorEdbSeriesCols struct {
+	PrimaryId      string
+	SeriesName     string
+	EdbInfoType    string
+	CalculateStep  string
+	CalculateState string
+	CreateTime     string
+	ModifyTime     string
+}
+
+func (m *FactorEdbSeries) Cols() FactorEdbSeriesCols {
+	return FactorEdbSeriesCols{
+		PrimaryId:      "factor_edb_series_id",
+		SeriesName:     "series_name",
+		EdbInfoType:    "edb_info_type",
+		CalculateStep:  "calculate_step",
+		CalculateState: "calculate_state",
+		CreateTime:     "create_time",
+		ModifyTime:     "modify_time",
+	}
+}
+
+func (m *FactorEdbSeries) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesId = int(id)
+	return
+}
+
+func (m *FactorEdbSeries) CreateMulti(items []*FactorEdbSeries) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeries) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeries) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesId).Exec()
+	return
+}
+
+func (m *FactorEdbSeries) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeries) GetItemById(id int) (item *FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeries) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeries) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeries) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeries) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesItem 多因子系列信息
+type FactorEdbSeriesItem struct {
+	SeriesId      int                            `description:"多因子系列ID"`
+	SeriesName    string                         `description:"系列名称"`
+	EdbInfoType   int                            `description:"关联指标类型:0-普通指标;1-预测指标"`
+	CalculateStep []FactorEdbSeriesCalculatePars `description:"计算步骤-JSON"`
+	CreateTime    string                         `description:"创建时间"`
+	ModifyTime    string                         `description:"修改时间"`
+}
+
+func (m *FactorEdbSeries) Format2Item() (item *FactorEdbSeriesItem) {
+	item = new(FactorEdbSeriesItem)
+	item.SeriesId = m.FactorEdbSeriesId
+	item.SeriesName = m.SeriesName
+	item.EdbInfoType = m.EdbInfoType
+	if m.CalculateStep != "" {
+		_ = json.Unmarshal([]byte(m.CalculateStep), &item.CalculateStep)
+	}
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+// FactorEdbSeriesCalculatePars 计算参数
+type FactorEdbSeriesCalculatePars struct {
+	Formula       interface{} `description:"N值/移动天数/指数修匀alpha值/计算公式等"`
+	Calendar      string      `description:"公历/农历"`
+	Frequency     string      `description:"需要转换的频度"`
+	MoveType      int         `description:"移动方式: 1-领先(默认); 2-滞后"`
+	MoveFrequency string      `description:"移动频度"`
+	FromFrequency string      `description:"来源的频度"`
+	Source        int         `description:"计算方式来源(不是指标来源)"`
+	Sort          int         `description:"计算顺序"`
+}
+
+// CreateSeriesAndMapping 新增系列和指标关联
+func (m *FactorEdbSeries) CreateSeriesAndMapping(item *FactorEdbSeries, mappings []*FactorEdbSeriesMapping) (seriesId int, err error) {
+	if item == nil {
+		err = fmt.Errorf("series is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	id, e := tx.Insert(item)
+	if e != nil {
+		err = fmt.Errorf("insert series err: %v", e)
+		return
+	}
+	seriesId = int(id)
+	item.FactorEdbSeriesId = seriesId
+
+	if len(mappings) > 0 {
+		for _, v := range mappings {
+			v.FactorEdbSeriesId = seriesId
+		}
+		_, e = tx.InsertMulti(200, mappings)
+		if e != nil {
+			err = fmt.Errorf("insert multi mapping err: %v", e)
+			return
+		}
+	}
+	return
+}
+
+// EditSeriesAndMapping 编辑系列和指标关联
+func (m *FactorEdbSeries) EditSeriesAndMapping(item *FactorEdbSeries, mappings []*FactorEdbSeriesMapping, updateCols []string) (err error) {
+	if item == nil {
+		err = fmt.Errorf("series is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	_, e = tx.Update(item, updateCols...)
+	if e != nil {
+		err = fmt.Errorf("update series err: %v", e)
+		return
+	}
+
+	// 清除原指标关联
+	mappingOb := new(FactorEdbSeriesMapping)
+	cond := fmt.Sprintf("%s = ?", mappingOb.Cols().FactorEdbSeriesId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, item.FactorEdbSeriesId)
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, mappingOb.TableName(), cond)
+	_, e = tx.Raw(sql, pars).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+
+	if len(mappings) > 0 {
+		for _, v := range mappings {
+			v.FactorEdbSeriesId = item.FactorEdbSeriesId
+		}
+		_, e = tx.InsertMulti(200, mappings)
+		if e != nil {
+			err = fmt.Errorf("insert multi mapping err: %v", e)
+			return
+		}
+	}
+	return
+}
+
+// FactorEdbSeriesStepCalculateResp 批量计算响应
+type FactorEdbSeriesStepCalculateResp struct {
+	SeriesId int                                  `description:"多因子指标系列ID"`
+	Fail     []FactorEdbSeriesStepCalculateResult `description:"计算失败的指标"`
+	Success  []FactorEdbSeriesStepCalculateResult `description:"计算成功的指标"`
+}
+
+// FactorEdbSeriesStepCalculateResult 批量计算结果
+type FactorEdbSeriesStepCalculateResult struct {
+	EdbInfoId int    `description:"指标ID"`
+	EdbCode   string `description:"指标编码"`
+	Msg       string `description:"提示信息"`
+	ErrMsg    string `description:"错误信息"`
+}
+
+// FactorEdbSeriesDetail 因子指标系列-详情
+type FactorEdbSeriesDetail struct {
+	*FactorEdbSeriesItem
+	EdbMappings []*FactorEdbSeriesMappingItem
+}
+
+// FactorEdbSeriesCorrelationMatrixResp 因子指标系列-相关性矩阵响应
+type FactorEdbSeriesCorrelationMatrixResp struct {
+	Fail    []FactorEdbSeriesCorrelationMatrixItem `description:"计算失败的指标"`
+	Success []FactorEdbSeriesCorrelationMatrixItem `description:"计算成功的指标"`
+}
+
+// FactorEdbSeriesCorrelationMatrixItem 因子指标系列-相关性矩阵信息
+type FactorEdbSeriesCorrelationMatrixItem struct {
+	SeriesId     int                                      `description:"因子指标系列ID"`
+	EdbInfoId    int                                      `description:"指标ID"`
+	EdbCode      string                                   `description:"指标编码"`
+	EdbName      string                                   `description:"指标名称"`
+	Values       []FactorEdbSeriesCorrelationMatrixValues `description:"X轴和Y轴数据"`
+	Msg          string                                   `description:"提示信息"`
+	ErrMsg       string                                   `description:"错误信息"`
+	Used         bool                                     `description:"是否选中"`
+	SourceName   string                                   `description:"指标来源名称"`
+	SourceNameEn string                                   `description:"英文指标来源名称"`
+}
+
+// FactorEdbSeriesCorrelationMatrixValues 因子指标系列-相关性矩阵XY值
+type FactorEdbSeriesCorrelationMatrixValues struct {
+	XData int     `description:"X轴数据"`
+	YData float64 `description:"Y轴数据"`
+}
+
+// FactorEdbSeriesCorrelationMatrixOrder 排序规则[0 1 2 3 -1 -2 -3]
+type FactorEdbSeriesCorrelationMatrixOrder []FactorEdbSeriesCorrelationMatrixValues
+
+func (a FactorEdbSeriesCorrelationMatrixOrder) Len() int {
+	return len(a)
+}
+
+func (a FactorEdbSeriesCorrelationMatrixOrder) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a FactorEdbSeriesCorrelationMatrixOrder) Less(i, j int) bool {
+	// 非负数优先
+	if a[i].XData >= 0 && a[j].XData < 0 {
+		return true
+	}
+	if a[i].XData < 0 && a[j].XData >= 0 {
+		return false
+	}
+	// 非负数升序排序
+	if a[i].XData >= 0 {
+		return a[i].XData < a[j].XData
+	}
+	// 负数按绝对值的降序排序(即数值的升序)
+	return a[i].XData > a[j].XData
+}

+ 190 - 0
models/data_manage/factor_edb_series_calculate_data.go

@@ -0,0 +1,190 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// FactorEdbSeriesCalculateData 因子指标系列-指标计算数据表
+type FactorEdbSeriesCalculateData struct {
+	FactorEdbSeriesCalculateDataId int       `orm:"column(factor_edb_series_calculate_data_id);pk"`
+	FactorEdbSeriesId              int       `description:"因子指标系列ID"`
+	EdbInfoId                      int       `description:"指标ID"`
+	EdbCode                        string    `description:"指标编码"`
+	DataTime                       time.Time `description:"数据日期"`
+	Value                          float64   `description:"数据值"`
+	CreateTime                     time.Time `description:"创建时间"`
+	ModifyTime                     time.Time `description:"修改时间"`
+	DataTimestamp                  int64     `description:"数据日期时间戳"`
+}
+
+func (m *FactorEdbSeriesCalculateData) TableName() string {
+	return "factor_edb_series_calculate_data"
+}
+
+type FactorEdbSeriesCalculateDataCols struct {
+	PrimaryId         string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbCode           string
+	DataTime          string
+	Value             string
+	CreateTime        string
+	ModifyTime        string
+	DataTimestamp     string
+}
+
+func (m *FactorEdbSeriesCalculateData) Cols() FactorEdbSeriesCalculateDataCols {
+	return FactorEdbSeriesCalculateDataCols{
+		PrimaryId:         "factor_edb_series_calculate_data_id",
+		FactorEdbSeriesId: "factor_edb_series_id",
+		EdbInfoId:         "edb_info_id",
+		EdbCode:           "edb_code",
+		DataTime:          "data_time",
+		Value:             "value",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+		DataTimestamp:     "data_timestamp",
+	}
+}
+
+func (m *FactorEdbSeriesCalculateData) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesCalculateDataId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) CreateMulti(items []*FactorEdbSeriesCalculateData) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(500, items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesCalculateDataId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) GetItemById(id int) (item *FactorEdbSeriesCalculateData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesCalculateData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesCalculateData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateData) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesCalculateData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesCalculateDataItem 因子指标系列-计算数据信息
+type FactorEdbSeriesCalculateDataItem struct {
+	DataId            int     `description:"数据ID"`
+	FactorEdbSeriesId int     `description:"因子指标系列ID"`
+	EdbInfoId         int     `description:"指标ID"`
+	EdbCode           string  `description:"指标编码"`
+	DataTime          string  `description:"数据日期"`
+	Value             float64 `description:"数据值"`
+}
+
+func (m *FactorEdbSeriesCalculateData) Format2Item() (item *FactorEdbSeriesCalculateDataItem) {
+	item = new(FactorEdbSeriesCalculateDataItem)
+	item.DataId = m.FactorEdbSeriesCalculateDataId
+	item.FactorEdbSeriesId = m.FactorEdbSeriesId
+	item.EdbInfoId = m.EdbInfoId
+	item.EdbCode = m.EdbCode
+	return
+}
+
+// TransEdbSeriesCalculateData2EdbDataList 转换数据格式
+func TransEdbSeriesCalculateData2EdbDataList(items []*FactorEdbSeriesCalculateData) (list []*EdbDataList) {
+	list = make([]*EdbDataList, 0)
+	for _, v := range items {
+		list = append(list, &EdbDataList{
+			DataTime: v.DataTime.Format(utils.FormatDate),
+			Value:    v.Value,
+		})
+	}
+	return
+}

+ 153 - 0
models/data_manage/factor_edb_series_calculate_func.go

@@ -0,0 +1,153 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// FactorEdbSeriesCalculateFunc 多因子系列-计算方式表
+type FactorEdbSeriesCalculateFunc struct {
+	FactorEdbSeriesCalculateFuncId int       `orm:"column(factor_edb_series_calculate_func_id);pk"`
+	CalculateName                  string    `description:"计算方式名称"`
+	Source                         int       `description:"计算方式来源"`
+	EdbInfoType                    int       `description:"指标计算类型:0-普通指标;1-预测指标"`
+	CreateTime                     time.Time `description:"创建时间"`
+	ModifyTime                     time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeriesCalculateFunc) TableName() string {
+	return "factor_edb_series_calculate_func"
+}
+
+type FactorEdbSeriesCalculateFuncCols struct {
+	PrimaryId     string
+	CalculateName string
+	Source        string
+	EdbInfoType   string
+	CreateTime    string
+	ModifyTime    string
+}
+
+func (m *FactorEdbSeriesCalculateFunc) Cols() FactorEdbSeriesCalculateFuncCols {
+	return FactorEdbSeriesCalculateFuncCols{
+		PrimaryId:     "factor_edb_series_calculate_func_id",
+		CalculateName: "calculate_name",
+		Source:        "source",
+		EdbInfoType:   "edb_info_type",
+		CreateTime:    "create_time",
+		ModifyTime:    "modify_time",
+	}
+}
+
+func (m *FactorEdbSeriesCalculateFunc) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesCalculateFuncId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) CreateMulti(items []*FactorEdbSeriesCalculateFunc) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesCalculateFuncId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) GetItemById(id int) (item *FactorEdbSeriesCalculateFunc, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesCalculateFunc, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesCalculateFunc, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateFunc) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesCalculateFunc, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesCalculateFuncItem 多因子系列-计算方式
+type FactorEdbSeriesCalculateFuncItem struct {
+	CalculateName string `description:"计算方式名称"`
+	Source        int    `description:"计算方式来源"`
+}
+
+func (m *FactorEdbSeriesCalculateFunc) Format2Item() (item *FactorEdbSeriesCalculateFuncItem) {
+	item = new(FactorEdbSeriesCalculateFuncItem)
+	item.CalculateName = m.CalculateName
+	item.Source = m.Source
+	return
+}

+ 211 - 0
models/data_manage/factor_edb_series_chart_mapping.go

@@ -0,0 +1,211 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	FactorEdbSeriesChartCalculateTypeCorrelation = 1 // 相关性计算
+)
+
+// FactorEdbSeriesChartMapping 因子指标系列-图表关联
+type FactorEdbSeriesChartMapping struct {
+	FactorEdbSeriesChartMappingId int       `orm:"column(factor_edb_series_chart_mapping_id);pk"`
+	ChartInfoId                   int       `description:"图表ID"`
+	Source                        int       `description:"图表来源, 同chart_info表source"`
+	CalculateType                 int       `description:"计算方式: 1-相关性"`
+	CalculatePars                 string    `description:"计算参数-JSON(如计算窗口等)"`
+	CalculateData                 string    `description:"计算数据-JSON(如相关性矩阵等)"`
+	FactorEdbSeriesId             int       `description:"因子指标系列ID"`
+	EdbInfoId                     int       `description:"指标ID"`
+	EdbUsed                       int       `description:"指标是否使用: 0-否; 1-是"`
+	CreateTime                    time.Time `description:"创建时间"`
+	ModifyTime                    time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeriesChartMapping) TableName() string {
+	return "factor_edb_series_chart_mapping"
+}
+
+type MultipleFactorSeriesChartMappingCols struct {
+	PrimaryId         string
+	ChartInfoId       string
+	Source            string
+	CalculateType     string
+	CalculatePars     string
+	CalculateData     string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbUsed           string
+	CreateTime        string
+	ModifyTime        string
+}
+
+func (m *FactorEdbSeriesChartMapping) Cols() MultipleFactorSeriesChartMappingCols {
+	return MultipleFactorSeriesChartMappingCols{
+		PrimaryId:         "factor_edb_series_chart_mapping_id",
+		ChartInfoId:       "chart_info_id",
+		Source:            "source",
+		CalculateType:     "calculate_type",
+		CalculatePars:     "calculate_pars",
+		CalculateData:     "calculate_data",
+		FactorEdbSeriesId: "factor_edb_series_id",
+		EdbInfoId:         "edb_info_id",
+		EdbUsed:           "edb_used",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+	}
+}
+
+func (m *FactorEdbSeriesChartMapping) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesChartMappingId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) CreateMulti(items []*FactorEdbSeriesChartMapping) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesChartMappingId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetItemById(id int) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetDistinctSeriesIdByChartId 获取图表关联的系列ID
+func (m *FactorEdbSeriesChartMapping) GetDistinctSeriesIdByChartId(chartId int) (seriesIds []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT DISTINCT %s FROM %s WHERE %s = ?`, m.Cols().FactorEdbSeriesId, m.TableName(), m.Cols().ChartInfoId)
+	_, err = o.Raw(sql, chartId).QueryRows(&seriesIds)
+	return
+}
+
+// ClearAndCreateMapping 新增图表关联
+func (m *FactorEdbSeriesChartMapping) ClearAndCreateMapping(seriesIds []int, mappings []*FactorEdbSeriesChartMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if len(seriesIds) > 0 {
+		sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s) AND %s = 0`, m.TableName(), m.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)), m.Cols().ChartInfoId)
+		_, e = tx.Raw(sql, seriesIds).Exec()
+		if e != nil {
+			err = fmt.Errorf("remove chart mapping err: %v", e)
+			return
+		}
+	}
+
+	if len(mappings) > 0 {
+		_, e = tx.InsertMulti(200, mappings)
+		if e != nil {
+			err = fmt.Errorf("insert multi mapping err: %v", e)
+			return
+		}
+	}
+	return
+}
+
+// FactorEdbSeriesChartCalculateCorrelationReq 图表相关性计算参数
+type FactorEdbSeriesChartCalculateCorrelationReq struct {
+	BaseEdbInfoId  int    `description:"标的指标ID"`
+	LeadValue      int    `description:"领先期数"`
+	LeadUnit       string `description:"频度"`
+	CalculateValue int    `description:"计算窗口"`
+	CalculateUnit  string `description:"计算频度"`
+}

+ 167 - 0
models/data_manage/factor_edb_series_mapping.go

@@ -0,0 +1,167 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// FactorEdbSeriesMapping 因子指标系列-指标关联表
+type FactorEdbSeriesMapping struct {
+	FactorEdbSeriesMappingId int       `orm:"column(factor_edb_series_mapping_id);pk"`
+	FactorEdbSeriesId        int       `description:"因子指标系列ID"`
+	EdbInfoId                int       `description:"指标ID"`
+	EdbCode                  string    `description:"指标编码"`
+	CreateTime               time.Time `description:"创建时间"`
+	ModifyTime               time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeriesMapping) TableName() string {
+	return "factor_edb_series_mapping"
+}
+
+type FactorEdbSeriesMappingCols struct {
+	PrimaryId         string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbCode           string
+	CreateTime        string
+	ModifyTime        string
+}
+
+func (m *FactorEdbSeriesMapping) Cols() FactorEdbSeriesMappingCols {
+	return FactorEdbSeriesMappingCols{
+		PrimaryId:         "factor_edb_series_mapping_id",
+		FactorEdbSeriesId: "factor_edb_series_id",
+		EdbInfoId:         "edb_info_id",
+		EdbCode:           "edb_code",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+	}
+}
+
+func (m *FactorEdbSeriesMapping) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesMappingId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) CreateMulti(items []*FactorEdbSeriesMapping) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesMappingId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesMapping) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesMapping) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetItemById(id int) (item *FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesMappingItem 因子指标系列-指标关联信息
+type FactorEdbSeriesMappingItem struct {
+	SeriesId  int    `description:"因子指标系列ID"`
+	EdbInfoId int    `description:"指标ID"`
+	EdbCode   string `description:"指标编码"`
+	EdbName   string `description:"指标名称"`
+	EdbNameEn string `description:"指标名称-英文"`
+}
+
+func (m *FactorEdbSeriesMapping) Format2Item() (item *FactorEdbSeriesMappingItem) {
+	item = new(FactorEdbSeriesMappingItem)
+	item.SeriesId = m.FactorEdbSeriesId
+	item.EdbInfoId = m.EdbInfoId
+	item.EdbCode = m.EdbCode
+	return
+}

+ 85 - 0
models/data_manage/mysteel_chemical_index.go

@@ -116,6 +116,7 @@ type MysteelChemicalList struct {
 	UniqueCode                        string             `description:"唯一编码"`
 	FrequencyName                     string             `orm:"column(frequency)"`
 	UpdateTime                        string             `orm:"column(modify_time)"`
+	IsStop                            int                `description:"是否停更:1:停更,0:未停更"`
 	Paging                            *paging.PagingItem `description:"分页数据"`
 	DataList                          []*MysteelChemicalData
 }
@@ -524,3 +525,87 @@ func ModifyMysteelChemicalUpdateStatus(edbIdList []int, indexCodeList []string,
 
 	return
 }
+
+// ModifyMysteelChemicalUpdateStatusByEdbInfoId
+// @Description:  修改单个钢联化工指标停更状态,同时停更依赖于该指标的计算指标
+// @author: Roc
+// @datetime 2024-01-08 16:23:31
+// @param edbIdList []int
+// @param indexCodeList []string
+// @param isStop int
+// @return err error
+func ModifyMysteelChemicalUpdateStatusByEdbInfoId(edbInfoId, isStop int, edbCode string, calculateEdbInfoIds []int) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+
+	// 更改数据源的更新状态
+	sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = ? WHERE index_code = ? `
+	_, err = o.Raw(sql, isStop, edbCode).Exec()
+	if err != nil {
+		return
+	}
+
+	// 更改指标的更新状态
+	sql = ` UPDATE edb_info SET no_update = ? WHERE source = ? AND sub_source= ? AND edb_info_id=? `
+	_, err = o.Raw(sql, isStop, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, 0, edbInfoId).Exec()
+	if err != nil {
+		return
+	}
+	if len(calculateEdbInfoIds) > 0 {
+		// 批量更新相关联的指标ID
+		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+func ModifyMysteelChemicalUpdateStatusByEdbInfoIds(edbInfoIds []int, isStop int, edbCodes []string, calculateEdbInfoIds []int) (err error) {
+	o, err := orm.NewOrmUsingDB("data").Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+
+	// 更改数据源的更新状态
+	sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = ? WHERE index_code IN (` + utils.GetOrmInReplace(len(edbCodes)) + `) `
+	_, err = o.Raw(sql, isStop, edbCodes).Exec()
+	if err != nil {
+		return
+	}
+
+	// 更改指标的更新状态
+	sql = ` UPDATE edb_info SET no_update = ? WHERE source = ? AND edb_info_id IN (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
+	_, err = o.Raw(sql, isStop, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, edbInfoIds).Exec()
+	if err != nil {
+		return
+	}
+	if len(calculateEdbInfoIds) > 0 {
+		// 批量更新相关联的指标ID
+		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 25 - 0
models/data_manage/request/factor_edb_series.go

@@ -0,0 +1,25 @@
+package request
+
+import "eta/eta_api/models/data_manage"
+
+// AddFactorEdbSeriesReq 新增因子指标系列
+type AddFactorEdbSeriesReq struct {
+	SeriesName  string                                     `description:"系列名称"`
+	EdbInfoType int                                        `description:"指标类型: 0-普通指标; 1-预测指标"`
+	Calculates  []data_manage.FactorEdbSeriesCalculatePars `description:"多步骤计算方式"`
+	EdbInfoIds  []int                                      `description:"指标IDs"`
+}
+
+// EditFactorEdbSeriesReq 编辑因子指标系列
+type EditFactorEdbSeriesReq struct {
+	SeriesId int `description:"系列ID"`
+	AddFactorEdbSeriesReq
+	Recalculate bool `description:"是否需要重新计算"`
+}
+
+// FactorEdbSeriesCorrelationMatrixReq 因子指标系列-相关性矩阵
+type FactorEdbSeriesCorrelationMatrixReq struct {
+	BaseEdbInfoId int                           `description:"标的指标ID"`
+	SeriesIds     []int                         `description:"因子指标系列IDs"`
+	Correlation   data_manage.CorrelationConfig `description:"相关性配置"`
+}

+ 12 - 10
models/data_manage/request/multiple_graph_config.go

@@ -14,16 +14,18 @@ type SaveMultipleGraphConfigReq struct {
 
 // SaveMultipleGraphChartReq 多图配置的单图保存请求
 type SaveMultipleGraphChartReq struct {
-	Source                int                                    `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
-	ChartName             string                                 `description:"图表名称"`
-	ClassifyId            int                                    `description:"分类id"`
-	MultipleGraphConfigId int                                    `description:"配置id"`
-	EdbInfoIdA            int                                    `description:"指标A"`
-	EdbInfoIdB            int                                    `description:"指标B"`
-	Curve                 data_manage.CurveConfig                `description:"曲线图配置"`
-	Correlation           data_manage.CorrelationConfig          `description:"相关性配置"`
-	RollingCorrelation    []data_manage.RollingCorrelationConfig `description:"滚动相关性配置"`
-	IsSaveAs              bool                                   `description:"是否另存为,true的话,就是另存为,不会建立与配置的关系"`
+	Source                 int                                          `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
+	ChartName              string                                       `description:"图表名称"`
+	ClassifyId             int                                          `description:"分类id"`
+	MultipleGraphConfigId  int                                          `description:"配置id"`
+	EdbInfoIdA             int                                          `description:"指标A"`
+	EdbInfoIdB             int                                          `description:"指标B"`
+	Curve                  data_manage.CurveConfig                      `description:"曲线图配置"`
+	Correlation            data_manage.CorrelationConfig                `description:"相关性配置"`
+	RollingCorrelation     []data_manage.RollingCorrelationConfig       `description:"滚动相关性配置"`
+	IsSaveAs               bool                                         `description:"是否另存为,true的话,就是另存为,不会建立与配置的关系"`
+	CorrelationExtraConfig *data_manage.CorrelationChartInfoExtraConfig `description:"普通相关性图表额外设置(含图例)"`
+	SourcesFrom            *data_manage.ChartInfoSourcesFrom            `description:"图表来源"`
 }
 
 // SaveMultipleGraphEdbReq 多图配置的单指标保存请求

+ 6 - 0
models/data_manage/response/edb_info.go

@@ -10,3 +10,9 @@ type EdbInfoChartListResp struct {
 	Paging *paging.PagingItem
 	List   []*data_manage.EdbInfoList
 }
+
+// WindEdbInfoListResp wind指标列表返回数据
+type WindEdbInfoListResp struct {
+	Paging *paging.PagingItem
+	List   []*data_manage.WindEdbInfoList
+}

+ 7 - 3
models/data_manage/response/multiple_graph_config.go

@@ -37,11 +37,15 @@ type MultipleGraphConfigDetailResp struct {
 	ChartMappingList    []MultipleGraphConfigChartMapping `description:"关联图表"`
 	EdbMappingList      []MultipleGraphConfigEdbMapping   `description:"关联指标"`
 }
+
 type MultipleGraphConfigChartMapping struct {
-	ChartInfoId            int `description:"图表id"`
-	Source                 int `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
-	MultipleLocationSource int `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
+	ChartInfoId            int    `description:"图表id"`
+	Source                 int    `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
+	MultipleLocationSource int    `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
+	CorrelationExtraConfig string `description:"普通相关性图表额外设置(含图例)-JSON字符串"`
+	SourcesFrom            string `description:"图表来源-JSON字符串"`
 }
+
 type MultipleGraphConfigEdbMapping struct {
 	EdbInfoId              int `description:"图表id"`
 	Source                 int `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`

+ 27 - 1
models/db.go

@@ -199,8 +199,11 @@ func init() {
 	// 初始化外汇日历
 	initFeCalendar()
 
+	// 初始化因子指标系列
+	initFactorEdbSeries()
+
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
-	data_manage.InitEdbSourceVar()
+	afterInitTable()
 }
 
 // initSystem 系统表 数据表
@@ -338,6 +341,7 @@ func initEdbData() {
 		new(data_manage.EdbDataInsertConfig),      // 指标数据插入配置表
 		new(data_manage.EdbInfoNoPermissionAdmin), //指标不可见用户配置表
 		new(data_manage.EdbTerminal),              //指标终端
+		new(data_manage.EdbInfoRelation),          //指标关系表
 	)
 }
 
@@ -601,3 +605,25 @@ func initFeCalendar() {
 		new(fe_calendar.FeCalendarMatter), // 事项表
 	)
 }
+
+// initFactorEdbSeries 因子指标系列数据表
+func initFactorEdbSeries() {
+	orm.RegisterModel(
+		new(data_manage.FactorEdbSeries),              // 因子指标系列
+		new(data_manage.FactorEdbSeriesChartMapping),  // 因子指标系列-图表关联
+		new(data_manage.FactorEdbSeriesMapping),       // 因子指标系列-指标计算数据
+		new(data_manage.FactorEdbSeriesCalculateData), // 因子指标系列-指标关联
+	)
+}
+
+// afterInitTable
+// @Description: 初始化表结构的的后置操作
+// @author: Roc
+// @datetime 2024-07-01 13:31:09
+func afterInitTable() {
+	// 初始化指标来源配置
+	data_manage.InitEdbSourceVar()
+
+	// 初始化是否启用mongo配置
+	InitUseMongoConf()
+}

+ 36 - 0
models/sandbox/sandbox.go

@@ -374,6 +374,14 @@ func GetSandboxByClassifyIdAndName(classifyId int, name string) (item *Sandbox,
 	return
 }
 
+// GetSandboxNameByIds 根据沙盘名称
+func GetSandboxNameByIds(ids []int) (items []*Sandbox, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT sandbox_id, name FROM sandbox WHERE sandbox_id in (` + utils.GetOrmInReplace(len(ids)) + `) `
+	_, err = o.Raw(sql, ids).QueryRows(&items)
+	return
+}
+
 func MoveSandbox(sandboxId, classifyId int) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` UPDATE  sandbox
@@ -403,6 +411,34 @@ func GetFirstSandboxByClassifyId(classifyId int) (item *Sandbox, err error) {
 	return
 }
 
+// ContentDataStruct 沙盘内容结构体
+type ContentDataStruct struct {
+	Cells []struct {
+		Data *NodeData `json:"data,omitempty"`
+	} `json:"cells"`
+}
+
+type NodeData struct {
+	LinkData []*LinkData `json:"linkData"`
+	LinkFold bool        `json:"linkFold"`
+}
+
+type LinkData struct {
+	RId          string       `json:"RId"`
+	Id           int          `json:"Id"`
+	Name         string       `json:"Name"`
+	Type         int          `json:"Type"`
+	Editing      bool         `json:"editing"`
+	DatabaseType int          `json:"databaseType"`
+	DetailParams DetailParams `json:"detailParams"`
+}
+
+type DetailParams struct {
+	Code       string `json:"code"`
+	Id         int    `json:"id"`
+	ClassifyId int    `json:"classifyId"`
+}
+
 // UpdateSandboxContent 更新沙盘内容
 func UpdateSandboxContent(list []Sandbox) (err error) {
 	o := orm.NewOrmUsingDB("data")

+ 198 - 0
routers/commentsRouter.go

@@ -133,6 +133,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/chart_classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"],
         beego.ControllerComments{
             Method: "Add",
@@ -214,6 +223,33 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultiFactorAdd",
+            Router: `/chart_info/multi_factor/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultiFactorDetail",
+            Router: `/chart_info/multi_factor/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultiFactorEdit",
+            Router: `/chart_info/multi_factor/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartInfoController"],
         beego.ControllerComments{
             Method: "Newest",
@@ -1312,6 +1348,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoImgSetBySvg",
+            Router: `/chart_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"],
         beego.ControllerComments{
             Method: "ChartList",
@@ -2419,6 +2464,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoImgSetBySvg",
+            Router: `/chart_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "ChartInfoMove",
@@ -2446,6 +2500,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "PreviewSeasonChartInfo",
+            Router: `/chart_info/preview/season`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "PreviewSectionScatterChartInfo",
@@ -3445,6 +3508,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "ChartImageSetBySvg",
+            Router: `/edb_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "SetEdbDataInsertConfig",
@@ -3598,6 +3670,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "SaveRelationEdbRefreshStatus",
+            Router: `/edb_info/relation/refresh/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "EdbInfoReplaceCheck",
@@ -3616,6 +3697,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "SaveEdbRefreshStatusSingle",
+            Router: `/edb_info/single_refresh/status/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "SmmEdbInfoBatchAdd",
@@ -4291,6 +4381,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "WindClassify",
+            Router: `/wind/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "WindEdbInfoList",
+            Router: `/wind/index`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "YongyiClassify",
@@ -4336,6 +4444,69 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoRelationController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoRelationController"],
+        beego.ControllerComments{
+            Method: "RelationEdbListDetail",
+            Router: `/edb_info/relation/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoRelationController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoRelationController"],
+        beego.ControllerComments{
+            Method: "RelationEdbList",
+            Router: `/edb_info/relation/edb_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/factor_edb_series/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"],
+        beego.ControllerComments{
+            Method: "CalculateFuncList",
+            Router: `/factor_edb_series/calculate_func/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"],
+        beego.ControllerComments{
+            Method: "CorrelationMatrix",
+            Router: `/factor_edb_series/correlation/matrix`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/factor_edb_series/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/factor_edb_series/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:JiaYueEdbSourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:JiaYueEdbSourceController"],
         beego.ControllerComments{
             Method: "FrequencyList",
@@ -4804,6 +4975,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"],
+        beego.ControllerComments{
+            Method: "ChartImageSetBySvg",
+            Router: `/predict_edb_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"],
         beego.ControllerComments{
             Method: "ClassifyEdbInfoItems",
@@ -6919,6 +7099,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:BusinessConfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:BusinessConfController"],
+        beego.ControllerComments{
+            Method: "GetSingle",
+            Router: `/single`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:BusinessConfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:BusinessConfController"],
+        beego.ControllerComments{
+            Method: "SingleSave",
+            Router: `/single/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:BusinessConfOpenController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:BusinessConfOpenController"],
         beego.ControllerComments{
             Method: "CodeEncrypt",

+ 2 - 0
routers/router.go

@@ -170,6 +170,8 @@ func init() {
 				&data_manage_permission.DataMangePermissionController{},
 				&data_manage.BloombergDataController{},
 				&data_manage.EdbBusinessController{},
+				&data_manage.EdbInfoRelationController{},
+				&data_manage.FactorEdbSeriesController{},
 			),
 		),
 		web.NSNamespace("/my_chart",

+ 11 - 0
services/data/base_edb_lib.go

@@ -558,3 +558,14 @@ func HttpPost(url, postData, lang string, params ...string) ([]byte, error) {
 	utils.FileLog.Debug("HttpPost:" + string(b))
 	return b, err
 }
+
+// BaseStepCalculate 基础运算-多步骤
+func BaseStepCalculate(param, lang string) (resp *BaseCalculateResp, err error) {
+	urlStr := "calculate/base/step_calculate"
+	_, resultByte, err := postAddEdbData(param, urlStr, lang)
+	err = json.Unmarshal(resultByte, &resp)
+	if err != nil {
+		return
+	}
+	return
+}

+ 66 - 0
services/data/base_from_business.go

@@ -71,6 +71,26 @@ func BusinessIndexSource2Edb(req BusinessIndexSource2EdbReq, lang string) (edb *
 // @return dataList []*data_manage.BaseFromBusinessIndexDataItem
 // @return err error
 func GetPageBaseBusinessIndexData(indexCode string, startSize, pageSize int) (dataCount int, dataList []*data_manage.BaseFromBusinessIndexDataItem, err error) {
+	if utils.UseMongo {
+		dataCount, dataList, err = getPageBaseBusinessIndexDataByMongo(indexCode, startSize, pageSize)
+	} else {
+		dataCount, dataList, err = getPageBaseBusinessIndexDataByMysql(indexCode, startSize, pageSize)
+	}
+
+	return
+}
+
+// getPageBaseBusinessIndexDataByMongo
+// @Description: 获取自有数据的分页数据(从mongo中获取)
+// @author: Roc
+// @datetime 2024-07-01 14:01:04
+// @param indexCode string
+// @param startSize int
+// @param pageSize int
+// @return dataCount int
+// @return dataList []*data_manage.BaseFromBusinessIndexDataItem
+// @return err error
+func getPageBaseBusinessIndexDataByMongo(indexCode string, startSize, pageSize int) (dataCount int, dataList []*data_manage.BaseFromBusinessIndexDataItem, err error) {
 	dataList = make([]*data_manage.BaseFromBusinessIndexDataItem, 0)
 
 	mogDataObj := mgo.BaseFromBusinessData{}
@@ -104,3 +124,49 @@ func GetPageBaseBusinessIndexData(indexCode string, startSize, pageSize int) (da
 
 	return
 }
+
+// getPageBaseBusinessIndexDataByMysql
+// @Description: 获取自有数据的分页数据(从mysql中获取)
+// @author: Roc
+// @datetime 2024-07-01 14:00:41
+// @param indexCode string
+// @param startSize int
+// @param pageSize int
+// @return dataCount int
+// @return dataList []*data_manage.BaseFromBusinessIndexDataItem
+// @return err error
+func getPageBaseBusinessIndexDataByMysql(indexCode string, startSize, pageSize int) (dataCount int, dataList []*data_manage.BaseFromBusinessIndexDataItem, err error) {
+	dataList = make([]*data_manage.BaseFromBusinessIndexDataItem, 0)
+
+	businessDataObj := data_manage.BaseFromBusinessData{}
+	var condition []string
+	var pars []interface{}
+
+	condition = append(condition, "index_code = ?")
+	pars = append(pars, indexCode)
+
+	// 获取数据总量
+	tmpCount, tmpErr := businessDataObj.GetCountDataList(condition, pars)
+	if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+		err = tmpErr
+		return
+	}
+	dataCount = int(tmpCount)
+
+	// 获取列表数据
+	tmpDataList, tmpErr := businessDataObj.GetPageDataList(condition, pars, " data_time desc", int64(startSize), int64(pageSize))
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	for _, v := range tmpDataList {
+		dataList = append(dataList, &data_manage.BaseFromBusinessIndexDataItem{
+			ID:        fmt.Sprint(v.BusinessDataId),
+			IndexCode: v.IndexCode,
+			DataTime:  v.DataTime.Format(utils.FormatDate),
+			Value:     v.Value,
+		})
+	}
+
+	return
+}

+ 589 - 1
services/data/chart_classify.go

@@ -261,8 +261,34 @@ func AddChartClassify(chartClassifyName string, parentId, level, source int, lan
 		}
 	}
 
+	if level > 6 {
+		errMsg = `最高只支持添加6级分类`
+		return
+	}
+
 	//获取该层级下最大的排序数
 	maxSort, err := data_manage.GetChartClassifyMaxSort(parentId, source)
+	if err != nil {
+		errMsg = "获取失败"
+		err = errors.New("查询排序信息失败,Err:" + err.Error())
+		return
+	}
+	//查询顶级rootId
+	rootId := 0
+	if parentId > 0 {
+		parentClassify, tErr := data_manage.GetChartClassifyById(parentId)
+		if tErr != nil {
+			if tErr.Error() == utils.ErrNoRow() {
+				errMsg = "父级分类不存在"
+				err = errors.New(errMsg)
+				return
+			}
+			errMsg = "获取失败"
+			err = errors.New("获取分类信息失败,Err:" + tErr.Error())
+			return
+		}
+		rootId = parentClassify.RootId
+	}
 
 	classifyInfo = new(data_manage.ChartClassify)
 	classifyInfo.ParentId = parentId
@@ -278,11 +304,20 @@ func AddChartClassify(chartClassifyName string, parentId, level, source int, lan
 	classifyInfo.UniqueCode = utils.MD5(utils.DATA_PREFIX + "_" + timestamp)
 	classifyInfo.Sort = maxSort + 1
 	classifyInfo.Source = source
+	classifyInfo.RootId = rootId
 
-	_, err = data_manage.AddChartClassify(classifyInfo)
+	newId, err := data_manage.AddChartClassify(classifyInfo)
 	if err != nil {
 		return
 	}
+	if parentId == 0 { //一级目录的rootId等于自己本身
+		classifyInfo.RootId = int(newId)
+		err = classifyInfo.Update([]string{"RootId"})
+		if err != nil {
+			errMsg = "更新分类失败"
+			return
+		}
+	}
 
 	// 目前只有ETA图库需要继承分类权限
 	if classifyInfo.Source == utils.CHART_SOURCE_DEFAULT {
@@ -374,3 +409,556 @@ func EditChartClassify(chartClassifyId, source int, chartClassifyName, lang stri
 
 	return
 }
+
+// MoveChartClassify 移动图表分类
+func MoveChartClassify(req data_manage.MoveChartClassifyReq, sysUser *system.Admin, source int) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	parentClassifyId := req.ParentClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	chartInfoId := req.ChartInfoId
+	prevChartInfoId := req.PrevChartInfoId
+	nextChartInfoId := req.NextChartInfoId
+
+	//首先确定移动的对象是分类还是指标
+	//判断上一个节点是分类还是指标
+	//判断下一个节点是分类还是指标
+	//同时更新分类目录下的分类sort和指标sort
+	//更新当前移动的分类或者指标sort
+
+	var parentChartClassifyInfo *data_manage.ChartClassify
+	if parentClassifyId > 0 {
+		parentChartClassifyInfo, err = data_manage.GetChartClassifyById(parentClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		chartClassifyInfo *data_manage.ChartClassify
+		prevClassify      *data_manage.ChartClassify
+		nextClassify      *data_manage.ChartClassify
+
+		chartInfo     *data_manage.ChartInfo
+		prevChartInfo *data_manage.ChartInfo
+		nextChartInfo *data_manage.ChartInfo
+		prevSort      int
+		nextSort      int
+	)
+
+	// 移动对象为分类, 判断权限
+	if chartInfoId == 0 {
+		chartClassifyInfo, err = data_manage.GetChartClassifyById(classifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前分类不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId > 0 && parentChartClassifyInfo.Level == 6 {
+			errMsg = "最高只支持添加6级分类"
+			err = errors.New(errMsg)
+			return
+		}
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		exists, e := data_manage.GetChartClassifyByParentIdAndName(parentClassifyId, chartClassifyInfo.ChartClassifyName, classifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取父级分类下的同名分类失败, Err: %s", e.Error())
+			return
+		}
+		if exists != nil {
+			errMsg = "移动失败,分类名称已存在"
+			return
+		}
+
+		// 权限校验
+		{
+			// 已授权分类id
+			permissionClassifyIdList, tmpErr := data_manage_permission.GetUserChartClassifyPermissionList(sysUser.AdminId, classifyId)
+			if tmpErr != nil {
+				errMsg = "移动失败"
+				err = errors.New("获取已授权分类id数据失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			// 数据权限
+			haveOperaAuth := data_manage_permission.CheckChartClassifyPermissionByPermissionIdList(chartClassifyInfo.IsJoinPermission, chartClassifyInfo.ChartClassifyId, permissionClassifyIdList)
+
+			button := GetChartClassifyOpButton(sysUser, chartClassifyInfo.SysUserId, haveOperaAuth)
+			if !button.MoveButton {
+				errMsg = "无操作权限"
+				err = errors.New(errMsg)
+				return
+			}
+		}
+
+	} else {
+		chartInfo, err = data_manage.GetChartInfoById(req.ChartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前图表不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId == 0 {
+			errMsg = "移动失败,图表必须挂在分类下"
+			err = errors.New(errMsg)
+			return
+		}
+
+		var haveOperaAuth bool
+		// 权限校验
+		{
+			haveOperaAuth, err = data_manage_permission.CheckChartPermissionByChartInfoId(chartInfo.ChartInfoId, chartInfo.ChartClassifyId, chartInfo.IsJoinPermission, sysUser.AdminId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("获取图表权限信息失败,Err:" + err.Error())
+				return
+			}
+		}
+
+		// 移动权限校验
+		button := GetChartOpButton(sysUser, chartInfo.SysUserId, haveOperaAuth)
+		if !button.MoveButton {
+			errMsg = "无操作权限"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	if prevClassifyId > 0 {
+		prevClassify, err = data_manage.GetChartClassifyById(prevClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	} else if prevChartInfoId > 0 {
+		prevChartInfo, err = data_manage.GetChartInfoById(prevChartInfoId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevChartInfo.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = data_manage.GetChartClassifyById(nextClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	} else if nextChartInfoId > 0 {
+		//下一个兄弟节点
+		nextChartInfo, err = data_manage.GetChartInfoById(nextChartInfoId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextChartInfo.Sort
+	}
+
+	err, errMsg = moveChartClassify(parentChartClassifyInfo, chartClassifyInfo, prevClassify, nextClassify, chartInfo, prevChartInfo, nextChartInfo, parentClassifyId, prevSort, nextSort, source)
+	return
+}
+
+// moveChartClassify 移动图表分类
+func moveChartClassify(parentChartClassifyInfo, chartClassifyInfo, prevClassify, nextClassify *data_manage.ChartClassify, chartInfo, prevChartInfo, nextChartInfo *data_manage.ChartInfo, parentClassifyId, prevSort, nextSort, source int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+
+	// 移动对象为分类, 判断分类是否存在
+	if chartClassifyInfo != nil {
+		oldParentId := chartClassifyInfo.ParentId
+		oldLevel := chartClassifyInfo.Level
+		var classifyIds []int
+		if oldParentId != parentClassifyId {
+			//更新子分类对应的level
+			childList, e, m := GetChildChartClassifyByClassifyId(chartClassifyInfo.ChartClassifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子分类失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.ChartClassifyId == chartClassifyInfo.ChartClassifyId {
+						continue
+					}
+					classifyIds = append(classifyIds, v.ChartClassifyId)
+				}
+			}
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if chartClassifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+			if chartClassifyInfo.Level != parentChartClassifyInfo.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			chartClassifyInfo.ParentId = parentChartClassifyInfo.ChartClassifyId
+			chartClassifyInfo.RootId = parentChartClassifyInfo.RootId
+			chartClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+			chartClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime")
+		} else if chartClassifyInfo.ParentId != parentClassifyId && parentClassifyId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == chartClassifyInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, prevClassify.ChartClassifyId, prevClassify.Sort, updateSortStr, source)
+					} else {
+						_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, 0, prevSort, updateSortStr, source)
+					}
+
+					//变更图表
+					if prevChartInfo != nil {
+						//变更兄弟节点的排序
+						_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, prevChartInfo.ChartInfoId, updateSortStr)
+					} else {
+						_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更分类
+						if prevClassify != nil {
+							_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, prevClassify.ChartClassifyId, prevSort, updateSortStr, source)
+						} else {
+							_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, 0, prevSort, updateSortStr, source)
+						}
+
+						//变更图表
+						if prevChartInfo != nil {
+							//变更兄弟节点的排序
+							_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, prevChartInfo.ChartInfoId, updateSortStr)
+						} else {
+							_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+
+					}
+				}
+			}
+
+			chartClassifyInfo.Sort = prevSort + 1
+			chartClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevChartInfo == nil && nextChartInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetChartClassifyMaxSort(parentClassifyId, source)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			chartClassifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			chartClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := data_manage.GetFirstChartClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, firstClassify.ChartClassifyId-1, 0, updateSortStr, source)
+				//该分类下的所有图表也需要+1
+				_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在图表,且第一个图表的排序等于0,那么需要调整排序
+				firstEdb, tErr := data_manage.GetFirstChartInfoByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, 0, firstEdb.ChartInfoId-1, updateSortStr)
+					_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, 0, 0, updateSortStr, source)
+				}
+			}
+
+			chartClassifyInfo.Sort = 0 //那就是排在第一位
+			chartClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = chartClassifyInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应分类的root_id和层级
+			if oldParentId != parentClassifyId {
+				if len(classifyIds) > 0 {
+					levelStep := chartClassifyInfo.Level - oldLevel
+					err = data_manage.UpdateEdbClassifyChildByParentClassifyId(classifyIds, chartClassifyInfo.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子分类失败,Err:" + err.Error())
+						return
+					}
+				}
+			}
+		}
+	} else {
+		if chartInfo == nil {
+			errMsg = "当前图表不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了分类,那么移动该图表数据
+		if chartInfo.ChartClassifyId != parentClassifyId {
+			chartInfo.ChartClassifyId = parentClassifyId
+			chartInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ChartClassifyId", "ModifyTime")
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == chartInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, prevClassify.ChartClassifyId, prevClassify.Sort, updateSortStr, source)
+					} else {
+						_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, 0, prevSort, updateSortStr, source)
+					}
+
+					//变更图表
+					if prevChartInfo != nil {
+						//变更兄弟节点的排序
+						_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, prevChartInfo.ChartInfoId, updateSortStr)
+					} else {
+						_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更分类
+						if prevClassify != nil {
+							_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, prevClassify.ChartClassifyId, prevSort, updateSortStr, source)
+						} else {
+							_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, 0, prevSort, updateSortStr, source)
+						}
+
+						//变更图表
+						if prevChartInfo != nil {
+							//变更兄弟节点的排序
+							_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, prevChartInfo.ChartInfoId, updateSortStr)
+						} else {
+							_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+					}
+				}
+			}
+
+			chartInfo.Sort = prevSort + 1
+			chartInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevChartInfo == nil && nextChartInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetChartClassifyMaxSort(parentClassifyId, source)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			chartInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			chartInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := data_manage.GetFirstChartClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, firstClassify.ChartClassifyId-1, 0, updateSortStr, source)
+				//该分类下的所有图表也需要+1
+				_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在图表,且第一个图表的排序等于0,那么需要调整排序
+				firstEdb, tErr := data_manage.GetFirstChartInfoByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = data_manage.UpdateChartInfoSortByClassifyIdV2(parentClassifyId, 0, firstEdb.ChartInfoId-1, updateSortStr)
+					_ = data_manage.UpdateChartClassifySortByParentIdAndSource(parentClassifyId, 0, 0, updateSortStr, source)
+				}
+			}
+
+			chartInfo.Sort = 0 //那就是排在第一位
+			chartInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = chartInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func GetChildChartClassifyByClassifyId(targetClassifyId int) (targetList []*data_manage.ChartClassifyIdItems, err error, errMsg string) {
+	//判断是否是挂在顶级目录下
+	targetClassify, err := data_manage.GetChartClassifyById(targetClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前分类不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	orderStr := ` order by level asc, sort asc, chart_classify_id asc`
+	tmpList, err := data_manage.GetChartClassifyByRootIdLevel(targetClassify.RootId, targetClassify.Source, orderStr)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		err = errors.New("获取数据失败,Err:" + err.Error())
+		return
+	}
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.ChartClassifyId == targetClassify.ChartClassifyId {
+				idMap[v.ChartClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.ChartClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ChartClassifyId]; ok {
+				targetItem := new(data_manage.ChartClassifyIdItems)
+				targetItem.ChartClassifyId = v.ChartClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.RootId = v.RootId
+				targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetItem.ChartClassifyName = v.ChartClassifyName
+				targetItem.IsJoinPermission = v.IsJoinPermission
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+
+	return
+}
+
+func GetChartClassifyMaxSort(parentId, source int) (maxSort int, err error) {
+	//获取该层级下最大的排序数
+	classifyMaxSort, err := data_manage.GetChartClassifyMaxSort(parentId, source)
+	if err != nil {
+		return
+	}
+	maxSort = classifyMaxSort
+	edbMaxSort, err := data_manage.GetChartInfoMaxSortByClassifyId(parentId)
+	if err != nil {
+		return
+	}
+	if maxSort < edbMaxSort {
+		maxSort = edbMaxSort
+	}
+	return
+}
+
+// GetChartClassifyTreeRecursive 递归获取分类树形结构
+func GetChartClassifyTreeRecursive(list []*data_manage.ChartClassifyItems, parentId int) []*data_manage.ChartClassifyItems {
+	res := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Children = GetChartClassifyTreeRecursive(list, v.ChartClassifyId)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// GetChartClassifyParentRecursive 根据子目录递归父级目录
+func GetChartClassifyParentRecursive(list []*data_manage.ChartClassifyItems, classifyId int) []*data_manage.ChartClassifyItems {
+	res := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range list {
+		if v.ChartClassifyId == classifyId {
+			if v.ParentId > 0 {
+				res = GetChartClassifyParentRecursive(list, v.ParentId)
+			}
+			res = append(res, v)
+		}
+	}
+	return res
+}

+ 876 - 6
services/data/chart_info.go

@@ -357,6 +357,10 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			dataResp, err = SeasonChartData(edbList, seasonExtraConfig)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -481,6 +485,9 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -586,7 +593,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && item.IsAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -624,6 +631,60 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && item.IsAxis == 0 {
+			// 右轴数据处理
+			xStartDate := "01-01"
+
+			jumpYear := 0
+			var seasonExtra data_manage.SeasonExtraItem
+			if seasonExtraConfig != "" {
+				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+				if err != nil {
+					return
+				}
+			}
+
+			if seasonExtra.XStartDate != "" {
+				xStartDate = seasonExtra.XStartDate
+				jumpYear = seasonExtra.JumpYear
+			}
+
+			length := len(dataList)
+			if length == 0 {
+				return
+			}
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+
+			var rightAxisDate time.Time
+			if jumpYear == 1 {
+				latestDate = latestDate.AddDate(-1, 0, 0)
+			}
+			latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
+			rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+			if err != nil {
+				return
+			}
+
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -2264,7 +2325,8 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 		err = errors.New("保存失败,Err:" + err.Error())
 		return
 	}
-
+	// 添加指标引用记录
+	_ = SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 
@@ -2616,6 +2678,8 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin, lang
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// todo 添加指标引用记录
+	_ = SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
 	//添加es数据
 	go EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据
@@ -2786,7 +2850,7 @@ func RadarChartData(mappingList []*data_manage.ChartEdbInfoMapping, edbDataListM
 }
 
 // GetChartConvertEdbData 获取图表数据转换的指标数据
-func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []data_manage.YData, dataResp interface{}, err error, errMsg string) {
+func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string, isAxis int) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []data_manage.YData, dataResp interface{}, err error, errMsg string) {
 	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
 	xEdbIdValue = make([]int, 0)
 	yDataList = make([]data_manage.YData, 0)
@@ -2842,13 +2906,17 @@ func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, end
 	}
 
 	// 指标对应的所有数据
-	edbDataListMap, edbList, err := getEdbConvertDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig)
+	edbDataListMap, edbList, err := getEdbConvertDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig, isAxis)
 	if err != nil {
 		return
 	}
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			dataResp, err = SeasonChartData(edbList, seasonExtraConfig)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -2896,7 +2964,7 @@ func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, end
 	return
 }
 
-func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
+func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string, isAxis int) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
 
@@ -2965,6 +3033,9 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -3066,7 +3137,7 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && isAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -3104,6 +3175,29 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && isAxis == 0 {
+			// 右轴数据处理,只要最新一年
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Year() == latestDate.Year() {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -3179,6 +3273,10 @@ func GetChartEdbDataV2(chartInfoId, chartType int, calendar, startDate, endDate
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			dataResp, err = SeasonChartData(edbList, seasonExtraConfig)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -3225,3 +3323,775 @@ func GetChartEdbDataV2(chartInfoId, chartType int, calendar, startDate, endDate
 	}
 	return
 }
+
+// SeasonChartData 季节性图的数据处理
+func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (dataResp data_manage.SeasonChartResp, err error) {
+	var seasonConfig data_manage.SeasonExtraItem
+	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonConfig)
+	if err != nil {
+		err = errors.New("季节性图配置异常, Err:" + err.Error())
+		return
+	}
+
+	for _, mappingItem := range dataList {
+		if mappingItem.IsAxis == 0 {
+			continue
+		}
+		quarterDataList := mappingItem.DataList.(data_manage.QuarterDataList)
+		// 上下限区间
+		if seasonConfig.MaxMinLimits.Year > 0 {
+			dataResp.MaxMinLimits.List = make([]*data_manage.MaxMinLimitsData, 0)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			maxValueMap := make(map[time.Time]float64)
+			minValueMap := make(map[time.Time]float64)
+
+			maxMinDataList := make([]*data_manage.MaxMinLimitsData, 0)
+			// 日度 周度插值
+			for _, v := range quarterDataList {
+				if mappingItem.Frequency == "日度" || mappingItem.Frequency == "周度" {
+					handleDataMap := make(map[string]float64)
+					dataTimeList, _, err = HandleDataByLinearRegressionToList(v.DataList, handleDataMap)
+					if err != nil {
+						err = errors.New("插值处理数据异常, Err:" + err.Error())
+						return
+					}
+					for _, date := range dataTimeList {
+						dateTime, e := time.Parse(utils.FormatDate, date)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						// 不包含今年
+						if dateTime.Year() == time.Now().Year() {
+							continue
+						}
+						newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						// 处理上下限列表
+						if value, ok := maxValueMap[newDate]; ok {
+							if value < handleDataMap[date] {
+								maxValueMap[newDate] = handleDataMap[date]
+							}
+						} else {
+							maxValueMap[newDate] = handleDataMap[date]
+						}
+
+						if value, ok := minValueMap[newDate]; ok {
+							if value > handleDataMap[date] {
+								minValueMap[newDate] = handleDataMap[date]
+							}
+						} else {
+							minValueMap[newDate] = handleDataMap[date]
+						}
+
+						dataTimeMap[newDate] = newDate
+					}
+				} else {
+					// 旬度、月度、季度、半年度 不插值,需要先把日期列表和数据map取出来
+					for _, vv := range v.DataList {
+						dateTime, e := time.Parse(utils.FormatDate, vv.DataTime)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						// 不包含今年
+						if dateTime.Year() == time.Now().Year() {
+							continue
+						}
+						newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+
+						if value, ok := maxValueMap[newDate]; ok {
+							if value < vv.Value {
+								maxValueMap[newDate] = vv.Value
+							}
+						} else {
+							maxValueMap[newDate] = vv.Value
+						}
+
+						if value, ok := minValueMap[newDate]; ok {
+							if value > vv.Value {
+								minValueMap[newDate] = vv.Value
+							}
+						} else {
+							minValueMap[newDate] = vv.Value
+						}
+						dataTimeMap[newDate] = newDate
+					}
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				maxMinItem := &data_manage.MaxMinLimitsData{}
+				if maxValue, ok := maxValueMap[v]; ok {
+					maxMinItem.MaxValue = maxValue
+				} else {
+					maxMinItem.MaxValue = minValueMap[v]
+				}
+
+				if minValue, ok := minValueMap[v]; ok {
+					maxMinItem.MinValue = minValue
+				} else {
+					maxMinItem.MinValue = maxValueMap[v]
+				}
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				maxMinDataList = append(maxMinDataList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(maxMinDataList, func(i, j int) bool {
+				return maxMinDataList[i].DataTime < maxMinDataList[j].DataTime
+			})
+
+			dataResp.MaxMinLimits.List = maxMinDataList
+			dataResp.MaxMinLimits.Color = seasonConfig.MaxMinLimits.Color
+			dataResp.MaxMinLimits.Legend = seasonConfig.MaxMinLimits.Legend
+			dataResp.MaxMinLimits.IsShow = seasonConfig.MaxMinLimits.IsShow
+			dataResp.MaxMinLimits.IsAdd = seasonConfig.MaxMinLimits.IsAdd
+			dataResp.MaxMinLimits.Year = seasonConfig.MaxMinLimits.Year
+		}
+
+		// 自定义同期均线
+		if seasonConfig.SamePeriodAverage.Year > 0 {
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			averageDataList := make([]*data_manage.SamePeriodAverageData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToList(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				averageItem := &data_manage.SamePeriodAverageData{}
+				if value, ok := valueMap[v]; ok {
+					averageItem.Value = value
+				}
+				averageItem.DataTime = v.Format(utils.FormatDate)
+				averageItem.DataTimestamp = v.UnixNano() / 1e6
+
+				averageDataList = append(averageDataList, averageItem)
+
+			}
+
+			// 排序
+			sort.Slice(averageDataList, func(i, j int) bool {
+				return averageDataList[i].DataTime < averageDataList[j].DataTime
+			})
+
+			dataResp.SamePeriodAverage.List = averageDataList
+			dataResp.SamePeriodAverage.Year = seasonConfig.SamePeriodAverage.Year
+			dataResp.SamePeriodAverage.Color = seasonConfig.SamePeriodAverage.Color
+			dataResp.SamePeriodAverage.Legend = seasonConfig.SamePeriodAverage.Legend
+			dataResp.SamePeriodAverage.IsShow = seasonConfig.SamePeriodAverage.IsShow
+			dataResp.SamePeriodAverage.LineType = seasonConfig.SamePeriodAverage.LineType
+			dataResp.SamePeriodAverage.LineWidth = seasonConfig.SamePeriodAverage.LineWidth
+			dataResp.SamePeriodAverage.IsAdd = seasonConfig.SamePeriodAverage.IsAdd
+
+		}
+
+		// 自定义同期标准差
+		if seasonConfig.SamePeriodStandardDeviation.Year > 1 && seasonConfig.SamePeriodStandardDeviation.Multiple > 0 {
+			// 先算均值,再算标准差
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeValueMap := make(map[time.Time][]float64)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			samePeriodStandardDeviationList := make([]*data_manage.MaxMinLimitsData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+					valueList := dataTimeValueMap[newDate]
+					valueList = append(valueList, handleDataMap[date])
+					dataTimeValueMap[newDate] = valueList
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				valueList := dataTimeValueMap[v]
+				stdev := utils.CalculateStandardDeviation(valueList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				maxMinItem := &data_manage.MaxMinLimitsData{}
+
+				if value, ok := valueMap[v]; ok {
+					maxMinItem.MaxValue = value + stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+					maxMinItem.MinValue = value - stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				samePeriodStandardDeviationList = append(samePeriodStandardDeviationList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(samePeriodStandardDeviationList, func(i, j int) bool {
+				return samePeriodStandardDeviationList[i].DataTime < samePeriodStandardDeviationList[j].DataTime
+			})
+
+			dataResp.SamePeriodStandardDeviation.List = samePeriodStandardDeviationList
+			dataResp.SamePeriodStandardDeviation.Color = seasonConfig.SamePeriodStandardDeviation.Color
+			dataResp.SamePeriodStandardDeviation.Legend = seasonConfig.SamePeriodStandardDeviation.Legend
+			dataResp.SamePeriodStandardDeviation.IsShow = seasonConfig.SamePeriodStandardDeviation.IsShow
+			dataResp.SamePeriodStandardDeviation.Multiple = seasonConfig.SamePeriodStandardDeviation.Multiple
+			dataResp.SamePeriodStandardDeviation.Year = seasonConfig.SamePeriodStandardDeviation.Year
+			dataResp.SamePeriodStandardDeviation.IsAdd = seasonConfig.SamePeriodStandardDeviation.IsAdd
+		}
+
+		// 自定义右轴
+		if seasonConfig.RightAxis.IndicatorType != 0 {
+			if seasonConfig.RightAxis.IndicatorType == 1 {
+				startTime, _ := time.Parse(utils.FormatDate, mappingItem.StartDate)
+				for i := len(quarterDataList) - 1; i > len(quarterDataList)-2 && i > 0; i-- {
+					var rightMappingItem data_manage.ChartEdbInfoMapping
+					rightMappingItem = *mappingItem
+					// 计算同比值
+					tbzDataList, minValue, maxValue, e := GetEdbDataTbzForSeason(mappingItem.Frequency, quarterDataList[i].DataList, startTime)
+					if e != nil {
+						err = errors.New("计算同比值失败, Err:" + e.Error())
+						return
+					}
+					rightMappingItem.DataList = tbzDataList
+					rightMappingItem.MaxData = maxValue
+					rightMappingItem.MinData = minValue
+					dataResp.RightAxis.EdbInfoList = append(dataResp.RightAxis.EdbInfoList, &rightMappingItem)
+				}
+			}
+			dataResp.RightAxis.SeasonRightAxis = seasonConfig.RightAxis
+		}
+	}
+
+	return
+}
+
+// 计算百分位对应的值
+func PercentileAlgorithm(x float64, data []float64) float64 {
+	N := float64(len(data))
+	n := (x / 100) * (N - 1)
+
+	// 如果n是整数,则直接返回第n个数值
+	if n == float64(int(n)) {
+		return data[int(n)-1]
+	}
+
+	// 如果n是小数,执行插值
+	nInt := int(n)
+	e := n - float64(nInt)
+
+	// 取出相邻的两个数值
+	Sn := data[nInt-1]
+	Sn1 := data[nInt]
+
+	// 计算插值结果
+	y := Sn + e*(Sn1-Sn)
+
+	return y
+}
+
+func MarkerLineCalculate(markerLine data_manage.MarkersLine, dataList interface{}, chartInfo *data_manage.ChartInfoView) (value string, err error) {
+	if markerLine.Calculation == 1 {
+		// 区间均值
+		averge := 0.0
+		length := 0
+		// 计算左轴
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							averge += vv.Value
+							length +=1
+						}
+					} else {
+						averge += vv.Value
+						length +=1
+					}
+				}
+				averge = averge / float64(length)
+				value = fmt.Sprintf("%.2f", averge)
+			}
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						averge += dataItem.Value
+						length += 1
+					}
+				} else {
+					averge += dataItem.Value
+					length += 1
+				}
+			}
+			averge = averge / float64(length)
+
+			value = fmt.Sprintf("%.2f", averge)
+		}
+	} else if markerLine.Calculation == 2 {
+		// 区间均值加N倍标准差
+		averge := 0.0
+		length := 0
+		// 计算左轴
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			faloatList := make([]float64, 0)
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							faloatList = append(faloatList, vv.Value)
+							averge += vv.Value
+							length += 1
+						}
+					} else {
+						faloatList = append(faloatList, vv.Value)
+						averge += vv.Value
+						length += 1
+					}
+				}
+				averge = averge / float64(length)
+				stdev := utils.CalculateStandardDeviation(faloatList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				value = fmt.Sprintf("%.2f", averge+stdev*markerLine.CalculationValue)
+			}
+
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			floatList := make([]float64, 0)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						floatList = append(floatList, dataItem.Value)
+						averge += dataItem.Value
+						length += 1
+					}
+				} else {
+					floatList = append(floatList, dataItem.Value)
+					averge += dataItem.Value
+					length += 1
+				}
+			}
+			averge = averge / float64(length)
+
+			stdev := utils.CalculateStandardDeviation(floatList)
+			stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+			value = fmt.Sprintf("%.2f", averge+stdev*markerLine.CalculationValue)
+		}
+	} else if markerLine.Calculation == 3 {
+		// 区间个数分位
+		markerLineValue := 0.0
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			faloatList := make([]float64, 0)
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							faloatList = append(faloatList, vv.Value)
+						}
+					} else {
+						faloatList = append(faloatList, vv.Value)
+					}
+				}
+
+				markerLineValue = PercentileAlgorithm(markerLine.CalculationValue, faloatList)
+				value = fmt.Sprintf("%.2f", markerLineValue)
+			}
+
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			floatList := make([]float64, 0)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						floatList = append(floatList, dataItem.Value)
+					}
+				} else {
+					floatList = append(floatList, dataItem.Value)
+				}
+			}
+
+			markerLineValue = PercentileAlgorithm(markerLine.CalculationValue, floatList)
+			value = fmt.Sprintf("%.2f", markerLineValue)
+		}
+	}
+	return
+}
+
+// HandleDateChange 处理日期变换
+func HandleDateChange(date string, edbDateConf data_manage.EdbDateChangeConf) (newDate string, err error) {
+	newDate = date
+	if newDate != "" {
+		if len(edbDateConf.DateChange) > 0 {
+			var dateTime time.Time
+			dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+			if err != nil {
+				err = fmt.Errorf("日期解析失败: %s", err.Error())
+				return
+			}
+			for _, v := range edbDateConf.DateChange {
+				if v.ChangeType == 1 {
+					dateTime = dateTime.AddDate(v.Year, v.Month, v.Day)
+					newDate = dateTime.Format(utils.FormatDate)
+				} else if v.ChangeType == 2 {
+					newDate, err, _ = handleSystemAppointDateT2(dateTime, v.FrequencyDay, v.Frequency)
+					if err != nil {
+						return
+					}
+					dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+					if err != nil {
+						err = fmt.Errorf("日期解析失败: %s", err.Error())
+						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 handleSystemAppointDateT2(currDate time.Time, 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
+}

+ 77 - 22
services/data/chart_info_excel_balance.go

@@ -89,18 +89,15 @@ func AddBalanceExcelChart(excelInfo *excelModel.ExcelInfo, req request.AddBalanc
 		var dataList []*excelModel.ExcelChartData
 		if excelInfo.BalanceType == 1 {
 			tmpList, ok := dataListMap[k]
-			if !ok {
-				errMsg = "查询图表数据失败!"
-				err = fmt.Errorf("查询图表数据失败!%s", err.Error())
-				return
-			}
-			for _, l := range tmpList {
-				tmp := &excelModel.ExcelChartData{
-					DataTime:      l.DataTime,
-					Value:         l.Value,
-					DataTimestamp: l.DataTimestamp,
+			if ok {
+				for _, l := range tmpList {
+					tmp := &excelModel.ExcelChartData{
+						DataTime:      l.DataTime,
+						Value:         l.Value,
+						DataTimestamp: l.DataTimestamp,
+					}
+					dataList = append(dataList, tmp)
 				}
-				dataList = append(dataList, tmp)
 			}
 		}
 
@@ -382,19 +379,20 @@ func EditBalanceExcelChart(excelInfo *excelModel.ExcelInfo, req request.AddBalan
 		var dataList []*excelModel.ExcelChartData
 		if excelInfo.BalanceType == 1 {
 			tmpList, ok := dataListMap[k]
-			if !ok {
-				errMsg = "查询图表数据失败!"
+			if ok {
+				/*errMsg = "查询图表数据失败!"
 				err = fmt.Errorf("查询图表数据失败!")
-				return
-			}
-			for _, l := range tmpList {
-				tmp := &excelModel.ExcelChartData{
-					DataTime:      l.DataTime,
-					Value:         l.Value,
-					DataTimestamp: l.DataTimestamp,
+				return*/
+				for _, l := range tmpList {
+					tmp := &excelModel.ExcelChartData{
+						DataTime:      l.DataTime,
+						Value:         l.Value,
+						DataTimestamp: l.DataTimestamp,
+					}
+					dataList = append(dataList, tmp)
 				}
-				dataList = append(dataList, tmp)
 			}
+
 		}
 
 		// 处理日期列表和值列表
@@ -837,6 +835,9 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -939,7 +940,7 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && item.IsAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -975,6 +976,60 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && item.IsAxis == 0 {
+			// 右轴数据处理
+			xStartDate := "01-01"
+
+			jumpYear := 0
+			var seasonExtra data_manage.SeasonExtraItem
+			if seasonExtraConfig != "" {
+				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+				if err != nil {
+					return
+				}
+			}
+
+			if seasonExtra.XStartDate != "" {
+				xStartDate = seasonExtra.XStartDate
+				jumpYear = seasonExtra.JumpYear
+			}
+
+			length := len(dataList)
+			if length == 0 {
+				return
+			}
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+
+			var rightAxisDate time.Time
+			if jumpYear == 1 {
+				latestDate = latestDate.AddDate(-1, 0, 0)
+				latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(),xStartDate)
+				rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+				if err != nil {
+					return
+				}
+			}
+
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {

+ 7 - 0
services/data/chart_theme.go

@@ -77,6 +77,10 @@ func GetThemePreviewChartEdbData(chartType int, calendar, startDate, endDate str
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			dataResp, err = SeasonChartData(edbList, seasonExtraConfig)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -173,6 +177,9 @@ func getThemePreviewEdbDataMapList(chartType int, calendar, startDate, endDate s
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time

+ 370 - 8
services/data/correlation/chart_info.go

@@ -200,7 +200,7 @@ func GetChartEdbInfoFormat(chartInfoId int, edbInfoMappingA, edbInfoMappingB *da
 }
 
 // GetChartDataByEdbInfo 相关性图表-根据指标信息获取x轴和y轴
-func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *data_manage.ChartEdbInfoMapping, leadValue int, leadUnit, startDate, endDate string) (xEdbIdValue []int, yDataList []data_manage.YData, err error) {
+func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *data_manage.ChartEdbInfoMapping, leadValue int, leadUnit, startDate, endDate, extraConfig string) (xEdbIdValue []int, yDataList []data_manage.YData, err error) {
 	xData := make([]int, 0)
 	yData := make([]float64, 0)
 	if leadValue == 0 {
@@ -377,13 +377,30 @@ func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *data_manage.ChartEd
 		}
 	}
 
+	// 图例
+	var extra data_manage.CorrelationChartInfoExtraConfig
+	legend := new(data_manage.CorrelationChartLegend)
+	if extraConfig != "" {
+		if e := json.Unmarshal([]byte(extraConfig), &extra); e != nil {
+			err = fmt.Errorf("图例解析异常, err: %v", e)
+			return
+		}
+		if len(extra.LegendConfig) > 0 {
+			legend = extra.LegendConfig[0]
+		}
+	}
+
 	xEdbIdValue = xData
 	yDataList = make([]data_manage.YData, 0)
 	yDate := "0000-00-00"
-	yDataList = append(yDataList, data_manage.YData{
-		Date:  yDate,
-		Value: yData,
-	})
+	var y data_manage.YData
+	y.Date = yDate
+	y.Value = yData
+	if legend != nil {
+		y.Name = legend.LegendName
+		y.Color = legend.Color
+	}
+	yDataList = append(yDataList, y)
 	return
 }
 
@@ -564,11 +581,13 @@ func GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *d
 }
 
 // ChartInfoRefresh 图表刷新
-func ChartInfoRefresh(chartInfoId int) (err error) {
+func ChartInfoRefresh(chartInfoId int, uniqueCode string) (isAsync bool, err error) {
 	var errMsg string
 	defer func() {
 		if err != nil {
-			go alarm_msg.SendAlarmMsg("CorrelationChartInfoRefresh: "+errMsg, 3)
+			tips := fmt.Sprintf("CorrelationChartInfoRefresh: %s", errMsg)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
 		}
 	}()
 	correlationChart := new(data_manage.ChartInfoCorrelation)
@@ -577,6 +596,53 @@ func ChartInfoRefresh(chartInfoId int) (err error) {
 		return
 	}
 
+	// 多因子刷新-异步
+	if correlationChart.AnalysisMode == 1 {
+		isAsync = true
+
+		go func() {
+			// 1.刷新图表关联的指标
+			mappings, e := data_manage.GetChartEdbMappingList(chartInfoId)
+			if e != nil {
+				utils.FileLog.Info(fmt.Sprintf("获取图表关联指标失败, err: %v", e))
+				return
+			}
+			if len(mappings) == 0 {
+				utils.FileLog.Info("图表无关联指标")
+				return
+			}
+			var edbIds []int
+			for _, v := range mappings {
+				edbIds = append(edbIds, v.EdbInfoId)
+			}
+			if e, _ = data.EdbInfoRefreshAllFromBaseV3(edbIds, false, true, false); e != nil {
+				utils.FileLog.Info(fmt.Sprintf("批量刷新指标失败, err: %v", e))
+				return
+			}
+
+			// 2.刷新指标系列计算数据
+			for _, v := range mappings {
+				_, e = data.PostRefreshFactorEdbRecalculate(v.EdbInfoId, v.EdbCode)
+				if e != nil {
+					utils.FileLog.Info(fmt.Sprintf("PostRefreshFactorEdbRecalculate err: %v", e))
+					continue
+				}
+			}
+
+			// 3.刷新图表矩阵
+			_, e = data.PostRefreshFactorEdbChartRecalculate(chartInfoId)
+			if e != nil {
+				utils.FileLog.Info(fmt.Sprintf("PostRefreshFactorEdbRecalculate err: %v", e))
+				return
+			}
+
+			// 4.清除图表缓存
+			key := utils.HZ_CHART_LIB_DETAIL + uniqueCode
+			_ = utils.Rc.Delete(key)
+		}()
+		return
+	}
+
 	// 批量刷新ETA指标
 	err, _ = data.EdbInfoRefreshAllFromBaseV3([]int{correlationChart.EdbInfoIdFirst, correlationChart.EdbInfoIdSecond}, false, true, false)
 	if err != nil {
@@ -594,7 +660,7 @@ func ChartInfoRefresh(chartInfoId int) (err error) {
 		errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + err.Error()
 		return
 	}
-	periodData, correlationData, err := GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
+	periodData, correlationData, err := GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate), "")
 	if err != nil {
 		errMsg = "获取相关性图表, 图表计算值失败, Err:" + err.Error()
 		return
@@ -821,6 +887,9 @@ func AddChartInfo(req data_manage.AddChartInfoReq, source int, sysUser *system.A
 	chartInfo.Instructions = req.Instructions
 	chartInfo.MarkersLines = req.MarkersLines
 	chartInfo.MarkersAreas = req.MarkersAreas
+	if req.ExtraConfig != "" {
+		chartInfo.ExtraConfig = req.ExtraConfig
+	}
 
 	// 指标信息
 	mapList := make([]*data_manage.ChartEdbMapping, 0)
@@ -913,6 +982,8 @@ func AddChartInfo(req data_manage.AddChartInfoReq, source int, sysUser *system.A
 		return
 	}
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfoId)
 
@@ -1154,6 +1225,8 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin, lang
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据
@@ -1219,3 +1292,292 @@ func CopyChartInfo(configId, classifyId int, chartName string, correlationChartI
 
 	return
 }
+
+// CalculateCorrelation 计算相关性-获取x轴和y轴
+func CalculateCorrelation(leadValue int, leadUnit, frequencyA, frequencyB string, dataListA, dataListB []*data_manage.EdbDataList) (xEdbIdValue []int, yDataList []data_manage.YData, err error) {
+	xData := make([]int, 0)
+	yData := make([]float64, 0)
+	if leadValue == 0 {
+		xData = append(xData, 0)
+	}
+	if leadValue > 0 {
+		leadMin := 0 - leadValue
+		xLen := 2*leadValue + 1
+		for i := 0; i < xLen; i++ {
+			n := leadMin + i
+			xData = append(xData, n)
+		}
+	}
+
+	// 计算窗口,不包含第一天
+	//startDateTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+	//startDate = startDateTime.AddDate(0, 0, 1).Format(utils.FormatDate)
+
+	//// 2023-03-02 时间序列始终以指标B为基准, 始终是A进行平移
+	//baseEdbInfo := edbInfoMappingB
+	//changeEdbInfo := edbInfoMappingA
+	// 2023-03-17 时间序列始终以指标A为基准, 始终是B进行平移
+	//baseEdbInfo := edbInfoMappingA
+	//changeEdbInfo := edbInfoMappingB
+
+	// 获取时间基准指标在时间区间内的值
+	//aDataList := make([]*data_manage.EdbDataList, 0)
+	//switch baseEdbInfo.EdbInfoCategoryType {
+	//case 0:
+	//	aDataList, err = data_manage.GetEdbDataList(baseEdbInfo.Source, baseEdbInfo.SubSource, baseEdbInfo.EdbInfoId, startDate, endDate)
+	//case 1:
+	//	_, aDataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(baseEdbInfo.EdbInfoId, startDate, endDate, false)
+	//default:
+	//	err = errors.New("指标base类型异常")
+	//	return
+	//}
+	//
+	//// 获取变频指标所有日期的值, 插值法完善数据
+	//bDataList := make([]*data_manage.EdbDataList, 0)
+	//switch changeEdbInfo.EdbInfoCategoryType {
+	//case 0:
+	//	bDataList, err = data_manage.GetEdbDataList(changeEdbInfo.Source, changeEdbInfo.SubSource, changeEdbInfo.EdbInfoId, "", "")
+	//case 1:
+	//	_, bDataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(changeEdbInfo.EdbInfoId, "", "", false)
+	//default:
+	//	err = errors.New("指标change类型异常")
+	//	return
+	//}
+	//changeDataMap := make(map[string]float64)
+	//newChangeDataList, e := HandleDataByLinearRegression(bDataList, changeDataMap)
+	//if e != nil {
+	//	err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+	//	return
+	//}
+
+	// 2023-03-17 时间序列始终以指标A为基准, 始终是B进行平移
+	baseDataList := make([]*data_manage.EdbDataList, 0)
+	baseDataMap := make(map[string]float64)
+	changeDataList := make([]*data_manage.EdbDataList, 0)
+	changeDataMap := make(map[string]float64)
+
+	// 先把低频指标升频为高频
+	{
+		frequencyIntMap := map[string]int{
+			"日度": 1,
+			"周度": 2,
+			"旬度": 3,
+			"月度": 4,
+			"季度": 5,
+			"年度": 6,
+		}
+
+		// 如果A指标是高频,那么就需要对B指标进行升频
+		if frequencyIntMap[frequencyA] < frequencyIntMap[frequencyB] {
+			tmpNewChangeDataList, e := HandleDataByLinearRegression(dataListB, changeDataMap)
+			if e != nil {
+				err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+				return
+			}
+			changeDataList = tmpNewChangeDataList
+			baseDataList = dataListA
+			for _, v := range baseDataList {
+				baseDataMap[v.DataTime] = v.Value
+			}
+
+		} else if frequencyIntMap[frequencyA] > frequencyIntMap[frequencyB] {
+			// 如果B指标是高频,那么就需要对A指标进行升频
+			tmpNewChangeDataList, e := HandleDataByLinearRegression(dataListA, baseDataMap)
+			if e != nil {
+				err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+				return
+			}
+			baseDataList = tmpNewChangeDataList
+
+			changeDataList = dataListB
+			for _, v := range changeDataList {
+				changeDataMap[v.DataTime] = v.Value
+			}
+		} else {
+			baseDataList = dataListA
+			for _, v := range baseDataList {
+				baseDataMap[v.DataTime] = v.Value
+			}
+			changeDataList = dataListB
+			for _, v := range changeDataList {
+				changeDataMap[v.DataTime] = v.Value
+			}
+		}
+
+	}
+
+	// 计算不领先也不滞后时的相关系数
+	baseCalculateData := make([]float64, 0)
+	baseDataTimeArr := make([]string, 0)
+	for i := range baseDataList {
+		baseDataTimeArr = append(baseDataTimeArr, baseDataList[i].DataTime)
+		baseCalculateData = append(baseCalculateData, baseDataList[i].Value)
+	}
+
+	//zeroBaseData := make([]float64, 0)
+	//zeroCalculateData := make([]float64, 0)
+	//for i := range baseDataTimeArr {
+	//	tmpBaseVal, ok1 := baseDataMap[baseDataTimeArr[i]]
+	//	tmpCalculateVal, ok2 := changeDataMap[baseDataTimeArr[i]]
+	//	if ok1 && ok2 {
+	//		zeroBaseData = append(zeroBaseData, tmpBaseVal)
+	//		zeroCalculateData = append(zeroCalculateData, tmpCalculateVal)
+	//	}
+	//}
+	//if len(zeroBaseData) != len(zeroCalculateData) {
+	//	err = fmt.Errorf("相关系数两组序列元素数不一致, %d-%d", len(baseCalculateData), len(zeroCalculateData))
+	//	return
+	//}
+	//zeroRatio := utils.CalculateCorrelationByIntArr(zeroBaseData, zeroCalculateData)
+	//if leadValue == 0 {
+	//	yData = append(yData, zeroRatio)
+	//}
+
+	// 计算领先/滞后N期
+	if leadValue > 0 {
+		// 平移变频指标领先/滞后的日期(单位天)
+		moveUnitDays := utils.FrequencyDaysMap[leadUnit]
+
+		for i := range xData {
+			//if xData[i] == 0 {
+			//	yData = append(yData, zeroRatio)
+			//	continue
+			//}
+			xCalculateData := make([]float64, 0)
+			yCalculateData := make([]float64, 0)
+
+			// 平移指定天数
+			mDays := int(moveUnitDays) * xData[i]
+			_, dMap := MoveDataDaysToNewDataList(changeDataList, mDays)
+
+			// 取出对应的基准日期的值
+			for i2 := range baseDataTimeArr {
+				tmpDate := baseDataTimeArr[i2]
+				if yVal, ok := dMap[tmpDate]; ok {
+					xCalculateData = append(xCalculateData, baseCalculateData[i2])
+					yCalculateData = append(yCalculateData, yVal)
+				}
+			}
+			if len(yCalculateData) <= 0 {
+				//err = fmt.Errorf("领先滞后相关系数两组序列元素数不一致, %d-%d", len(baseCalculateData), len(yCalculateData))
+				//return
+				// 领先滞后后,没有可以计算的数据了
+				continue
+			}
+
+			// 公式计算出领先/滞后频度对应点的相关性系数
+			ratio := utils.CalculateCorrelationByIntArr(xCalculateData, yCalculateData)
+			yData = append(yData, ratio)
+		}
+	}
+
+	xEdbIdValue = xData
+	yDataList = make([]data_manage.YData, 0)
+	yDate := "0000-00-00"
+	yDataList = append(yDataList, data_manage.YData{
+		Date:  yDate,
+		Value: yData,
+	})
+	return
+}
+
+// GetFactorChartDataByChartId 获取多因子相关性图表数据
+func GetFactorChartDataByChartId(chartInfoId int, extraConfig string) (xEdbIdValue []int, yDataList []data_manage.YData, err error) {
+	if chartInfoId <= 0 {
+		return
+	}
+	// 指标对应的图例
+	extra := new(data_manage.CorrelationChartInfoExtraConfig)
+	if extraConfig != "" {
+		if e := json.Unmarshal([]byte(extraConfig), extra); e != nil {
+			err = fmt.Errorf("解析图表额外配置失败, err: %v", e)
+			return
+		}
+	}
+	legends := make(map[string]*data_manage.CorrelationChartLegend)
+	if extra != nil {
+		for _, v := range extra.LegendConfig {
+			s := fmt.Sprintf("%d-%d", v.SeriesId, v.EdbInfoId)
+			legends[s] = v
+		}
+	}
+
+	// 获取图表引用到的系列指标
+	chartMappingOb := new(data_manage.FactorEdbSeriesChartMapping)
+	cond := fmt.Sprintf(" AND %s = ? AND %s = 1", chartMappingOb.Cols().ChartInfoId, chartMappingOb.Cols().EdbUsed)
+	pars := make([]interface{}, 0)
+	pars = append(pars, chartInfoId)
+	chartMappings, e := chartMappingOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("获取图表引用系列指标失败")
+		return
+	}
+
+	// 取出计算结果
+	yDataList = make([]data_manage.YData, 0)
+	yDate := "0000-00-00"
+	for k, m := range chartMappings {
+		var values []data_manage.FactorEdbSeriesCorrelationMatrixValues
+		if m.CalculateData != "" {
+			e = json.Unmarshal([]byte(m.CalculateData), &values)
+			if e != nil {
+				err = fmt.Errorf("系列指标计算数据有误, err: %v", e)
+				return
+			}
+		}
+		var y []float64
+		for _, v := range values {
+			if k == 0 {
+				xEdbIdValue = append(xEdbIdValue, v.XData)
+			}
+			y = append(y, v.YData)
+		}
+		var yData data_manage.YData
+		yData.Date = yDate
+		yData.Value = y
+		yData.SeriesEdb.SeriesId = m.FactorEdbSeriesId
+		yData.SeriesEdb.EdbInfoId = m.EdbInfoId
+
+		// 图例
+		s := fmt.Sprintf("%d-%d", m.FactorEdbSeriesId, m.EdbInfoId)
+		legend := legends[s]
+		if legend != nil {
+			yData.Name = legend.LegendName
+			yData.Color = legend.Color
+		}
+		yDataList = append(yDataList, yData)
+	}
+	return
+}
+
+// FormatChartEdbInfoMappings 补充指标信息
+func FormatChartEdbInfoMappings(chartInfoId int, mappings []*data_manage.ChartEdbInfoMapping) (edbList []*data_manage.ChartEdbInfoMapping, err error) {
+	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
+	if len(mappings) == 0 {
+		return
+	}
+
+	for _, v := range mappings {
+		if chartInfoId <= 0 {
+			v.IsAxis = 1
+			v.LeadValue = 0
+			v.LeadUnit = ""
+			v.ChartEdbMappingId = 0
+			v.ChartInfoId = 0
+			v.IsOrder = false
+			v.EdbInfoType = 1
+			v.ChartStyle = ""
+			v.ChartColor = ""
+			v.ChartWidth = 0
+		} else {
+			v.LeadUnitEn = data.GetLeadUnitEn(v.LeadUnit)
+			v.LeadUnitEn = data.GetLeadUnitEn(v.LeadUnit)
+		}
+		v.FrequencyEn = data.GetFrequencyEn(v.Frequency)
+		if v.Unit == `无` {
+			v.Unit = ``
+		}
+		edbList = append(edbList, v)
+	}
+	return
+}

+ 9 - 3
services/data/cross_variety/chart.go

@@ -650,6 +650,7 @@ func AddChartInfo(req request.AddChartReq, sysUser *system.Admin, lang string) (
 		}
 	}
 
+	edbInfoIdArr := make([]int, 0)
 	// 数据校验(品种、标签、指标)
 	{
 		// 标签m
@@ -691,6 +692,7 @@ func AddChartInfo(req request.AddChartReq, sysUser *system.Admin, lang string) (
 			isSendEmail = false
 			return
 		}
+		edbInfoIdArr = edbInfoIdList
 		mappingList, tmpErr := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdList)
 		if tmpErr != nil {
 			errMsg = "获取指标信息失败"
@@ -793,7 +795,9 @@ func AddChartInfo(req request.AddChartReq, sysUser *system.Admin, lang string) (
 		err = errors.New("新增相关性图表失败, Err: " + e.Error())
 		return
 	}
-
+	chartInfo.ChartInfoId = chartInfoId
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfoId)
 
@@ -905,7 +909,7 @@ func EditChartInfo(req request.EditChartReq, sysUser *system.Admin, lang string)
 		isSendEmail = false
 		return
 	}
-	
+	edbInfoIdArr := make([]int, 0)
 	// 数据校验(品种、标签、指标)
 	{
 		// 标签m
@@ -947,6 +951,7 @@ func EditChartInfo(req request.EditChartReq, sysUser *system.Admin, lang string)
 			isSendEmail = false
 			return
 		}
+		edbInfoIdArr = edbInfoIdList
 		mappingList, tmpErr := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdList)
 		if tmpErr != nil {
 			errMsg = "获取指标信息失败"
@@ -1032,7 +1037,8 @@ func EditChartInfo(req request.EditChartReq, sysUser *system.Admin, lang string)
 	resp.ChartInfoId = chartItem.ChartInfoId
 	resp.UniqueCode = chartItem.UniqueCode
 	//resp.ChartType = req.ChartType
-
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据

+ 65 - 0
services/data/edb_classify.go

@@ -127,6 +127,52 @@ func GetFullClassifyByClassifyId(targetClassifyId int) (targetList []*data_manag
 	return
 }
 
+// GetFullClassifyByRootId 查询指标列表里的分类信息
+func GetFullClassifyByRootId(targetClassify *data_manage.EdbClassify, tmpList []*data_manage.EdbClassifyItems) (targetList []*data_manage.EdbClassifyIdItems, err error, errMsg string) {
+	if targetClassify.ParentId == 0 {
+		targetItem := new(data_manage.EdbClassifyIdItems)
+		targetItem.ClassifyId = targetClassify.ClassifyId
+		targetItem.ParentId = targetClassify.ParentId
+		targetItem.RootId = targetClassify.RootId
+		targetItem.UniqueCode = targetClassify.UniqueCode
+		targetItem.Level = targetClassify.Level
+		targetItem.ClassifyName = targetClassify.ClassifyName
+		targetItem.ClassifyName = targetClassify.ClassifyName
+		targetItem.IsJoinPermission = targetClassify.IsJoinPermission
+		targetList = append(targetList, targetItem)
+		return
+	}
+
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.ClassifyId == targetClassify.ClassifyId {
+				idMap[v.ClassifyId] = struct{}{}
+				idMap[v.ParentId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ClassifyId]; ok {
+				idMap[v.ParentId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ClassifyId]; ok {
+				targetItem := new(data_manage.EdbClassifyIdItems)
+				targetItem.ClassifyId = v.ClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.RootId = v.RootId
+				targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetItem.ClassifyName = v.ClassifyName
+				targetItem.IsJoinPermission = v.IsJoinPermission
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+	return
+}
+
 func GetChildClassifyByClassifyId(targetClassifyId int) (targetList []*data_manage.EdbClassifyIdItems, err error, errMsg string) {
 	//判断是否是挂在顶级目录下
 	targetClassify, err := data_manage.GetEdbClassifyById(targetClassifyId)
@@ -650,6 +696,25 @@ func DeleteCheck(classifyId, edbInfoId int, sysUser *system.Admin) (deleteStatus
 				return
 			}*/
 		}
+
+		// 判断指标是否用于因子指标系列
+		{
+			ob := new(data_manage.FactorEdbSeriesMapping)
+			cond := fmt.Sprintf(" AND %s = ?", ob.Cols().EdbInfoId)
+			pars := make([]interface{}, 0)
+			pars = append(pars, edbInfoId)
+			count, e := ob.GetCountByCondition(cond, pars)
+			if e != nil {
+				errMsg = "删除失败"
+				err = fmt.Errorf("获取指标是否用于系列失败, err: %v", e)
+				return
+			}
+			if count > 0 {
+				deleteStatus = 3
+				tipsMsg = "当前指标已用于因子指标系列, 不可删除"
+				return
+			}
+		}
 	}
 	return
 }

+ 107 - 1
services/data/edb_data.go

@@ -409,7 +409,7 @@ func GetPageData(edbInfoId, source, subSource int, endDataTime string, startSize
 	dataList = make([]*data_manage.EdbData, 0)
 
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getPageDataByMongo(edbInfoId, source, subSource, endDataTime, startSize, pageSize)
 	}
 
@@ -515,3 +515,109 @@ func getPageDataByMongo(edbInfoId, source, subSource int, endDataTime string, st
 
 	return
 }
+
+// GetEdbDataTbzForSeason 获取指标的同比值数据
+func GetEdbDataTbzForSeason(frequency string, tmpDataList []*data_manage.EdbDataList, startDateTime time.Time) (dataList []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
+	dataList = make([]*data_manage.EdbDataList, 0)
+
+	// 数据处理
+	var dateArr []string
+	dataMap := make(map[string]*data_manage.EdbDataList)
+	for _, v := range tmpDataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	for _, av := range dateArr {
+		currentItem, ok := dataMap[av]
+		// 如果找不到当前日期的数据,那么终止当前循环,进入下一循环
+		if !ok {
+			continue
+		}
+		tmpItem := *currentItem
+		var isOk bool //是否计算出来结果
+
+		//当前日期
+		currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 如果存在开始日期,同时,当前日期早于开始日期,那么终止当前循环,进入下一循环
+		if !startDateTime.IsZero() && currentDate.Before(startDateTime) {
+			continue
+		}
+		//上一年的日期
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+			tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+			isOk = true
+		} else {
+			if frequency == "月度" { //向上和向下,各找一个月
+				for i := 0; i <= 35; i++ {
+					nextDateDay := preDate.AddDate(0, 0, i)
+					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						preDateDay := preDate.AddDate(0, 0, -i)
+						preDateDayStr := preDateDay.Format(utils.FormatDate)
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			} else if frequency == "季度" || frequency == "年度" {
+				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期->下一个月找到
+					tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+					isOk = true
+					break
+				}
+			} else {
+				nextDateDay := preDate.AddDate(0, 0, 1)
+				nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+
+				preDateDay := preDate.AddDate(0, 0, -1)
+				preDateDayStr := preDateDay.Format(utils.FormatDate)
+
+				for i := 0; i < 35; i++ {
+					if i >= 1 {
+						nextDateDay = nextDateDay.AddDate(0, 0, i)
+						nextDateDayStr = nextDateDay.Format(utils.FormatDate)
+					}
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						if i >= 1 {
+							preDateDay = preDate.AddDate(0, 0, -i)
+							preDateDayStr = nextDateDay.Format(utils.FormatDate)
+						}
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			}
+		}
+
+		if isOk {
+			if tmpItem.Value > maxValue {
+				maxValue = tmpItem.Value
+			}
+			if tmpItem.Value < minValue {
+				minValue = tmpItem.Value
+			}
+			dataList = append(dataList, &tmpItem)
+		}
+	}
+
+	return
+}

+ 16 - 3
services/data/edb_info.go

@@ -3,6 +3,7 @@ package data
 import (
 	"encoding/json"
 	"errors"
+	"eta/eta_api/cache"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/services/alarm_msg"
@@ -103,8 +104,10 @@ func EdbInfoRefreshAllFromBaseV3(edbInfoIdList []int, refreshAll, isSync, isRefr
 	}
 
 	// 需要刷新的指标数量
-	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
-
+	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr) + len(newBasePredictEdbInfoArr)
+	if totalEdbInfo == 0 {
+		return
+	}
 	if totalEdbInfo <= 20 || isSync {
 		err = edbInfoRefreshAll(refreshAll, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
 	} else {
@@ -173,6 +176,9 @@ func getEdbInfoIdList(edbInfoIdList []int) (newBaseEdbInfoArr, newBasePredictEdb
 		// 普通计算指标
 		for _, edbInfo := range tmpCalculateMap {
 			if _, ok := newCalculateMap[edbInfo.EdbInfoId]; !ok {
+				if edbInfo.NoUpdate == 1 {
+					continue
+				}
 				newCalculateMap[edbInfo.EdbInfoId] = edbInfo
 				calculateArr = append(calculateArr, edbInfo.EdbInfoId)
 			}
@@ -1903,6 +1909,12 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 	//添加es
 	AddOrEditEdbInfoToEs(int(edbInfoId))
 
+	// 更新钢联化工状态为启用
+	if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+		// 启动钢联的刷新
+		_ = data_manage.UpdateMysteelChemicalRefreshStatus(edbCode, 0)
+	}
+
 	return
 }
 
@@ -2434,7 +2446,8 @@ func EdbInfoReplace(oldEdbInfo, newEdbInfo *data_manage.EdbInfo, sysAdminId int,
 	if err != nil {
 		return
 	}
-
+	//加入到缓存队列中处理
+	go cache.AddReplaceEdbInfo(oldEdbInfo, newEdbInfo)
 	// 更新所有的关联指标
 	err, _ = EdbInfoRefreshAllFromBaseV3(relationEdbInfoIdList, true, true, true)
 	return

+ 140 - 0
services/data/edb_info_calculate.go

@@ -9,6 +9,7 @@ import (
 	"github.com/shopspring/decimal"
 	"math"
 	"regexp"
+	"sort"
 	"strings"
 	"time"
 )
@@ -659,3 +660,142 @@ func GetMaxMinEdbInfo(formula string) string {
 	formula = strings.ReplaceAll(formula, "min", "MIN")
 	return formula
 }
+
+// HandleDataByLinearRegressionToList 插值法补充数据(线性方程式)
+func HandleDataByLinearRegressionToList (edbInfoDataList []*data_manage.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string,valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *data_manage.EdbDataList
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			//startEdbInfoData.DataTime = startEdbInfoData.DataTime[:5]+ "01-01"
+			continue
+		}
+
+		// 获取两条数据之间相差的天数
+		startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+		currDataTime, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+		betweenDay := betweenHour / 24
+
+		// 如果相差一天,那么过滤
+		if betweenDay <= 1 {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			tmpCoordinate1 := utils.Coordinate{
+				X: 1,
+				Y: startEdbInfoData.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate1)
+			tmpCoordinate2 := utils.Coordinate{
+				X: float64(betweenDay) + 1,
+				Y: v.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate2)
+
+			a, b = utils.GetLinearResult(coordinateData)
+			if math.IsNaN(a) || math.IsNaN(b) {
+				err = errors.New("线性方程公式生成失败")
+				return
+			}
+		}
+
+		// 生成对应的值
+		{
+			for i := 1; i < betweenDay; i++ {
+				tmpDataTime := startDataTime.AddDate(0, 0, i)
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, tmpDataTime.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}
+
+// HandleDataByLinearRegressionToList 保证生成365个数据点的线性插值法
+func HandleDataByLinearRegressionToListV2(edbInfoDataList []*data_manage.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string, valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	// 确保至少有两天数据来生成线性方程
+	if len(edbInfoDataList) < 2 {
+		err = errors.New("至少需要两天的数据来执行线性插值")
+		return
+	}
+
+	// 对数据按日期排序,确保顺序正确
+	sort.Slice(edbInfoDataList, func(i, j int) bool {
+		t1, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[i].DataTime, time.Local)
+		t2, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[j].DataTime, time.Local)
+		return t1.Before(t2)
+	})
+
+	startEdbInfoData := edbInfoDataList[0]
+	endEdbInfoData := edbInfoDataList[len(edbInfoDataList)-1]
+
+	// 计算起始和结束日期间实际的天数
+	startDate, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+	endDate, _ := time.ParseInLocation(utils.FormatDate, endEdbInfoData.DataTime, time.Local)
+	actualDays := endDate.Sub(startDate).Hours() / 24
+
+	// 生成365个数据点,首先处理已有数据
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		valueList = append(valueList, v.Value)
+	}
+
+	// 如果已有数据跨越天数不足365天,则对缺失的日期进行线性插值
+	if actualDays < 365 {
+		// 使用已有数据点生成线性方程(这里简化处理,实际可能需更细致处理边界情况)
+		var a, b float64
+		coordinateData := []utils.Coordinate{
+			{X: 1, Y: startEdbInfoData.Value},
+			{X: float64(len(edbInfoDataList)), Y: endEdbInfoData.Value},
+		}
+		a, b = utils.GetLinearResult(coordinateData)
+		if math.IsNaN(a) || math.IsNaN(b) {
+			err = errors.New("线性方程公式生成失败")
+			return
+		}
+
+		// 对剩余日期进行插值
+		for i := 1; i < 365; i++ {
+			day := startDate.AddDate(0, 0, i)
+			if _, exists := handleDataMap[day.Format(utils.FormatDate)]; !exists {
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[day.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, day.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+	}
+
+	return
+}

+ 680 - 0
services/data/edb_info_relation.go

@@ -0,0 +1,680 @@
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	excelModel "eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/fe_calendar"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/services/sandbox"
+	"eta/eta_api/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// SaveChartEdbInfoRelation 添加/编辑图表指标引用关联记录
+func SaveChartEdbInfoRelation(edbInfoIds []int, chartInfo *data_manage.ChartInfo) (err error) {
+	//更新指标刷新状态为启用
+	err = saveEdbInfoRelation(edbInfoIds, chartInfo.ChartInfoId, utils.EDB_RELATION_CHART, chartInfo.Source)
+	return
+}
+
+// saveEdbInfoRelation 添加/编辑图表指标引用关联记录
+func saveEdbInfoRelation(edbInfoIds []int, objectId, objectType, objectSubType int) (err error) {
+	// 实现添加引用记录的逻辑
+	if len(edbInfoIds) == 0 {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := "实现添加引用记录的逻辑-添加/编辑图表指标引用关联记录失败, ErrMsg:\n" + err.Error()
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+	refreshIds := make([]int, 0)
+	indexCodeList := make([]string, 0)
+	// 查询指标信息
+	edbInfoListTmp, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("查询指标信息失败,%s", e.Error())
+		return
+	}
+	// 过滤预测指标
+	edbInfoList := make([]*data_manage.EdbInfo, 0)
+	for _, v := range edbInfoListTmp {
+		if v.EdbInfoType == 0 {
+			edbInfoList = append(edbInfoList, v)
+		}
+	}
+	// 查询计算指标信息,并且建立关联关系
+	// 查询间接引用的指标信息
+	calculateEdbMappingListMap, calculateEdbMappingIdsMap, e := GetEdbListByEdbInfoId(edbInfoList)
+	if e != nil {
+		err = fmt.Errorf("查询计算指标信息失败,%s", e.Error())
+		return
+	}
+	// 只统计钢联化工和wind来源的指标
+	for _, edbInfo := range edbInfoList {
+		/*if edbInfo.Source != utils.DATA_SOURCE_WIND && edbInfo.Source != utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			continue
+		}*/
+		refreshIds = append(refreshIds, edbInfo.EdbInfoId)
+		if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			indexCodeList = append(indexCodeList, edbInfo.EdbCode)
+		}
+	}
+	// 循转组装引用
+	// 查询已有的引用关系
+	existList, e := data_manage.GetEdbInfoRelationByReferObjectId(objectId, objectType)
+	if e != nil {
+		err = fmt.Errorf("查询已有的引用关系失败,%s", e.Error())
+		return
+	}
+	deleteMap := make(map[int]bool)
+	relationMap := make(map[int]bool)
+	for _, exist := range existList {
+		deleteMap[exist.EdbInfoId] = true
+		relationMap[exist.EdbInfoId] = true
+	}
+	// 新增不存在的引用关系
+	// 删除不再需要的引用关系
+	nowTime := time.Now()
+	addList := make([]*data_manage.EdbInfoRelation, 0)
+	deleteEdbInfoIds := make([]int, 0)
+	for _, edbInfo := range edbInfoList {
+		/*if edbInfo.Source != utils.DATA_SOURCE_WIND && edbInfo.Source != utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			continue
+		}*/
+		if _, ok := relationMap[edbInfo.EdbInfoId]; ok {
+			delete(deleteMap, edbInfo.EdbInfoId)
+		} else {
+			tmp := &data_manage.EdbInfoRelation{
+				ReferObjectId:      objectId,
+				ReferObjectType:    objectType,
+				ReferObjectSubType: objectSubType,
+				EdbInfoId:          edbInfo.EdbInfoId,
+				EdbName:            edbInfo.EdbName,
+				Source:             edbInfo.Source,
+				EdbCode:            edbInfo.EdbCode,
+				CreateTime:         nowTime,
+				ModifyTime:         nowTime,
+				RelationTime:       nowTime,
+			}
+			tmp.RelationCode = fmt.Sprintf("%d_%d_%d_%d", tmp.EdbInfoId, tmp.ReferObjectId, tmp.ReferObjectType, tmp.ReferObjectSubType)
+			addList = append(addList, tmp)
+			if edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0 {
+				childEdbMappingIds, ok1 := calculateEdbMappingIdsMap[edbInfo.EdbInfoId]
+				if !ok1 {
+					continue
+				}
+				for _, childEdbMappingId := range childEdbMappingIds {
+					childEdbMapping, ok2 := calculateEdbMappingListMap[childEdbMappingId]
+					if !ok2 {
+						continue
+					}
+
+					if childEdbMapping.FromSource == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+						indexCodeList = append(indexCodeList, childEdbMapping.FromEdbCode)
+					}
+					tmp1 := &data_manage.EdbInfoRelation{
+						ReferObjectId:      objectId,
+						ReferObjectType:    objectType,
+						ReferObjectSubType: objectSubType,
+						EdbInfoId:          childEdbMapping.FromEdbInfoId,
+						EdbName:            childEdbMapping.FromEdbName,
+						Source:             childEdbMapping.FromSource,
+						EdbCode:            childEdbMapping.FromEdbCode,
+						CreateTime:         nowTime,
+						ModifyTime:         nowTime,
+						RelationTime:       nowTime,
+						RelationType:       1,
+						RootEdbInfoId:      edbInfo.EdbInfoId,
+						ChildEdbInfoId:     childEdbMapping.EdbInfoId,
+						RelationCode:       tmp.RelationCode,
+					}
+					addList = append(addList, tmp1)
+					refreshIds = append(refreshIds, childEdbMapping.FromEdbInfoId)
+					// todo 防止重复
+				}
+			}
+
+		}
+	}
+
+	// 删除不再需要的引用关系
+	for deleteId, _ := range deleteMap {
+		deleteEdbInfoIds = append(deleteEdbInfoIds, deleteId)
+	}
+	//更新指标刷新状态为启用
+	err = data_manage.AddOrUpdateEdbInfoRelation(objectId, objectType, addList, deleteEdbInfoIds, refreshIds, indexCodeList)
+	if err != nil {
+		err = fmt.Errorf("删除不再需要的引用关系失败,%s", err.Error())
+		return
+	}
+	return
+}
+
+// SaveSandBoxEdbInfoRelation 添加/编辑 eta逻辑图指标引用
+func SaveSandBoxEdbInfoRelation(sandBoxId int, sandBoxContent string) (err error) {
+	edbInfoIds, err := sandbox.GetSandBoxEdbIdsByContent(sandBoxContent)
+	if err != nil {
+		return
+	}
+	if len(edbInfoIds) == 0 {
+		return
+	}
+	//更新指标刷新状态为启用
+	err = saveEdbInfoRelation(edbInfoIds, sandBoxId, utils.EDB_RELATION_SANDBOX, 0)
+	return
+}
+
+// SaveCalendarEdbInfoRelation 添加/编辑 事件日历指标引用
+func SaveCalendarEdbInfoRelation(chartPermissionId int, matterDate string, editMatters, removeMatters []*fe_calendar.FeCalendarMatter) (err error) {
+	defer func() {
+		if err != nil {
+			err = fmt.Errorf("添加/编辑 事件日历指标引用失败,%s", err.Error())
+			go alarm_msg.SendAlarmMsg(err.Error(), 3)
+		}
+	}()
+	//整理相关的事件ID
+	matterIds := make([]int, 0)
+	updateMatterMap := make(map[int]*fe_calendar.FeCalendarMatter)
+	deleteMatterMap := make(map[int]struct{})
+	for _, matter := range removeMatters {
+		deleteMatterMap[matter.FeCalendarMatterId] = struct{}{}
+		matterIds = append(matterIds, matter.FeCalendarMatterId)
+	}
+	for _, matter := range editMatters {
+		updateMatterMap[matter.FeCalendarMatterId] = matter
+		matterIds = append(matterIds, matter.FeCalendarMatterId)
+	}
+
+	//删除ID,先删除
+	deleteObjectIds := make([]int, 0)
+	if len(matterIds) > 0 {
+		relationList, e := data_manage.GetEdbInfoRelationAllByReferObjectIds(matterIds, utils.EDB_RELATION_CALENDAR)
+		if e != nil {
+			err = fmt.Errorf("查询事件日历指标引用失败,%s", e.Error())
+			return
+		}
+		for _, relation := range relationList {
+			if _, ok := deleteMatterMap[relation.ReferObjectId]; ok {
+				deleteObjectIds = append(deleteObjectIds, relation.ReferObjectId)
+			}
+			if newMatter, ok := updateMatterMap[relation.ReferObjectId]; ok {
+				if relation.EdbInfoId != newMatter.EdbInfoId {
+					deleteObjectIds = append(deleteObjectIds, relation.ReferObjectId)
+				}
+			}
+		}
+	}
+	if len(deleteObjectIds) > 0 {
+		err = data_manage.DeleteEdbRelationByObjectIds(deleteObjectIds, utils.EDB_RELATION_CALENDAR)
+		if err != nil {
+			err = fmt.Errorf("删除事件日历指标引用失败,%s", err.Error())
+			return
+		}
+	}
+
+	// 获取已有事项
+	matterOb := new(fe_calendar.FeCalendarMatter)
+	cond := fmt.Sprintf(` AND %s = ? AND %s = ?`, fe_calendar.FeCalendarMatterCols.ChartPermissionId, fe_calendar.FeCalendarMatterCols.MatterDate)
+	pars := make([]interface{}, 0)
+	pars = append(pars, chartPermissionId, matterDate)
+	order := fmt.Sprintf(`%s ASC`, fe_calendar.FeCalendarMatterCols.Sort)
+	matters, e := matterOb.GetItemsByCondition(cond, pars, []string{}, order)
+	if e != nil {
+		err = fmt.Errorf("查询事件日历事项失败,%s", e.Error())
+		return
+	}
+	// 循环查询matters
+	edbInfoIds := make([]int, 0)
+	refreshIds := make([]int, 0)
+	indexCodeList := make([]string, 0)
+
+	newMatterIds := make([]int, 0)
+	for _, matter := range matters {
+		newMatterIds = append(newMatterIds, matter.FeCalendarMatterId)
+		edbInfoIds = append(edbInfoIds, matter.EdbInfoId)
+	}
+
+	// 查询指标信息
+	edbInfoListTmp, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("查询指标信息失败,%s", e.Error())
+		return
+	}
+	// 过滤预测指标
+	edbInfoList := make([]*data_manage.EdbInfo, 0)
+	for _, v := range edbInfoListTmp {
+		if v.EdbInfoType == 0 {
+			edbInfoList = append(edbInfoList, v)
+		}
+	}
+	// 查询计算指标信息,并且建立关联关系
+	// 查询间接引用的指标信息
+	calculateEdbMappingListMap, calculateEdbMappingIdsMap, e := GetEdbListByEdbInfoId(edbInfoList)
+	if e != nil {
+		err = fmt.Errorf("查询计算指标信息失败,%s", e.Error())
+		return
+	}
+	addEdbInfoIdMap := make(map[int]*data_manage.EdbInfo)
+	for _, edbInfo := range edbInfoList {
+		/*if edbInfo.Source != utils.DATA_SOURCE_WIND && edbInfo.Source != utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			continue
+		}*/
+		refreshIds = append(refreshIds, edbInfo.EdbInfoId)
+		addEdbInfoIdMap[edbInfo.EdbInfoId] = edbInfo
+		if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			indexCodeList = append(indexCodeList, edbInfo.EdbCode)
+		}
+	}
+
+	relationMap := make(map[int]struct{})
+	if len(newMatterIds) > 0 {
+		//查询已有的matters
+		relationList, e := data_manage.GetEdbInfoRelationByReferObjectIds(newMatterIds, utils.EDB_RELATION_CALENDAR)
+		if e != nil {
+			err = fmt.Errorf("查询事件日历指标引用失败,%s", e.Error())
+			return
+		}
+		for _, relation := range relationList {
+			relationMap[relation.ReferObjectId] = struct{}{}
+		}
+	}
+	addList := make([]*data_manage.EdbInfoRelation, 0)
+	nowTime := time.Now()
+	for _, matter := range matters {
+		_, ok1 := relationMap[matter.FeCalendarMatterId]
+		edbInfo, ok2 := addEdbInfoIdMap[matter.EdbInfoId]
+		if !ok1 && ok2 {
+			tmp := &data_manage.EdbInfoRelation{
+				ReferObjectId:      matter.FeCalendarMatterId,
+				ReferObjectType:    utils.EDB_RELATION_CALENDAR,
+				ReferObjectSubType: 0,
+				EdbInfoId:          edbInfo.EdbInfoId,
+				EdbName:            edbInfo.EdbName,
+				Source:             edbInfo.Source,
+				EdbCode:            edbInfo.EdbCode,
+				CreateTime:         nowTime,
+				ModifyTime:         nowTime,
+				RelationTime:       matter.CreateTime,
+			}
+			tmp.RelationCode = fmt.Sprintf("%d_%d_%d_%d", tmp.EdbInfoId, tmp.ReferObjectId, tmp.ReferObjectType, tmp.ReferObjectSubType)
+			addList = append(addList, tmp)
+			//添加指标间接引用
+			if edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0 {
+				childEdbMappingIds, ok1 := calculateEdbMappingIdsMap[edbInfo.EdbInfoId]
+				if !ok1 {
+					continue
+				}
+				for _, childEdbMappingId := range childEdbMappingIds {
+					childEdbMapping, ok2 := calculateEdbMappingListMap[childEdbMappingId]
+					if !ok2 {
+						continue
+					}
+					if childEdbMapping.FromSource == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+						indexCodeList = append(indexCodeList, childEdbMapping.FromEdbCode)
+					}
+					tmp1 := &data_manage.EdbInfoRelation{
+						ReferObjectId:      matter.FeCalendarMatterId,
+						ReferObjectType:    utils.EDB_RELATION_CALENDAR,
+						ReferObjectSubType: 0,
+						EdbInfoId:          childEdbMapping.FromEdbInfoId,
+						EdbName:            childEdbMapping.FromEdbName,
+						Source:             childEdbMapping.FromSource,
+						EdbCode:            childEdbMapping.FromEdbCode,
+						CreateTime:         nowTime,
+						ModifyTime:         nowTime,
+						RelationTime:       nowTime,
+						RelationType:       1,
+						RootEdbInfoId:      edbInfo.EdbInfoId,
+						ChildEdbInfoId:     childEdbMapping.EdbInfoId,
+						RelationCode:       tmp.RelationCode,
+					}
+					addList = append(addList, tmp1)
+					refreshIds = append(refreshIds, childEdbMapping.FromEdbInfoId)
+					// todo 防止重复
+				}
+			}
+		}
+	}
+	//更新指标刷新状态为启用
+	err = data_manage.AddOrUpdateEdbInfoRelationMulti(addList, refreshIds, indexCodeList)
+	if err != nil {
+		err = fmt.Errorf("添加指标引用,%s", err.Error())
+		return
+	}
+	return
+}
+
+// GetEdbRelationList 获取指标引用列表
+func GetEdbRelationList(source, edbType int, classifyId, sysUserId, frequency, keyword, status string, startSize, pageSize int, sortParam, sortType string) (total int, list []*data_manage.BaseRelationEdbInfo, err error) {
+	var pars []interface{}
+	var condition string
+
+	list = make([]*data_manage.BaseRelationEdbInfo, 0)
+
+	isStop := -1
+	if status == `暂停` {
+		isStop = 1
+	} else if status == "启用" {
+		isStop = 0
+	}
+
+	switch source {
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL, utils.DATA_SOURCE_WIND:
+		condition += ` AND e.source = ? `
+		pars = append(pars, source)
+	}
+
+	if edbType == 2 { //计算指标
+		condition += ` AND e.edb_type = ? AND e.edb_info_type = 0`
+		pars = append(pars, edbType)
+	}
+
+	if isStop >= 0 {
+		condition += " AND e.no_update = ? "
+		pars = append(pars, isStop)
+	}
+
+	if classifyId != `` {
+		classifyIdSlice := strings.Split(classifyId, ",")
+		condition += ` AND e.classify_id IN (` + utils.GetOrmInReplace(len(classifyIdSlice)) + `)`
+		pars = append(pars, classifyIdSlice)
+	}
+	if sysUserId != `` {
+		sysUserIdSlice := strings.Split(sysUserId, ",")
+		condition += ` AND e.sys_user_id IN (` + utils.GetOrmInReplace(len(sysUserIdSlice)) + `)`
+		pars = append(pars, sysUserIdSlice)
+	}
+	if frequency != `` {
+		frequencySlice := strings.Split(frequency, ",")
+		condition += ` AND e.frequency IN (` + utils.GetOrmInReplace(len(frequencySlice)) + `)`
+		pars = append(pars, frequencySlice)
+	}
+	if keyword != `` {
+		keywordSlice := strings.Split(keyword, " ")
+		if len(keywordSlice) > 0 {
+			tmpConditionSlice := make([]string, 0)
+			tmpConditionSlice = append(tmpConditionSlice, ` e.edb_name like ? or e.edb_code like ? `)
+			pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+
+			for _, v := range keywordSlice {
+				if v == ` ` || v == `` {
+					continue
+				}
+				tmpConditionSlice = append(tmpConditionSlice, ` e.edb_name like ? or e.edb_code like ? `)
+				pars = utils.GetLikeKeywordPars(pars, v, 2)
+			}
+			condition += ` AND (` + strings.Join(tmpConditionSlice, " or ") + `)`
+
+		} else {
+			condition += ` AND (e.edb_name like ? or e.edb_code like ? )`
+			pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+		}
+	}
+
+	sortStr := ``
+	if sortParam != `` {
+		sortStr = fmt.Sprintf("%s %s,e.edb_info_id desc ", sortParam, sortType)
+	}
+
+	total, list, err = data_manage.GetEdbInfoRelationList(condition, pars, sortStr, startSize, pageSize)
+
+	return
+}
+
+// 查找当前计算指标的所有溯源指标
+func GetEdbListByEdbInfoId(edbInfoList []*data_manage.EdbInfo) (edbMappingListMap map[int]*data_manage.EdbInfoCalculateMapping, edbInfoMappingRootIdsMap map[int][]int, err error) {
+	if len(edbInfoList) == 0 {
+		return
+	}
+	edbInfoIds := make([]int, 0)
+	for _, v := range edbInfoList {
+		if v.EdbType == 2 && v.EdbInfoType == 0 { //普通计算指标,排除预算指标
+			edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+		}
+	}
+	if len(edbInfoIds) == 0 {
+		return
+	}
+	//查询指标信息
+	allEdbMappingMap := make(map[int][]*data_manage.EdbInfoCalculateMappingInfo, 0)
+	allMappingList, e := data_manage.GetEdbInfoCalculateMappingListByEdbInfoIds(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("GetEdbInfoCalculateMappingListByEdbInfoIds err: %s", e.Error())
+		return
+	}
+	for _, v := range allMappingList {
+		if _, ok := allEdbMappingMap[v.EdbInfoId]; !ok {
+			allEdbMappingMap[v.EdbInfoId] = make([]*data_manage.EdbInfoCalculateMappingInfo, 0)
+		}
+		allEdbMappingMap[v.EdbInfoId] = append(allEdbMappingMap[v.EdbInfoId], v)
+	}
+	//查询指标映射
+	//查询所有指标数据
+	//查询这个指标相关的mapping信息放到数组里,
+	//将得到的指标ID信息放到数组里
+	hasFindMap := make(map[int]struct{})
+	edbInfoIdMap := make(map[int]struct{})
+	edbMappingList := make([]*data_manage.EdbInfoCalculateMapping, 0)
+	edbInfoMappingRootIdsMap = make(map[int][]int, 0)
+	edbMappingMap := make(map[int]struct{})
+	for _, edbInfo := range edbInfoList {
+		if edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0 {
+			edbInfoId := edbInfo.EdbInfoId
+			edbMappingList, err = getCalculateEdbInfoByEdbInfoId(allEdbMappingMap, edbInfoId, hasFindMap, edbInfoIdMap, edbMappingList, edbMappingMap, edbInfoMappingRootIdsMap, edbInfoId)
+			if err != nil {
+				err = fmt.Errorf(" GetCalculateEdbInfoByEdbInfoId err: %s", err.Error())
+				return
+			}
+		}
+	}
+	if len(edbMappingList) == 0 {
+		return
+	}
+	// 查询指标信息
+	// 指标信息map
+	edbInfoIdList := make([]int, 0)
+	for k, _ := range edbInfoIdMap {
+		edbInfoIdList = append(edbInfoIdList, k)
+	}
+	edbMappingListMap = make(map[int]*data_manage.EdbInfoCalculateMapping)
+
+	if len(edbMappingList) > 0 {
+		for _, v := range edbMappingList {
+			edbMappingListMap[v.EdbInfoCalculateMappingId] = v
+		}
+	}
+	return
+}
+
+// getCalculateEdbInfoByEdbInfoId 计算指标追溯
+func getCalculateEdbInfoByEdbInfoId(allEdbMappingMap map[int][]*data_manage.EdbInfoCalculateMappingInfo, edbInfoId int, hasFindMap map[int]struct{}, edbInfoIdMap map[int]struct{}, edbMappingList []*data_manage.EdbInfoCalculateMapping, edbMappingMap map[int]struct{}, edbInfoMappingRootIdsMap map[int][]int, rootEdbInfoId int) (newEdbMappingList []*data_manage.EdbInfoCalculateMapping, err error) {
+	newEdbMappingList = edbMappingList
+	_, ok := hasFindMap[edbInfoId]
+	if ok {
+		return
+	}
+
+	if _, ok1 := edbInfoIdMap[edbInfoId]; !ok1 {
+		edbInfoIdMap[edbInfoId] = struct{}{}
+	}
+	edbInfoMappingList := make([]*data_manage.EdbInfoCalculateMappingInfo, 0)
+	edbInfoMappingList, ok = allEdbMappingMap[edbInfoId]
+	if !ok {
+		edbInfoMappingList, err = data_manage.GetEdbInfoCalculateMappingListByEdbInfoId(edbInfoId)
+		if err != nil {
+			err = fmt.Errorf("GetEdbInfoCalculateMappingListByEdbInfoId err: %s", err.Error())
+			return
+		}
+	}
+	hasFindMap[edbInfoId] = struct{}{}
+	if len(edbInfoMappingList) > 0 {
+		fromEdbInfoIdList := make([]int, 0)
+		edbInfoMappingIdList := make([]int, 0)
+		for _, v := range edbInfoMappingList {
+			fromEdbInfoIdList = append(fromEdbInfoIdList, v.FromEdbInfoId)
+			edbInfoMappingIdList = append(edbInfoMappingIdList, v.EdbInfoCalculateMappingId)
+			if _, ok1 := edbInfoIdMap[v.FromEdbInfoId]; !ok1 {
+				edbInfoIdMap[v.FromEdbInfoId] = struct{}{}
+			}
+			if _, ok2 := edbMappingMap[v.EdbInfoCalculateMappingId]; !ok2 {
+				edbMappingMap[v.EdbInfoCalculateMappingId] = struct{}{}
+				tmp := &data_manage.EdbInfoCalculateMapping{
+					EdbInfoCalculateMappingId: v.EdbInfoCalculateMappingId,
+					EdbInfoId:                 v.EdbInfoId,
+					Source:                    v.Source,
+					SourceName:                v.SourceName,
+					EdbCode:                   v.EdbCode,
+					FromEdbInfoId:             v.FromEdbInfoId,
+					FromEdbCode:               v.FromEdbCode,
+					FromEdbName:               v.FromEdbName,
+					FromSource:                v.FromSource,
+					FromSourceName:            v.FromSourceName,
+					FromTag:                   v.FromTag,
+					Sort:                      v.Sort,
+					CreateTime:                v.CreateTime,
+					ModifyTime:                v.ModifyTime,
+				}
+				newEdbMappingList = append(newEdbMappingList, tmp)
+
+			}
+
+			if edbInfoId != v.FromEdbInfoId && (v.FromEdbType == 2 || v.FromEdbInfoType == 1) {
+				// 查过了就不查了
+				if _, ok2 := hasFindMap[v.FromEdbInfoId]; !ok2 {
+					newEdbMappingList, err = getCalculateEdbInfoByEdbInfoId(allEdbMappingMap, v.FromEdbInfoId, hasFindMap, edbInfoIdMap, newEdbMappingList, edbMappingMap, edbInfoMappingRootIdsMap, rootEdbInfoId)
+					if err != nil {
+						err = fmt.Errorf("traceEdbInfoByEdbInfoId err: %s", err.Error())
+						return
+					}
+				}
+			}
+			hasFindMap[v.FromEdbInfoId] = struct{}{}
+		}
+		edbInfoMappingRootIdsMap[rootEdbInfoId] = append(edbInfoMappingRootIdsMap[rootEdbInfoId], edbInfoMappingIdList...)
+	}
+
+	return
+}
+
+// SaveExcelEdbInfoRelation 添加/编辑表格时同步修改指标引用关联记录
+func SaveExcelEdbInfoRelation(excelInfoId, source int, addChildExcel bool) (err error) {
+	defer func() {
+		if err != nil {
+			err = fmt.Errorf("添加/编辑表格时同步修改指标引用关联记录失败,%s", err.Error())
+			go alarm_msg.SendAlarmMsg(err.Error(), 3)
+		}
+	}()
+	//查询和指标相关的时间序列表格和混合表格
+	if !utils.InArrayByInt([]int{utils.TIME_TABLE, utils.MIXED_TABLE, utils.BALANCE_TABLE}, source) {
+		return
+	}
+	if addChildExcel && source == utils.BALANCE_TABLE {
+		//查询excel信息,
+		excelInfoList, e := excelModel.GetChildExcelInfoByParentId(excelInfoId)
+		if e != nil {
+			err = fmt.Errorf("查询excel信息失败,错误:%s", e.Error())
+			return
+		}
+
+		//查询
+		if len(excelInfoList) > 0 {
+			//汇总表格ID
+			excelIds := make([]int, 0)
+			for _, v := range excelInfoList {
+				if v.BalanceType == 0 {
+					excelIds = append(excelIds, v.ExcelInfoId)
+				}
+			}
+			mappingList, e := excelModel.GetAllExcelEdbMappingByExcelInfoIds(excelIds)
+			if e != nil {
+				err = fmt.Errorf("查询和指标相关的表格失败,错误:%s", e.Error())
+				return
+			}
+			//整理map
+			mappingMap := make(map[int][]int)
+			for _, v := range mappingList {
+				mappingMap[v.ExcelInfoId] = append(mappingMap[v.ExcelInfoId], v.EdbInfoId)
+			}
+
+			// 添加指标引用
+			for _, v := range excelInfoList {
+				edbInfoIds, ok := mappingMap[v.ExcelInfoId]
+				if !ok {
+					continue
+				}
+				err = saveEdbInfoRelation(edbInfoIds, v.ExcelInfoId, utils.EDB_RELATION_TABLE, source)
+			}
+			//更新
+		}
+	}
+	mappingList, err := excelModel.GetAllExcelEdbMappingByExcelInfoId(excelInfoId)
+	if err != nil {
+		err = fmt.Errorf("查询和指标相关的表格失败,错误:%s", err.Error())
+		return
+	}
+	//查询指标ID
+	edbInfoIds := make([]int, 0)
+	for _, v := range mappingList {
+		edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+	}
+	//查询指标引用关联记录
+	//更新指标刷新状态为启用
+	if len(edbInfoIds) == 0 {
+		return
+	}
+	err = saveEdbInfoRelation(edbInfoIds, excelInfoId, utils.EDB_RELATION_TABLE, source)
+	return
+}
+
+// GetCalculateEdbByFromEdbInfo 找到依赖于该基础指标的所有计算指标
+func GetCalculateEdbByFromEdbInfo(edbInfoIds []int, calculateEdbIds []int, hasFind map[int]struct{}) (newCalculateEdbIds []int, err error) {
+	if len(edbInfoIds) == 0 {
+		return
+	}
+	newCalculateEdbIds = calculateEdbIds
+	newEdbInfoIds := make([]int, 0)
+	for _, v := range edbInfoIds {
+		if _, ok := hasFind[v]; ok {
+			continue
+		}
+		newEdbInfoIds = append(newEdbInfoIds, v)
+	}
+	if len(newEdbInfoIds) == 0 {
+		return
+	}
+	var condition string
+	var pars []interface{}
+	// 关联指标
+	condition += ` AND b.from_edb_info_id in (` + utils.GetOrmInReplace(len(newEdbInfoIds)) + `)`
+	pars = append(pars, newEdbInfoIds)
+
+	//获取关联图表列表
+	list, err := data_manage.GetRelationEdbInfoListMappingByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取关联指标信息失败,Err:%s", err.Error())
+		return
+	}
+	calculateEdbIdsTmp := make([]int, 0)
+	for _, mapping := range list {
+		if mapping.EdbType == 2 && mapping.EdbInfoType == 0 { // 如果指标库里的计算指标,则加入,否则继续找
+			newCalculateEdbIds = append(newCalculateEdbIds, mapping.EdbInfoId)
+			calculateEdbIdsTmp = append(calculateEdbIdsTmp, mapping.EdbInfoId)
+		}
+	}
+	for _, v := range newEdbInfoIds {
+		hasFind[v] = struct{}{}
+	}
+	if len(calculateEdbIdsTmp) > 0 {
+		newCalculateEdbIds, err = GetCalculateEdbByFromEdbInfo(calculateEdbIdsTmp, newCalculateEdbIds, hasFind)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 3 - 3
services/data/excel/balance_table.go

@@ -208,7 +208,7 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excelModel.ExcelChartEdb, newMi
 					err = errors.New(errMsg)
 					return
 				}
-				dateList = append(dateList, currCell.Value)
+				dateList = append(dateList, currCell.ShowValue)
 				//dateList[i] = currCell.ShowValue
 				i++
 			}
@@ -268,7 +268,7 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excelModel.ExcelChartEdb, newMi
 					err = errors.New(errMsg)
 					return
 				}
-				dataList = append(dataList, currCell.Value)
+				dataList = append(dataList, currCell.ShowValue)
 				//dataList[i] = currCell.ShowValue
 				i++
 			}
@@ -304,7 +304,7 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excelModel.ExcelChartEdb, newMi
 					err = errors.New(errMsg)
 					return
 				}
-				dataList = append(dataList, currCell.Value)
+				dataList = append(dataList, currCell.ShowValue)
 				//dataList[i] = currCell.ShowValue
 				i++
 			}

+ 4 - 1
services/data/excel/excel_op.go

@@ -2,6 +2,7 @@ package excel
 
 import (
 	"errors"
+	"eta/eta_api/models/data_manage"
 	excelModel "eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
@@ -98,7 +99,7 @@ func Delete(excelInfo *excelModel.ExcelInfo, sysUser *system.Admin) (err error,
 			err = fmt.Errorf("删除图表,指标信息失败 Err:%s", err.Error())
 			return
 		}
-
+		_ = data_manage.DeleteEdbRelationByObjectIds(excelIds, utils.EDB_RELATION_TABLE)
 		return
 	}
 
@@ -107,6 +108,8 @@ func Delete(excelInfo *excelModel.ExcelInfo, sysUser *system.Admin) (err error,
 	excelInfo.ModifyTime = time.Now()
 	err = excelInfo.Update([]string{"IsDelete", "ModifyTime"})
 
+	// 删除指标引用
+	_ = data_manage.DeleteEdbRelationByObjectIds([]int{excelInfo.ExcelInfoId}, utils.EDB_RELATION_TABLE)
 	return
 }
 

+ 4 - 3
services/data/excel/mixed_table.go

@@ -730,7 +730,7 @@ func HandleMixTableDateChange(date, conf string) (newDate string, err error) {
 					dateTime = dateTime.AddDate(v.Year, v.Month, v.Day)
 					newDate = dateTime.Format(utils.FormatDate)
 				} else if v.ChangeType == 2 {
-					newDate, err, _ = handleSystemAppointDateT(dateTime, v.FrequencyDay, v.Frequency)
+					newDate, err, _ = HandleSystemAppointDateT(dateTime, v.FrequencyDay, v.Frequency)
 					if err != nil {
 						return
 					}
@@ -841,7 +841,7 @@ func handleSystemCalculateDateT(num int, frequency string) (date string, err err
 // @return date string
 // @return err error
 // @return errMsg string
-func handleSystemAppointDateT(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
+func HandleSystemAppointDateT(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
 	//currDate := time.Now()
 	switch frequency {
 	case "本周":
@@ -1258,7 +1258,8 @@ func changePointDecimalPlaces(str string, changeNum int, numberType string, isPe
 		val, _ = decimal.NewFromFloat(val).Round(int32(decimalPlaces)).Float64()
 		newStr = strconv.FormatFloat(val, 'f', decimalPlaces, 64)
 	} else {
-		newStr = fmt.Sprintf("%v", val)
+		// 此处用%.f避免科学计数法, 从而导致后面出现很多位0
+		newStr = fmt.Sprintf("%.f", val)
 	}
 	// 计算小数位数
 	decimalPlaces = 0

+ 191 - 0
services/data/factor_edb_series.go

@@ -0,0 +1,191 @@
+package data
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"sync"
+	"time"
+)
+
+// FactorEdbStepCalculate 因子指标-多公式计算
+func FactorEdbStepCalculate(seriesId int, edbArr []*data_manage.EdbInfo, calculates []data_manage.FactorEdbSeriesCalculatePars, lang string, recalculate bool) (calculateResp data_manage.FactorEdbSeriesStepCalculateResp, err error) {
+	if len(edbArr) == 0 || len(calculates) == 0 {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("StepCalculate计算失败, ErrMsg: %v", err)
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+		if len(calculateResp.Fail) > 0 {
+			tips := "StepCalculate计算失败, ErrMsg: "
+			for _, f := range calculateResp.Fail {
+				tips += fmt.Sprintf("code: %s, err: %s\n", f.EdbCode, f.ErrMsg)
+			}
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+
+	// 重新计算-先清除原数据
+	calculateDataOb := new(data_manage.FactorEdbSeriesCalculateData)
+	if recalculate {
+		cond := fmt.Sprintf("%s = ?", calculateDataOb.Cols().FactorEdbSeriesId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, seriesId)
+		if e := calculateDataOb.RemoveByCondition(cond, pars); e != nil {
+			err = fmt.Errorf("清除原数据失败, err: %v", e)
+			return
+		}
+	}
+
+	wg := sync.WaitGroup{}
+	calculateWorkers := make(chan struct{}, 10)
+	for _, edb := range edbArr {
+		wg.Add(1)
+		go func(v *data_manage.EdbInfo) {
+			defer func() {
+				wg.Done()
+				<-calculateWorkers
+			}()
+			calculateWorkers <- struct{}{}
+
+			var result data_manage.FactorEdbSeriesStepCalculateResult
+			result.EdbInfoId = v.EdbInfoId
+			result.EdbCode = v.EdbCode
+			result.Msg = "计算失败"
+
+			// 获取基础数据
+			edbData, e := data_manage.GetEdbDataAllByEdbCode(v.EdbCode, v.Source, v.SubSource, 0)
+			if e != nil {
+				result.ErrMsg = fmt.Sprintf("获取基础数据失败, edbCode: %s, err: %v", v.EdbCode, e)
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+			if len(edbData) == 0 {
+				result.Msg = "该指标无基础数据"
+				result.ErrMsg = fmt.Sprintf("该指标无基础数据, edbCode: %s", v.EdbCode)
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+
+			// 请求指标服务进行计算
+			j, e := json.Marshal(data_manage.BaseStepCalculateReq{
+				DataList:   edbData,
+				Calculates: calculates,
+			})
+			if e != nil {
+				result.ErrMsg = fmt.Sprintf("请求体JSON格式化失败, edbCode: %s, err: %v", v.EdbCode, e)
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+			requestRes, e := BaseStepCalculate(string(j), lang)
+			if e != nil {
+				result.ErrMsg = fmt.Sprintf("指标计算响应失败, edbCode: %s, err: %v", v.EdbCode, e)
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+			if requestRes.Ret != 200 {
+				result.Msg = requestRes.Msg
+				result.ErrMsg = requestRes.ErrMsg
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+
+			// 计算成功的保存结果
+			dataArr := make([]*data_manage.FactorEdbSeriesCalculateData, 0)
+			for _, d := range requestRes.Data.DateList {
+				val, ok := requestRes.Data.DataMap[d]
+				if !ok {
+					continue
+				}
+				dataTime, e := time.ParseInLocation(time.DateOnly, d, time.Local)
+				if e != nil {
+					result.ErrMsg = fmt.Sprintf("解析计算结果日期失败, edbCode: %s, date: %s, err: %v, ", v.EdbCode, d, e)
+					calculateResp.Fail = append(calculateResp.Fail, result)
+					return
+				}
+				dataArr = append(dataArr, &data_manage.FactorEdbSeriesCalculateData{
+					FactorEdbSeriesId: seriesId,
+					EdbInfoId:         v.EdbInfoId,
+					EdbCode:           v.EdbCode,
+					DataTime:          dataTime,
+					Value:             val,
+					CreateTime:        time.Now().Local(),
+					ModifyTime:        time.Now().Local(),
+					DataTimestamp:     dataTime.UnixNano() / 1e6,
+				})
+			}
+			if len(dataArr) == 0 {
+				result.Msg = "计算结果无数据"
+				result.ErrMsg = fmt.Sprintf("计算结果无数据, edbCode: %s", v.EdbCode)
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+			if e = calculateDataOb.CreateMulti(dataArr); e != nil {
+				result.ErrMsg = fmt.Sprintf("保存计算结果失败, edbCode: %s, err: %v, ", v.EdbCode, e)
+				calculateResp.Fail = append(calculateResp.Fail, result)
+				return
+			}
+
+			result.Msg = "计算成功"
+			calculateResp.Success = append(calculateResp.Success, result)
+		}(edb)
+	}
+	wg.Wait()
+	return
+}
+
+// PostRefreshFactorEdbRecalculate 因子指标重计算
+func PostRefreshFactorEdbRecalculate(edbInfoId int, edbCode string) (resp *models.BaseResponse, err error) {
+	param := make(map[string]interface{})
+	param["EdbInfoId"] = edbInfoId
+	param["EdbCode"] = edbCode
+	postUrl := fmt.Sprintf("%s%s", utils.EDB_LIB_URL, "factor_edb_series/recalculate")
+	postData, e := json.Marshal(param)
+	if e != nil {
+		err = fmt.Errorf("param json err: %v", e)
+		return
+	}
+	result, e := HttpPost(postUrl, string(postData), utils.ZhLangVersion, "application/json")
+	if e != nil {
+		err = fmt.Errorf("http post err: %v", e)
+		return
+	}
+	utils.FileLog.Info("PostRefreshFactorEdbRecalculate:" + postUrl + ";" + string(postData) + ";result:" + string(result))
+	if e = json.Unmarshal(result, &resp); e != nil {
+		err = fmt.Errorf("resp unmarshal err: %v", e)
+		return
+	}
+	return
+}
+
+// PostRefreshFactorEdbChartRecalculate 因子指标图表重计算
+func PostRefreshFactorEdbChartRecalculate(chartInfoId int) (resp *models.BaseResponse, err error) {
+	param := make(map[string]interface{})
+	param["ChartInfoId"] = chartInfoId
+	postUrl := fmt.Sprintf("%s%s", utils.EDB_LIB_URL, "factor_edb_series/chart_recalculate")
+	postData, e := json.Marshal(param)
+	if e != nil {
+		err = fmt.Errorf("param json err: %v", e)
+		return
+	}
+	result, e := HttpPost(postUrl, string(postData), utils.ZhLangVersion, "application/json")
+	if e != nil {
+		err = fmt.Errorf("http post err: %v", e)
+		return
+	}
+	utils.FileLog.Info("PostRefreshFactorEdbChartRecalculate:" + postUrl + ";" + string(postData) + ";result:" + string(result))
+	if e = json.Unmarshal(result, &resp); e != nil {
+		err = fmt.Errorf("resp unmarshal err: %v", e)
+		return
+	}
+	return
+}

+ 5 - 0
services/data/line_equation/chart_info.go

@@ -547,6 +547,8 @@ func BatchAddChartInfo(batchAddChartReq []request.AddChart, lineChartInfoConfig
 
 	// 更改es
 	for _, v := range batchAddChartList {
+		// 添加指标引用记录
+		_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, v.ChartInfo)
 		//添加es数据
 		go data.EsAddOrEditChartInfo(v.ChartInfo.ChartInfoId)
 	}
@@ -787,6 +789,9 @@ func BatchSaveChartInfo(multipleGraphConfigId int, batchAddChartReq []request.Ad
 
 	// 更改es
 	for _, v := range batchAddChartList {
+		// 添加指标引用记录
+		_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, v.ChartInfo)
+
 		//添加es数据
 		go data.EsAddOrEditChartInfo(v.ChartInfo.ChartInfoId)
 	}

+ 5 - 1
services/data/line_feature/chart_info.go

@@ -532,7 +532,8 @@ func AddChartInfo(req data_manage.AddChartInfoReq, edbInfoMapping *data_manage.C
 		err = errors.New("新增相关性图表失败, Err: " + e.Error())
 		return
 	}
-
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfoId)
 
@@ -659,6 +660,9 @@ func EditChartInfo(req data_manage.EditChartInfoReq, edbInfoMapping *data_manage
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
+
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据

+ 274 - 0
services/edb_info_replace.go

@@ -7,9 +7,11 @@ import (
 	excelModel "eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/services/data"
 	"eta/eta_api/services/sandbox"
 	"eta/eta_api/utils"
 	"fmt"
+	"strconv"
 	"time"
 )
 
@@ -31,6 +33,7 @@ func DealReplaceEdbCache() {
 			}
 			oldEdbInfo := record.OldEdbInfo
 			newEdbInfo := record.NewEdbInfo
+			utils.FileLog.Info(fmt.Sprintf("指标开始替换 DealReplaceEdbCache: 旧指标ID:%d,新指标ID:%d", oldEdbInfo.EdbInfoId, newEdbInfo.EdbInfoId))
 			deleteCache := true
 			setNxKey := fmt.Sprintf("EDB_INFO_REPLACE:%d-%d", oldEdbInfo.EdbInfoId, newEdbInfo.EdbInfoId)
 			defer func() {
@@ -70,6 +73,9 @@ func DealReplaceEdbCache() {
 				err = fmt.Errorf("替换逻辑图中的指标失败,errmsg:%s", err.Error())
 				return
 			}
+
+			// todo 重置指标引用表
+			ReplaceEdbInRelation(oldEdbInfo, newEdbInfo)
 		})
 	}
 }
@@ -252,3 +258,271 @@ func replaceEdbInTimeExcel(oldEdbInfo, newEdbInfo *data_manage.EdbInfo, excelInf
 	newExcelInfo = excelInfo
 	return
 }
+
+func ReplaceEdbInRelation(oldEdbInfo, newEdbInfo *data_manage.EdbInfo) {
+	var err error
+	var logMsg string
+	var replaceTotal int
+	defer func() {
+		if err != nil {
+			msg := fmt.Sprintf(" 替换指标引用表中的指标,并修改引用时间 replaceEdbInRelation  err: %v", err)
+			utils.FileLog.Info(msg)
+			fmt.Println(msg)
+			go alarm_msg.SendAlarmMsg(msg, 3)
+		}
+		if logMsg != `` {
+			utils.FileLog.Info(fmt.Sprintf("替换指标引用表中的指标记录 替换总数%d,旧的指标id:%d,新的指标id:%d;涉及到的引用id:%s", replaceTotal, oldEdbInfo.EdbInfoId, newEdbInfo.EdbInfoId, logMsg))
+		}
+	}()
+
+	calculateEdbMappingListMap := make(map[int]*data_manage.EdbInfoCalculateMapping)
+	calculateEdbMappingIdsMap := make(map[int][]int)
+	childEdbMappingIds := make([]int, 0)
+	//indexCodeList := make([]string, 0)
+	//refreshIds := make([]int, 0)
+
+	//分页查询,每次处理500条记录
+	pageSize := 500
+
+	// 替换直接引用中的指标
+	if newEdbInfo.EdbType == 2 {
+		edbInfoList := make([]*data_manage.EdbInfo, 0)
+		edbInfoList = append(edbInfoList, newEdbInfo)
+		calculateEdbMappingListMap, calculateEdbMappingIdsMap, err = data.GetEdbListByEdbInfoId(edbInfoList)
+		if err != nil {
+			err = fmt.Errorf("查询指标关联指标列表失败 Err:%s", err)
+			return
+		}
+		var ok bool
+		childEdbMappingIds, ok = calculateEdbMappingIdsMap[newEdbInfo.EdbInfoId]
+		if !ok {
+			err = fmt.Errorf("查询%d指标关联指标列表为空", newEdbInfo.EdbInfoId)
+			return
+		}
+	}
+	total, err := data_manage.GetReplaceEdbInfoRelationTotal(oldEdbInfo.EdbInfoId)
+	if err != nil {
+		err = fmt.Errorf("查询引用表中关联的指标总数失败 err: %v", err)
+		return
+	}
+	totalPage := 0
+	if total > 0 {
+		totalPage = (total + pageSize - 1) / pageSize // 使用整数除法,并添加一页以防有余数
+		//查询图表列表
+		for i := 0; i < totalPage; i += 1 {
+			startSize := i * pageSize
+			list, e := data_manage.GetReplaceEdbInfoRelationList(oldEdbInfo.EdbInfoId, startSize, pageSize)
+			if e != nil {
+				err = fmt.Errorf("查询图表关联指标列表失败 Err:%s", e)
+				return
+			}
+			if len(list) == 0 {
+				break
+			}
+			replaceTotal1, logMsg1, e := replaceEdbInRelation(oldEdbInfo, newEdbInfo, list, childEdbMappingIds, calculateEdbMappingListMap)
+			if e != nil {
+				err = e
+				return
+			}
+			replaceTotal += replaceTotal1
+			logMsg += logMsg1
+		}
+	}
+
+	// 更新间接引用中的指标
+	//查询相关的记录总数
+	total, err = data_manage.GetReplaceChildEdbInfoRelationTotal(oldEdbInfo.EdbInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		} else {
+			err = fmt.Errorf("查询引用表中关联的指标总数失败 err: %v", err)
+			return
+		}
+	}
+	if total > 0 {
+		totalPage = (total + pageSize - 1) / pageSize // 使用整数除法,并添加一页以防有余数
+		//查询列表
+		for i := 0; i < totalPage; i += 1 {
+			startSize := i * pageSize
+			tmpList, e := data_manage.GetReplaceChildEdbInfoRelationList(oldEdbInfo.EdbInfoId, startSize, pageSize)
+			if e != nil {
+				err = fmt.Errorf("查询图表关联指标列表失败 Err:%s", e)
+				return
+			}
+			// 查询直接引用
+			relationIds := make([]int, 0)
+			for _, v := range tmpList {
+				relationIds = append(relationIds, v.ParentRelationId)
+			}
+			if len(relationIds) > 0 {
+				list, e := data_manage.GetEdbInfoRelationByRelationIds(relationIds)
+				if e != nil {
+					err = fmt.Errorf("查询图表关联指标列表失败 Err:%s", e)
+					return
+				}
+				//查询直接引用指标关联关系
+				edbInfoListMap := make(map[int]struct{})
+				edbInfoIds := make([]int, 0)
+				for _, v := range list {
+					if _, ok := edbInfoListMap[v.EdbInfoId]; !ok {
+						edbInfoListMap[v.EdbInfoId] = struct{}{}
+						edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+					}
+				}
+				edbInfoList := make([]*data_manage.EdbInfo, 0)
+				if len(edbInfoIds) > 0 {
+					// 查询指标信息
+					edbInfoList, err = data_manage.GetEdbInfoByIdList(edbInfoIds)
+					if err != nil {
+						err = fmt.Errorf("查询指标信息失败 Err:%s", err)
+						return
+					}
+				}
+				calculateEdbMappingListMap, calculateEdbMappingIdsMap, err = data.GetEdbListByEdbInfoId(edbInfoList)
+				if err != nil {
+					err = fmt.Errorf("查询指标关联指标列表失败 Err:%s", err)
+					return
+				}
+
+				//如何过滤掉只有间接引用,没有直接引用的
+				replaceTotal1, logMsg1, e := UpdateSecondEdbInRelation(list, calculateEdbMappingListMap, calculateEdbMappingIdsMap, edbInfoList)
+				if e != nil {
+					err = e
+					return
+				}
+				replaceTotal += replaceTotal1
+				logMsg += logMsg1
+			}
+		}
+	}
+
+	return
+}
+
+// 完成直接引用中的指标替换工程
+func replaceEdbInRelation(oldEdbInfo, newEdbInfo *data_manage.EdbInfo, list []*data_manage.EdbInfoRelation, childEdbMappingIds []int, calculateEdbMappingListMap map[int]*data_manage.EdbInfoCalculateMapping) (replaceTotal int, logMsg string, err error) {
+	replaceEdbIds := make([]int, 0)
+	//calculateEdbMappingListMap := make(map[int]*data_manage.EdbInfoCalculateMapping)
+	//calculateEdbMappingIdsMap := make(map[int][]int)
+	//childEdbMappingIds := make([]int, 0)
+	indexCodeList := make([]string, 0)
+	addList := make([]*data_manage.EdbInfoRelation, 0)
+	refreshIds := make([]int, 0)
+	nowTime := time.Now()
+	for _, v := range list {
+		replaceEdbIds = append(replaceEdbIds, v.EdbInfoRelationId)
+		if newEdbInfo.EdbType == 2 {
+			for _, childEdbMappingId := range childEdbMappingIds {
+				childEdbMapping, ok2 := calculateEdbMappingListMap[childEdbMappingId]
+				if !ok2 {
+					continue
+				}
+
+				if childEdbMapping.FromSource == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+					indexCodeList = append(indexCodeList, childEdbMapping.FromEdbCode)
+				}
+				tmp1 := &data_manage.EdbInfoRelation{
+					ReferObjectId:      v.ReferObjectId,
+					ReferObjectType:    v.ReferObjectType,
+					ReferObjectSubType: v.ReferObjectSubType,
+					EdbInfoId:          childEdbMapping.FromEdbInfoId,
+					EdbName:            childEdbMapping.FromEdbName,
+					Source:             childEdbMapping.FromSource,
+					EdbCode:            childEdbMapping.FromEdbCode,
+					CreateTime:         nowTime,
+					ModifyTime:         nowTime,
+					RelationTime:       nowTime,
+					RelationType:       1,
+					RootEdbInfoId:      newEdbInfo.EdbInfoId,
+					ChildEdbInfoId:     childEdbMapping.EdbInfoId,
+				}
+				tmp1.RelationCode = fmt.Sprintf("%d_%d_%d_%d", tmp1.RootEdbInfoId, tmp1.ReferObjectId, tmp1.ReferObjectType, tmp1.ReferObjectSubType)
+				addList = append(addList, tmp1)
+				refreshIds = append(refreshIds, childEdbMapping.FromEdbInfoId)
+				// todo 防止重复
+			}
+		}
+		logMsg += strconv.Itoa(v.EdbInfoRelationId) + ";"
+	}
+	if len(replaceEdbIds) > 0 {
+		err = data_manage.ReplaceRelationEdbInfoId(oldEdbInfo, newEdbInfo, replaceEdbIds, addList, refreshIds, indexCodeList)
+		if err != nil {
+			logMsg = ""
+			err = fmt.Errorf("替换指标引用表中的指标ID失败 Err:%s", err)
+			return
+		}
+		replaceTotal = len(replaceEdbIds)
+	}
+	return
+}
+
+func UpdateSecondEdbInRelation(list []*data_manage.EdbInfoRelation, calculateEdbMappingListMap map[int]*data_manage.EdbInfoCalculateMapping, calculateEdbMappingIdsMap map[int][]int, edbInfoList []*data_manage.EdbInfo) (replaceTotal int, logMsg string, err error) {
+	nowTime := time.Now()
+	edbInfoRelationIds := make([]int, 0)
+	indexCodeList := make([]string, 0)
+	addList := make([]*data_manage.EdbInfoRelation, 0)
+	refreshIds := make([]int, 0)
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	for _, v := range edbInfoList {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+	// 查询所有的直接引用,删除所有的间接引用,添加所有直接引用的间接引用
+	for _, v := range list {
+		if v.RelationType == 0 {
+			edbInfoRelationIds = append(edbInfoRelationIds, v.EdbInfoRelationId)
+			edbInfo, ok := edbInfoMap[v.EdbInfoId]
+			if !ok {
+				err = fmt.Errorf("查询指标信息失败 EdbInfoId:%d", v.EdbInfoId)
+				return
+			}
+			if edbInfo.EdbType == 2 { //计算指标
+				childEdbMappingIds, ok := calculateEdbMappingIdsMap[edbInfo.EdbInfoId]
+				if !ok {
+					err = fmt.Errorf("查询%d指标关联指标列表为空", edbInfo.EdbInfoId)
+					return
+				}
+				for _, childEdbMappingId := range childEdbMappingIds {
+					childEdbMapping, ok2 := calculateEdbMappingListMap[childEdbMappingId]
+					if !ok2 {
+						continue
+					}
+
+					if childEdbMapping.FromSource == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+						indexCodeList = append(indexCodeList, childEdbMapping.FromEdbCode)
+					}
+					tmp1 := &data_manage.EdbInfoRelation{
+						ReferObjectId:      v.ReferObjectId,
+						ReferObjectType:    v.ReferObjectType,
+						ReferObjectSubType: v.ReferObjectSubType,
+						EdbInfoId:          childEdbMapping.FromEdbInfoId,
+						EdbName:            childEdbMapping.FromEdbName,
+						Source:             childEdbMapping.FromSource,
+						EdbCode:            childEdbMapping.FromEdbCode,
+						CreateTime:         nowTime,
+						ModifyTime:         nowTime,
+						RelationTime:       nowTime,
+						RelationType:       1,
+						RootEdbInfoId:      edbInfo.EdbInfoId,
+						ChildEdbInfoId:     childEdbMapping.EdbInfoId,
+					}
+					tmp1.RelationCode = fmt.Sprintf("%d_%d_%d_%d", tmp1.RootEdbInfoId, tmp1.ReferObjectId, tmp1.ReferObjectType, tmp1.ReferObjectSubType)
+					addList = append(addList, tmp1)
+					refreshIds = append(refreshIds, childEdbMapping.FromEdbInfoId)
+				}
+			}
+			logMsg += strconv.Itoa(v.EdbInfoRelationId) + ";"
+		}
+	}
+
+	if len(edbInfoRelationIds) > 0 {
+		err = data_manage.UpdateSecondRelationEdbInfoId(edbInfoRelationIds, addList, refreshIds, indexCodeList)
+		if err != nil {
+			logMsg = ""
+			err = fmt.Errorf("替换指标引用表中的指标ID失败 Err:%s", err)
+			return
+		}
+		replaceTotal = len(edbInfoRelationIds)
+	}
+	return
+}

+ 1 - 1
services/eta_forum/eta_forum_hub.go

@@ -267,7 +267,7 @@ func GetEdbListByEdbInfoId(edbInfoIds []int) (edbInfoList []*data_manage.EdbInfo
 
 	for _, v := range edbInfoList {
 		var dataList []*data_manage.EdbDataBase
-		if v.Source == utils.DATA_SOURCE_BUSINESS {
+		if v.Source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 			dataList, err = data_manage.GetEdbDataBaseMongoByEdbInfoId(v.EdbInfoId, v.Source, v.SubSource)
 		} else {
 			dataList, err = data_manage.GetEdbDataBaseByEdbInfoId(v.EdbInfoId, v.Source, v.SubSource)

+ 12 - 1
services/excel/lucky_sheet.go

@@ -1717,7 +1717,7 @@ func GetTableDataByCustomData(excelType int, data request.TableDataReq, lang str
 }
 
 // GetTableDataByMixedTableData 通过混合表格数据获取表格数据
-func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq) (selfTableData TableData, err error) {
+func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hideMerged bool) (selfTableData TableData, err error) {
 	tableDataList := make([][]LuckySheetDataValue, 0)
 	mergeList := make([]TableDataMerge, 0)
 
@@ -1731,6 +1731,17 @@ func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq) (sel
 					Monitor:   cell.ShowValue,
 					MergeCell: LuckySheetDataConfigMerge{},
 				}
+				// TODO: 混合表格/平衡表-合并单元格
+				// 前端需要隐藏被合并的单元格, 混合表格/平衡表通过这个字段判断, 不通过HandleTableCell方法隐藏
+				if cell.MerData != nil {
+					if hideMerged && cell.MerData.Type == "merged" {
+						continue
+					}
+					tmp.MergeCell.Rs = cell.MerData.Mer.Rowspan
+					tmp.MergeCell.Cs = cell.MerData.Mer.Colspan
+					tmp.MergeCell.Row = cell.MerData.Mer.Row
+					tmp.MergeCell.Column = cell.MerData.Mer.Col
+				}
 				if cell.ShowStyle != "" {
 					showFormatValue := fmt.Sprintf("%v", cell.ShowFormatValue)
 					tmp.Monitor = showFormatValue

+ 99 - 0
services/file.go

@@ -8,6 +8,7 @@ import (
 	"io"
 	"mime/multipart"
 	"os"
+	"os/exec"
 	"time"
 )
 
@@ -80,3 +81,101 @@ func saveToFile(fileMulti multipart.File, tofile string) error {
 	io.Copy(f, fileMulti)
 	return nil
 }
+
+// GetResourceUrlBySvgImg
+// @Description: 通过svg图片生成图片资源地址(传到OSS后的地址)
+// @author: Roc
+// @datetime 2024-07-16 10:18:09
+// @param imgData string
+// @return resourceUrl string
+// @return err error
+// @return errMsg string
+func GetResourceUrlBySvgImg(imgData string) (resourceUrl string, err error, errMsg string) {
+	errMsg = "图表保存失败"
+	uploadDir := "static/images/"
+	if !utils.FileIsExist(uploadDir) {
+		err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+		if err != nil {
+			err = errors.New("存储目录创建失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	//var saveToOssPath string
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	var fileName, outFileName string
+	fileName = randStr + ".txt"
+	fileName = uploadDir + fileName
+	err = utils.SaveToFile(imgData, fileName)
+	if err != nil {
+		err = errors.New("图片保存失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除临时存储的svg文件
+	defer func() {
+		err = os.Remove(fileName)
+		if err != nil {
+			utils.FileLog.Info("删除临时存储的svg文件失败, err: " + err.Error())
+		}
+	}()
+	outFileName = randStr + ".png"
+
+	doneChannel := make(chan bool, 1)
+	errorChannel := make(chan error, 1)
+
+	cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
+
+	go func() {
+		output, err := cmd.CombinedOutput()
+		if err != nil {
+			utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
+			errorChannel <- err
+			return
+		}
+		doneChannel <- true
+	}()
+	defer func() {
+		_ = os.Remove(outFileName)
+		if err != nil {
+			utils.FileLog.Info("删除生产的图片文件失败, err: " + err.Error())
+		}
+	}()
+
+	select {
+	case <-time.After(30 * time.Second):
+		utils.FileLog.Info("执行超过30秒 杀死超时进程")
+		e := cmd.Process.Kill()
+		if e != nil {
+			fmt.Println("cmd kill err: ", e.Error())
+			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+			errMsg = "图片生成失败"
+			err = errors.New("图片生成失败, 执行超时" + e.Error())
+			return
+		}
+		fmt.Println("timeout kill process")
+	case <-doneChannel:
+		fmt.Println("done")
+	case e := <-errorChannel:
+		errMsg = "文件上传失败"
+		err = errors.New(fmt.Sprintf("execute command failure err: %s", e.Error()))
+		fmt.Println("execute command failure err:" + e.Error())
+		return
+	}
+
+	//上传到阿里云 和 minio
+	ossClient := NewOssClient()
+	if ossClient == nil {
+		errMsg = "上传失败"
+		err = errors.New("初始化OSS服务失败")
+		return
+	}
+	resourceUrl, err = ossClient.UploadFile(outFileName, outFileName, "")
+	if err != nil {
+		errMsg = "文件上传失败"
+		err = errors.New("文件上传失败,Err:" + err.Error())
+		return
+	}
+
+	return
+}

+ 41 - 0
services/sandbox/sandbox.go

@@ -601,6 +601,27 @@ type ContentStruct struct {
 	} `json:"cells"`
 }
 
+type SendBoxNodeData struct {
+	linkData []SandBoxLinkData `json:"linkData"`
+	linkFold bool              `json:"linkFold"`
+}
+
+type SandBoxLinkData struct {
+	RId          string              `json:"RId"`
+	Id           int                 `json:"Id"`
+	Name         string              `json:"Name"`
+	Type         int                 `json:"Type"`
+	Editing      bool                `json:"editing"`
+	DatabaseType int                 `json:"databaseType"`
+	DetailParams SandBoxDetailParams `json:"detailParams"`
+}
+
+type SandBoxDetailParams struct {
+	Code       string `json:"code"`
+	Id         int    `json:"id"`
+	ClassifyId int    `json:"classifyId"`
+}
+
 // checkoutContent 校验内容是否变更
 func checkoutContent(oldContent, reqContent string) (isUpdate bool) {
 	defer func() {
@@ -855,6 +876,26 @@ func sandboxClassifyHaveChildV2(allNode []*sandbox.SandboxClassifyItems, node *s
 	return
 }
 
+func GetSandBoxEdbIdsByContent(content string) (edbInfoIds []int, err error) {
+	var contentInfo sandbox.ContentDataStruct
+	err = json.Unmarshal([]byte(content), &contentInfo)
+	if err != nil {
+		err = fmt.Errorf("json.Unmarshal err:%s", err.Error())
+		return
+	}
+	// 遍历所有节点
+	for _, node := range contentInfo.Cells {
+		if node.Data == nil {
+			continue
+		}
+		for _, v := range node.Data.LinkData {
+			if v.Type == 1 {
+				edbInfoIds = append(edbInfoIds, v.Id)
+			}
+		}
+	}
+	return
+}
 func ReplaceEdbInSandbox(oldEdbInfoId, newEdbInfoId int) (err error) {
 	updateTotal := 0
 	logMsg := ""

+ 4 - 0
utils/config.go

@@ -267,6 +267,10 @@ var (
 	ViperConfig *viper.Viper
 )
 
+var (
+	UseMongo bool // 是否使用mongo
+)
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {

+ 30 - 0
utils/constants.go

@@ -309,6 +309,7 @@ const (
 // 图表样式类型
 const (
 	CHART_TYPE_CURVE           = 1  //曲线图
+	CHART_TYPE_SEASON          = 2  //季节性图
 	CHART_TYPE_BAR             = 7  //柱形图
 	CHART_TYPE_SECTION_SCATTER = 10 //截面散点图样式
 	CHART_TYPE_RADAR           = 11 //雷达图
@@ -432,3 +433,32 @@ const (
 	ZhLangVersion = "zh" // 中文语言版本
 	EnLangVersion = "en" // 英文语言版本
 )
+
+// 指标引用对象
+const (
+	EDB_RELATION_CHART    = 1 // 图表
+	EDB_RELATION_SANDBOX  = 2 // ETA逻辑
+	EDB_RELATION_CALENDAR = 3 // 事件日历
+	EDB_RELATION_TABLE    = 4 // 表格
+)
+
+// 指标计算方式
+const (
+	EdbBaseCalculateLjzzy                = 1  // 累计值转月->1
+	EdbBaseCalculateLjzzj                = 2  // 累计值转季->2
+	EdbBaseCalculateTbz                  = 3  // 同比值->3
+	EdbBaseCalculateTcz                  = 4  // 同差值->4
+	EdbBaseCalculateNszydpjjs            = 5  // N数值移动平均数计算->5
+	EdbBaseCalculateHbz                  = 6  // 环比值->6
+	EdbBaseCalculateHcz                  = 7  // 环差值->7
+	EdbBaseCalculateUpFrequency          = 8  // 升频->8
+	EdbBaseCalculateDownFrequency        = 9  // 降频->9
+	EdbBaseCalculateTimeShift            = 10 // 时间移位->10
+	EdbBaseCalculateCjjx                 = 11 // 超季节性->11
+	EdbBaseCalculateAnnualized           = 12 // 年化->12
+	EdbBaseCalculateLjz                  = 13 // 累计值->13
+	EdbBaseCalculateLjzNczj              = 14 // 累计值年初至今->14
+	EdbBaseCalculateExponentialSmoothing = 15 // 指数修匀->15
+	EdbBaseCalculateRjz                  = 16 // 日均值->16
+
+)