Forráskód Böngészése

Merge branch 'master' of eta_server/eta_api into custom

xyxie 7 hónapja
szülő
commit
bb1ce3cf64
88 módosított fájl, 17688 hozzáadás és 460 törlés
  1. 3 0
      controllers/business_conf.go
  2. 51 0
      controllers/data_manage/base_from_ly_classify_controller.go
  3. 525 0
      controllers/data_manage/base_from_ly_index_controller.go
  4. 12 0
      controllers/data_manage/chart_common.go
  5. 1 35
      controllers/data_manage/chart_info.go
  6. 35 0
      controllers/data_manage/edb_info.go
  7. 37 0
      controllers/data_manage/edb_info_refresh.go
  8. 614 11
      controllers/data_manage/eia_steo.go
  9. 414 32
      controllers/data_manage/excel/excel_info.go
  10. 556 1
      controllers/data_manage/fenwei_data.go
  11. 74 2
      controllers/data_manage/manual_edb.go
  12. 1 101
      controllers/data_manage/mysteel_chemical_data.go
  13. 832 0
      controllers/data_manage/range_analysis/chart_classify.go
  14. 2101 0
      controllers/data_manage/range_analysis/chart_info.go
  15. 23 0
      controllers/report_v2.go
  16. 334 2
      controllers/trade_analysis/trade_analysis.go
  17. 1812 0
      controllers/trade_analysis/warehouse.go
  18. 683 0
      controllers/trade_analysis/warehouse_classify.go
  19. 19 0
      controllers/user_login.go
  20. 5 0
      go.mod
  21. 11 0
      go.sum
  22. 80 0
      models/common_classify.go
  23. 36 16
      models/data_manage/base_from_eia_stero.go
  24. 141 0
      models/data_manage/base_from_fenwei.go
  25. 13 0
      models/data_manage/base_from_fenwei_classify.go
  26. 48 0
      models/data_manage/base_from_ly_classify.go
  27. 108 0
      models/data_manage/base_from_ly_data.go
  28. 244 0
      models/data_manage/base_from_ly_index.go
  29. 14 2
      models/data_manage/chart_info.go
  30. 438 0
      models/data_manage/chart_info_range_analysis.go
  31. 20 0
      models/data_manage/chart_series.go
  32. 4 0
      models/data_manage/edb_data_base.go
  33. 6 0
      models/data_manage/edb_info.go
  34. 18 17
      models/data_manage/edb_info_calculate.go
  35. 16 5
      models/data_manage/edb_info_relation.go
  36. 82 0
      models/data_manage/excel/excel_info_rule_mapping.go
  37. 111 1
      models/data_manage/excel/request/excel_info.go
  38. 7 2
      models/data_manage/excel/request/mixed_table.go
  39. 7 1
      models/data_manage/excel/response/excel_info.go
  40. 62 0
      models/data_manage/factor_edb_series.go
  41. 210 0
      models/data_manage/factor_edb_series_calculate_data_qjjs.go
  42. 8 0
      models/data_manage/factor_edb_series_chart_mapping.go
  43. 7 0
      models/data_manage/factor_edb_series_mapping.go
  44. 7 0
      models/data_manage/multiple_graph_config_chart_mapping.go
  45. 52 1
      models/data_manage/multiple_graph_config_edb_mapping.go
  46. 23 0
      models/data_manage/mysteel_chemical_index.go
  47. 122 0
      models/data_manage/trade_analysis/base_from_trade_exchange.go
  48. 20 0
      models/data_manage/trade_analysis/request/warehouse.go
  49. 17 0
      models/data_manage/trade_analysis/response/warehouse.go
  50. 372 0
      models/data_manage/trade_analysis/trade_analysis.go
  51. 76 0
      models/data_manage/trade_analysis/trade_classify.go
  52. 129 0
      models/data_manage/trade_analysis/trade_futures_company.go
  53. 103 0
      models/data_manage/trade_analysis/warehouse.go
  54. 340 0
      models/data_manage/trade_analysis/warehouse_process_classify.go
  55. 15 13
      models/db.go
  56. 33 0
      models/manual_edb.go
  57. 31 20
      models/target.go
  58. 594 0
      routers/commentsRouter.go
  59. 11 0
      routers/router.go
  60. 6 0
      services/data/base_edb_lib.go
  61. 79 0
      services/data/base_from_eia_steo.go
  62. 162 0
      services/data/base_from_fenwei_index_service.go
  63. 47 0
      services/data/base_from_ly_classify_service.go
  64. 249 0
      services/data/base_from_ly_index_service.go
  65. 485 0
      services/data/common_classify.go
  66. 106 0
      services/data/common_classify_ctx.go
  67. 24 0
      services/data/edb_info.go
  68. 33 5
      services/data/edb_info_relation.go
  69. 384 3
      services/data/excel/excel_info.go
  70. 26 2
      services/data/excel/mixed_table.go
  71. 11 4
      services/data/excel/time_table.go
  72. 33 1
      services/data/manual.go
  73. 2247 0
      services/data/range_analysis/chart_info.go
  74. 13 6
      services/data/trade_analysis/trade_analysis.go
  75. 647 0
      services/data/trade_analysis/trade_analysis_data.go
  76. 181 0
      services/data/trade_analysis/trade_analysis_interface.go
  77. 418 0
      services/data/trade_analysis/warehouse.go
  78. 44 166
      services/email.go
  79. 49 2
      services/excel/lucky_sheet.go
  80. 224 0
      services/excel/lucky_sheet_table.go
  81. 88 0
      services/file.go
  82. 16 0
      services/report_v2.go
  83. 8 8
      services/smart_report.go
  84. 2 1
      static/ErrMsgConfig.json
  85. BIN
      static/SimHei.gob
  86. 229 0
      utils/common.go
  87. 35 0
      utils/constants.go
  88. 74 0
      utils/time.go

+ 3 - 0
controllers/business_conf.go

@@ -199,6 +199,9 @@ func (this *BusinessConfController) Save() {
 			return
 		}
 	}
+	// 删除研报声明缓存
+	disclaimerCacheKey := "hongze_yb:business_conf:disclaimer"
+	_ = utils.Rc.Delete(disclaimerCacheKey)
 
 	// 操作日志
 	go func() {

+ 51 - 0
controllers/data_manage/base_from_ly_classify_controller.go

@@ -0,0 +1,51 @@
+// Package data_manage
+// @Author gmy 2024/8/12 14:32:00
+package data_manage
+
+import (
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/services/data"
+)
+
+type BaseFromLyClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// LyClassifyList
+// @Title 包含分类下的指标名称
+// @Description 包含分类下的指标名称
+// @Success 200 {object} []data_manage.BaseFromLyClassifyAndIndexInfo
+// @router /ly/classify/list [get]
+func (this *BaseFromLyClassifyController) LyClassifyList() {
+	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
+	}
+	// 获取分类列表
+	lyClassifyList, err := data.LyClassifyList()
+	if err != nil {
+		br.Msg = "获取分类列表失败"
+		br.ErrMsg = "获取分类列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = lyClassifyList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 525 - 0
controllers/data_manage/base_from_ly_index_controller.go

@@ -0,0 +1,525 @@
+// Package data_manage
+// @Author gmy 2024/8/12 14:31:00
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	etaTrialService "eta/eta_api/services/eta_trial"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/tealeg/xlsx"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type BaseFromLyIndexController struct {
+	controllers.BaseAuthController
+}
+
+var (
+	codeMax = 30 // 指标编码最大数量
+)
+
+// LyIndexList
+// @Title 获取指标列表
+// @Description 获取指标列表
+// @Success 200 {object} []data_manage.BaseFromLyIndexPage
+// @router /ly/index/list [get]
+func (this *BaseFromLyIndexController) LyIndexList() {
+	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")
+	searchParam := this.GetString("SearchParam")
+	classifyId := this.GetString("ClassifyId")
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	// 获取指标列表
+	indexList, err := data.GetIndexPage(classifyId, searchParam, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取指标列表失败"
+		br.ErrMsg = "获取指标列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = indexList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// LyIndexDataList
+// @Title 获取指标对应的数据列表
+// @Description 获取指标对应的数据列表
+// @Success 200 {object} data_manage.BaseFromLyDataPage
+// @router /ly/index/data/list [get]
+func (this *BaseFromLyIndexController) LyIndexDataList() {
+	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
+	}
+
+	// 获取指标数据列表
+	indexId, err := this.GetInt("IndexId")
+	if err != nil {
+		br.Msg = "获取指标ID失败"
+		br.ErrMsg = "获取指标ID失败,Err:" + err.Error()
+		return
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	indexDataList, err := data.GetIndexDataPage(indexId, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取指标数据列表失败"
+		br.ErrMsg = "获取指标数据列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = indexDataList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// LyIndexAddValidate
+// @Title 新增加入到指标库校验
+// @Description 新增加入到指标库校验
+// @Success 200 {object} []data_manage.IndexCheckData
+// @router /ly/index/add/validate [post]
+func (this *BaseFromLyIndexController) LyIndexAddValidate() {
+	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 *data_manage.BaseFromLyIndexBatchAddCheckReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	codeLen := len(req.IndexCodes)
+	if codeLen > codeMax {
+		br.Msg = "批量添加指标数量不得超过" + strconv.Itoa(codeMax) + "个"
+		br.ErrMsg = "批量添加指标数量不得超过" + strconv.Itoa(codeMax) + "个"
+		return
+	}
+
+	// 校验指标编码是否存在
+	addValidate, err := data.LyIndexAddValidate(req.IndexCodes)
+	if err != nil {
+		return
+	}
+	br.Data = addValidate
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// LyIndexAdd
+// @Title 指标添加到指标库
+// @Description 指标添加到指标库
+// @Success 200 string "操作成功"
+// @router /ly/index/add [post]
+func (this *BaseFromLyIndexController) LyIndexAdd() {
+	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
+	}
+
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_BATCH_ADD_LY_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+
+	var req []*data_manage.AddEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if len(req) > codeMax {
+		br.Msg = "批量添加指标数量不得超过" + strconv.Itoa(codeMax) + "个"
+		return
+	}
+
+	indexNames := make([]string, 0)
+	resp := make([]*data_manage.NameCheckResult, 0)
+	for _, index := range req {
+		index.EdbCode = strings.TrimSpace(index.EdbCode)
+		if index.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		index.EdbName = strings.TrimSpace(index.EdbName)
+		if index.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		index.Frequency = strings.TrimSpace(index.Frequency)
+		if index.Frequency == "" {
+			br.Msg = "请选择频度"
+			return
+		}
+		index.Unit = strings.TrimSpace(index.Unit)
+		if index.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		if index.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+		indexNames = append(indexNames, index.EdbName)
+		resp = append(resp, &data_manage.NameCheckResult{
+			IndexCode: index.EdbCode,
+			IndexName: index.EdbName,
+			Exist:     false,
+		})
+	}
+
+	// 指标名称重复校验
+	nameCheck, err := data.LyIndexNameCheck(indexNames, resp)
+	if err != nil {
+		br.Msg = err.Error()
+		br.ErrMsg = err.Error()
+		return
+	}
+	for _, v := range nameCheck {
+		if v.Exist {
+			br.Msg = "指标名称重复"
+			br.Data = nameCheck
+			br.Ret = 200
+			br.Success = true
+			return
+		}
+	}
+
+	for _, v := range req {
+		var r data.LyIndexAddReq
+		r.EdbCode = v.EdbCode
+		r.EdbName = v.EdbName
+		r.Frequency = v.Frequency
+		r.Unit = v.Unit
+		r.ClassifyId = v.ClassifyId
+		r.AdminId = sysUser.AdminId
+		r.AdminRealName = sysUser.RealName
+
+		// 新增指标到指标库
+		edbInfo, e, errMsg, skip := data.LyIndexAdd(r, this.Lang)
+		if e != nil {
+			br.Msg = "操作失败"
+			if errMsg != "" {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = e.Error()
+			return
+		}
+		if skip {
+			continue
+		}
+
+		// 试用平台更新用户累计新增指标数
+		if utils.BusinessCode == utils.BusinessCodeSandbox {
+			go func() {
+				adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+				if e != nil {
+					tips := fmt.Sprintf("试用平台更新用户累计新增指标数-获取用户失败, Err: " + e.Error())
+					utils.FileLog.Info(tips)
+					return
+				}
+				if adminItem.DepartmentName != "ETA试用客户" {
+					return
+				}
+				var ur etaTrialService.EtaTrialUserReq
+				ur.Mobile = adminItem.Mobile
+				_, _ = etaTrialService.UpdateUserIndexNum(ur)
+			}()
+		}
+
+		// 新增操作日志
+		{
+			edbLog := new(data_manage.EdbInfoLog)
+			edbLog.EdbInfoId = edbInfo.EdbInfoId
+			edbLog.SourceName = edbInfo.SourceName
+			edbLog.Source = edbInfo.Source
+			edbLog.EdbCode = edbInfo.EdbCode
+			edbLog.EdbName = edbInfo.EdbName
+			edbLog.ClassifyId = edbInfo.ClassifyId
+			edbLog.SysUserId = sysUser.AdminId
+			edbLog.SysUserRealName = sysUser.RealName
+			edbLog.CreateTime = time.Now()
+			edbLog.Content = string(this.Ctx.Input.RequestBody)
+			edbLog.Status = "新增指标"
+			edbLog.Method = this.Ctx.Input.URI()
+			go data_manage.AddEdbInfoLog(edbLog)
+		}
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// LyIndexDataExport
+// @Title 导出指标数据
+// @Description 导出指标数据
+// @Success 200 string "操作成功"
+// @router /ly/index/data/export [get]
+func (this *BaseFromLyIndexController) LyIndexDataExport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	classifyId, _ := this.GetInt("ClassifyId") //分类
+	indexCode := this.GetString("IndexCode")   //指标唯一编码
+
+	//userId := sysUser.AdminId
+	//超管账号可以查看分类下的所有频度数据
+	/*if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
+		userId = 0
+	}*/
+	//获取账户所拥有权限的分类id集合
+	/*classifyIdStrList, err := data.GetEdbClassifyListByAdminId(int64(userId))
+	if err != nil {
+		br.Msg = "获取分类数据失败"
+		return
+	}*/
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+
+	downLoadFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+	frequencies, err := data_manage.GetLyIndexFrequency(classifyId)
+	if err != nil {
+		br.Msg = "查询频度失败"
+		br.ErrMsg = "查询频度失败"
+		return
+	}
+
+	fileName := `粮油商务网`
+	if classifyId > 0 && indexCode == "" {
+		lyClassify, err := data_manage.GetLyClassifyById(classifyId)
+		if err != nil {
+			return
+		}
+		fileName = lyClassify.ClassifyName
+	}
+	if frequencies == nil {
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+	}
+
+	for _, frequency := range frequencies {
+		lyIndices, err := data_manage.GetLyIndexByCodeAndClassify(indexCode, classifyId, frequency)
+		if err != nil {
+			return
+		}
+		var sheet *xlsx.Sheet
+		if len(lyIndices) > 0 {
+			sheet, err = xlsxFile.AddSheet(*frequency)
+			if err != nil {
+				br.Msg = "新增Sheet失败"
+				br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+				return
+			}
+		} else {
+			continue
+		}
+
+		if indexCode != "" {
+			fileName = lyIndices[0].IndexName
+		}
+
+		//获取指标数据
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("指标名称/Metric Name")
+		rowFrequency := sheet.AddRow()
+		celFrequency := rowFrequency.AddCell()
+		celFrequency.SetValue("频度/Frequency")
+		rowUnit := sheet.AddRow()
+		celUnit := rowUnit.AddCell()
+		celUnit.SetValue("单位/Unit")
+		rowModifyDate := sheet.AddRow()
+		rowModifyCell := rowModifyDate.AddCell()
+		rowModifyCell.SetValue("更新时间/Update Time")
+
+		dataMap := make(map[string]map[string]*data_manage.BaseFromLyData)
+		var tradeCodeList []string
+		for _, v := range lyIndices {
+			cellSenName := rowSecName.AddCell()
+			cellSenName.SetValue(v.IndexName)
+			celFrequency := rowFrequency.AddCell()
+			celFrequency.SetValue(v.Frequency)
+			celUnit := rowUnit.AddCell()
+			celUnit.SetValue(v.Unit)
+			rowModifyCell := rowModifyDate.AddCell()
+			rowModifyCell.SetValue(v.ModifyTime)
+			tradeCodeList = append(tradeCodeList, v.IndexCode)
+
+			var lyDataList []*data_manage.BaseFromLyData
+			lyDataList, err = data_manage.GetBaseFromLyDataByIndexCode(v.IndexCode)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.ErrMsg = "GetBaseFromLyDataByIndexCode,Err:" + err.Error()
+				br.Msg = "获取数据失败"
+				return
+			}
+			for _, item := range lyDataList {
+				if dataMap[item.IndexCode] == nil {
+					dataMap[item.IndexCode] = make(map[string]*data_manage.BaseFromLyData)
+				}
+				dataMap[item.IndexCode][item.DataTime] = item
+			}
+		}
+
+		tradeCodeStr := strings.Join(tradeCodeList, "','")
+		tradeCodeStr = "'" + tradeCodeStr + "'"
+		dataTimeList, err := data_manage.GetLyDataListByIndexCodes(tradeCodeStr)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		for _, dataTime := range dataTimeList {
+			rowData := sheet.AddRow()
+			celDate := rowData.AddCell()
+			celDate.SetValue(dataTime)
+
+			for _, m := range lyIndices {
+				celData := rowData.AddCell()
+				if dataMap[m.IndexCode][dataTime] != nil {
+					celData.SetValue(dataMap[m.IndexCode][dataTime].Value)
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadFilePath)
+	if err != nil {
+		//有指标无数据时先导出一遍空表
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+		err = xlsxFile.Save(downLoadFilePath)
+		if err != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+
+	fileName += time.Now().Format("06.01.02") + `.xlsx` //文件名称
+	this.Ctx.Output.Download(downLoadFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}

+ 12 - 0
controllers/data_manage/chart_common.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
+	"eta/eta_api/controllers/data_manage/range_analysis"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
@@ -167,6 +168,17 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_RANGE_ANALYSIS:
+		resp, isOk, msg, errMsg := range_analysis.GetChartInfoDetailFromUniqueCode(chartInfo, isCache, sysUser)
+		if !isOk {
+			br.Msg = msg
+			br.ErrMsg = errMsg
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
 	default:
 		br.Msg = "错误的图表"
 		br.ErrMsg = "错误的图表"

+ 1 - 35
controllers/data_manage/chart_info.go

@@ -18,7 +18,6 @@ import (
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"os"
 	"os/exec"
-	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -92,40 +91,7 @@ func (this *ChartInfoController) ChartInfoSave() {
 		req.Calendar = "公历"
 	}
 
-	var edbInfoIdArr []int
-	edbList := make([]*data_manage.EdbInfo, 0)
-
-	for _, v := range req.ChartEdbInfoList {
-		edbInfo, err := data_manage.GetEdbInfoById(v.EdbInfoId)
-		if err != nil {
-			if err.Error() == utils.ErrNoRow() {
-				br.Msg = "图表不存在!"
-				br.ErrMsg = "图表不存在,ChartInfoId:" + strconv.Itoa(v.EdbInfoId)
-				return
-			} else {
-				br.Msg = "获取图表信息失败!"
-				br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
-				return
-			}
-		}
-
-		if edbInfo == nil {
-			br.Msg = "指标不存在!"
-			br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(v.EdbInfoId)
-			return
-		}
-
-		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
-		edbInfo.EdbNameSource = edbInfo.EdbName
-		edbList = append(edbList, edbInfo)
-	}
-	sort.Ints(edbInfoIdArr)
-	var edbInfoIdArrStr []string
-	for _, v := range edbInfoIdArr {
-		edbInfoIdArrStr = append(edbInfoIdArrStr, strconv.Itoa(v))
-	}
-	edbInfoIdStr := strings.Join(edbInfoIdArrStr, ",")
-	err = data_manage.ModifyChartInfoAndMapping(edbInfoIdStr, &req, chartItem.ChartType)
+	err = data_manage.ModifyChartInfoAndMapping(chartItem.EdbInfoIds, &req, chartItem.ChartType)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存失败,Err:" + err.Error()

+ 35 - 0
controllers/data_manage/edb_info.go

@@ -2232,6 +2232,8 @@ func (this *EdbInfoController) EdbInfoList() {
 
 	//获取英文频度
 	edbInfoItem.FrequencyEn = data.GetFrequencyEn(edbInfoItem.Frequency)
+	// 获取是否供应商停更
+	edbInfoItem.IsSupplierStop = data.GetIsSupplierStop(edbInfoItem.Source, edbInfoItem.EdbCode)
 
 	//查询目录
 	classifyList, err, errMsg := data.GetFullClassifyByClassifyId(edbInfoItem.ClassifyId)
@@ -3852,6 +3854,9 @@ func (this *ChartInfoController) EdbInfoData() {
 		return
 	}
 	fullEdb.EdbInfo = edbInfo
+	// 是否供应商停更
+	fullEdb.IsSupplierStop = data.GetIsSupplierStop(edbInfo.Source, edbInfo.EdbCode)
+
 	fullEdb.ClassifyList = classifyList
 
 	var currClassifyItem *data_manage.EdbClassifyIdItems
@@ -5508,12 +5513,42 @@ func (this *EdbInfoController) EdbChartList() {
 				return
 			}
 
+			issSupplierStopIndexCodeMap := make(map[string]bool)
+			{
+				tmpEdbCodeList := make([]string, 0)
+				for _, v := range list {
+					if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+						tmpEdbCodeList = append(tmpEdbCodeList, v.EdbCode)
+					}
+				}
+				if len(tmpEdbCodeList) > 0 {
+					notIsSupplierStopIndexList, e := data_manage.GetNotIsSupplierStopIndexByCodeList(tmpEdbCodeList, 1)
+					if e != nil {
+						br.Msg = "获取失败"
+						br.ErrMsg = "获取数据失败,Err:" + e.Error()
+						return
+					}
+
+					// 已被供应商暂停的指标编码
+					for _, v := range notIsSupplierStopIndexList {
+						issSupplierStopIndexCodeMap[v.IndexCode] = true
+					}
+				}
+			}
+
 			for _, v := range list {
 				if currClassify, ok := classifyMap[v.ClassifyId]; ok {
 					v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(v.IsJoinPermission, currClassify.IsJoinPermission, v.EdbInfoId, v.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
 
 					v.Button = data.GetEdbOpButton(sysUser, v.SysUserId, v.EdbType, v.EdbInfoType, v.HaveOperaAuth)
 				}
+
+				// 供应商停用
+				if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+					if _, ok := issSupplierStopIndexCodeMap[v.EdbCode]; ok {
+						v.IsSupplierStop = 1
+					}
+				}
 			}
 		}
 

+ 37 - 0
controllers/data_manage/edb_info_refresh.go

@@ -723,6 +723,43 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 		br.ErrMsg = "获取数据失败,Err:" + e.Error()
 		return
 	}
+
+	// 如果是钢联化工,那么需要过滤供应商暂停的指标
+	if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+		// 获取未被供应商暂停的指标
+		tmpEdbCodeList := make([]string, 0)
+		for _, v := range edbList {
+			tmpEdbCodeList = append(tmpEdbCodeList, v.EdbCode)
+		}
+		notIsSupplierStopIndexList, e := data_manage.GetNotIsSupplierStopIndexByCodeList(tmpEdbCodeList, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + e.Error()
+			return
+		}
+
+		// 未被供应商暂停的指标编码
+		notIsSupplierStopIndexCodeList := make([]string, 0)
+		for _, v := range notIsSupplierStopIndexList {
+			notIsSupplierStopIndexCodeList = append(notIsSupplierStopIndexCodeList, v.IndexCode)
+		}
+
+		//查询未被供应商暂停的指标信息
+		edbList, err = data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, notIsSupplierStopIndexCodeList)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+
+		if len(edbList) <= 0 {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "保存成功"
+			return
+		}
+	}
+
 	fromEdbIdList := make([]int, 0)
 	for _, v := range edbList {
 		if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {

+ 614 - 11
controllers/data_manage/eia_steo.go

@@ -1,14 +1,23 @@
 package data_manage
 
 import (
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/tealeg/xlsx"
+	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/data_stat"
+	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
+	"fmt"
 	"os"
 	"path/filepath"
+	"strconv"
+	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
 )
 
 // EiaSteoClassify
@@ -35,16 +44,24 @@ func (this *EdbInfoController) EiaSteoClassify() {
 		br.ErrMsg = "获取分类失败,Err:" + err.Error()
 		return
 	}
+	childClassifyMap := make(map[int][]*data_manage.BaseFromEiaSteoClassifyView)
+	rootList := make([]*data_manage.BaseFromEiaSteoClassifyView, 0)
 	for _, v := range classifyList {
-		if v.ClassifyName == `` {
-			v.ClassifyName = v.ClassifyNameOriginal
+		if v.Level == 1 {
+			rootList = append(rootList, v)
+		} else {
+			childClassifyMap[v.ParentId] = append(childClassifyMap[v.ParentId], v)
+		}
+	}
+	for _, v := range rootList {
+		if existItems, ok := childClassifyMap[v.BaseFromEiaSteoClassifyId]; ok {
+			v.Child = existItems
 		}
 	}
-
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
-	br.Data = classifyList
+	br.Data = rootList
 }
 
 // EiaSteoData
@@ -98,8 +115,18 @@ func (this *EdbInfoController) EiaSteoData() {
 	}
 	classifyId, _ := this.GetInt("BaseFromEiaSteoClassifyId")
 	if classifyId > 0 {
-		condition += ` AND base_from_eia_steo_classify_id = ? `
-		pars = append(pars, classifyId)
+		classifyList, err := data.GetClassifyALLById(classifyId)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取分类数据失败,Err:" + err.Error()
+			return
+		}
+		var classifyIds []int
+		for _, v := range classifyList {
+			classifyIds = append(classifyIds, v.BaseFromEiaSteoClassifyId)
+		}
+		condition += ` AND base_from_eia_steo_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) `
+		pars = append(pars, classifyIds)
 	}
 
 	//获取指标
@@ -109,6 +136,20 @@ func (this *EdbInfoController) EiaSteoData() {
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
+	indexCodes := make([]string, 0)
+	for _, v := range indexList {
+		indexCodes = append(indexCodes, v.IndexCode)
+	}
+	edbinfoList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_EIA_STEO, indexCodes)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	existEdbInfo := make(map[string]int)
+	for _, v := range edbinfoList {
+		existEdbInfo[v.EdbCode] = v.EdbInfoId
+	}
 	resultList := make([]data_manage.EiaSteoIndexListResp, 0)
 	for _, v := range indexList {
 
@@ -122,6 +163,7 @@ func (this *EdbInfoController) EiaSteoData() {
 			IndexCode:                 v.IndexCode,
 			IndexName:                 indexName,
 			//IndexNameOriginal:          v.IndexNameOriginal,
+			EdbInfoId:  existEdbInfo[v.IndexCode],
 			Unit:       v.Unit,
 			Frequency:  v.Frequency,
 			StartDate:  v.StartDate.Format(utils.FormatDate),
@@ -133,6 +175,11 @@ func (this *EdbInfoController) EiaSteoData() {
 		}
 
 		total, err := data_manage.GetEiaSteoIndexDataCount(v.IndexCode)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标统计数据失败,Err:" + err.Error()
+			return
+		}
 		page := paging.GetPaging(currentIndex, pageSize, total)
 
 		dataList, err := data_manage.GetEiaSteoIndexDataList(v.IndexCode, startSize, pageSize)
@@ -213,20 +260,25 @@ func EiaSteoDataExport(this *EdbInfoController, list []data_manage.EiaSteoIndexL
 	//获取指标数据
 	rowSecName := sheet.AddRow()
 	celSecName := rowSecName.AddCell()
-	celSecName.SetValue("指标名称")
+	celSecName.SetValue("指标名称/Metric Name")
+	frequency := sheet.AddRow()
+	freqCell := frequency.AddCell()
+	freqCell.SetValue("频度/Frequency")
 	rowUnit := sheet.AddRow()
 	celUnit := rowUnit.AddCell()
-	celUnit.SetValue("单位")
+	celUnit.SetValue("单位/Unit")
 
 	rowModifyDate := sheet.AddRow()
 	rowModifyCell := rowModifyDate.AddCell()
-	rowModifyCell.SetValue("更新时间")
+	rowModifyCell.SetValue("更新时间/Update Time")
 
 	dataMap := make(map[string]map[string]*data_manage.BaseFromEiaSteoDataItem)
 	var indexCodeList []string
 	for _, v := range list {
 		cellSenName := rowSecName.AddCell()
 		cellSenName.SetValue(v.IndexName)
+		freqCell := frequency.AddCell()
+		freqCell.SetValue(v.Frequency)
 		celUnit := rowUnit.AddCell()
 		celUnit.SetValue(v.Unit)
 		rowModifyCell := rowModifyDate.AddCell()
@@ -289,3 +341,554 @@ func EiaSteoDataExport(this *EdbInfoController, list []data_manage.EiaSteoIndexL
 	br.Success = true
 	br.Msg = "success"
 }
+
+// EiaSteoBatchSearch
+// @Title EiaSteo批量操作查询接口
+// @Description EiaSteo批量操作查询接口
+// @Param   BaseFromEiaSteoClassifyIds   query   string  true       "分类id"
+// @Param   Keyword   query   string  true       "名称关键词"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /eia_steo/batch_search [get]
+func (this *EdbInfoController) EiaSteoBatchSearch() {
+	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
+	}
+	classifyIdStr := this.GetString("BaseFromEiaSteoClassifyIds")
+
+	var condition string
+	var pars []interface{}
+	classifyIds := strings.Split(classifyIdStr, ",")
+	if len(classifyIds) > 0 && classifyIds[0] != `` {
+		condition += " AND base_from_eia_steo_classify_id IN (" + utils.GetOrmInReplace(len(classifyIds)) + " ) "
+		pars = append(pars, classifyIds)
+	}
+	keyword := this.GetString("Keyword")
+	if keyword != `` {
+		condition += " AND (index_name like ? OR index_code like ?) "
+		pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+	}
+
+	if classifyIdStr == `` && keyword == `` {
+		var list = make([]*data_manage.BaseFromEiaSteoIndex, 0)
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = list
+		return
+	}
+
+	list, err := data_manage.GetEiaSteoIndexList(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// AddCheck
+// @Title 新增校验
+// @Description 新增校验
+// @Param	request	body request.BusinessDataBatchAddCheckReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /eia_steo/edb_info/add_check [post]
+func (c *EdbInfoController) EiaSteoAddCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.EiaSteoDataBatchAddCheckReq
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	codeMax := 30
+	codeLen := len(req.IndexCodes)
+	if codeLen == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+
+	// 获取指标库已有指标
+	existsEdb, e := data_manage.GetEdbCodesBySource(utils.DATA_SOURCE_EIA_STEO)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取自有数据已添加的指标失败, Err: " + e.Error()
+		return
+	}
+	existMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range existsEdb {
+		existMap[v.EdbCode] = v
+	}
+
+	// 查询选中的指标
+	cond := fmt.Sprintf(` AND index_code IN (%s) `, utils.GetOrmInReplace(len(req.IndexCodes)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.IndexCodes)
+	list, err := data_manage.GetEiaSteoIndexList(cond, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取eiaSteo原始指标列表失败, Err: " + err.Error()
+		return
+	}
+
+	resp := make([]*data_manage.BaseFromEiaSteoIndexItem, 0)
+	for _, v := range list {
+		if edbInfo, ok := existMap[v.IndexCode]; ok {
+			resp = append(resp, &data_manage.BaseFromEiaSteoIndexItem{
+				BaseFromEiaSteoIndexId:    v.BaseFromEiaSteoIndexId,
+				BaseFromEiaSteoClassifyId: v.BaseFromEiaSteoClassifyId,
+				IndexCode:                 v.IndexCode,
+				IndexName:                 v.IndexName,
+				EdbInfoId:                 edbInfo.EdbInfoId,
+				EdbUniqueCode:             edbInfo.UniqueCode,
+				EdbClassifyId:             edbInfo.ClassifyId,
+				EdbExist:                  1,
+			})
+		} else {
+			resp = append(resp, &data_manage.BaseFromEiaSteoIndexItem{
+				BaseFromEiaSteoIndexId:    v.BaseFromEiaSteoIndexId,
+				BaseFromEiaSteoClassifyId: v.BaseFromEiaSteoClassifyId,
+				IndexCode:                 v.IndexCode,
+				IndexName:                 v.IndexName,
+				EdbInfoId:                 0,
+				EdbUniqueCode:             "",
+				EdbClassifyId:             0,
+				EdbExist:                  0,
+			})
+		}
+	}
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// EiaSteoNameCheck
+// @Title 重名校验
+// @Description 批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /eia_steo/name_check [post]
+func (c *EdbInfoController) EiaSteoNameCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req []*data_manage.NameCheckEdbInfoReq
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	codeMax := 30
+	codeLen := len(req)
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+
+	type NameCheckResult struct {
+		EdbCode string
+		EdbName string
+		Exist   bool
+	}
+	indexNames := make([]string, 0)
+	resp := make([]*NameCheckResult, 0)
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		indexNames = append(indexNames, v.EdbName)
+		resp = append(resp, &NameCheckResult{
+			EdbCode: v.EdbCode,
+			EdbName: v.EdbName,
+		})
+		dataItems, err := data_manage.GetEdbDataAllByEdbCode(v.EdbCode, utils.DATA_SOURCE_EIA_STEO, 0, utils.EDB_DATA_LIMIT)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
+			return
+		}
+		if len(dataItems) <= 0 {
+			respItem, err := data.AddEdbData(utils.DATA_SOURCE_EIA_STEO, v.EdbCode, v.Frequency)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				return
+			}
+			if respItem.Ret != 200 {
+				br.Msg = "未搜索到该指标"
+				br.ErrMsg = respItem.ErrMsg + ";EdbCode:" + v.EdbCode
+				return
+			}
+		}
+	}
+
+	// 重名校验
+	edbList, e := data_manage.GetEdbInfoByNameArr(indexNames, utils.EDB_INFO_TYPE)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取重名指标失败, Err: " + e.Error()
+		return
+	}
+	nameExists := make(map[string]bool)
+	for _, v := range edbList {
+		nameExists[v.EdbName] = true
+	}
+	if len(nameExists) > 0 {
+		for _, v := range resp {
+			v.Exist = nameExists[v.EdbName]
+		}
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// EiaSteoBatchAdd
+// @Title eiaSteo批量新增
+// @Description eiaSteo批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /eia_steo/batch_add [post]
+func (this *EdbInfoController) EiaSteoBatchAdd() {
+	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
+	}
+
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_BATCH_ADD_EIA_STEO_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req []*data_manage.AddEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if len(req) > 30 {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		v.Frequency = strings.TrimSpace(v.Frequency)
+		if v.Frequency == "" {
+			br.Msg = "请选择频度"
+			return
+		}
+		v.Unit = strings.TrimSpace(v.Unit)
+		if v.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		if v.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+	}
+
+	// 限定同一时间最多批量新增30个指标
+	for _, v := range req {
+		var r data.EiaSteoIndexSource2EdbReq
+		r.EdbCode = v.EdbCode
+		r.EdbName = v.EdbName
+		r.Frequency = v.Frequency
+		r.Unit = v.Unit
+		r.ClassifyId = v.ClassifyId
+		r.AdminId = sysUser.AdminId
+		r.AdminRealName = sysUser.RealName
+
+		edbInfo, e, errMsg, skip := data.EiaSteoIndexSource2Edb(r, this.Lang)
+		if e != nil {
+			br.Msg = "操作失败"
+			if errMsg != "" {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = e.Error()
+			return
+		}
+		if skip {
+			continue
+		}
+
+		// 试用平台更新用户累计新增指标数
+		if utils.BusinessCode == utils.BusinessCodeSandbox {
+			go func() {
+				adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+				if e != nil {
+					tips := fmt.Sprintf("试用平台更新用户累计新增指标数-获取用户失败, Err: " + e.Error())
+					utils.FileLog.Info(tips)
+					return
+				}
+				if adminItem.DepartmentName != "ETA试用客户" {
+					return
+				}
+				var ur etaTrialService.EtaTrialUserReq
+				ur.Mobile = adminItem.Mobile
+				_, _ = etaTrialService.UpdateUserIndexNum(ur)
+			}()
+		}
+
+		// 新增操作日志
+		{
+			edbLog := new(data_manage.EdbInfoLog)
+			edbLog.EdbInfoId = edbInfo.EdbInfoId
+			edbLog.SourceName = edbInfo.SourceName
+			edbLog.Source = edbInfo.Source
+			edbLog.EdbCode = edbInfo.EdbCode
+			edbLog.EdbName = edbInfo.EdbName
+			edbLog.ClassifyId = edbInfo.ClassifyId
+			edbLog.SysUserId = sysUser.AdminId
+			edbLog.SysUserRealName = sysUser.RealName
+			edbLog.CreateTime = time.Now()
+			edbLog.Content = string(this.Ctx.Input.RequestBody)
+			edbLog.Status = "新增指标"
+			edbLog.Method = this.Ctx.Input.URI()
+			go data_manage.AddEdbInfoLog(edbLog)
+		}
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// EiaSteoAdd
+// @Title 新增eiaSteo指标接口
+// @Description 新增eiaSteo指标接口
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /eia_steo/edb_info/add [post]
+func (this *EdbInfoController) EiaSteoAdd() {
+	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
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req data_manage.AddEdbInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	req.EdbName = strings.Trim(req.EdbName, " ")
+	req.EdbCode = strings.Trim(req.EdbCode, " ")
+
+	if req.EdbCode == "" {
+		br.Msg = "指标ID不能为空"
+		return
+	}
+
+	if req.EdbName == "" {
+		br.Msg = "指标名称不能为空"
+		return
+	}
+
+	if req.Frequency == "" {
+		br.Msg = "频率不能为空"
+		return
+	}
+
+	if req.Unit == "" {
+		br.Msg = "单位不能为空"
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	tmpInfo, err := data_manage.GetBaseFromEiaSteoIndexByCode(req.EdbCode)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	}
+	var source int
+	if tmpInfo != nil {
+		source = utils.DATA_SOURCE_EIA_STEO
+	} else {
+		br.Msg = "指标不存在"
+		return
+	}
+
+	// 指标入库
+	edbInfo, err, errMsg, isSendEmail := data.EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, req.StartDate, req.EndDate, sysUser.AdminId, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 试用平台更新用户累计新增指标数
+	adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + e.Error()
+		return
+	}
+	if utils.BusinessCode == utils.BusinessCodeSandbox && adminItem.DepartmentName == "ETA试用客户" {
+		go func() {
+			var r etaTrialService.EtaTrialUserReq
+			r.Mobile = adminItem.Mobile
+			_, _ = etaTrialService.UpdateUserIndexNum(r)
+		}()
+	}
+
+	//新增操作日志
+	{
+		// 添加钢联指标更新日志
+		if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			go data_stat.AddEdbInfoUpdateLog(edbInfo.EdbInfoId, 1, "", sysUser, 2)
+		}
+
+		edbLog := new(data_manage.EdbInfoLog)
+		edbLog.EdbInfoId = edbInfo.EdbInfoId
+		edbLog.SourceName = edbInfo.SourceName
+		edbLog.Source = edbInfo.Source
+		edbLog.EdbCode = edbInfo.EdbCode
+		edbLog.EdbName = edbInfo.EdbName
+		edbLog.ClassifyId = edbInfo.ClassifyId
+		edbLog.SysUserId = sysUser.AdminId
+		edbLog.SysUserRealName = sysUser.RealName
+		edbLog.CreateTime = time.Now()
+		edbLog.Content = string(this.Ctx.Input.RequestBody)
+		edbLog.Status = "新增指标"
+		edbLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddEdbInfoLog(edbLog)
+	}
+
+	// 更新es
+	go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+
+	resp := new(data_manage.AddEdbInfoResp)
+	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.UniqueCode = edbInfo.UniqueCode
+	resp.ClassifyId = edbInfo.ClassifyId
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}

+ 414 - 32
controllers/data_manage/excel/excel_info.go

@@ -16,15 +16,16 @@ import (
 	"eta/eta_api/services/excel"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"io"
 	"os"
 	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
 )
 
 // ExcelInfoController ETA表格管理
@@ -159,30 +160,31 @@ func (c *ExcelInfoController) Add() {
 	if req.Source == utils.TIME_TABLE {
 		jsonStrByte, err := json.Marshal(req.TableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		var tableData request.TableDataReq
 		err = json.Unmarshal(jsonStrByte, &tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 
 		tableDataConfig, err := excel2.GetTableDataConfig(tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		edbInfoIdList = tableDataConfig.EdbInfoIdList
 
+		tableDataConfig.DecimalConfig = tableData.DecimalConfig
 		contentByte, err := json.Marshal(tableDataConfig)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取后,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		content = string(contentByte)
@@ -675,21 +677,21 @@ func (c *ExcelInfoController) Detail() {
 	if excelDetail.Source == utils.TIME_TABLE {
 		jsonStrByte, err := json.Marshal(excelDetail.TableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		var tableData request.TableDataReq
 		err = json.Unmarshal(jsonStrByte, &tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList(tableData.EdbInfoIdList)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		excelDetail.ExcelSource = strings.Join(sourceNameList, ",")
@@ -936,28 +938,29 @@ func (c *ExcelInfoController) Edit() {
 	case utils.TIME_TABLE: // 自定义表格
 		jsonStrByte, err := json.Marshal(req.TableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		var tableData request.TableDataReq
 		err = json.Unmarshal(jsonStrByte, &tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		edbInfoIdList = tableData.EdbInfoIdList
 		tableDataConfig, err := excel2.GetTableDataConfig(tableData)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
+		tableDataConfig.DecimalConfig = tableData.DecimalConfig
 		contentByte, err := json.Marshal(tableDataConfig)
 		if err != nil {
-			br.Msg = "自定义表格数据获取失败"
-			br.ErrMsg = "自定义表格数据获取后,转json失败,Err:" + err.Error()
+			br.Msg = "时间序列表格数据获取失败"
+			br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
 			return
 		}
 		content = string(contentByte)
@@ -1643,7 +1646,10 @@ func (c *ExcelInfoController) GetExcelTableData() {
 	}
 
 	tableData = excel.HandleTableCell(tableData)
-
+	tableData, err = excel.HandleRuleToTableCell(excelInfo.ExcelInfoId, tableData)
+	if err != nil {
+		utils.FileLog.Info("表格管理规则处理失败,HandleRuleToTableCell err:", err.Error())
+	}
 	config := response.ExcelTableDetailConfigResp{
 		FontSize: 9,
 	}
@@ -1835,7 +1841,7 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 		return
 	}
 
-	dataList, err := excel2.GetFirstEdbDataList(edbInfo, num, []string{})
+	dataList, err := excel2.GetFirstEdbDataList(edbInfo, num, []string{}, -1)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1857,6 +1863,10 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 	}
 	excelSource := strings.Join(sourceNameList, ",")
 	excelSourceEn := strings.Join(sourceNameEnList, ",")
+	if len(dataList) == 0 {
+		br.Msg = "指标数据异常,请检查"
+		return
+	}
 
 	br.Ret = 200
 	br.Success = true
@@ -1864,6 +1874,7 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 	br.Data = response.TableDataItem{
 		EdbInfoId:     edbInfoId,
 		Data:          dataList,
+		Decimal:       -1,
 		ExcelSource:   excelSource,
 		ExcelSourceEn: excelSourceEn,
 	}
@@ -1914,7 +1925,7 @@ func (c *ExcelInfoController) GetOtherEdbData() {
 		br.ErrMsg = fmt.Sprint("获取指标信息失败,Err:", err.Error())
 		return
 	}
-	dataList, err := excel2.GetOtherEdbDataList(edbInfo, req.DateList)
+	dataList, err := excel2.GetOtherEdbDataListFollowDate(edbInfo, req.DateList, req.DateDecimal)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -1943,11 +1954,148 @@ func (c *ExcelInfoController) GetOtherEdbData() {
 	br.Data = response.TableDataItem{
 		EdbInfoId:     req.EdbInfoId,
 		Data:          dataList,
+		Decimal:       -1,
 		ExcelSource:   excelSource,
 		ExcelSourceEn: excelSourceEn,
 	}
 }
 
+// GetBatchEdbData
+// @Title 批量获取指定日期数据接口
+// @Description 批量获取未来日期数据接口
+// @Param	request	body request.GetBatchEdbDateDataReq true "type json string"
+// @router /excel_info/table/batch_edb_data_list [post]
+func (c *ExcelInfoController) GetBatchEdbData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.GetBatchEdbDateDataReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.EdbInfoId) == 0 {
+		br.Msg = "请选择指标"
+		br.IsSendEmail = false
+		return
+	}
+	tableList := make([]*response.TableDataItem, 0)
+	var firstDataList []string
+	for i, v := range req.EdbInfoId {
+		if req.Num <= 0 {
+			br.Msg = "期数必须大于0"
+			br.IsSendEmail = false
+			return
+		}
+		edbInfo, err := data_manage.GetEdbInfoById(v)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprint("获取指标信息失败,Err:", err.Error())
+			return
+		}
+		if len(req.DateList) == 0 {
+			req.Num = 12
+		}
+		if len(firstDataList) == 0 {
+			firstDataList = req.DateList
+		}
+		if len(firstDataList) != 0 && len(firstDataList) != req.Num {
+			req.Num = len(firstDataList)
+		}
+		if i == 0 {
+			dataList, err := excel2.GetFirstEdbDataList(edbInfo, req.Num, firstDataList, req.Decimal[i])
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
+				return
+			}
+
+			// 默认是倒序返回的数据,如果要正序的话,那么翻转下就好了
+			if req.SortType == "asc" {
+				for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+					dataList[i], dataList[j] = dataList[j], dataList[i]
+				}
+			}
+			dataList = excel2.PadFirstEdbDataList(dataList, req.DateList, req.SortType, req.Decimal[i])
+
+			sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{v})
+			if err != nil {
+				br.Msg = "时间序列表格数据获取失败"
+				br.ErrMsg = "时间序列表格数据获取失败,Err:" + err.Error()
+				return
+			}
+			excelSource := strings.Join(sourceNameList, ",")
+			excelSourceEn := strings.Join(sourceNameEnList, ",")
+			if len(dataList) == 0 {
+				br.Msg = "指标数据异常,请检查"
+				return
+			}
+			tableList = append(tableList, &response.TableDataItem{
+				EdbInfoId:     v,
+				Data:          dataList,
+				Decimal:       req.Decimal[i],
+				ExcelSource:   excelSource,
+				ExcelSourceEn: excelSourceEn,
+			})
+			if len(firstDataList) == 0 {
+				tmpDataList := make([]string, 0)
+				for _, v := range dataList {
+					tmpDataList = append(tmpDataList, v.DataTime)
+				}
+				firstDataList = tmpDataList
+			}
+		} else {
+			dataList, err := excel2.GetOtherEdbDataList(edbInfo, firstDataList, req.Decimal[i])
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
+				return
+			}
+
+			// 默认是倒序返回的数据,如果要正序的话,那么翻转下就好了
+			if req.SortType == "asc" {
+				for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+					dataList[i], dataList[j] = dataList[j], dataList[i]
+				}
+			}
+
+			sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{v})
+			if err != nil {
+				br.Msg = "自定义表格数据获取失败"
+				br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+				return
+			}
+			excelSource := strings.Join(sourceNameList, ",")
+			excelSourceEn := strings.Join(sourceNameEnList, ",")
+			tableList = append(tableList, &response.TableDataItem{
+				EdbInfoId:     v,
+				Decimal:       req.Decimal[i],
+				Data:          dataList,
+				ExcelSource:   excelSource,
+				ExcelSourceEn: excelSourceEn,
+			})
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = tableList
+}
+
 // GetFutureDateData
 // @Title 获取未来日期数据接口
 // @Description 获取未来日期数据接口
@@ -2091,7 +2239,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 		for i := lenDate - 1; i >= 0; i-- {
 			dateStrList = append(dateStrList, dateList[i].Format(utils.FormatDate))
 		}
-		for _, v := range req.EdbInfoIdList {
+		for j, v := range req.EdbInfoIdList {
 			tmpEdbInfo, ok := edbInfoMap[v]
 			if !ok {
 				br.Msg = "获取指标信息失败"
@@ -2099,7 +2247,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 				return
 			}
 
-			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
+			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList, req.Decimal[j])
 			if err != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -2107,6 +2255,7 @@ func (c *ExcelInfoController) GetFutureDateData() {
 			}
 			result = append(result, response.TableDataItem{
 				EdbInfoId: v,
+				Decimal:   req.Decimal[j],
 				Data:      dataList,
 			})
 		}
@@ -2182,7 +2331,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 			br.ErrMsg = "获取指标信息失败,err:" + err.Error()
 			return
 		}
-		firstDataList, err := excel2.GetFirstHistoryEdbDataList(tmpEdbInfo, req.Num, req.EndDate)
+		firstDataList, err := excel2.GetFirstHistoryEdbDataList(tmpEdbInfo, req.Num, req.EndDate, req.Decimal[0])
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -2191,6 +2340,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 
 		result = append(result, response.TableDataItem{
 			EdbInfoId: req.EdbInfoIdList[0],
+			Decimal:   req.Decimal[0],
 			Data:      firstDataList,
 		})
 
@@ -2211,7 +2361,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 				return
 			}
 
-			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList)
+			dataList, err := excel2.GetOtherEdbDataList(tmpEdbInfo, dateStrList, req.Decimal[k])
 			if err != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprint("获取失败,Err:", err.Error())
@@ -2219,6 +2369,7 @@ func (c *ExcelInfoController) GetHistoryDateData() {
 			}
 			result = append(result, response.TableDataItem{
 				EdbInfoId: v,
+				Decimal:   req.Decimal[k],
 				Data:      dataList,
 			})
 		}
@@ -2966,3 +3117,234 @@ func (c *ExcelInfoController) GetEdbSource() {
 	br.Ret = 200
 	br.Success = true
 }
+
+// ExcelRule
+// @Title 添加表格规则
+// @Description 添加表格规则
+// @Param	request	body excel3.BatchRefreshExcelReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/rule/add [post]
+func (c *ExcelInfoController) AddExcelRule() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req *request.ExcelRuleMappingReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.LeftValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	if req.Scope == "" {
+		br.Msg = "应用选区不能为空"
+		return
+	}
+	if req.FontColor == "" && req.BackgroundColor == "" {
+		br.Msg = "字体颜色和背景颜色不能同时为空"
+		return
+	}
+	if req.RuleType == 3 && req.RightValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	err = excel2.AddExcelRule(req, c.Lang)
+	if err != nil {
+		br.Msg = "规则添加失败"
+		br.ErrMsg = "规则添加失败,Err:" + err.Error()
+		return
+	}
+	br.Msg = "添加成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// EditExcelRule
+// @Title 编辑表格规则
+// @Description 编辑表格规则
+// @Param	request	body request.ExcelRuleMappingReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/rule/edit [post]
+func (c *ExcelInfoController) EditExcelRule() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req *request.ExcelRuleMappingReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.LeftValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	if req.Scope == "" {
+		br.Msg = "应用选区不能为空"
+		return
+	}
+	if req.FontColor == "" {
+		br.Msg = "字体颜色不能为空"
+		return
+	}
+	if req.BackgroundColor == "" {
+		br.Msg = "背景颜色不能为空"
+		return
+	}
+	if req.RuleType == 3 && req.RightValue == "" {
+		br.Msg = "条件值不能为空"
+		return
+	}
+	if req.ExcelRuleMappingId <= 0 {
+		br.Msg = "非法的管理规则ID"
+		return
+	}
+	err = excel2.ModifyExcelRule(req, c.Lang)
+	if err != nil {
+		br.Msg = "规则编辑失败"
+		br.ErrMsg = "规则编辑失败,Err:" + err.Error()
+		return
+	}
+	br.Msg = "编辑成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// DeleteExcelRule
+// @Title 删除表格规则
+// @Description 删除表格规则
+// @Param	request	body request.ExcelRuleMappingReq true "type json string"
+// @Success Ret=200 刷新成功
+// @router /excel_info/rule/delete [post]
+func (c *ExcelInfoController) DeleteExcelRule() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.DeleteExcelRuleMappingReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ExcelRuleMappingId <= 0 {
+		br.Msg = "非法的管理规则ID"
+		return
+	}
+	err = excel3.DeleteExcelRuleMappingById(req.ExcelRuleMappingId)
+	if err != nil {
+		br.Msg = "规则删除失败"
+		br.ErrMsg = "规则删除失败,Err:" + err.Error()
+		return
+	}
+	br.Msg = "删除成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetExcelRuleList
+// @Title 表格规则列表
+// @Description 表格规则列表
+// @Param   ExcelInfoId   query   int  true       "id"
+// @Success Ret=200 获取成功
+// @router /excel_info/rule/list [get]
+func (c *ExcelInfoController) GetExcelRuleList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	ExcelInfoId, _ := c.GetInt("ExcelInfoId")
+
+	if ExcelInfoId <= 0 {
+		br.Msg = "请选择表格"
+		return
+	}
+
+	items, err := excel2.GetExcelRuleList(ExcelInfoId)
+	if err != nil {
+		br.Msg = "管理规则编辑失败"
+		br.ErrMsg = "管理规则添加失败,Err:" + err.Error()
+		return
+	}
+	if items.List == nil {
+		items.List = []*excel3.ExcelInfoRuleMappingView{}
+	}
+
+	br.Data = items
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetExcelRuleDetail
+// @Title 表格规则详情
+// @Description 表格规则详情
+// @Param   ExcelInfoRuleMappingId   query   int  true       "id"
+// @Success Ret=200 获取成功
+// @router /excel_info/rule/detail [get]
+func (c *ExcelInfoController) GetExcelRuleDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	excelInfoRuleMappingId, _ := c.GetInt("ExcelInfoRuleMappingId")
+
+	items, err := excel2.GetExcelRuleDetail(excelInfoRuleMappingId)
+	if err != nil {
+		br.Msg = "管理规则编辑失败"
+		br.ErrMsg = "管理规则添加失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = items
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 556 - 1
controllers/data_manage/fenwei_data.go

@@ -1,14 +1,19 @@
 package data_manage
 
 import (
+	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"github.com/tealeg/xlsx"
 	"os"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -58,7 +63,7 @@ func (this *EdbInfoController) FenweiClassify() {
 		}
 	}
 	for _, v := range resp {
-		v.Child = parentMap[v.ClassifyId]
+		addChildren(v, parentMap)
 	}
 
 	br.Data = resp
@@ -67,12 +72,22 @@ func (this *EdbInfoController) FenweiClassify() {
 	br.Msg = "获取成功"
 }
 
+func addChildren(parent *data_manage.BaseFromFenweiClassifyItem, parentMap map[int][]*data_manage.BaseFromFenweiClassifyItem) {
+	if children, ok := parentMap[parent.ClassifyId]; ok {
+		parent.Child = children
+		for _, child := range children {
+			addChildren(child, parentMap)
+		}
+	}
+}
+
 // FenweiIndexData
 // @Title 获取汾渭数据
 // @Description 获取汾渭数据接口
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   ClassifyId   query   string  true       "分类id"
+// @Param   Frequency   query   string  true       "频率"
 // @Success 200 {object} data_manage.LzFrequency
 // @router /fenwei/index/data [get]
 func (this *EdbInfoController) FenweiIndexData() {
@@ -110,6 +125,7 @@ func (this *EdbInfoController) FenweiIndexData() {
 		br.ErrMsg = "请选择分类"
 		return
 	}
+	frequency := this.GetString("Frequency")
 
 	// 获取指标
 	var condition string
@@ -119,6 +135,11 @@ func (this *EdbInfoController) FenweiIndexData() {
 		pars = append(pars, classifyId)
 	}
 
+	if frequency != "" {
+		condition += ` AND frequency=? `
+		pars = append(pars, frequency)
+	}
+
 	indexes, err := data_manage.GetFenweiIndex(condition, pars)
 	if err != nil {
 		br.Msg = "获取数据失败"
@@ -141,6 +162,16 @@ func (this *EdbInfoController) FenweiIndexData() {
 		countMap[v.IndexCode] = v.Count
 	}
 
+	// 判断是否存在于指标库
+	edbCodeList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_FENWEI, indexCodes)
+	if err != nil {
+		return
+	}
+	edbCodeMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range edbCodeList {
+		edbCodeMap[v.EdbCode] = v
+	}
+
 	resultList := make([]*data_manage.BaseFromFenweiIndexList, 0)
 	for _, v := range indexes {
 		product := new(data_manage.BaseFromFenweiIndexList)
@@ -153,6 +184,11 @@ func (this *EdbInfoController) FenweiIndexData() {
 		product.CreateTime = v.CreateTime
 		product.ModifyTime = v.ModifyTime
 
+		edbInfo := edbCodeMap[v.IndexCode]
+		if edbInfo != nil {
+			product.EdbInfoId = edbInfo.EdbInfoId
+		}
+
 		total := countMap[v.IndexCode]
 		page := paging.GetPaging(currentIndex, pageSize, total)
 		dataList, e := data_manage.GetFenweiIndexData(v.IndexCode, startSize, pageSize)
@@ -267,6 +303,12 @@ func (this *EdbInfoController) FenweiSingleData() {
 		br.ErrMsg = "获取数据失败,Err:" + err.Error()
 		return
 	}
+	indexCodeList := []string{indexCode}
+	edbCodeList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_FENWEI, indexCodeList)
+	if err != nil {
+		return
+	}
+
 	var ret data_manage.FenweiSingleDataResp
 	var dataList []*data_manage.FenweiSingleData
 
@@ -286,6 +328,10 @@ func (this *EdbInfoController) FenweiSingleData() {
 		dataList = append(dataList, tmp)
 	}
 	ret.Data = dataList
+	if len(edbCodeList) > 0 {
+		edbInfo := edbCodeList[0]
+		ret.EdbInfoId = edbInfo.EdbInfoId
+	}
 
 	br.Ret = 200
 	br.Success = true
@@ -490,3 +536,512 @@ func (this *EdbInfoController) ExportFenweiList() {
 	br.Msg = "success"
 
 }
+
+// GetFenWeiFrequencyList
+// @Title 查询频率列表
+// @Description 查询频率列表
+// @Param   classifyId   query  int  false   "指标唯一编码"
+// @Success 200 {object} []string
+// @router /fenwei/frequency/list [get]
+func (this *EdbInfoController) GetFenWeiFrequencyList() {
+	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
+	}
+
+	classifyId, _ := this.GetInt("ClassifyId")
+
+	frequencyList, err := data_manage.GetFenWeiIndexFrequency(classifyId)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = frequencyList
+}
+
+// FenWeiIndexAddValidate
+// @Title 新增加入到指标库校验
+// @Description 新增加入到指标库校验
+// @Param   req    body   data_manage.BaseFromFenWeiIndexBatchAddCheckReq     true        "请求参数"
+// @Success 200 {object} []data_manage.IndexCheckData
+// @router /fenwei/index/add/validate [post]
+func (this *EdbInfoController) FenWeiIndexAddValidate() {
+	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 *data_manage.BaseFromFenWeiIndexBatchAddCheckReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	codeLen := len(req.IndexCodes)
+	var codeMax = 30
+	if codeLen > codeMax {
+		br.Msg = "批量添加指标数量不得超过" + strconv.Itoa(codeMax) + "个"
+		br.ErrMsg = "批量添加指标数量不得超过" + strconv.Itoa(codeMax) + "个"
+		return
+	}
+
+	// 校验指标编码是否存在
+	addValidate, err := data.FenWeiIndexAddValidate(req.IndexCodes)
+	if err != nil {
+		return
+	}
+	br.Data = addValidate
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// FenWeiIndexAdd
+// @Title 指标添加到指标库
+// @Description 指标添加到指标库
+// @Param   req    body   []data_manage.AddEdbInfoReq     true        "请求参数"
+// @Success 200 string "操作成功"
+// @router /fenwei/index/add [post]
+func (this *EdbInfoController) FenWeiIndexAdd() {
+	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
+	}
+
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_BATCH_ADD_FENWEI_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+
+	var req []*data_manage.AddEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	codeMax := 30
+	if len(req) > codeMax {
+		br.Msg = "批量添加指标数量不得超过" + strconv.Itoa(codeMax) + "个"
+		return
+	}
+
+	indexNames := make([]string, 0)
+	resp := make([]*data_manage.FenWeiNameCheckResult, 0)
+	for _, index := range req {
+		index.EdbCode = strings.TrimSpace(index.EdbCode)
+		if index.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		index.EdbName = strings.TrimSpace(index.EdbName)
+		if index.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		index.Frequency = strings.TrimSpace(index.Frequency)
+		if index.Frequency == "" {
+			br.Msg = "请选择频度"
+			return
+		}
+		index.Unit = strings.TrimSpace(index.Unit)
+		if index.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		if index.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+		indexNames = append(indexNames, index.EdbName)
+		resp = append(resp, &data_manage.FenWeiNameCheckResult{
+			IndexCode: index.EdbCode,
+			IndexName: index.EdbName,
+			Exist:     false,
+		})
+	}
+
+	// 指标名称重复校验
+	nameCheck, err := data.FenWeiIndexNameCheck(indexNames, resp)
+	if err != nil {
+		br.Msg = err.Error()
+		br.ErrMsg = err.Error()
+		return
+	}
+	for _, v := range nameCheck {
+		if v.Exist {
+			br.Msg = "指标名称重复"
+			br.Data = nameCheck
+			br.Ret = 200
+			br.Success = true
+			return
+		}
+	}
+
+	for _, v := range req {
+		var fenWeiIndexAddReq data_manage.FenWeiIndexAddReq
+		fenWeiIndexAddReq.EdbCode = v.EdbCode
+		fenWeiIndexAddReq.EdbName = v.EdbName
+		fenWeiIndexAddReq.Frequency = v.Frequency
+		fenWeiIndexAddReq.Unit = v.Unit
+		fenWeiIndexAddReq.ClassifyId = v.ClassifyId
+		fenWeiIndexAddReq.AdminId = sysUser.AdminId
+		fenWeiIndexAddReq.AdminRealName = sysUser.RealName
+
+		// 新增指标到指标库
+		edbInfo, e, errMsg, skip := data.FenWeiIndexAdd(fenWeiIndexAddReq, this.Lang)
+		if e != nil {
+			br.Msg = "操作失败"
+			if errMsg != "" {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = e.Error()
+			return
+		}
+		if skip {
+			continue
+		}
+
+		// todo 下面两段代码能否抽离出来???
+		// 试用平台更新用户累计新增指标数
+		if utils.BusinessCode == utils.BusinessCodeSandbox {
+			go func() {
+				adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+				if e != nil {
+					tips := fmt.Sprintf("试用平台更新用户累计新增指标数-获取用户失败, Err: " + e.Error())
+					utils.FileLog.Info(tips)
+					return
+				}
+				if adminItem.DepartmentName != "ETA试用客户" {
+					return
+				}
+				var ur etaTrialService.EtaTrialUserReq
+				ur.Mobile = adminItem.Mobile
+				_, _ = etaTrialService.UpdateUserIndexNum(ur)
+			}()
+		}
+
+		// 新增操作日志
+		{
+			edbLog := new(data_manage.EdbInfoLog)
+			edbLog.EdbInfoId = edbInfo.EdbInfoId
+			edbLog.SourceName = edbInfo.SourceName
+			edbLog.Source = edbInfo.Source
+			edbLog.EdbCode = edbInfo.EdbCode
+			edbLog.EdbName = edbInfo.EdbName
+			edbLog.ClassifyId = edbInfo.ClassifyId
+			edbLog.SysUserId = sysUser.AdminId
+			edbLog.SysUserRealName = sysUser.RealName
+			edbLog.CreateTime = time.Now()
+			edbLog.Content = string(this.Ctx.Input.RequestBody)
+			edbLog.Status = "新增指标"
+			edbLog.Method = this.Ctx.Input.URI()
+			go data_manage.AddEdbInfoLog(edbLog)
+		}
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// FenWeiIndexDataExport
+// @Title 导出指标数据
+// @Description 导出指标数据
+// @Param  IndexCode     query   string     false        "指标编码"
+// @Param  ClassifyId     query   int     false        "分类ID"
+// @Success 200 string "操作成功"
+// @router /fenwei/index/data/export [get]
+func (this *EdbInfoController) FenWeiIndexDataExport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	classifyId, _ := this.GetInt("ClassifyId") //分类
+	indexCode := this.GetString("IndexCode")   //指标唯一编码
+
+	if classifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	//userId := sysUser.AdminId
+	//超管账号可以查看分类下的所有频度数据
+	/*if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
+		userId = 0
+	}*/
+	//获取账户所拥有权限的分类id集合
+	/*classifyIdStrList, err := data.GetEdbClassifyListByAdminId(int64(userId))
+	if err != nil {
+		br.Msg = "获取分类数据失败"
+		return
+	}*/
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+
+	downLoadFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+	frequencies, err := data_manage.GetFenWeiIndexFrequency(classifyId)
+	if err != nil {
+		br.Msg = "查询频度失败"
+		br.ErrMsg = "查询频度失败"
+		return
+	}
+
+	fileName := `汾渭网页数据`
+	if classifyId > 0 && indexCode == "" {
+		fenWeiClassify, err := data_manage.GetFenweiClassifyItemByClassifyId(classifyId)
+		if err != nil {
+			return
+		}
+		fileName = fenWeiClassify.ClassifyName
+	}
+	if frequencies == nil {
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+	}
+
+	for _, frequency := range frequencies {
+		fenWeiIndices, err := data_manage.GetFenWeiIndexByCodeAndClassify(indexCode, classifyId, frequency)
+		if err != nil {
+			return
+		}
+		var sheet *xlsx.Sheet
+		if len(fenWeiIndices) > 0 {
+			sheetName := *frequency
+			if sheetName == "" {
+				sheetName = "无频度"
+			}
+			sheet, err = xlsxFile.AddSheet(sheetName)
+			if err != nil {
+				br.Msg = "新增Sheet失败"
+				br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+				return
+			}
+		} else {
+			continue
+		}
+
+		if indexCode != "" {
+			fileName = fenWeiIndices[0].IndexName
+		}
+
+		//获取指标数据
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("指标名称")
+		rowFrequency := sheet.AddRow()
+		celFrequency := rowFrequency.AddCell()
+		celFrequency.SetValue("频率")
+		rowUnit := sheet.AddRow()
+		celUnit := rowUnit.AddCell()
+		celUnit.SetValue("单位")
+		rowModifyDate := sheet.AddRow()
+		rowModifyCell := rowModifyDate.AddCell()
+		rowModifyCell.SetValue("更新时间")
+
+		dataMap := make(map[string]map[string]*data_manage.BaseFromFenweiData)
+		var tradeCodeList []string
+		for _, v := range fenWeiIndices {
+			cellSenName := rowSecName.AddCell()
+			cellSenName.SetValue(v.IndexName)
+			celFrequency := rowFrequency.AddCell()
+			celFrequency.SetValue(v.Frequency)
+			celUnit := rowUnit.AddCell()
+			celUnit.SetValue(v.Unit)
+			rowModifyCell := rowModifyDate.AddCell()
+			updateTimeStr := utils.TimeToStr(v.ModifyTime, utils.FormatDate)
+			rowModifyCell.SetValue(updateTimeStr)
+			tradeCodeList = append(tradeCodeList, v.IndexCode)
+
+			var dataList []*data_manage.BaseFromFenweiData
+			dataList, err = data_manage.GetBaseFromFenWeiDataByIndexCode(v.IndexCode)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.ErrMsg = "GetBaseFromFenWeiDataByIndexCode,Err:" + err.Error()
+				br.Msg = "获取数据失败"
+				return
+			}
+			for _, item := range dataList {
+				if dataMap[item.IndexCode] == nil {
+					dataMap[item.IndexCode] = make(map[string]*data_manage.BaseFromFenweiData)
+				}
+				dataMap[item.IndexCode][item.DataTime] = item
+			}
+		}
+
+		tradeCodeStr := strings.Join(tradeCodeList, "','")
+		tradeCodeStr = "'" + tradeCodeStr + "'"
+		dataTimeList, err := data_manage.GetFenWeiDataListByIndexCodes(tradeCodeStr)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		for _, dataTime := range dataTimeList {
+			rowData := sheet.AddRow()
+			celDate := rowData.AddCell()
+			celDate.SetValue(dataTime)
+
+			for _, m := range fenWeiIndices {
+				celData := rowData.AddCell()
+				if dataMap[m.IndexCode][dataTime] != nil {
+					celData.SetValue(dataMap[m.IndexCode][dataTime].Value)
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadFilePath)
+	if err != nil {
+		//有指标无数据时先导出一遍空表
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+		err = xlsxFile.Save(downLoadFilePath)
+		if err != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+
+	fileName += time.Now().Format("06.01.02") + `.xlsx` //文件名称
+	this.Ctx.Output.Download(downLoadFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}
+
+// GetFenWeiIndexInfo
+// @Title 添加指标-根据条件获取指标信息
+// @Description 添加指标-根据条件获取指标信息
+// @Param   KeyWord   query   string  false       "关键字"
+// @Param   ClassifyIds   query   string  false       "分类id"
+// @Param   Frequencies   query   string  false       "频率"
+// @Param   PageSize   query   int  false       "每页数据条数"
+// @Param   CurrentIndex   query   int  false       "当前页页码,从1开始"
+// @Success 200 {object} data_manage.BaseFromFenWeiIndexPage
+// @router /fenwei/get/index/info [get]
+func (this *EdbInfoController) GetFenWeiIndexInfo() {
+	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")
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}*/
+
+	keyWord := this.GetString("KeyWord")
+	classifyIds := this.GetString("ClassifyIds")
+	frequencies := this.GetString("Frequencies")
+
+	var classifyIdList []string
+	var frequencyList []string
+	if classifyIds != "" {
+		classifyIdList = strings.Split(classifyIds, ",")
+	}
+	if frequencies != "" {
+		frequencyList = strings.Split(frequencies, ",")
+	}
+	indexInfoList, err := data.GetFenWeiIndexInfo(keyWord, classifyIdList, frequencyList)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexInfoList
+}

+ 74 - 2
controllers/data_manage/manual_edb.go

@@ -102,6 +102,11 @@ func (c *ManualEdbController) EdbDetail() {
 			br.ErrMsg = "获取明细数据失败,err:" + err.Error()
 			return
 		}
+		// 获取下期时间 -- 不用了,由前端处理
+		/*dataNextDateTime, err := fillDataNextDateTime(dataList)
+		if err != nil {
+			return
+		}*/
 
 		manualEdbInfo.DataList = dataList
 	}
@@ -117,6 +122,45 @@ func (c *ManualEdbController) EdbDetail() {
 	br.Data = resp
 }
 
+// 封装指标数据下期时间
+func fillDataNextDateTime(list []*models.Edbdata) ([]*models.Edbdata, error) {
+	if len(list) == 0 {
+		return nil, nil
+	}
+	nextDataDateTime := models.Edbdata{}
+	nextDataDateTime.TradeCode = list[0].TradeCode
+	indexCodes := []string{list[0].TradeCode}
+	edbinfoList, err := models.GetEdbinfoListByCodeListByCodeIdList(indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	if len(edbinfoList) == 0 {
+		return list, nil
+	}
+
+	date := utils.StrDateToDate(list[0].Dt)
+	frequency := edbinfoList[0].Frequency
+	switch frequency {
+	case "日度":
+		nextDataDateTime.Dt = date.AddDate(0, 0, 1).Format(utils.FormatDate)
+	case "周度":
+		nextDataDateTime.Dt = date.AddDate(0, 0, 7).Format(utils.FormatDate)
+	case "旬度":
+		nextDataDateTime.Dt = date.AddDate(0, 0, 10).Format(utils.FormatDate)
+	case "月度":
+		nextDataDateTime.Dt = date.AddDate(0, 1, 0).Format(utils.FormatDate)
+	case "季度":
+		nextDataDateTime.Dt = date.AddDate(0, 3, 0).Format(utils.FormatDate)
+	case "半年度":
+		nextDataDateTime.Dt = date.AddDate(0, 6, 0).Format(utils.FormatDate)
+	case "年度":
+		nextDataDateTime.Dt = date.AddDate(1, 0, 0).Format(utils.FormatDate)
+	}
+	// nextDataDateTime 添加到list第一个
+	list = append([]*models.Edbdata{&nextDataDateTime}, list...)
+	return list, nil
+}
+
 // ClassifyEdbList
 // @Title 分类指标列表
 // @Description 指标列表
@@ -317,6 +361,8 @@ func (c *ManualEdbController) EdbList() {
 
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
+	orderField := c.GetString(`OrderField`)
+	orderType := c.GetString("OrderType")
 
 	var startSize int
 	if pageSize <= 0 {
@@ -416,12 +462,14 @@ func (c *ManualEdbController) EdbList() {
 		return
 	}
 
-	list, err := models.GetEdbInfoList(condition, pars, startSize, pageSize)
+	list, err := models.GetEdbInfoSortList(condition, pars, startSize, pageSize, orderField, orderType)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		return
 	}
+	// 获取预测时间
+	fillNextDateTime(list)
 
 	resp := models.EdbListResp{
 		List:   list,
@@ -433,6 +481,29 @@ func (c *ManualEdbController) EdbList() {
 	br.Data = resp
 }
 
+// 封装指标列表下期时间
+func fillNextDateTime(list []*models.EdbInfoListItem) {
+	for _, item := range list {
+		date := utils.StrDateToDate(item.EndDate)
+		switch item.Frequency {
+		case "日度":
+			item.NextDateTime = date.AddDate(0, 0, 1).Format(utils.FormatDate)
+		case "周度":
+			item.NextDateTime = date.AddDate(0, 0, 7).Format(utils.FormatDate)
+		case "旬度":
+			item.NextDateTime = date.AddDate(0, 0, 10).Format(utils.FormatDate)
+		case "月度":
+			item.NextDateTime = date.AddDate(0, 1, 0).Format(utils.FormatDate)
+		case "季度":
+			item.NextDateTime = date.AddDate(0, 3, 0).Format(utils.FormatDate)
+		case "半年度":
+			item.NextDateTime = date.AddDate(0, 6, 0).Format(utils.FormatDate)
+		case "年度":
+			item.NextDateTime = date.AddDate(1, 0, 0).Format(utils.FormatDate)
+		}
+	}
+}
+
 // EditExcelData
 // @Title 根据excel的样式去编辑指标
 // @Description 根据excel的样式去编辑指标
@@ -1454,7 +1525,7 @@ func (c *ManualEdbController) ImportData() {
 		defer os.Remove(path)
 	}
 
-	successCount, failCount, err, errMsg := data.ImportManualData(path, sysUser)
+	successCount, failCount, indexCount, err, errMsg := data.ImportManualData(path, sysUser)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()
@@ -1464,6 +1535,7 @@ func (c *ManualEdbController) ImportData() {
 	resp := models.EdbdataImportResp{
 		SuccessCount: successCount,
 		FailCount:    failCount,
+		IndexCount:   indexCount,
 	}
 	if failCount > 0 {
 		if successCount == 0 {

+ 1 - 101
controllers/data_manage/mysteel_chemical_data.go

@@ -1630,7 +1630,7 @@ func (this *EdbInfoController) Add() {
 	adminItem, e := system.GetSysAdminById(sysUser.AdminId)
 	if e != nil {
 		br.Msg = "操作失败"
-		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error()
+		br.ErrMsg = "获取系统用户数据失败,Err:" + e.Error()
 		return
 	}
 	if utils.BusinessCode == utils.BusinessCodeSandbox && adminItem.DepartmentName == "ETA试用客户" {
@@ -1677,106 +1677,6 @@ func (this *EdbInfoController) Add() {
 	br.IsAddLog = true
 }
 
-// AddCheck
-// @Title 新增指标检测接口
-// @Description 新增指标检测接口
-// @Param   EdbCode   query   string  true      "指标编码/指标代码"
-// @Success Ret=200 保存成功
-// @router /mysteel_chemical/edb_info/add_check [get]
-// func (this *EdbInfoController) AddCheck() {
-// 	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
-// 	}
-// var req []*data_manage.AddCheckEdbInfoReq
-// 	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
-// 		br.Msg = "参数解析异常!"
-// 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-// 		return
-// 	}
-
-// 	source := utils.DATA_SOURCE_MYSTEEL_CHEMICAL
-// 	indexNameMap := make(map[string]string)
-// 	indexNames := make([]string, 0)
-// 	for _, v := range req {
-// 		v.EdbCode = strings.Trim(v.EdbCode, "\t")
-// 		v.EdbCode = strings.Trim(v.EdbCode, " ")
-// 		v.EdbCode = strings.Replace(v.EdbCode, "\t", "", -1)
-// 		if v.EdbCode == "" {
-// 			br.Msg = "请输入指标ID"
-// 			return
-// 		}
-// 		v.EdbName = strings.Trim(v.EdbName, "\t")
-// 		v.EdbName = strings.Trim(v.EdbName, " ")
-// 		v.EdbName = strings.Replace(v.EdbName, "\t", "", -1)
-// 		if v.EdbName == "" {
-// 			br.Msg = "请输入指标名称"
-// 			return
-// 		}
-// 		indexNames = append(indexNames, v.EdbName)
-
-// 		item, err := data_manage.GetEdbInfoByEdbCode(source, edbCode)
-// 		if err != nil && err.Error() != utils.ErrNoRow() {
-// 			br.Msg = "获取失败"
-// 			br.ErrMsg = "获取失败,Err:" + err.Error()
-// 			return
-// 		}
-
-// 		resp := new(data_manage.EdbInfoMySteelChemicalCheckResp)
-// 		if item != nil && item.EdbInfoId > 0 {
-// 			resp.Status = 1
-// 			// 查询该指标是否有权限
-// 			obj := data_manage.EdbInfoNoPermissionAdmin{}
-// 			conf, err := obj.GetByEdbInfoIdAndAdminId(this.SysUser.AdminId, item.EdbInfoId)
-// 			if err != nil && err.Error() != utils.ErrNoRow() {
-// 				br.Msg = "获取失败"
-// 				br.ErrMsg = "获取当前账号的不可见指标配置失败,err:" + err.Error()
-// 				return
-// 			}
-// 			if conf != nil {
-// 				resp.Status = 3
-// 			}
-// 		} else {
-// 			resp.Status = 2
-// 			dataItems, err := data_manage.GetEdbDataAllByEdbCode(edbCode, source, 0, utils.EDB_DATA_LIMIT)
-// 			if err != nil && err.Error() != utils.ErrNoRow() {
-// 				br.Msg = "获取失败"
-// 				br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
-// 				return
-// 			}
-
-// 			if len(dataItems) <= 0 {
-// 				respItem, err := data.AddEdbData(source, edbCode, frequency)
-// 				if err != nil {
-// 					br.Msg = "获取失败"
-// 					br.ErrMsg = "获取失败,Err:" + err.Error()
-// 					return
-// 				}
-// 				if respItem.Ret != 200 {
-// 					br.Msg = "未搜索到该指标"
-// 					br.ErrMsg = respItem.ErrMsg + ";EdbCode:" + edbCode
-// 					return
-// 				}
-// 			}
-// 		}
-// 	}
-
-// 	br.Ret = 200
-// 	br.Success = true
-// 	br.Msg = "保存成功"
-// 	br.Data = resp
-// 	br.IsAddLog = true
-// }
-
 // AddCheck
 // @Title 新增校验
 // @Description 新增校验

+ 832 - 0
controllers/data_manage/range_analysis/chart_classify.go

@@ -0,0 +1,832 @@
+package range_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"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"
+)
+
+// RangeChartClassifyController 	区间分析图表
+type RangeChartClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// ChartClassifyList
+// @Title 区间分析图表分类列表
+// @Description 区间分析图表分类列表接口
+// @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 *RangeChartClassifyController) 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")
+	parentId, _ := this.GetInt("ParentId")
+	source, _ := this.GetInt("Source", utils.CHART_SOURCE_RANGE_ANALYSIS)
+
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	// 查询分类节点
+	rootList, err := data_manage.GetChartClassifyByParentId(parentId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	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)
+		}
+	}
+
+	// 查询图表节点, ParentId=0时说明仅查询一级目录节点
+	if parentId > 0 {
+		// 查询当前分类信息
+		currClassify, e := data_manage.GetChartClassifyById(parentId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取当前分类信息失败,Err:" + e.Error()
+			return
+		}
+
+		// 获取所有有权限的指标和分类
+		permissionEdbIdList, permissionClassifyIdList, e := data_manage_permission.GetUserChartAndClassifyPermissionList(this.SysUser.AdminId, 0, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + e.Error()
+			return
+		}
+
+		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)
+		}
+	}
+
+	// 整体排序
+	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 = "获取成功"
+	br.Data = resp
+}
+
+// getChartClassifyListForMe 获取我创建的图表
+func getChartClassifyListForMe(adminInfo system.Admin, resp *data_manage.ChartClassifyListResp) (errMsg string, err error) {
+	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	classifyAll, err := data_manage.GetChartClassifyAll(utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	allChartInfo, err := data_manage.GetChartInfoByAdminId([]int{utils.CHART_SOURCE_RANGE_ANALYSIS, utils.CHART_SOURCE_RANGE_ANALYSIS}, adminInfo.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	chartInfoMap := make(map[int][]*data_manage.ChartClassifyItems)
+	for _, v := range allChartInfo {
+		chartInfoMap[v.ChartClassifyId] = append(chartInfoMap[v.ChartClassifyId], v)
+	}
+	rootChildMap := make(map[int][]*data_manage.ChartClassifyItems)
+	for _, v := range classifyAll {
+		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
+		}
+	}
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	for _, v := range rootList {
+		if existItems, ok := rootChildMap[v.ChartClassifyId]; ok {
+			v.Children = existItems
+		} else {
+			items := make([]*data_manage.ChartClassifyItems, 0)
+			v.Children = items
+		}
+		nodeAll = append(nodeAll, v)
+	}
+	resp.AllNodes = nodeAll
+
+	return
+}
+
+// ChartClassifyItems
+// @Title 获取所有区间分析图表分类接口-不包含图表
+// @Description 获取所有区间分析图表分类接口-不包含图表
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /chart_classify/items [get]
+func (this *RangeChartClassifyController) ChartClassifyItems() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		nodeAll = append(nodeAll, rootNode)
+	}
+	resp := new(data_manage.ChartClassifyListResp)
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// AddChartClassify
+// @Title 新增区间分析图表分类
+// @Description 新增区间分析图表分类接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chart_classify/add [post]
+func (this *RangeChartClassifyController) AddChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.AddChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 新增图表分类
+	_, err, errMsg, isSendEmail := data.AddChartClassify(req.ChartClassifyName, req.ParentId, req.Level, utils.CHART_SOURCE_RANGE_ANALYSIS, this.Lang, this.SysUser)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "添加分类失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "添加成功"
+	br.Success = true
+}
+
+// EditChartClassify
+// @Title 修改区间分析图表分类
+// @Description 修改区间分析图表分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /chart_classify/edit [post]
+func (this *RangeChartClassifyController) EditChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.EditChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 编辑图表分类
+	_, err, errMsg, isSendEmail := data.EditChartClassify(req.ChartClassifyId, utils.CHART_SOURCE_RANGE_ANALYSIS, req.ChartClassifyName, this.Lang, this.SysUser)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "修改成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// DeleteChartClassifyCheck
+// @Title 删除图表检测接口
+// @Description 删除图表检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /chart_classify/delete/check [post]
+func (this *RangeChartClassifyController) DeleteChartClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.ChartClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartClassifyId < 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+	//删除分类
+	if req.ChartClassifyId > 0 && req.ChartInfoId == 0 {
+		//判断区间分析图表分类下,是否含有图表
+		count, err := data_manage.GetChartInfoCountByClassifyId(req.ChartClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联图表不可删除"
+		}
+	}
+
+	if deleteStatus != 1 && req.ChartInfoId == 0 {
+		classifyCount, err := data_manage.GetChartClassifyCountByClassifyId(req.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+		if classifyCount > 0 {
+			deleteStatus = 2
+			tipsMsg = "确认删除当前目录及包含的子目录吗"
+		}
+	}
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+
+	resp := new(data_manage.ChartClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// DeleteChartClassify
+// @Title 删除区间分析图表分类/图表
+// @Description 删除区间分析图表分类/图表接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /chart_classify/delete [post]
+func (this *RangeChartClassifyController) DeleteChartClassify() {
+	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
+	}
+
+	var req data_manage.DeleteChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartClassifyId < 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	//删除分类
+	if req.ChartClassifyId > 0 && req.ChartInfoId == 0 {
+		//判断是否含有指标
+		count, err := data_manage.GetChartInfoCountByClassifyId(req.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			br.Msg = "该目录下存在关联指标,不可删除"
+			br.IsSendEmail = false
+			return
+		}
+
+		err = data_manage.DeleteChartClassify(req.ChartClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+	resp := new(data_manage.AddChartInfoResp)
+	//删除图表
+	if req.ChartInfoId > 0 {
+		chartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "图表已删除,请刷新页面"
+				br.ErrMsg = "指标不存在,Err:" + err.Error()
+				return
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,获取指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+		if chartInfo == nil {
+			br.Msg = "图表已删除,请刷新页面"
+			return
+		}
+		//图表操作权限
+		ok := data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+		if !ok {
+			br.Msg = "没有该图表的操作权限"
+			br.ErrMsg = "没有该图表的操作权限"
+			return
+		}
+
+		// 获取引用该图表的MyCharts, 用于ES删除
+		var myCond string
+		var myPars []interface{}
+		myCond += ` AND a.chart_info_id = ? `
+		myPars = append(myPars, chartInfo.ChartInfoId)
+		myCharts, e := data_manage.GetMyChartListGroupByCharyInfoIdAndAdminIdByCondition(myCond, myPars)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取引用图表的MyChats失败, Err: " + e.Error()
+			return
+		}
+		myIds := make([]int, 0)
+		for _, m := range myCharts {
+			myIds = append(myIds, m.MyChartId)
+		}
+
+		source := chartInfo.Source // 区间分析图表(滚动相关性)
+		//删除图表及关联指标
+		err = data_manage.DeleteChartInfoAndData(chartInfo.ChartInfoId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+
+		// 删除图表系列
+		chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+		seriesMappingItem, e := chartSeriesOb.GetItemByChartInfoId(chartInfo.ChartInfoId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取图表关联失败, Err: " + e.Error()
+				return
+			}
+		} else {
+			factorSeriesOb := new(data_manage.FactorEdbSeries)
+			e = factorSeriesOb.RemoveSeriesAndMappingByFactorEdbSeriesId(seriesMappingItem)
+			if e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取图表关联失败, Err: " + e.Error()
+				return
+			}
+		}
+		//删除ES
+		{
+			go data.EsDeleteChartInfo(chartInfo.ChartInfoId)
+			// 删除MY ETA 图表 es数据
+			//go data.EsDeleteMyChartInfoByChartInfoId(chartInfo.ChartInfoId)
+			go data.EsDeleteMyChartInfoByMyChartIds(myIds)
+		}
+		// 删除配置关联指标数据
+		multiConfig, e := data_manage.GetMultipleGraphConfigChartMappingByChartId(chartInfo.ChartInfoId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取图表关联配置失败, Err: " + e.Error()
+			return
+		}
+		if multiConfig != nil { // 删除配置关联指标数据
+			e = data_manage.DeleteMultipleGraphConfigByChartInfoId(chartInfo.ChartInfoId, multiConfig.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+			if e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除图表关联配置失败, Err: " + e.Error()
+				return
+			}
+		}
+
+		var condition string
+		var pars []interface{}
+		condition += " AND chart_classify_id=? AND source = ? "
+		pars = append(pars, chartInfo.ChartClassifyId, source)
+
+		condition += " AND chart_info_id>? ORDER BY create_time ASC LIMIT 1 "
+		pars = append(pars, req.ChartInfoId)
+
+		nextItem, err := data_manage.GetChartInfoByCondition(condition, pars)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
+			return
+		}
+
+		if nextItem != nil {
+			resp.UniqueCode = nextItem.UniqueCode
+			resp.ChartInfoId = nextItem.ChartInfoId
+		} else {
+			var condition string
+			var pars []interface{}
+
+			condition += " AND level=1 "
+			//pars = append(pars, chartInfo.ChartClassifyId)
+
+			condition += " AND chart_classify_id>? ORDER BY chart_classify_id ASC LIMIT 1 "
+			pars = append(pars, chartInfo.ChartClassifyId)
+
+			classifyItem, err := data_manage.GetChartClassifyByCondition(condition, pars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取下一级图库分类信息失败,Err:" + err.Error()
+				return
+			}
+			if classifyItem != nil {
+				nextItem, err = data_manage.GetNextChartInfo(chartInfo.ChartClassifyId)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
+					return
+				}
+				if nextItem != nil {
+					resp.UniqueCode = nextItem.UniqueCode
+					resp.ChartInfoId = nextItem.ChartInfoId
+				}
+			}
+		}
+		//新增操作日志
+		{
+			chartLog := new(data_manage.ChartInfoLog)
+			chartLog.ChartName = chartInfo.ChartName
+			chartLog.ChartInfoId = req.ChartInfoId
+			chartLog.ChartClassifyId = chartInfo.ChartClassifyId
+			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)
+		}
+	}
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// ChartClassifyMove
+// @Title 区间分析图表分类移动接口
+// @Description 区间分析图表分类移动接口
+// @Success 200 {object} data_manage.MoveChartClassifyReq
+// @router /chart_classify/move [post]
+func (this *RangeChartClassifyController) 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 = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.MoveChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "请选择拖动目标,分类目录或者指标"
+		return
+	}
+
+	err, errMsg := data.MoveChartClassify(req, sysUser, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		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_RANGE_ANALYSIS {
+	//	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 = "移动成功"
+}
+
+// ClassifyTree
+// @Title 多层分类列表树
+// @Description 多层分类列表树
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /chart_classify/tree [get]
+func (this *RangeChartClassifyController) ClassifyTree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	allList, err := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取所有分类失败, Err:" + err.Error()
+		return
+	}
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+
+	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
+		}
+
+		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
+		}
+
+		nodeAll = data.GetChartClassifyTreeRecursive(allList, 0)
+		//根据sort值排序
+		sort.Slice(nodeAll, func(i, j int) bool {
+			return nodeAll[i].Sort < nodeAll[j].Sort
+		})
+	}
+
+	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.Data = resp
+}

+ 2101 - 0
controllers/data_manage/range_analysis/chart_info.go

@@ -0,0 +1,2101 @@
+package range_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/chart_theme"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/data/data_manage_permission"
+	rangeServ "eta/eta_api/services/data/range_analysis"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// RangeChartChartInfoController 区间计算图表管理
+type RangeChartChartInfoController struct {
+	controllers.BaseAuthController
+}
+
+// Preview
+// @Title 区间计算图表-预览数据
+// @Description 区间计算图表-获取预览数据
+// @Param   StartDate	query	string	true	"自定义开始日期"
+// @Param   EndDate		query	string	true	"自定义结束日期"
+// @Param   LeadValue	query	int		true	"领先值"
+// @Param   LeadUnit	query	string	true	"领先单位:年,天,月,季,周"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview [post]
+func (this *RangeChartChartInfoController) Preview() {
+	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
+	}
+	var req data_manage.ChartRangeAnalysisPreviewReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	// 基础校验
+	if len(req.ChartEdbInfoList) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	err, msg, isSendEmail := rangeServ.CheckChartRangeExtraConfig(req.ExtraConfig)
+	if err != nil {
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 获取指标信息
+	//chartInfo.CorrelationLeadUnit = req.LeadUnit
+	edbInfoMappingList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoMapping, e := data_manage.GetChartEdbMappingByEdbInfoId(v.EdbInfoId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = v.EdbAliasName + "指标不存在"
+				br.ErrMsg = v.EdbAliasName + "指标不存在"
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取区间计算图表, A指标mapping信息失败, Err:" + e.Error()
+			return
+		}
+		if v.EdbAliasName != "" {
+			edbInfoMapping.EdbAliasName = v.EdbAliasName
+		}
+		edbInfoMapping.IsAxis = v.IsAxis
+		edbInfoMappingList = append(edbInfoMappingList, edbInfoMapping)
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+	chartInfo.ChartType = 1
+	chartInfo.Source = utils.CHART_SOURCE_RANGE_ANALYSIS
+	chartThemeType, err := chart_theme.GetChartThemeTypeByChartTypeAndSource(chartInfo.ChartType, 1)
+	if err != nil {
+		return
+	}
+	chartTheme, err := data.GetChartThemeConfig(chartThemeType.DefaultChartThemeId, utils.CHART_SOURCE_DEFAULT, chartInfo.ChartType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	// 获取图表x轴y轴
+	edbList, xEdbIdValue, dataResp, e := rangeServ.GetChartDataByEdbInfoList(0, req.DateType, req.StartYear, req.StartDate, req.EndDate, edbInfoMappingList, &req.ExtraConfig)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取区间计算图表, 图表计算值失败, Err:" + e.Error()
+		return
+	}
+	//添加配置信息
+	if req.ExtraConfig.MultipleGraphConfigId == 0 {
+		multipleGraphConfig := &data_manage.MultipleGraphConfig{
+			//MultipleGraphConfigId: 0,
+			SysUserId:       sysUser.AdminId,
+			SysUserRealName: sysUser.RealName,
+			ModifyTime:      time.Now(),
+			CreateTime:      time.Now(),
+		}
+		err = data_manage.AddMultipleGraphConfig(multipleGraphConfig)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "新增区间计算图表配置失败, Err: " + err.Error()
+			return
+		}
+		dataResp.MultipleGraphConfigId = multipleGraphConfig.MultipleGraphConfigId
+	}
+	// 图表的指标来源
+	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.DataResp = dataResp
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增图表接口
+// @Description 新增图表接口
+// @Param	request	body data_manage.AddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/add [post]
+func (this *RangeChartChartInfoController) Add() {
+	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
+	}
+	deleteCache := true
+	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req data_manage.AddChartInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.DateType == 0 {
+		req.DateType = 3
+	}
+	chartInfo, err, errMsg, isSendEmail := rangeServ.AddChartInfo(req, utils.CHART_SOURCE_RANGE_ANALYSIS, sysUser, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ChartClassifyId
+		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
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// Edit
+// @Title 编辑图表接口
+// @Description 编辑图表接口
+// @Param	request	body data_manage.EditChartInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /chart_info/edit [post]
+func (this *RangeChartChartInfoController) Edit() {
+	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
+	}
+
+	var req data_manage.EditChartInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartItem, err, errMsg, isSendEmail := rangeServ.EditChartInfo(req, sysUser, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartItem.ChartInfoId
+	resp.UniqueCode = chartItem.UniqueCode
+	resp.ChartType = req.ChartType
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑区间计算图表"
+		chartLog.Method = this.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// Detail
+// @Title 获取图表详情
+// @Description 获取图表详情接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   DateType   query   int  true       "日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"
+// @Param   StartDate   query   string  true       "自定义开始日期"
+// @Param   EndDate   query   string  true       "自定义结束日期"
+// @Param   Calendar   query   string  true       "公历/农历"
+// @Param   SeasonStartDate   query   string  true       "季节性图开始日期"
+// @Param   SeasonEndDate   query   string  true       "季节性图结束日期"
+// @Param   EdbInfoId   query   string  true       "指标ID,多个用英文逗号隔开"
+// @Param   ChartType   query   int  true       "生成样式:1:曲线图,2:季节性图"
+// @Success 200 {object} data_manage.ChartInfoDetailResp
+// @router /chart_info/detail [get]
+func (this *RangeChartChartInfoController) Detail() {
+	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
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	if chartInfoId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	dateType, _ := this.GetInt("DateType")
+	fmt.Println("dateType:", dateType)
+
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	startYear, _ := this.GetInt("StartYear")
+
+	var err error
+	chartInfo := new(data_manage.ChartInfoView)
+	chartInfo, err = data_manage.GetChartInfoViewById(chartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图被删除,请刷新页面"
+			br.ErrMsg = "图被删除,请刷新页面,Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+	if dateType <= 0 {
+		dateType = chartInfo.DateType
+	}
+	if startDate == "" {
+		startDate = chartInfo.StartDate
+	}
+	if endDate == "" {
+		endDate = chartInfo.EndDate
+	}
+
+	if startYear <= 0 {
+		startYear = chartInfo.StartYear
+	}
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, chartInfo.ChartType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
+	// 获取指标信息
+	//chartInfo.CorrelationLeadUnit = req.LeadUnit
+	edbInfoMappingList, err := data_manage.GetChartEdbMappingList(chartInfoId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	// 获取图表数据
+	if len(edbInfoMappingList) == 0 {
+		br.Msg = "获取失败"
+		br.ErrMsg = "图表没有指标,无法计算"
+		return
+	}
+
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if chartInfo.ExtraConfig == `` {
+		br.Msg = "配置信息错误"
+		return
+	}
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		br.Msg = "配置信息错误"
+		br.ErrMsg = "图表配置信息错误,Err:" + err.Error()
+		return
+	}
+
+	// 获取图表x轴y轴
+	edbList, xEdbIdValue, dataResp, e := rangeServ.GetChartDataByEdbInfoList(chartInfoId, dateType, startYear, startDate, endDate, edbInfoMappingList, &extraConfig)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取区间计算图表, 图表计算值失败, Err:" + e.Error()
+		return
+	}
+
+	// 判断是否加入我的图库
+	if chartInfoId > 0 && chartInfo != nil {
+		{
+			var myChartCondition string
+			var myChartPars []interface{}
+			myChartCondition += ` AND a.admin_id=? `
+			myChartPars = append(myChartPars, sysUser.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() {
+				br.Msg = "获取失败"
+				br.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
+			}
+		}
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    chartInfo.IsEdit,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	// 指标权限
+	{
+		edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
+		classifyMap := make(map[int]*data_manage.EdbClassify)
+		// 分类
+		{
+			classifyIdList := make([]int, 0)
+			for _, v := range edbList {
+				classifyIdList = append(classifyIdList, v.ClassifyId)
+			}
+			classifyList, tmpErr := data_manage.GetEdbClassifyByIdList(classifyIdList)
+			if tmpErr != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类列表失败,Err:" + tmpErr.Error()
+				return
+			}
+			for _, v := range classifyList {
+				classifyMap[v.ClassifyId] = v
+			}
+		}
+
+		// 遍历到校验map
+		for _, v := range edbList {
+			edbClassifyPermissionMap[v.EdbInfoId] = data_manage_permission.EdbClassifyPermission{
+				ClassifyId:       v.ClassifyId,
+				IsJoinPermission: v.IsJoinPermission,
+				EdbInfoId:        v.EdbInfoId,
+			}
+		}
+
+		// 获取所有有权限的指标和分类
+		permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserEdbAndClassifyPermissionList(sysUser.AdminId, 0, 0)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+			return
+		}
+
+		for _, v := range edbList {
+			// 数据权限
+			edbItem, ok := edbClassifyPermissionMap[v.EdbInfoId]
+			if !ok {
+				continue
+			}
+
+			if currClassify, ok := classifyMap[edbItem.ClassifyId]; ok {
+				v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(edbItem.IsJoinPermission, currClassify.IsJoinPermission, edbItem.EdbInfoId, edbItem.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
+			}
+		}
+	}
+
+	// 图表当前分类的分类树
+	classifyLevels := make([]string, 0)
+	{
+		list, e := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_RANGE_ANALYSIS)
+		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
+	resp.XEdbIdValue = xEdbIdValue
+	resp.DataResp = dataResp
+	resp.ClassifyLevels = classifyLevels
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// DetailFromUniqueCode
+// @Title 根据编码获取图表详情
+// @Description 根据编码获取图表详情接口
+// @Param   UniqueCode   query   int  true       "图表唯一编码,如果是管理后台访问,传固定字符串:7c69b590249049942070ae9dcd5bf6dc"
+// @Param   IsCache   query   bool  true       "是否走缓存,默认false"
+// @Success 200 {object} data_manage.ChartInfoDetailFromUniqueCodeResp
+// @router /chart_info/detail/from_unique_code [get]
+func (this *RangeChartChartInfoController) DetailFromUniqueCode() {
+	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
+	}
+	adminId := sysUser.AdminId
+
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,uniqueCode is empty"
+		return
+	}
+
+	//是否走缓存
+	isCache, _ := this.GetBool("IsCache")
+
+	resp := new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+	status := true
+	chartInfo, err := data_manage.GetChartInfoViewByUniqueCode(uniqueCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			status = false
+		} else {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+	if chartInfo == nil {
+		status = false
+	}
+	if !status {
+		endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+		resp.EdbInfoList = endInfoList
+		resp.ChartInfo = chartInfo
+		resp.Status = false
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if d, e := utils.Rc.RedisBytes(key); e == nil {
+				err := json.Unmarshal(d, &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() {
+						br.Msg = "获取失败"
+						br.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
+					}
+
+					br.Ret = 200
+					br.Success = true
+					br.Msg = "获取成功"
+					br.Data = resp
+					return
+				}
+			}
+		}
+	}
+	resp, isOk, msg, errMsg := GetChartInfoDetailFromUniqueCode(chartInfo, isCache, sysUser)
+	if !isOk {
+		br.Msg = msg
+		br.ErrMsg = errMsg
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// GetChartInfoDetailFromUniqueCode 根据编码获取图表详情
+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)
+
+	adminId := sysUser.AdminId
+
+	// 指标数据map
+	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
+
+			// 指标权限
+			{
+				classifyMap := make(map[int]*data_manage.EdbClassify)
+				// 分类
+				{
+					classifyIdList := make([]int, 0)
+					for _, v := range resp.EdbInfoList {
+						classifyIdList = append(classifyIdList, v.ClassifyId)
+					}
+					classifyList, tmpErr := data_manage.GetEdbClassifyByIdList(classifyIdList)
+					if tmpErr != nil {
+						errMsg = "获取分类列表失败,Err:" + tmpErr.Error()
+						return
+					}
+					for _, v := range classifyList {
+						classifyMap[v.ClassifyId] = v
+					}
+				}
+
+				// 指标
+				if len(edbClassifyPermissionMap) < 0 {
+					edbInfoIdList := make([]int, 0)
+					for _, v := range resp.EdbInfoList {
+						edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+					}
+					edbInfoList, tmpErr := data_manage.GetEdbInfoByIdList(edbInfoIdList)
+					if tmpErr != nil {
+						errMsg = "获取指标列表失败,Err:" + tmpErr.Error()
+						return
+					}
+					for _, v := range edbInfoList {
+						edbClassifyPermissionMap[v.EdbInfoId] = data_manage_permission.EdbClassifyPermission{
+							ClassifyId:       v.ClassifyId,
+							IsJoinPermission: v.IsJoinPermission,
+							EdbInfoId:        v.EdbInfoId,
+						}
+					}
+				}
+
+				// 获取所有有权限的指标和分类
+				permissionEdbIdList, permissionClassifyIdList, err := data_manage_permission.GetUserEdbAndClassifyPermissionList(sysUser.AdminId, 0, 0)
+				if err != nil {
+					errMsg = "获取所有有权限的指标和分类失败,Err:" + err.Error()
+					return
+				}
+
+				for _, v := range resp.EdbInfoList {
+					// 数据权限
+					edbItem, ok := edbClassifyPermissionMap[v.EdbInfoId]
+					if !ok {
+						continue
+					}
+
+					if currClassify, ok := classifyMap[edbItem.ClassifyId]; ok {
+						v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(edbItem.IsJoinPermission, currClassify.IsJoinPermission, edbItem.EdbInfoId, edbItem.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
+					}
+				}
+			}
+		}
+	}()
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		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 {
+					return
+				}
+				isOk = true
+				fmt.Println("source redis")
+				return
+			}
+		}
+	}
+
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
+	chartInfoId := chartInfo.ChartInfoId
+
+	// 获取指标信息
+	//chartInfo.CorrelationLeadUnit = req.LeadUnit
+	edbInfoMappingList, err := data_manage.GetChartEdbMappingList(chartInfoId)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	dateType := chartInfo.DateType
+	// 开始/结束日期
+	startYear := chartInfo.StartYear
+	startDate := chartInfo.StartDate
+	endDate := chartInfo.EndDate
+
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if chartInfo.ExtraConfig == `` {
+		msg = "配置信息错误"
+		return
+	}
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		msg = "配置信息错误"
+		errMsg = "图表配置信息错误,Err:" + err.Error()
+		return
+	}
+
+	// 获取图表数据
+	if len(edbInfoMappingList) == 0 {
+		msg = "获取失败"
+		errMsg = "图表没有指标,无法计算"
+		return
+	}
+
+	// 获取图表x轴y轴
+	edbList, xEdbIdValue, dataResp, e := rangeServ.GetChartDataByEdbInfoList(chartInfoId, dateType, startYear, startDate, endDate, edbInfoMappingList, &extraConfig)
+	if e != nil {
+		msg = "获取失败"
+		errMsg = "获取区间计算图表, 图表计算值失败, Err:" + e.Error()
+		return
+	}
+
+	if chartInfoId > 0 && chartInfo != nil {
+		//判断是否加入我的图库
+		{
+			var myChartCondition string
+			var myChartPars []interface{}
+			myChartCondition += ` AND a.admin_id=? `
+			myChartPars = append(myChartPars, sysUser.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
+			}
+		}
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+	chartInfo.UnitEn = ""
+
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    chartInfo.IsEdit,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.DataResp = dataResp
+	resp.Status = true
+
+	// 遍历到校验map
+	for _, v := range edbList {
+		edbClassifyPermissionMap[v.EdbInfoId] = data_manage_permission.EdbClassifyPermission{
+			ClassifyId:       v.ClassifyId,
+			IsJoinPermission: v.IsJoinPermission,
+			EdbInfoId:        v.EdbInfoId,
+		}
+	}
+
+	// 将数据加入缓存
+	if utils.Re == nil {
+		d, _ := json.Marshal(resp)
+		_ = utils.Rc.Put(key, d, 2*time.Hour)
+	}
+	isOk = true
+	return
+}
+
+// List
+// @Title 区间计算图表列表接口
+// @Description 区间计算图表列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ChartClassifyId   query   int  true       "分类id"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "图表类型,3:区间计算,4:滚动区间计算"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /chart_info/list [get]
+func (this *RangeChartChartInfoController) List() {
+	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
+	}
+
+	chartClassifyId, _ := this.GetInt("ChartClassifyId")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("KeyWord")
+
+	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)
+
+	source := utils.CHART_SOURCE_RANGE_ANALYSIS
+
+	var condition string
+	var pars []interface{}
+
+	// 普通图表
+	condition += ` AND source = ? `
+	pars = append(pars, source)
+
+	if chartClassifyId > 0 {
+		chartClassifyId, err := data_manage.GetChartClassify(chartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取图表信息失败"
+			br.ErrMsg = "获取信息失败,GetChartClassify,Err:" + err.Error()
+			return
+		}
+		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
+	}
+	if keyword != "" {
+		//将关键词按照空格分割
+		keywords := strings.Split(keyword, " ")
+		condition += ` AND  ( chart_name LIKE '%` + keywords[0] + `%' `
+		for k, key := range keywords {
+			if k == 0 {
+				continue
+			}
+			condition += ` OR chart_name LIKE '%` + key + `%' `
+		}
+		condition += ` )`
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		condition += ` AND sys_user_id = ? `
+		pars = append(pars, sysUser.AdminId)
+	}
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		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 {
+			noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+		}
+	}
+
+	lenNoPermissionChartIdList := len(noPermissionChartIdList)
+	if lenNoPermissionChartIdList > 0 {
+		condition += ` AND chart_info_id not in (` + utils.GetOrmInReplace(lenNoPermissionChartIdList) + `) `
+		pars = append(pars, noPermissionChartIdList)
+	}
+
+	//获取图表信息
+	list, err := data_manage.GetChartListByCondition(condition, pars, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	myChartList, err := data_manage.GetMyChartListByAdminId(sysUser.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取我的图表信息失败,Err:" + err.Error()
+		return
+	}
+	myChartMap := make(map[int]*data_manage.MyChartView)
+	for _, v := range myChartList {
+		myChartMap[v.ChartInfoId] = v
+	}
+	listLen := len(list)
+	chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+	if listLen > 0 {
+		chartInfoIds := ""
+		for _, v := range list {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+	}
+	for i := 0; i < listLen; i++ {
+		//判断是否需要展示英文标识
+		if edbTmpList, ok := chartEdbMap[list[i].ChartInfoId]; ok {
+			list[i].IsEnChart = data.CheckIsEnChart(list[i].ChartNameEn, edbTmpList, list[i].Source, list[i].ChartType)
+		}
+
+		if existItem, ok := myChartMap[list[i].ChartInfoId]; ok {
+			list[i].IsAdd = true
+			list[i].MyChartId = existItem.MyChartId
+			list[i].MyChartClassifyId = existItem.MyChartClassifyId
+		}
+	}
+
+	resp := new(data_manage.ChartListResp)
+	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
+		items := make([]*data_manage.ChartInfoView, 0)
+		resp.Paging = page
+		resp.List = items
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	dataCount, err := data_manage.GetChartListCountByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, dataCount)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Copy
+// @Title 复制并新增图表接口
+// @Description 新增图表接口
+// @Params	request	body data_manage.CopyAddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/copy [post]
+func (this *RangeChartChartInfoController) 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 = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		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() {
+		if deleteCache {
+			utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+
+	chartSource := utils.CHART_SOURCE_RANGE_ANALYSIS
+	// 校验分类、图表名称
+	{
+		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
+		}
+	}
+
+	// 图表信息
+	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 originChart.Source == utils.CHART_SOURCE_ROLLING_CORRELATION {
+		br.Msg = `滚动区间计算图不支持另存为`
+		br.IsSendEmail = false
+		return
+	}
+
+	// 普通区间计算图表
+	chartInfo := new(data_manage.ChartInfo)
+
+	newChart, err, errMsg, isSendEmail := rangeServ.CopyChartInfo(req.ChartClassifyId, req.ChartName, originChart, sysUser, this.Lang)
+	chartInfo = newChart
+
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ChartClassifyId
+		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)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = data_manage.AddChartInfoResp{
+		ChartInfoId: chartInfo.ChartInfoId,
+		UniqueCode:  chartInfo.UniqueCode,
+		ChartType:   chartInfo.ChartType,
+	}
+	br.IsAddLog = true
+}
+
+// Refresh
+// @Title 图表刷新接口
+// @Description 图表刷新接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   UniqueCode   query   string  true       "唯一code"
+// @Success Ret=200 刷新成功
+// @router /chart_info/refresh [get]
+func (this *RangeChartChartInfoController) Refresh() {
+	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
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	uniqueCode := this.GetString("UniqueCode")
+	if chartInfoId <= 0 && uniqueCode == `` {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误:chartInfoId:" + strconv.Itoa(chartInfoId) + ",UniqueCode:" + uniqueCode
+		return
+	}
+
+	var chartInfo *data_manage.ChartInfo
+	var err error
+	if chartInfoId > 0 {
+		chartInfo, err = data_manage.GetChartInfoById(chartInfoId)
+	} else {
+		chartInfo, err = data_manage.GetChartInfoByUniqueCode(uniqueCode)
+	}
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,无需刷新"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		br.Msg = "刷新失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 刷新区间计算图表
+	isAsync, e := rangeServ.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
+	}
+
+	//清除图表缓存
+	{
+		key := utils.HZ_CHART_LIB_DETAIL + chartInfo.UniqueCode
+		_ = utils.Rc.Delete(key)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "刷新成功"
+}
+
+// MultipleGraphConfigSaveEdb
+// @Title 保存指标接口
+// @Description 保存指标接口
+// @Param	request	body request.SaveMultipleGraphEdbReq true "type json string"
+// @Success Ret=200 返回指标id
+// @router /edb/save [post]
+func (this *RangeChartChartInfoController) MultipleGraphConfigSaveEdb() {
+	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
+	}
+
+	var req data_manage.SaveChartRangeAnalysisEdbReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	cacheKey := "CACHE_CHART_EDB_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+	edbInfoIds := make([]int, 0)
+	for _, v := range req.EdbInfoList {
+		if v.EdbInfoId > 0 {
+			edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+		}
+		edbInfoIds = append(edbInfoIds, v.FromEdbInfoId)
+	}
+	edbInfoMappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIds)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoMap := make(map[int]*data_manage.ChartEdbInfoMapping, 0)
+	for _, v := range edbInfoMappingList {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+
+	for _, v := range req.EdbInfoList {
+		if _, ok := edbInfoMap[v.FromEdbInfoId]; !ok {
+			br.Msg = "指标信息不存在"
+			br.ErrMsg = "指标信息不存在,EdbInfoId:" + strconv.Itoa(v.FromEdbInfoId)
+			br.IsSendEmail = false
+			return
+		}
+		if v.EdbInfoId > 0 {
+			if _, ok := edbInfoMap[v.EdbInfoId]; !ok {
+				v.EdbInfoId = 0
+			}
+		}
+	}
+	// 区间计算图表配置校验
+	extraConfig := req.ExtraConfig
+	// 区间计算图表配置校验
+	var config data_manage.ChartRangeAnalysisExtraConf
+	if req.ExtraConfig == `` {
+		br.Msg = "请输入配置信息"
+		return
+	}
+	err = json.Unmarshal([]byte(req.ExtraConfig), &config)
+	if err != nil {
+		br.Msg = "配置信息格式错误"
+		br.ErrMsg = "配置信息格式错误,Err:" + err.Error()
+		return
+	}
+
+	err, errMsg, isSendEmail := rangeServ.CheckChartRangeExtraConfig(config)
+	if err != nil {
+		br.Msg = "配置信息校验失败"
+		br.ErrMsg = errMsg
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	multipleGraphConfigEdbMappingList, err := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "查询配置与图表的关联关系失败,ERR:" + err.Error()
+		return
+	}
+	deleteEdbInfoIds := make([]int, 0) // 需要解除绑定的指标ID
+	deleteEdbInfoIdMap := make(map[int]int, 0)
+	configMapping := make(map[int]*data_manage.MultipleGraphConfigEdbMapping, 0)
+	if len(multipleGraphConfigEdbMappingList) == 0 || req.IsSaveAs {
+		// 需要新增的指标
+	} else {
+		// 需要更新的指标
+		// 查询原先所有指标的来源指标,进一步筛选出需要新增,或者更新 或者删除的指标
+		for _, v := range multipleGraphConfigEdbMappingList {
+			deleteEdbInfoIdMap[v.EdbInfoId] = v.EdbInfoId
+			configMapping[v.EdbInfoId] = v
+		}
+	}
+	resp := data_manage.BatchEdbInfoCalculateBatchSaveResp{
+		Fail:    make([]data_manage.BatchEdbInfoCalculateBatchSaveFailResp, 0),
+		Success: make([]data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp, 0),
+	}
+	// 普通指标批量新增\批量编辑
+	calculateEdbList := make([]data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp, 0)
+	addReqEdbList := make([]*data_manage.CalculateEdbInfoItem, 0)
+	editReqEdbList := make([]*data_manage.CalculateEdbInfoItem, 0)
+	for _, v := range req.EdbInfoList {
+
+		v.CalculateId = strconv.Itoa(v.FromEdbInfoId)
+		v.EdbName = strings.Trim(v.EdbName, " ")
+		if v.EdbName == "" {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "指标名称不能为空",
+			})
+			continue
+		}
+
+		if v.Frequency == "" {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "频率不能为空",
+			})
+			continue
+		}
+
+		if v.Unit == "" {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "单位不能为空",
+			})
+			continue
+		}
+
+		if v.ClassifyId <= 0 {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: v.CalculateId,
+				Msg:         "请选择分类",
+			})
+			continue
+		}
+
+		//加入缓存机制,避免创建同一个名称的指标 start
+		redisKey := fmt.Sprint("edb_info:calculate:batch:save:", utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS, ":", v.EdbName)
+		//加入缓存机制,避免创建同一个名称的指标 start
+		if req.EdbInfoType == 1 {
+			redisKey = fmt.Sprint("predict_edb_info:calculate:batch:save:", sysUser.AdminId, ":", utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS, ":", v.CalculateId)
+		}
+		isExist := utils.Rc.IsExist(redisKey)
+		if isExist {
+			resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+				CalculateId: strconv.Itoa(v.FromEdbInfoId),
+				Msg:         "指标正在处理,请勿重复提交",
+			})
+			continue
+		} else {
+		}
+		if v.EdbInfoId > 0 {
+			editReqEdbList = append(editReqEdbList, v)
+			delete(deleteEdbInfoIdMap, v.EdbInfoId)
+		} else {
+			addReqEdbList = append(addReqEdbList, v)
+		}
+	}
+	if len(editReqEdbList) <= 0 && len(addReqEdbList) <= 0 {
+		br.Msg = "新增失败!"
+		if len(resp.Fail) > 0 {
+			br.ErrMsg = resp.Fail[0].Msg
+		} else {
+			br.Msg = "请选择指标"
+		}
+		return
+	}
+	if req.EdbInfoType == 0 { //普通指标
+		for _, v := range addReqEdbList {
+			req2 := &data_manage.EdbInfoCalculateBatchSaveReqByEdbLib{
+				AdminId:    sysUser.AdminId,
+				AdminName:  sysUser.RealName,
+				EdbInfoId:  v.EdbInfoId,
+				EdbName:    v.EdbName,
+				Frequency:  v.Frequency,
+				Unit:       v.Unit,
+				ClassifyId: v.ClassifyId,
+				//Formula:          v.Formula, //N数值移动平均计算、环比值、环差值
+				FromEdbInfoId:    v.FromEdbInfoId,
+				CalculateFormula: extraConfig,
+				Source:           utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS,
+			}
+
+			// 调用指标库去更新
+			reqJson, err := json.Marshal(req2)
+			if err != nil {
+				br.Msg = "参数解析异常!"
+				br.ErrMsg = "参数解析失败,Err:" + err.Error()
+				return
+			}
+			respItem, e := data.BatchSaveEdbCalculateData(string(reqJson), this.Lang)
+			if e != nil {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         "新增失败!",
+					ErrMsg:      "新增失败,Err:" + e.Error(),
+				})
+				continue
+			}
+			if respItem.Ret != 200 {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         respItem.Msg,
+					ErrMsg:      respItem.ErrMsg,
+				})
+				continue
+			}
+
+			calculateEdbList = append(calculateEdbList, data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp{
+				CalculateId: v.CalculateId,
+				EdbInfoId:   respItem.Data.EdbInfoId,
+				UniqueCode:  respItem.Data.UniqueCode,
+				ClassifyId:  v.ClassifyId,
+			})
+		}
+
+		if len(editReqEdbList) > 0 {
+			for _, v := range editReqEdbList {
+				req2 := &data_manage.EdbInfoCalculateBatchEditReqByEdbLib{
+					AdminId:    sysUser.AdminId,
+					AdminName:  sysUser.RealName,
+					EdbInfoId:  v.EdbInfoId,
+					EdbName:    v.EdbName,
+					Frequency:  v.Frequency,
+					Unit:       v.Unit,
+					ClassifyId: v.ClassifyId,
+					//Formula:          v.Formula, //N数值移动平均计算、环比值、环差值
+					FromEdbInfoId:    v.FromEdbInfoId,
+					CalculateFormula: extraConfig,
+					Source:           utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS,
+				}
+
+				// 调用指标库去更新
+				reqJson, err := json.Marshal(req2)
+				if err != nil {
+					br.Msg = "参数解析异常!"
+					br.ErrMsg = "参数解析失败,Err:" + err.Error()
+					return
+				}
+				respItem, e := data.BatchEditEdbCalculateData(string(reqJson), this.Lang)
+				if e != nil {
+					resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+						CalculateId: v.CalculateId,
+						Msg:         "新增失败!",
+						ErrMsg:      "新增失败,Err:" + e.Error(),
+					})
+					continue
+				}
+				if respItem.Ret != 200 {
+					resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+						CalculateId: v.CalculateId,
+						Msg:         respItem.Msg,
+						ErrMsg:      respItem.ErrMsg,
+					})
+					continue
+				}
+
+				calculateEdbList = append(calculateEdbList, data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp{
+					CalculateId: v.CalculateId,
+					EdbInfoId:   respItem.Data.EdbInfoId,
+					UniqueCode:  respItem.Data.UniqueCode,
+					ClassifyId:  v.ClassifyId,
+				})
+			}
+		}
+	} else if req.EdbInfoType == 1 {
+		// 预测指标
+		for _, v := range req.EdbInfoList {
+			predictReq := new(data_manage.PredictEdbInfoCalculateBatchSaveReq)
+			predictReq.EdbInfoId = v.EdbInfoId
+			predictReq.CalculateFormula = extraConfig
+			predictReq.EdbName = v.EdbName
+			predictReq.Frequency = v.Frequency
+			predictReq.Unit = v.Unit
+			predictReq.ClassifyId = v.ClassifyId
+			predictReq.FromEdbInfoId = v.FromEdbInfoId
+			predictReq.AdminId = sysUser.AdminId
+			predictReq.AdminName = sysUser.RealName
+			predictReq.Source = utils.DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS
+			reqJson, e := json.Marshal(predictReq)
+			if e != nil {
+				br.Msg = "参数解析异常!"
+				br.ErrMsg = "参数解析失败,Err:" + e.Error()
+				return
+			}
+			respItem, e := data.BatchSavePredictEdbData(string(reqJson), this.Lang)
+			if e != nil {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         "新增失败!",
+					ErrMsg:      "新增失败,Err:" + e.Error(),
+				})
+				continue
+			}
+			if respItem.Ret != 200 {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         respItem.Msg,
+					ErrMsg:      respItem.ErrMsg,
+				})
+				continue
+			}
+
+			calculateEdbList = append(calculateEdbList, data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp{
+				CalculateId: v.CalculateId,
+				EdbInfoId:   respItem.Data.EdbInfoId,
+				UniqueCode:  respItem.Data.UniqueCode,
+				ClassifyId:  predictReq.ClassifyId,
+			})
+		}
+	}
+
+	// 批量删除
+	for k, _ := range deleteEdbInfoIdMap {
+		deleteEdbInfoIds = append(deleteEdbInfoIds, k)
+	}
+	if len(deleteEdbInfoIds) > 0 {
+		err = data_manage.DeleteMultipleGraphConfigEdbMappingByEdbIds(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS, deleteEdbInfoIds)
+		if err != nil {
+			br.Msg = "更新失败!"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	// 如果不是另存为的话,那么需要建立配置与指标的关系
+	if !req.IsSaveAs {
+		// 如果指标没有建立关联关系,那么就需要添加关系
+		configEdbMappingList := make([]*data_manage.MultipleGraphConfigEdbMapping, 0)
+		for _, v := range calculateEdbList {
+			item, ok := configMapping[v.EdbInfoId]
+			if !ok {
+				multipleGraphConfigEdbMapping := &data_manage.MultipleGraphConfigEdbMapping{
+					//Id:                    0,
+					MultipleGraphConfigId: req.MultipleGraphConfigId,
+					EdbInfoId:             v.EdbInfoId,
+					Source:                utils.CHART_SOURCE_RANGE_ANALYSIS,
+					ModifyTime:            time.Now(),
+					CreateTime:            time.Now(),
+				}
+				configEdbMappingList = append(configEdbMappingList, multipleGraphConfigEdbMapping)
+			} else {
+				item.ModifyTime = time.Now()
+				err = item.Update([]string{"ModifyTime"})
+				if err != nil {
+					br.Msg = "保存失败"
+					br.ErrMsg = "保存配置与指标的关联关系失败,ERR:" + err.Error()
+					return
+				}
+			}
+		}
+		if len(configEdbMappingList) > 0 {
+			err = data_manage.AddMultipleGraphConfigEdbMappingList(configEdbMappingList)
+			if err != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = "保存配置与指标的关联关系失败,ERR:" + err.Error()
+				return
+			}
+		}
+	}
+	resp.Success = calculateEdbList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// MultipleGraphConfigEdbList
+// @Title 获取指标列表接口
+// @Description 获取指标列表接口
+// @Param	request	body request.SaveMultipleGraphEdbReq true "type json string"
+// @Success Ret=200 返回指标id
+// @router /edb/list [get]
+func (this *RangeChartChartInfoController) MultipleGraphConfigEdbList() {
+	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
+	}
+
+	multipleGraphConfigId, _ := this.GetInt("MultipleGraphConfigId")
+	if multipleGraphConfigId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,MultipleGraphConfigId Is Empty"
+		return
+	}
+
+	list := make([]*data_manage.ChartRangeAnalysisConfigEdbItem, 0)
+	resp := data_manage.ChartRangeAnalysisConfigEdbResp{EdbInfoList: list}
+
+	multipleGraphConfigEdbMappingList, err := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(multipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+	if err != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "查询配置与图表的关联关系失败,ERR:" + err.Error()
+		return
+	}
+	if len(multipleGraphConfigEdbMappingList) == 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		br.Data = resp
+		br.IsAddLog = true
+		return
+	}
+
+	edbInfoIds := make([]int, 0)
+	for _, v := range multipleGraphConfigEdbMappingList {
+		edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+	}
+	edbList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIds)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoMap := make(map[int]*data_manage.ChartEdbInfoMapping, 0)
+	for _, v := range edbList {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+
+	edbCalculateMappingList, e := data_manage.GetEdbInfoCalculateMappingListByEdbInfoIds(edbInfoIds)
+	if e != nil {
+		br.Msg = `保存失败`
+		br.ErrMsg = "获取计算指标信息失败,ERR:" + e.Error()
+		return
+	}
+
+	// 说明指标还在,没有被删除
+	for _, v := range edbCalculateMappingList {
+		tmp := new(data_manage.ChartRangeAnalysisConfigEdbItem)
+		info, ok := edbInfoMap[v.EdbInfoId]
+		if ok {
+			tmp.EdbInfoId = v.EdbInfoId
+			tmp.Unit = info.Unit
+			tmp.UnitEn = info.UnitEn
+			tmp.Frequency = info.Frequency
+			tmp.EdbNameEn = info.EdbNameEn
+			tmp.EdbName = info.EdbName
+			tmp.ClassifyId = info.ClassifyId
+			tmp.FromEdbInfoId = v.FromEdbInfoId
+			tmp.EdbTypeInfo = info.EdbInfoCategoryType
+			list = append(list, tmp)
+		}
+	}
+	resp.EdbInfoList = list
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// SearchByEs
+// @Title 图表模糊搜索(从es获取)
+// @Description  图表模糊搜索(从es获取)
+// @Param   Keyword   query   string  true       "图表名称"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "来源,3:相关性,4:滚动相关性,默认0:全部"
+// @Success 200 {object} data_manage.ChartInfo
+// @router /chart_info/search_by_es [get]
+func (this *RangeChartChartInfoController) SearchByEs() {
+	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
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := this.GetString("Keyword")
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	sourceList := make([]int, 0)
+	sourceList = append(sourceList, utils.CHART_SOURCE_RANGE_ANALYSIS)
+
+	var searchList []*data_manage.ChartInfo
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		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 {
+			noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+		}
+	}
+
+	if keyword != "" {
+		searchList, total, err = data.EsSearchChartInfo(keyword, showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+	} else {
+		total, searchList, err = data_manage.ChartInfoSearchByEmptyKeyWord(showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	finalList := make([]*data_manage.ChartInfoMore, 0)
+	if len(searchList) > 0 {
+		chartInfoIds := ""
+		chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+		for _, v := range searchList {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+
+		for _, v := range searchList {
+			tmp := new(data_manage.ChartInfoMore)
+			tmp.ChartInfo = *v
+			// 图表数据权限
+			tmp.HaveOperaAuth = true
+			//判断是否需要展示英文标识
+			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
+			}
+			finalList = append(finalList, tmp)
+		}
+	}
+	//新增搜索词记录
+	{
+		searchKeyword := new(data_manage.SearchKeyword)
+		searchKeyword.KeyWord = keyword
+		searchKeyword.CreateTime = time.Now()
+		go data_manage.AddSearchKeyword(searchKeyword)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
+	resp := data_manage.ChartInfoListByEsResp{
+		Paging: page,
+		List:   finalList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// BaseInfoEdit
+// @Title 编辑图表基础信息接口
+// @Description 编辑图表基础信息接口
+// @Param	request	body data_manage.EditChartInfoBaseReq true "type json string"
+// @Success Ret=200 编辑成功
+// @router /chart_info/base/edit [post]
+func (this *RangeChartChartInfoController) BaseInfoEdit() {
+	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
+	}
+	var req data_manage.EditChartRangeBaseReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartInfoId <= 0 {
+		br.Msg = "请选择图表"
+		return
+	}
+	if req.ChartName == "" {
+		br.Msg = "请输入图表名称"
+		return
+	}
+
+	chartItem, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取图表信息失败, Err: " + e.Error()
+		return
+	}
+	if chartItem.Source != utils.CHART_SOURCE_RANGE_ANALYSIS {
+		br.Msg = "该图不是区间计算图表"
+		return
+	}
+
+	var condition string
+	var pars []interface{}
+
+	condition += " AND chart_info_id <> ? AND source = ? "
+	pars = append(pars, req.ChartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+
+	switch this.Lang {
+	case utils.EnLangVersion:
+		condition += " AND chart_name_en = ? "
+	default:
+		condition += " AND chart_name = ? "
+	}
+	pars = append(pars, req.ChartName)
+	existItem, err := data_manage.GetChartInfoByCondition(condition, pars)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			br.Msg = "判断英文图表名称是否存在失败"
+			br.ErrMsg = "判断英文图表名称是否存在失败,Err:" + err.Error()
+			return
+		}
+	}
+	if err == nil && existItem.ChartInfoId > 0 {
+		br.Msg = existItem.ChartName + ":" + req.ChartName + "图表名称已存在"
+		return
+	}
+
+	switch this.Lang {
+	case utils.EnLangVersion:
+		chartItem.ChartNameEn = req.ChartName
+	default:
+		chartItem.ChartName = req.ChartName
+	}
+
+	chartItem.ModifyTime = time.Now().Local()
+	if e := chartItem.Update([]string{"ChartName", "ChartNameEn", "ModifyTime"}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新图表信息失败, Err: " + e.Error()
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑区间计算图表基础信息"
+		chartLog.Method = this.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	// 清除缓存
+	if utils.Re == nil && utils.Rc != nil {
+		_ = utils.Rc.Delete(utils.HZ_CHART_LIB_DETAIL + chartItem.UniqueCode) //图表分享链接缓存
+		_ = utils.Rc.Delete(data.GetChartInfoDataKey(req.ChartInfoId))
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+	br.IsAddLog = true
+}
+
+// @Title 保存图表接口
+// @Description 保存图表接口
+// @Param	request	body data_manage.SaveChartInfoReq true "type json string"
+// @Success Ret=200 返回图表id
+// @router /chart_info/save [post]
+func (this *RangeChartChartInfoController) ChartInfoSave() {
+	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
+	}
+
+	var req data_manage.SaveChartRangeReq
+	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 = "参数错误!"
+		return
+	}
+
+	chartItem, err := data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,请刷新页面!"
+			br.ErrMsg = "图表已被删除,请刷新页面,ChartInfoId:" + strconv.Itoa(req.ChartInfoId)
+			return
+		}
+		br.Msg = "获取图表信息失败!"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	//图表操作权限
+	//ok := data.CheckOpChartPermission(sysUser, chartItem.SysUserId)
+	//if !ok {
+	//	br.Msg = "没有该图表的操作权限"
+	//	br.ErrMsg = "没有该图表的操作权限"
+	//	return
+	//}
+
+	err = data_manage.EditRangeChartInfo(&req)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	key := utils.HZ_CHART_LIB_DETAIL + chartItem.UniqueCode
+	if utils.Re == nil && utils.Rc != nil {
+		utils.Rc.Delete(key)
+	}
+
+	//修改es数据
+	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+	//修改my eta es数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.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)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.IsAddLog = true
+}

+ 23 - 0
controllers/report_v2.go

@@ -14,6 +14,8 @@ import (
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"html"
+	"io"
+	"os"
 	"strconv"
 	"strings"
 	"time"
@@ -1954,3 +1956,24 @@ func fixSmartReport() {
 
 	fmt.Println("修复智能研报完成")
 }
+func initPdf() {
+	inFile := "anNNgk3Bbi4LRULwcJgNOPrREYh5.pdf"
+	f2, err := services.GeneralWaterMarkPdf(inFile, "颜鹏 - 18170239278")
+	//f2, err := services.GeneralWaterMarkPdf(inFile, "上周美国馏分油库存累库95万桶,馏分油表需环比下降(-25.6万桶/日)。本期馏分油产量继续抬升,在供增需减的环比变动下库存持续累库。馏分油供应的增加我们认为可能和进口的油种有关,今年以来美国进口的中重质原油占比不断走高,尤其是5")
+	if err != nil {
+		fmt.Println("生成失败,ERR:", err)
+		return
+	}
+
+	// 创建一个新的文件
+	newPdf, err := os.Create("new0555.pdf")
+	if err != nil {
+		fmt.Println("创建临时文件失败,Err:", err)
+		return
+	}
+	defer func() {
+		_ = newPdf.Close()
+	}()
+
+	_, _ = io.Copy(newPdf, f2)
+}

+ 334 - 2
controllers/trade_analysis/trade_analysis.go

@@ -3,8 +3,11 @@ package trade_analysis
 import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
-	trade_analysisModel "eta/eta_api/models/data_manage/trade_analysis"
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
 	"eta/eta_api/services/data/trade_analysis"
+	"eta/eta_api/utils"
+	"fmt"
+	"strings"
 )
 
 // TradeAnalysisController 供应分析
@@ -61,7 +64,7 @@ func (c *TradeAnalysisController) GetPositionTop() {
 		c.ServeJSON()
 	}()
 
-	req := trade_analysisModel.GetPositionTopReq{
+	req := tradeAnalysisModel.GetPositionTopReq{
 		Exchange:     c.GetString("Exchange"),
 		ClassifyName: c.GetString("ClassifyName"),
 		ClassifyType: c.GetString("ClassifyType"),
@@ -92,3 +95,332 @@ func (c *TradeAnalysisController) GetPositionTop() {
 
 	return
 }
+
+// GetTradeExchangeList
+// @Title 获取交易所列表
+// @Description 获取交易所列表
+// @Param   IsTotal  query  bool  false  "是否显示全部"
+// @Success 200 {object} data_manage.BaseFromTradeExchangeItem
+// @router /exchange_list [get]
+func (this *TradeAnalysisController) GetTradeExchangeList() {
+	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
+	}
+	isTotal, _ := this.GetBool("IsTotal", false)
+
+	var cond string
+	var pars []interface{}
+	exchangeOb := new(tradeAnalysisModel.BaseFromTradeExchange)
+	if !isTotal {
+		cond += fmt.Sprintf(` AND %s = ?`, exchangeOb.Cols().AnalysisState)
+		pars = append(pars, 1)
+	}
+	list, e := exchangeOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取交易所列表失败, %v", e)
+		return
+	}
+	resp := make([]*tradeAnalysisModel.BaseFromTradeExchangeItem, 0)
+	for _, v := range list {
+		resp = append(resp, v.Format2Item())
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetTradeClassifyList
+// @Title 获取品种列表
+// @Description 获取品种列表
+// @Param   Exchange  query  string  false  "交易所标识"
+// @Param   ClassifyName  query  string  false  "品种名称"
+// @Success 200 {object} tradeAnalysisModel.BaseFromTradeClassifyItem
+// @router /classify_list [get]
+func (this *TradeAnalysisController) GetTradeClassifyList() {
+	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
+	}
+	exchange := this.GetString("Exchange")
+	exchange = strings.TrimSpace(exchange)
+	classifyName := this.GetString("ClassifyName")
+	classifyName = strings.TrimSpace(classifyName)
+
+	// 交易所名称
+	exchangeName := make(map[string]string)
+	{
+		var cond string
+		var pars []interface{}
+		exchangeOb := new(tradeAnalysisModel.BaseFromTradeExchange)
+		if exchange != "" {
+			cond += fmt.Sprintf(` AND %s = ?`, exchangeOb.Cols().Exchange)
+			pars = append(pars, exchange)
+			item, e := exchangeOb.GetItemByCondition(cond, pars, "")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取交易所信息失败, %v", e)
+				return
+			}
+			exchangeName[item.Exchange] = item.ExchangeName
+		} else {
+			list, e := exchangeOb.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取交易所列表失败, %v", e)
+				return
+			}
+			for _, v := range list {
+				exchangeName[v.Exchange] = v.ExchangeName
+			}
+		}
+	}
+
+	// 搜索品种
+	var cond string
+	var pars []interface{}
+	classifyOb := new(tradeAnalysisModel.BaseFromTradeClassify)
+	if exchange != "" {
+		cond += fmt.Sprintf(` AND %s = ?`, classifyOb.Cols().Exchange)
+		pars = append(pars, exchange)
+	}
+	if classifyName != "" {
+		cond += fmt.Sprintf(` AND %s LIKE ?`, classifyOb.Cols().ClassifyName)
+		pars = append(pars, fmt.Sprint("%", classifyName, "%"))
+	}
+	fields := []string{classifyOb.Cols().ClassifyName, classifyOb.Cols().Exchange}
+	list, e := classifyOb.GetClassifyItemsByCondition(cond, pars, fields, "id ASC")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取品种列表失败, %v", e)
+		return
+	}
+
+	resp := make([]*tradeAnalysisModel.BaseFromTradeClassifyItem, 0)
+	classifyExist := make(map[string]bool)
+	for _, v := range list {
+		// 郑商所
+		if v.Exchange == tradeAnalysisModel.TradeExchangeZhengzhou {
+			name := trade_analysis.GetZhengzhouClassifyName(v.ClassifyName)
+			if name == "" {
+				continue
+			}
+			if classifyExist[name] {
+				continue
+			}
+			classifyExist[name] = true
+			resp = append(resp, &tradeAnalysisModel.BaseFromTradeClassifyItem{
+				ClassifyName: name,
+				Exchange:     v.Exchange,
+				ExchangeName: exchangeName[v.Exchange],
+			})
+			continue
+		}
+		resp = append(resp, &tradeAnalysisModel.BaseFromTradeClassifyItem{
+			ClassifyName: v.ClassifyName,
+			Exchange:     v.Exchange,
+			ExchangeName: exchangeName[v.Exchange],
+		})
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetTradeContractList
+// @Title 获取合约列表
+// @Description 获取合约列表
+// @Param   Exchange  query  string  false  "交易所标识"
+// @Param   ClassifyName  query  string  false  "品种名称"
+// @Param   ContractName  query  string  false  "合约名称"
+// @Param   ExceptTop  query  bool  false  "排除TOP20: true-不包含; false-包含(默认)"
+// @Success 200 {object} tradeAnalysisModel.BaseFromTradeContractItem
+// @router /contract_list [get]
+func (this *TradeAnalysisController) GetTradeContractList() {
+	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
+	}
+	exchange := this.GetString("Exchange")
+	exchange = strings.TrimSpace(exchange)
+	classifyName := this.GetString("ClassifyName")
+	classifyName = strings.TrimSpace(classifyName)
+	contractName := this.GetString("ContractName")
+	contractName = strings.TrimSpace(contractName)
+	exceptTop, _ := this.GetBool("ExceptTop")
+
+	// 交易所名称
+	exchangeName := make(map[string]string)
+	{
+		var cond string
+		var pars []interface{}
+		exchangeOb := new(tradeAnalysisModel.BaseFromTradeExchange)
+		if exchange != "" {
+			cond += fmt.Sprintf(` AND %s = ?`, exchangeOb.Cols().Exchange)
+			pars = append(pars, exchange)
+			item, e := exchangeOb.GetItemByCondition(cond, pars, "")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取交易所信息失败, %v", e)
+				return
+			}
+			exchangeName[item.Exchange] = item.ExchangeName
+		} else {
+			list, e := exchangeOb.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取交易所列表失败, %v", e)
+				return
+			}
+			for _, v := range list {
+				exchangeName[v.Exchange] = v.ExchangeName
+			}
+		}
+	}
+
+	// 搜索合约
+	var cond string
+	var pars []interface{}
+	classifyOb := new(tradeAnalysisModel.BaseFromTradeClassify)
+	if exchange != "" {
+		cond += fmt.Sprintf(` AND %s = ?`, classifyOb.Cols().Exchange)
+		pars = append(pars, exchange)
+	}
+	// 郑商所品种名称在列表中过滤
+	if classifyName != "" {
+		cond += fmt.Sprintf(` AND ((%s = ? AND %s <> ?) OR %s = ?)`, classifyOb.Cols().ClassifyName, classifyOb.Cols().Exchange, classifyOb.Cols().Exchange)
+		pars = append(pars, classifyName, tradeAnalysisModel.TradeExchangeZhengzhou, tradeAnalysisModel.TradeExchangeZhengzhou)
+	}
+	if contractName != "" {
+		cond += fmt.Sprintf(` AND %s LIKE ?`, classifyOb.Cols().ClassifyType)
+		pars = append(pars, fmt.Sprint("%", contractName, "%"))
+	}
+	list, e := classifyOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC, %s DESC", classifyOb.Cols().Exchange, classifyOb.Cols().LatestDate))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取合约列表失败, %v", e)
+		return
+	}
+
+	resp := make([]*tradeAnalysisModel.BaseFromTradeContractItem, 0)
+	if !exceptTop && contractName == "" {
+		resp = append(resp, &tradeAnalysisModel.BaseFromTradeContractItem{
+			ClassifyType: tradeAnalysisModel.TradeFuturesCompanyTop20,
+		})
+	}
+	for _, v := range list {
+		// 郑商所
+		classifyType := v.ClassifyType
+		if classifyName != "" && v.Exchange == tradeAnalysisModel.TradeExchangeZhengzhou {
+			name := trade_analysis.GetZhengzhouClassifyName(v.ClassifyName)
+			if classifyName != name {
+				continue
+			}
+			classifyType = v.ClassifyName
+		}
+		resp = append(resp, &tradeAnalysisModel.BaseFromTradeContractItem{
+			ClassifyName: v.ClassifyName,
+			ClassifyType: classifyType,
+			Exchange:     v.Exchange,
+			ExchangeName: exchangeName[v.Exchange],
+			LatestDate:   v.LatestDate.Format(utils.FormatDate),
+			CreateTime:   v.CreateTime.Format(utils.FormatDateTime),
+		})
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetTradeFuturesCompanyList
+// @Title 获取期货公司列表
+// @Description 获取期货公司列表
+// @Param   CompanyName  query  string  false  "公司名称"
+// @Success 200 {object} tradeAnalysisModel.BaseFromTradeContractItem
+// @router /company_list [get]
+func (this *TradeAnalysisController) GetTradeFuturesCompanyList() {
+	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
+	}
+	companyName := this.GetString("CompanyName")
+	companyName = strings.TrimSpace(companyName)
+
+	var cond string
+	var pars []interface{}
+	companyOb := new(tradeAnalysisModel.TradeFuturesCompany)
+	if companyName != "" {
+		cond += fmt.Sprintf(` AND %s LIKE ?`, companyOb.Cols().CompanyName)
+		pars = append(pars, fmt.Sprint("%", companyName, "%"))
+	}
+	list, e := companyOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", companyOb.Cols().Sort))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取期货公司列表失败, %v", e)
+		return
+	}
+	resp := make([]*tradeAnalysisModel.TradeFuturesCompanyItem, 0)
+	for _, v := range list {
+		resp = append(resp, &tradeAnalysisModel.TradeFuturesCompanyItem{
+			CompanyId:   v.TradeFuturesCompanyId,
+			CompanyName: v.CompanyName,
+			Sort:        v.Sort,
+		})
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 1812 - 0
controllers/trade_analysis/warehouse.go

@@ -0,0 +1,1812 @@
+package trade_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
+	tradeAnalysisRequest "eta/eta_api/models/data_manage/trade_analysis/request"
+	tradeAnalysisResponse "eta/eta_api/models/data_manage/trade_analysis/response"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/services/data"
+	tradeAnalysisService "eta/eta_api/services/data/trade_analysis"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// WarehouseController 建仓过程
+type WarehouseController struct {
+	controllers.BaseAuthController
+}
+
+// Preview
+// @Title 图表预览/编辑页预览
+// @Description 图表预览/编辑页预览
+// @Param	request	body tradeAnalysisRequest.WarehousePreviewReq true "type json string"
+// @Success 200 {object} request.WarehousePreviewReq
+// @router /warehouse/chart/preview [post]
+func (this *WarehouseController) Preview() {
+	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 tradeAnalysisRequest.WarehousePreviewReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	// 非编辑页预览时需要校验参数
+	if req.ChartInfoId <= 0 {
+		if req.ExtraConfig == nil {
+			br.Msg = "图表参数有误"
+			return
+		}
+		pass, tips := tradeAnalysisService.CheckWarehouseChartExtraConfig(*req.ExtraConfig)
+		if !pass {
+			br.Msg = tips
+			return
+		}
+	}
+	resp := new(tradeAnalysisResponse.WarehouseChartDetailResp)
+	resp.WarehouseCharts = make([]*data_manage.ChartInfoDetailResp, 0)
+
+	var (
+		multiConfigId                     int
+		extraConfig                       tradeAnalysisModel.WarehouseExtraConfig
+		configBuy, configSold, configPure tradeAnalysisModel.WarehouseChartPars
+		chartBuy, chartSold, chartPure    *data_manage.ChartInfoView
+	)
+	configBuy.WarehouseChartType = tradeAnalysisModel.WarehouseBuyChartType
+	configSold.WarehouseChartType = tradeAnalysisModel.WarehouseSoldChartType
+	configPure.WarehouseChartType = tradeAnalysisModel.WarehousePureBuyChartType
+
+	// 编辑页预览, 图表默认取自己的配置, 入参有值时则均取入参
+	if req.ChartInfoId > 0 {
+		chartInfoView, e := data_manage.GetChartInfoViewById(req.ChartInfoId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "图表已被删除, 请刷新页面"
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图表信息失败, %v", e)
+			return
+		}
+		if chartInfoView.ExtraConfig == `` {
+			br.Msg = "图表配置信息错误"
+			return
+		}
+		if e = json.Unmarshal([]byte(chartInfoView.ExtraConfig), &extraConfig); e != nil {
+			br.Msg = "配置信息错误"
+			br.ErrMsg = fmt.Sprintf("图表配置信息错误, %v", e)
+			return
+		}
+		multiConfigId = extraConfig.MultipleGraphConfigId
+
+		// 图表的图例设置
+		seriesList, e := data_manage.GetChartSeriesByChartInfoId(req.ChartInfoId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取图例失败, %v", e)
+			return
+		}
+		saveItems := make([]*data_manage.ChartSaveItem, 0)
+		for _, v := range seriesList {
+			saveItems = append(saveItems, &data_manage.ChartSaveItem{
+				UniqueFlag:   v.UniqueFlag,
+				EdbAliasName: v.SeriesName,
+				IsAxis:       v.IsAxis,
+			})
+		}
+
+		// 图表取自己的日期配置
+		switch extraConfig.WarehouseChartType {
+		case tradeAnalysisModel.WarehouseBuyChartType:
+			chartBuy = chartInfoView
+			configBuy.WarehouseChartType = extraConfig.WarehouseChartType
+			configBuy.DateType = chartInfoView.DateType
+			configBuy.DateTypeNum = chartInfoView.DateTypeNum
+			configBuy.StartDate = chartInfoView.StartDate
+			configBuy.EndDate = chartInfoView.EndDate
+			configBuy.ChartEdbInfoList = saveItems
+		case tradeAnalysisModel.WarehouseSoldChartType:
+			chartSold = chartInfoView
+			configSold.WarehouseChartType = extraConfig.WarehouseChartType
+			configSold.DateType = chartInfoView.DateType
+			configSold.DateTypeNum = chartInfoView.DateTypeNum
+			configSold.StartDate = chartInfoView.StartDate
+			configSold.EndDate = chartInfoView.EndDate
+			configSold.ChartEdbInfoList = saveItems
+		case tradeAnalysisModel.WarehousePureBuyChartType:
+			chartPure = chartInfoView
+			configPure.WarehouseChartType = extraConfig.WarehouseChartType
+			configPure.DateType = chartInfoView.DateType
+			configPure.DateTypeNum = chartInfoView.DateTypeNum
+			configPure.StartDate = chartInfoView.StartDate
+			configPure.EndDate = chartInfoView.EndDate
+			configPure.ChartEdbInfoList = saveItems
+		}
+	}
+
+	// 入参有图表配置那么均取入参的
+	if req.ExtraConfig != nil {
+		extraConfig = *req.ExtraConfig
+	}
+	if len(req.ChartsConfig) > 0 {
+		for _, v := range req.ChartsConfig {
+			if v.WarehouseChartType == tradeAnalysisModel.WarehouseBuyChartType {
+				configBuy = v
+				continue
+			}
+			if v.WarehouseChartType == tradeAnalysisModel.WarehouseSoldChartType {
+				configSold = v
+				continue
+			}
+			if v.WarehouseChartType == tradeAnalysisModel.WarehousePureBuyChartType {
+				configPure = v
+			}
+		}
+	}
+
+	// 查询配置关联的指标
+	multiEdbBuy, multiEdbSold, multiEdbPure := make([]*tradeAnalysisModel.WarehouseEdbSaveItem, 0), make([]*tradeAnalysisModel.WarehouseEdbSaveItem, 0), make([]*tradeAnalysisModel.WarehouseEdbSaveItem, 0)
+	if multiConfigId > 0 {
+		multiEdbList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(multiConfigId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取配置与图表指标关系失败, %v", e)
+			return
+		}
+		var edbIds []int
+		for _, v := range multiEdbList {
+			edbIds = append(edbIds, v.EdbInfoId)
+		}
+		if len(edbIds) > 0 {
+			edbList, e := data_manage.GetEdbInfoByIdList(edbIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取配置关联指标信息失败, %v", e)
+				return
+			}
+			for _, v := range edbList {
+				if v.CalculateFormula == "" {
+					utils.FileLog.Info(fmt.Sprintf("持仓分析图表详情-指标计算公式为空, EdbInfoId: %d", v.EdbInfoId))
+					continue
+				}
+				var conf tradeAnalysisModel.WarehouseExtraConfig
+				if e = json.Unmarshal([]byte(v.CalculateFormula), &conf); e != nil {
+					utils.FileLog.Info(fmt.Sprintf("持仓分析图表详情-指标计算公式解析失败, EdbInfoId: %d, err: %v", v.EdbInfoId, e))
+					continue
+				}
+				if len(conf.Companies) != 1 {
+					utils.FileLog.Info(fmt.Sprintf("持仓分析图表详情-指标计算公式信息有误, EdbInfoId: %d", v.EdbInfoId))
+					continue
+				}
+				item := new(tradeAnalysisModel.WarehouseEdbSaveItem)
+				item.EdbInfoId = v.EdbInfoId
+				item.EdbName = v.EdbName
+				item.Unit = v.Unit
+				item.Frequency = v.Frequency
+				item.ClassifyId = v.ClassifyId
+				item.UniqueFlag = conf.Companies[0]
+				switch conf.WarehouseChartType {
+				case tradeAnalysisModel.WarehouseBuyChartType:
+					multiEdbBuy = append(multiEdbBuy, item)
+				case tradeAnalysisModel.WarehouseSoldChartType:
+					multiEdbSold = append(multiEdbSold, item)
+				case tradeAnalysisModel.WarehousePureBuyChartType:
+					multiEdbPure = append(multiEdbPure, item)
+				}
+			}
+		}
+	}
+
+	// 获取指标数据, 该图表未用实际指标, 为了统一数据格式用ChartEdbInfoMapping
+	companyTradeData, e := tradeAnalysisService.GetOriginTradeData(extraConfig.Exchange, extraConfig.ClassifyName, extraConfig.Contracts, extraConfig.Companies, extraConfig.PredictRatio)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取期货公司持仓加总数据失败, %v", e)
+		return
+	}
+
+	// 获取三种图表
+	buyChartResp, e := tradeAnalysisService.GetWarehouseChartResp(chartBuy, companyTradeData, multiEdbBuy, extraConfig, configBuy)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取建仓过程多单图表数据失败, %v", e)
+		return
+	}
+	soldChartResp, e := tradeAnalysisService.GetWarehouseChartResp(chartSold, companyTradeData, multiEdbSold, extraConfig, configSold)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取建仓过程空单图表数据失败, %v", e)
+		return
+	}
+	pureChartResp, e := tradeAnalysisService.GetWarehouseChartResp(chartPure, companyTradeData, multiEdbPure, extraConfig, configPure)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取建仓过程净多单图表数据失败, %v", e)
+		return
+	}
+	resp.MultipleGraphConfigId = multiConfigId
+	resp.WarehouseCharts = append(resp.WarehouseCharts, buyChartResp, soldChartResp, pureChartResp)
+
+	// 多图配置
+	if multiConfigId == 0 {
+		multipleGraphConfig := &data_manage.MultipleGraphConfig{
+			SysUserId:       sysUser.AdminId,
+			SysUserRealName: sysUser.RealName,
+			ModifyTime:      time.Now(),
+			CreateTime:      time.Now(),
+		}
+		if e = data_manage.AddMultipleGraphConfig(multipleGraphConfig); e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("新增建仓过程多图配置失败, %v", e)
+			return
+		}
+		resp.MultipleGraphConfigId = multipleGraphConfig.MultipleGraphConfigId
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Add
+// @Title 新增图表接口
+// @Description 新增图表接口
+// @Param	request	body data_manage.AddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /warehouse/chart/add [post]
+func (this *WarehouseController) 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
+	}
+	deleteCache := true
+	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req data_manage.AddChartInfoReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	// 图表校验
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartName == "" {
+		br.Msg = "请填写图表名称"
+		return
+	}
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	var extraConfig tradeAnalysisModel.WarehouseExtraConfig
+	if req.ExtraConfig == `` {
+		br.Msg = "配置有误"
+		br.ErrMsg = fmt.Sprintf("建仓图表配置有误, conf: %s", req.ExtraConfig)
+		return
+	}
+	if e := json.Unmarshal([]byte(req.ExtraConfig), &extraConfig); e != nil {
+		br.Msg = "配置有误"
+		br.ErrMsg = fmt.Sprintf("建仓图表配置解析失败, err: %v; conf: %s", e, req.ExtraConfig)
+		return
+	}
+	pass, tips := tradeAnalysisService.CheckWarehouseChartExtraConfig(extraConfig)
+	if !pass {
+		br.Msg = tips
+		return
+	}
+	// 校验图例
+	if len(req.ChartEdbInfoList) == 0 {
+		br.Msg = "请填写图例"
+		return
+	}
+	if len(req.ChartEdbInfoList) != len(extraConfig.Companies) {
+		br.Msg = "图例不一致"
+		br.ErrMsg = fmt.Sprintf("图例不一致, len: %d, companies: %d", len(req.ChartEdbInfoList), len(extraConfig.Companies))
+		return
+	}
+	flagExist := make(map[string]bool)
+	for _, v := range req.ChartEdbInfoList {
+		if v.UniqueFlag == "" {
+			br.Msg = ""
+		}
+		if flagExist[v.UniqueFlag] {
+			br.Msg = "图例异常"
+			br.ErrMsg = fmt.Sprintf("建仓图表图例异常, %s", v.UniqueFlag)
+			return
+		}
+	}
+
+	// 校验分类
+	_, e := data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+
+	// 校验图表是否存在
+	{
+		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, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+		count, e := data_manage.GetChartInfoCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取同名图表计数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "图表名称已存在, 请重新填写"
+			return
+		}
+	}
+
+	// 新增图表
+	chartInfo, e := tradeAnalysisService.AddWarehouseChart(req, extraConfig, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("新增持仓分析图表失败, %v", e)
+		return
+	}
+
+	// 新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ChartClassifyId
+		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
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// Edit
+// @Title 编辑图表接口
+// @Description 编辑图表接口
+// @Param	request	body data_manage.EditChartInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /warehouse/chart/edit [post]
+func (this *WarehouseController) 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 data_manage.EditChartInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, %v", e)
+		return
+	}
+
+	// 图表校验
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartName == "" {
+		br.Msg = "请填写图表名称"
+		return
+	}
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	var extraConfig tradeAnalysisModel.WarehouseExtraConfig
+	if req.ExtraConfig == `` {
+		br.Msg = "配置有误"
+		br.ErrMsg = fmt.Sprintf("建仓图表配置有误, conf: %s", req.ExtraConfig)
+		return
+	}
+	if e := json.Unmarshal([]byte(req.ExtraConfig), &extraConfig); e != nil {
+		br.Msg = "配置有误"
+		br.ErrMsg = fmt.Sprintf("建仓图表配置解析失败, err: %v; conf: %s", e, req.ExtraConfig)
+		return
+	}
+	pass, tips := tradeAnalysisService.CheckWarehouseChartExtraConfig(extraConfig)
+	if !pass {
+		br.Msg = tips
+		return
+	}
+
+	// 图表校验
+	chartItem, e := data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "图表不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取图表信息失败, %v", e)
+		return
+	}
+	if chartItem.Source != utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS {
+		br.Msg = "图表类型异常"
+		br.ErrMsg = fmt.Sprintf("图表类型异常: %d", chartItem.Source)
+		return
+	}
+
+	// 图表操作权限
+	ok := data.CheckOpChartPermission(sysUser, chartItem.SysUserId, true)
+	if !ok {
+		br.Msg = "没有该图表的操作权限"
+		return
+	}
+
+	// 校验图例
+	if len(req.ChartEdbInfoList) == 0 {
+		br.Msg = "请填写图例"
+		return
+	}
+	if len(req.ChartEdbInfoList) != len(extraConfig.Companies) {
+		br.Msg = "图例不一致"
+		br.ErrMsg = fmt.Sprintf("图例不一致, len: %d, companies: %d", len(req.ChartEdbInfoList), len(extraConfig.Companies))
+		return
+	}
+	flagExist := make(map[string]bool)
+	for _, v := range req.ChartEdbInfoList {
+		if v.UniqueFlag == "" {
+			br.Msg = ""
+		}
+		if flagExist[v.UniqueFlag] {
+			br.Msg = "图例异常"
+			br.ErrMsg = fmt.Sprintf("建仓图表图例异常, %s", v.UniqueFlag)
+			return
+		}
+	}
+
+	// 校验分类
+	_, e = data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+
+	// 校验图表是否存在
+	{
+		var cond string
+		var pars []interface{}
+		cond += " AND chart_info_id <> ? "
+		pars = append(pars, req.ChartInfoId)
+
+		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, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+		count, e := data_manage.GetChartInfoCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取同名图表计数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "图表名称已存在, 请重新填写"
+			return
+		}
+	}
+
+	// 更新图表
+	chartItem, e = tradeAnalysisService.EditWarehouseChart(req)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("新增持仓分析图表失败, %v", e)
+		return
+	}
+	resp := new(data_manage.AddChartInfoResp)
+	resp.ChartInfoId = chartItem.ChartInfoId
+	resp.UniqueCode = chartItem.UniqueCode
+	resp.ChartType = req.ChartType
+
+	// 新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.UniqueCode
+		chartLog.CreateTime = time.Now()
+		chartLog.Content = string(this.Ctx.Input.RequestBody)
+		chartLog.Status = "编辑持仓分析图表"
+		chartLog.Method = this.Ctx.Input.URL()
+		go data_manage.AddChartInfoLog(chartLog)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// Detail
+// @Title 获取图表详情
+// @Description 获取图表详情接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   DateType   query   int  true       "日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"
+// @Param   StartDate   query   string  true       "自定义开始日期"
+// @Param   EndDate   query   string  true       "自定义结束日期"
+// @Param   Calendar   query   string  true       "公历/农历"
+// @Param   SeasonStartDate   query   string  true       "季节性图开始日期"
+// @Param   SeasonEndDate   query   string  true       "季节性图结束日期"
+// @Param   EdbInfoId   query   string  true       "指标ID,多个用英文逗号隔开"
+// @Param   ChartType   query   int  true       "生成样式:1:曲线图,2:季节性图"
+// @Success 200 {object} data_manage.ChartInfoDetailResp
+// @router /warehouse/chart/detail [get]
+func (this *WarehouseController) 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
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	if chartInfoId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	dateType, _ := this.GetInt("DateType")
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	dateTypeNum, _ := this.GetInt("DateTypeNum")
+
+	var err error
+	chartInfo := new(data_manage.ChartInfoView)
+	chartInfo, err = data_manage.GetChartInfoViewById(chartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图被删除,请刷新页面"
+			br.ErrMsg = "图被删除,请刷新页面,Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+	if dateType <= 0 {
+		dateType = chartInfo.DateType
+	}
+	if startDate == "" {
+		startDate = chartInfo.StartDate
+	}
+	if endDate == "" {
+		endDate = chartInfo.EndDate
+	}
+	if dateTypeNum <= 0 {
+		dateTypeNum = chartInfo.DateTypeNum
+	}
+
+	// 持仓分析图表配置校验
+	var extraConfig tradeAnalysisModel.WarehouseExtraConfig
+	if chartInfo.ExtraConfig == `` {
+		br.Msg = "配置信息错误"
+		return
+	}
+	if e := json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig); e != nil {
+		br.Msg = "配置信息错误"
+		br.ErrMsg = fmt.Sprintf("图表配置信息错误, %v", e)
+		return
+	}
+
+	// 获取图表数据
+	companyTradeData, e := tradeAnalysisService.GetOriginTradeData(extraConfig.Exchange, extraConfig.ClassifyName, extraConfig.Contracts, extraConfig.Companies, extraConfig.PredictRatio)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取期货公司持仓加总数据失败, %v", e)
+		return
+	}
+
+	var chartConfig tradeAnalysisModel.WarehouseChartPars
+	chartConfig.WarehouseChartType = extraConfig.WarehouseChartType
+	chartConfig.DateType = dateType
+	chartConfig.StartDate = startDate
+	chartConfig.EndDate = endDate
+	chartConfig.DateTypeNum = dateTypeNum
+	// 获取图表的图例设置
+	seriesList, e := data_manage.GetChartSeriesByChartInfoId(chartInfoId)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取图例失败, %v", e)
+		return
+	}
+	saveItems := make([]*data_manage.ChartSaveItem, 0)
+	for _, v := range seriesList {
+		saveItems = append(saveItems, &data_manage.ChartSaveItem{
+			UniqueFlag:   v.UniqueFlag,
+			EdbAliasName: v.SeriesName,
+			IsAxis:       v.IsAxis,
+		})
+	}
+	chartConfig.ChartEdbInfoList = saveItems
+
+	// 查询配置关联的指标
+	multiEdb := make([]*tradeAnalysisModel.WarehouseEdbSaveItem, 0)
+	if extraConfig.MultipleGraphConfigId > 0 {
+		multiEdbList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(extraConfig.MultipleGraphConfigId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取配置与图表指标关系失败, %v", e)
+			return
+		}
+		var edbIds []int
+		for _, v := range multiEdbList {
+			edbIds = append(edbIds, v.EdbInfoId)
+		}
+		if len(edbIds) > 0 {
+			edbList, e := data_manage.GetEdbInfoByIdList(edbIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取配置关联指标信息失败, %v", e)
+				return
+			}
+			for _, v := range edbList {
+				if v.CalculateFormula == "" {
+					utils.FileLog.Info(fmt.Sprintf("持仓分析图表详情-指标计算公式为空, EdbInfoId: %d", v.EdbInfoId))
+					continue
+				}
+				var conf tradeAnalysisModel.WarehouseExtraConfig
+				if e = json.Unmarshal([]byte(v.CalculateFormula), &conf); e != nil {
+					utils.FileLog.Info(fmt.Sprintf("持仓分析图表详情-指标计算公式解析失败, EdbInfoId: %d, err: %v", v.EdbInfoId, e))
+					continue
+				}
+				if len(conf.Companies) != 1 {
+					utils.FileLog.Info(fmt.Sprintf("持仓分析图表详情-指标计算公式信息有误, EdbInfoId: %d", v.EdbInfoId))
+					continue
+				}
+				item := new(tradeAnalysisModel.WarehouseEdbSaveItem)
+				item.EdbInfoId = v.EdbInfoId
+				item.EdbName = v.EdbName
+				item.Unit = v.Unit
+				item.Frequency = v.Frequency
+				item.ClassifyId = v.ClassifyId
+				item.UniqueFlag = conf.Companies[0]
+				if conf.WarehouseChartType == extraConfig.WarehouseChartType {
+					multiEdb = append(multiEdb, item)
+				}
+			}
+		}
+	}
+
+	chartResp, e := tradeAnalysisService.GetWarehouseChartResp(chartInfo, companyTradeData, multiEdb, extraConfig, chartConfig)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取图表详情失败, %v", e)
+		return
+	}
+	resp := chartResp
+
+	// 判断是否加入我的图库
+	if chartInfoId > 0 && chartInfo != nil {
+		{
+			var myChartCondition string
+			var myChartPars []interface{}
+			myChartCondition += ` AND a.admin_id=? `
+			myChartPars = append(myChartPars, sysUser.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() {
+				br.Msg = "获取失败"
+				br.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
+			}
+		}
+	}
+
+	// 图表操作权限
+	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	// 判断是否需要展示英文标识
+	//chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, chartInfo.Source, chartInfo.ChartType)
+
+	// 数据来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	sourceNameList = append(sourceNameList, utils.SourceNameTradeAnalysis)
+	sourceNameEnList = append(sourceNameEnList, utils.SourceNameTradeAnalysis)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	// 另存为
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    chartInfo.IsEdit,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	// 图表当前分类的分类树
+	classifyLevels := make([]string, 0)
+	{
+		list, e := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+		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.ClassifyLevels = classifyLevels
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// List
+// @Title 持仓分析图表列表接口
+// @Description 持仓分析图表列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ChartClassifyId   query   int  true       "分类id"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /warehouse/chart/list [get]
+func (this *WarehouseController) List() {
+	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
+	}
+
+	chartClassifyId, _ := this.GetInt("ChartClassifyId")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("KeyWord")
+
+	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)
+
+	source := utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS
+
+	var condition string
+	var pars []interface{}
+
+	// 普通图表
+	condition += ` AND source = ? `
+	pars = append(pars, source)
+
+	if chartClassifyId > 0 {
+		classifyIds, err := data_manage.GetChartClassify(chartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取图表信息失败"
+			br.ErrMsg = "获取信息失败,GetChartClassify,Err:" + err.Error()
+			return
+		}
+		if classifyIds == "" {
+			resp := new(data_manage.ChartListResp)
+			page = paging.GetPaging(currentIndex, pageSize, 0)
+			resp.Paging = page
+			resp.List = make([]*data_manage.ChartInfoView, 0)
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			br.Data = resp
+			return
+		}
+		condition += " AND chart_classify_id IN(" + classifyIds + ") "
+	}
+	if keyword != "" {
+		//将关键词按照空格分割
+		keywords := strings.Split(keyword, " ")
+		condition += ` AND  ( chart_name LIKE '%` + keywords[0] + `%' `
+		for k, key := range keywords {
+			if k == 0 {
+				continue
+			}
+			condition += ` OR chart_name LIKE '%` + key + `%' `
+		}
+		condition += ` )`
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		condition += ` AND sys_user_id = ? `
+		pars = append(pars, sysUser.AdminId)
+	}
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		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 {
+			noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+		}
+	}
+
+	lenNoPermissionChartIdList := len(noPermissionChartIdList)
+	if lenNoPermissionChartIdList > 0 {
+		condition += ` AND chart_info_id not in (` + utils.GetOrmInReplace(lenNoPermissionChartIdList) + `) `
+		pars = append(pars, noPermissionChartIdList)
+	}
+
+	//获取图表信息
+	list, err := data_manage.GetChartListByCondition(condition, pars, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	myChartList, err := data_manage.GetMyChartListByAdminId(sysUser.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取图表信息失败"
+		br.ErrMsg = "获取我的图表信息失败,Err:" + err.Error()
+		return
+	}
+	myChartMap := make(map[int]*data_manage.MyChartView)
+	for _, v := range myChartList {
+		myChartMap[v.ChartInfoId] = v
+	}
+	listLen := len(list)
+	chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+	if listLen > 0 {
+		chartInfoIds := ""
+		for _, v := range list {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+	}
+	for i := 0; i < listLen; i++ {
+		//判断是否需要展示英文标识
+		if edbTmpList, ok := chartEdbMap[list[i].ChartInfoId]; ok {
+			list[i].IsEnChart = data.CheckIsEnChart(list[i].ChartNameEn, edbTmpList, list[i].Source, list[i].ChartType)
+		}
+
+		if existItem, ok := myChartMap[list[i].ChartInfoId]; ok {
+			list[i].IsAdd = true
+			list[i].MyChartId = existItem.MyChartId
+			list[i].MyChartClassifyId = existItem.MyChartClassifyId
+		}
+	}
+
+	resp := new(data_manage.ChartListResp)
+	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
+		items := make([]*data_manage.ChartInfoView, 0)
+		resp.Paging = page
+		resp.List = items
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	dataCount, err := data_manage.GetChartListCountByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, dataCount)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Copy
+// @Title 复制并新增图表接口
+// @Description 新增图表接口
+// @Params	request	body data_manage.CopyAddChartInfoReq true "type json string"
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /warehouse/chart/copy [post]
+func (this *WarehouseController) 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 = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		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() {
+		if deleteCache {
+			utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+
+	chartSource := utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS
+	// 校验分类、图表名称
+	{
+		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
+		}
+	}
+
+	// 原图表
+	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
+	}
+
+	chartInfo := new(data_manage.ChartInfo)
+	newChart, e := tradeAnalysisService.CopyWarehouseChart(req.ChartClassifyId, req.ChartName, originChart, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("复制图表失败, %v", e)
+		return
+	}
+	chartInfo = newChart
+
+	// 新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartInfoId = chartInfo.ChartInfoId
+		chartLog.ChartName = req.ChartName
+		chartLog.ChartClassifyId = req.ChartClassifyId
+		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)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = data_manage.AddChartInfoResp{
+		ChartInfoId: chartInfo.ChartInfoId,
+		UniqueCode:  chartInfo.UniqueCode,
+		ChartType:   chartInfo.ChartType,
+	}
+	br.IsAddLog = true
+}
+
+// Refresh
+// @Title 图表刷新接口
+// @Description 图表刷新接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   UniqueCode   query   string  true       "唯一code"
+// @Success Ret=200 刷新成功
+// @router /warehouse/chart/refresh [get]
+func (this *WarehouseController) Refresh() {
+	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
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	uniqueCode := this.GetString("UniqueCode")
+	if chartInfoId <= 0 && uniqueCode == `` {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误:chartInfoId:" + strconv.Itoa(chartInfoId) + ",UniqueCode:" + uniqueCode
+		return
+	}
+
+	var chartInfo *data_manage.ChartInfo
+	var err error
+	if chartInfoId > 0 {
+		chartInfo, err = data_manage.GetChartInfoById(chartInfoId)
+	} else {
+		chartInfo, err = data_manage.GetChartInfoByUniqueCode(uniqueCode)
+	}
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,无需刷新"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		br.Msg = "刷新失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 清除图表缓存
+	{
+		key := utils.HZ_CHART_LIB_DETAIL + chartInfo.UniqueCode
+		_ = utils.Rc.Delete(key)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "刷新成功"
+}
+
+// SearchByEs
+// @Title 图表模糊搜索(从es获取)
+// @Description  图表模糊搜索(从es获取)
+// @Param   Keyword   query   string  true       "图表名称"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "来源,3:相关性,4:滚动相关性,默认0:全部"
+// @Success 200 {object} data_manage.ChartInfo
+// @router /warehouse/chart/search_by_es [get]
+func (this *WarehouseController) SearchByEs() {
+	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
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := this.GetString("Keyword")
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	sourceList := make([]int, 0)
+	sourceList = append(sourceList, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+
+	var searchList []*data_manage.ChartInfo
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标
+	noPermissionChartIdList := make([]int, 0)
+	{
+		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 {
+			noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+		}
+	}
+
+	if keyword != "" {
+		searchList, total, err = data.EsSearchChartInfo(keyword, showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+	} else {
+		total, searchList, err = data_manage.ChartInfoSearchByEmptyKeyWord(showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	finalList := make([]*data_manage.ChartInfoMore, 0)
+	if len(searchList) > 0 {
+		chartInfoIds := ""
+		chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+		for _, v := range searchList {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+
+		for _, v := range searchList {
+			tmp := new(data_manage.ChartInfoMore)
+			tmp.ChartInfo = *v
+			// 图表数据权限
+			tmp.HaveOperaAuth = true
+			//判断是否需要展示英文标识
+			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
+			}
+			finalList = append(finalList, tmp)
+		}
+	}
+	//新增搜索词记录
+	{
+		searchKeyword := new(data_manage.SearchKeyword)
+		searchKeyword.KeyWord = keyword
+		searchKeyword.CreateTime = time.Now()
+		go data_manage.AddSearchKeyword(searchKeyword)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
+	resp := data_manage.ChartInfoListByEsResp{
+		Paging: page,
+		List:   finalList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ChartInfoSave
+// @Title 保存图表接口
+// @Description 保存图表接口
+// @Param	request	body data_manage.SaveChartInfoReq true "type json string"
+// @Success Ret=200 返回图表id
+// @router /warehouse/chart/save [post]
+func (this *WarehouseController) ChartInfoSave() {
+	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
+	}
+	var req data_manage.SaveChartInfoReq
+	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 = "参数错误!"
+		return
+	}
+
+	chartItem, err := data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "图表已被删除,请刷新页面!"
+			br.ErrMsg = "图表已被删除,请刷新页面,ChartInfoId:" + strconv.Itoa(req.ChartInfoId)
+			return
+		}
+		br.Msg = "获取图表信息失败!"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	chartItem.StartDate = req.StartDate
+	chartItem.EndDate = req.EndDate
+	chartItem.DateType = req.DateType
+	chartItem.DateTypeNum = req.DateTypeNum
+	chartItem.LeftMin = req.LeftMin
+	chartItem.LeftMax = req.LeftMax
+	chartItem.RightMin = req.RightMin
+	chartItem.RightMax = req.RightMax
+	chartItem.Right2Min = req.Right2Min
+	chartItem.Right2Max = req.Right2Max
+	chartItem.MinMaxSave = req.MinMaxSave
+	updateCols := []string{"StartDate", "EndDate", "DateType", "DateTypeNum", "LeftMin", "LeftMax", "RightMin", "RightMax", "Right2Min", "Right2Max", "MinMaxSave"}
+	if e := chartItem.Update(updateCols); e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = fmt.Sprintf("更新图表基础信息失败, %v", e)
+		return
+	}
+
+	key := utils.HZ_CHART_LIB_DETAIL + chartItem.UniqueCode
+	if utils.Re == nil && utils.Rc != nil {
+		_ = utils.Rc.Delete(key)
+	}
+
+	// 更新ES
+	go func() {
+		data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+		data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+	}()
+
+	//新增操作日志
+	{
+		chartLog := new(data_manage.ChartInfoLog)
+		chartLog.ChartName = chartItem.ChartName
+		chartLog.ChartInfoId = req.ChartInfoId
+		chartLog.ChartClassifyId = chartItem.ChartClassifyId
+		chartLog.SysUserId = sysUser.AdminId
+		chartLog.SysUserRealName = sysUser.RealName
+		chartLog.UniqueCode = chartItem.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)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.IsAddLog = true
+}
+
+// EdbSaveCheck
+// @Title 保存指标校验
+// @Description 保存指标校验
+// @Param	request	body request.WarehouseEdbSaveReq true "type json string"
+// @Success Ret=200 返回指标id
+// @router /warehouse/edb/save_check [post]
+func (this *WarehouseController) EdbSaveCheck() {
+	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 tradeAnalysisRequest.WarehouseEdbSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+
+	// 参数校验
+	if !req.IsSaveAs && req.MultipleGraphConfigId <= 0 {
+		br.Msg = "多图配置有误"
+		br.ErrMsg = fmt.Sprintf("配置ID有误, %d", req.MultipleGraphConfigId)
+		return
+	}
+	//var extraConfig tradeAnalysisModel.WarehouseExtraConfig
+	extraConfig := *req.ExtraConfig
+	//if req.ExtraConfig == `` {
+	//	br.Msg = "配置有误"
+	//	br.ErrMsg = fmt.Sprintf("建仓图表配置有误, conf: %s", req.ExtraConfig)
+	//	return
+	//}
+	//if e := json.Unmarshal([]byte(req.ExtraConfig), &extraConfig); e != nil {
+	//	br.Msg = "配置有误"
+	//	br.ErrMsg = fmt.Sprintf("建仓图表配置解析失败, err: %v; conf: %s", e, req.ExtraConfig)
+	//	return
+	//}
+	pass, tips := tradeAnalysisService.CheckWarehouseChartExtraConfig(extraConfig)
+	if !pass {
+		br.Msg = tips
+		return
+	}
+	if extraConfig.WarehouseChartType <= 0 {
+		br.Msg = "配置类型有误"
+		br.ErrMsg = fmt.Sprintf("配置类型有误, %d", extraConfig.WarehouseChartType)
+		return
+	}
+
+	// 根据extraConfig与已绑定关系的指标进行比对, 看是否需要新增指标
+	multiEdbList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取配置与图表指标关系失败, %v", e)
+		return
+	}
+	resp := make([]*tradeAnalysisModel.WarehouseEdbSaveItem, 0)
+
+	// 校验是否需要新增指标
+	newEdbList, _, e := tradeAnalysisService.CheckEdbSave(extraConfig, multiEdbList, req.IsSaveAs)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("校验指标是否需要新增失败, %v", e)
+		return
+	}
+	if len(newEdbList) > 0 {
+		resp = append(resp, newEdbList...)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// EdbSave
+// @Title 保存指标接口
+// @Description 保存指标接口
+// @Param	request	body request.WarehouseEdbSaveReq true "type json string"
+// @Success Ret=200 返回指标id
+// @router /warehouse/edb/save [post]
+func (this *WarehouseController) EdbSave() {
+	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 tradeAnalysisRequest.WarehouseEdbSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	cacheKey := "CACHE_CHART_EDB_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		br.Msg = "系统处理中,请稍后重试!"
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
+	// 参数校验
+	if !req.IsSaveAs && req.MultipleGraphConfigId <= 0 {
+		br.Msg = "多图配置有误"
+		br.ErrMsg = fmt.Sprintf("配置ID有误, %d", req.MultipleGraphConfigId)
+		return
+	}
+	extraConfig := *req.ExtraConfig
+	pass, tips := tradeAnalysisService.CheckWarehouseChartExtraConfig(extraConfig)
+	if !pass {
+		br.Msg = tips
+		return
+	}
+	if len(req.EdbInfoList) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	edbNameExist := make(map[string]bool)
+	for _, v := range req.EdbInfoList {
+		if v.UniqueFlag == "" {
+			br.Msg = "指标标识有误"
+			br.ErrMsg = fmt.Sprintf("指标标识有误, %s", v.UniqueFlag)
+			return
+		}
+		// 仅新增的时候校验下面的参数
+		if v.EdbInfoId > 0 {
+			continue
+		}
+		edbName := strings.TrimSpace(v.EdbName)
+		if edbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		if edbNameExist[edbName] {
+			br.Msg = "指标名称重复, 请重新输入"
+			return
+		}
+		edbNameExist[edbName] = true
+		if v.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		if v.Frequency == "" {
+			br.Msg = "请选择频度"
+			return
+		}
+		if v.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+	}
+	resp := tradeAnalysisResponse.WarehouseEdbSaveResp{
+		Fail:    make([]*tradeAnalysisModel.WarehouseEdbSaveRespItem, 0),
+		Success: make([]*tradeAnalysisModel.WarehouseEdbSaveRespItem, 0),
+	}
+
+	// 获取需要移除关联的指标
+	multiEdbList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取配置与图表指标关系失败, %v", e)
+		return
+	}
+	_, removeIds, e := tradeAnalysisService.CheckEdbSave(extraConfig, multiEdbList, req.IsSaveAs)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("校验指标是否需要新增失败, %v", e)
+		return
+	}
+
+	// 根据EdbInfoId判断是否需要更新指标
+	var newEdbIds []int
+	edbUpdateCols := []string{"CalculateFormula", "ModifyTime"}
+	newEdbRefresh := make([]*data_manage.EdbInfo, 0)
+	for _, v := range req.EdbInfoList {
+		// 图表配置-即指标计算公式
+		conf := extraConfig
+		conf.Companies = []string{v.UniqueFlag}
+		b, e := json.Marshal(conf)
+		if e != nil {
+			resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+				WarehouseEdbSaveItem: *v,
+				Tips:                 "获取失败",
+				ErrMsg:               fmt.Sprintf("指标配置JSON格式化异常, %v", e),
+			})
+			continue
+		}
+		formula := string(b)
+		//edb.CalculateFormula = string(b)
+		// 更新指标
+		edbInfo := new(data_manage.EdbInfo)
+		if v.EdbInfoId > 0 {
+			edb, e := data_manage.GetEdbInfoById(v.EdbInfoId)
+			if e != nil {
+				resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+					WarehouseEdbSaveItem: *v,
+					Tips:                 "获取失败",
+					ErrMsg:               fmt.Sprintf("获取失败, %v", e),
+				})
+				continue
+			}
+			edb.CalculateFormula = formula
+			edb.ModifyTime = time.Now().Local()
+			if e = edb.Update(edbUpdateCols); e != nil {
+				resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+					WarehouseEdbSaveItem: *v,
+					Tips:                 "更新失败",
+					ErrMsg:               fmt.Sprintf("更新指标计算公式失败, %v", e),
+				})
+				continue
+			}
+			edbInfo = edb
+
+			// 刷新指标数据
+			res, e := data.RefreshEdbData(edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource, edbInfo.EdbCode, utils.BASE_START_DATE)
+			if e != nil {
+				resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+					WarehouseEdbSaveItem: *v,
+					Tips:                 "刷新失败",
+					ErrMsg:               fmt.Sprintf("刷新失败, %v", e),
+				})
+				continue
+			}
+			if res.Ret != 200 {
+				resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+					WarehouseEdbSaveItem: *v,
+					Tips:                 "刷新失败",
+					ErrMsg:               fmt.Sprintf("刷新失败, Ret: %d, err: %v", res.Ret, e),
+				})
+				continue
+			}
+		}
+
+		// 新增指标
+		if v.EdbInfoId == 0 {
+			edbCode, e := utils.GenerateEdbCode(1, "")
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = fmt.Sprintf("生成指标编码失败, %v", e)
+				return
+			}
+			// PS:新增的开始结束日期为均为初始化日期
+			edb, e, errMsg, _ := data.EdbInfoAdd(utils.DATA_SOURCE_TRADE_ANALYSIS, utils.DATA_SUB_SOURCE_EDB, v.ClassifyId, edbCode, v.EdbName, v.Frequency, v.Unit, utils.BASE_START_DATE, utils.BASE_START_DATE, sysUser.AdminId, sysUser.RealName, this.Lang)
+			if e != nil {
+				resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+					WarehouseEdbSaveItem: *v,
+					Tips:                 errMsg,
+					ErrMsg:               fmt.Sprintf("新增失败, %v", e),
+				})
+				continue
+			}
+			edb.CalculateFormula = formula
+			edb.ModifyTime = time.Now().Local()
+			if e = edb.Update(edbUpdateCols); e != nil {
+				resp.Fail = append(resp.Fail, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+					WarehouseEdbSaveItem: *v,
+					Tips:                 "更新失败",
+					ErrMsg:               fmt.Sprintf("更新指标计算公式失败, %v", e),
+				})
+				continue
+			}
+			newEdbIds = append(newEdbIds, edb.EdbInfoId)
+			edbInfo = edb
+
+			// 新增指标的刷新走异步吧, 指标如果没数据, 用户自己删了就行
+			newEdbRefresh = append(newEdbRefresh, edbInfo)
+		}
+
+		v.EdbInfoId = edbInfo.EdbInfoId
+		resp.Success = append(resp.Success, &tradeAnalysisModel.WarehouseEdbSaveRespItem{
+			WarehouseEdbSaveItem: *v,
+			Tips:                 "操作成功",
+		})
+	}
+
+	// 移除关联的指标
+	if len(removeIds) > 0 {
+		if e = data_manage.DeleteMultipleGraphConfigEdbMappingByEdbIds(req.MultipleGraphConfigId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS, removeIds); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("移除配置与指标关联失败, %v", e)
+			return
+		}
+	}
+
+	// 新增指标关联
+	if !req.IsSaveAs && len(newEdbIds) > 0 {
+		newMultiEdb := make([]*data_manage.MultipleGraphConfigEdbMapping, 0)
+		for _, v := range newEdbIds {
+			newMultiEdb = append(newMultiEdb, &data_manage.MultipleGraphConfigEdbMapping{
+				MultipleGraphConfigId: req.MultipleGraphConfigId,
+				EdbInfoId:             v,
+				Source:                utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS,
+				ModifyTime:            time.Now(),
+				CreateTime:            time.Now(),
+			})
+		}
+		if len(newMultiEdb) > 0 {
+			if e = data_manage.AddMultipleGraphConfigEdbMappingList(newMultiEdb); e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = fmt.Sprintf("新增指标关联失败, %v", e)
+				return
+			}
+		}
+	}
+
+	// 异步刷新新增指标
+	if len(newEdbRefresh) > 0 {
+		go func() {
+			var refreshTips string
+			for _, edb := range newEdbRefresh {
+				_, e = data.RefreshEdbData(edb.EdbInfoId, edb.Source, edb.SubSource, edb.EdbCode, utils.BASE_START_DATE)
+				if e != nil {
+					refreshTips += fmt.Sprintf("刷新指标失败: %s, err: %v\n", edb.EdbCode, e)
+				}
+			}
+			if refreshTips != "" {
+				utils.FileLog.Info(refreshTips)
+				alarm_msg.SendAlarmMsg(refreshTips, 2)
+			}
+		}()
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.IsAddLog = true
+}

+ 683 - 0
controllers/trade_analysis/warehouse_classify.go

@@ -0,0 +1,683 @@
+package trade_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"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"
+)
+
+// WarehouseClassifyController 建仓过程-分类
+type WarehouseClassifyController struct {
+	controllers.BaseAuthController
+}
+
+//func (c *WarehouseClassifyController) ClassifyMove() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		if br.ErrMsg == "" {
+//			br.IsSendEmail = false
+//		}
+//		c.Data["json"] = br
+//		c.ServeJSON()
+//	}()
+//	var pars models.CommonClassifyMoveReq
+//
+//	classifyOb := new(trade_analysis.WareHouseProcessClassify)
+//	ctx := data.NewCommonClassifyCtx(classifyOb)
+//
+//	tips, e := data.CommonClassifyMove(pars, ctx)
+//	if tips != "" {
+//		br.Msg = tips
+//		return
+//	}
+//	if e != nil {
+//		br.Msg = "操作失败"
+//		br.ErrMsg = fmt.Sprintf("移动失败, %v", e)
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "操作成功"
+//}
+
+// ChartClassifyList
+// @Title 分类列表
+// @Description 分类列表接口
+// @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 /warehouse/classify/list [get]
+func (this *WarehouseClassifyController) 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)
+
+	isShowMe, _ := this.GetBool("IsShowMe")
+	parentId, _ := this.GetInt("ParentId")
+	source, _ := this.GetInt("Source", utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	// 查询分类节点
+	rootList, err := data_manage.GetChartClassifyByParentId(parentId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+	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)
+		}
+	}
+
+	// 查询图表节点, ParentId=0时说明仅查询一级目录节点
+	if parentId > 0 {
+		// 查询当前分类信息
+		currClassify, e := data_manage.GetChartClassifyById(parentId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取当前分类信息失败,Err:" + e.Error()
+			return
+		}
+
+		// 获取所有有权限的指标和分类
+		permissionEdbIdList, permissionClassifyIdList, e := data_manage_permission.GetUserChartAndClassifyPermissionList(this.SysUser.AdminId, 0, 0)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取所有有权限的指标和分类失败,Err:" + e.Error()
+			return
+		}
+
+		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)
+		}
+	}
+
+	// 整体排序
+	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 = "获取成功"
+	br.Data = resp
+}
+
+// ChartClassifyItems
+// @Title 获取所有分类接口-不包含图表
+// @Description 获取所有分类接口-不包含图表
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /warehouse/classify/items [get]
+func (this *WarehouseClassifyController) ChartClassifyItems() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	rootList, err := data_manage.GetChartClassifyByParentId(0, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		nodeAll = append(nodeAll, rootNode)
+	}
+	resp := new(data_manage.ChartClassifyListResp)
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// AddChartClassify
+// @Title 新增分类
+// @Description 新增分类接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /warehouse/classify/add [post]
+func (this *WarehouseClassifyController) AddChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.AddChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 新增图表分类
+	_, err, errMsg, isSendEmail := data.AddChartClassify(req.ChartClassifyName, req.ParentId, req.Level, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS, this.Lang, this.SysUser)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "添加分类失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "添加成功"
+	br.Success = true
+}
+
+// EditChartClassify
+// @Title 修改分类
+// @Description 修改分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /warehouse/classify/edit [post]
+func (this *WarehouseClassifyController) EditChartClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.EditChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.ChartClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 编辑图表分类
+	_, err, errMsg, isSendEmail := data.EditChartClassify(req.ChartClassifyId, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS, req.ChartClassifyName, this.Lang, this.SysUser)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "修改成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// DeleteChartClassifyCheck
+// @Title 删除图表检测接口
+// @Description 删除图表检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /warehouse/classify/delete/check [post]
+func (this *WarehouseClassifyController) DeleteChartClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.ChartClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartClassifyId < 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+	//删除分类
+	if req.ChartClassifyId > 0 && req.ChartInfoId == 0 {
+		//判断分类下,是否含有图表
+		count, err := data_manage.GetChartInfoCountByClassifyId(req.ChartClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联图表不可删除"
+		}
+	}
+
+	if deleteStatus != 1 && req.ChartInfoId == 0 {
+		classifyCount, err := data_manage.GetChartClassifyCountByClassifyId(req.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+		if classifyCount > 0 {
+			deleteStatus = 2
+			tipsMsg = "确认删除当前目录及包含的子目录吗"
+		}
+	}
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+
+	resp := new(data_manage.ChartClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// DeleteChartClassify
+// @Title 删除分类/图表
+// @Description 删除分类/图表接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /warehouse/classify/delete [post]
+func (this *WarehouseClassifyController) DeleteChartClassify() {
+	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
+	}
+
+	var req data_manage.DeleteChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartClassifyId < 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	//删除分类
+	if req.ChartClassifyId > 0 && req.ChartInfoId == 0 {
+		//判断是否含有指标
+		count, err := data_manage.GetChartInfoCountByClassifyId(req.ChartClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			br.Msg = "该目录下存在关联指标,不可删除"
+			br.IsSendEmail = false
+			return
+		}
+
+		err = data_manage.DeleteChartClassify(req.ChartClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+	resp := new(data_manage.AddChartInfoResp)
+	//删除图表
+	if req.ChartInfoId > 0 {
+		chartInfo, err := data_manage.GetChartInfoById(req.ChartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "图表已删除,请刷新页面"
+				br.ErrMsg = "指标不存在,Err:" + err.Error()
+				return
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,获取指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+		if chartInfo == nil {
+			br.Msg = "图表已删除,请刷新页面"
+			return
+		}
+		//图表操作权限
+		ok := data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+		if !ok {
+			br.Msg = "没有该图表的操作权限"
+			br.ErrMsg = "没有该图表的操作权限"
+			return
+		}
+
+		// 获取引用该图表的MyCharts, 用于ES删除
+		var myCond string
+		var myPars []interface{}
+		myCond += ` AND a.chart_info_id = ? `
+		myPars = append(myPars, chartInfo.ChartInfoId)
+		myCharts, e := data_manage.GetMyChartListGroupByCharyInfoIdAndAdminIdByCondition(myCond, myPars)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取引用图表的MyChats失败, Err: " + e.Error()
+			return
+		}
+		myIds := make([]int, 0)
+		for _, m := range myCharts {
+			myIds = append(myIds, m.MyChartId)
+		}
+
+		source := chartInfo.Source
+		//删除图表及关联指标
+		err = data_manage.DeleteChartInfoAndData(chartInfo.ChartInfoId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+		//删除ES
+		{
+			go data.EsDeleteChartInfo(chartInfo.ChartInfoId)
+			// 删除MY ETA 图表 es数据
+			//go data.EsDeleteMyChartInfoByChartInfoId(chartInfo.ChartInfoId)
+			go data.EsDeleteMyChartInfoByMyChartIds(myIds)
+		}
+
+		// 删除图例和多图关联
+		seriesOb := new(data_manage.ChartSeries)
+		{
+			cond := fmt.Sprintf("chart_info_id = ?")
+			pars := make([]interface{}, 0)
+			pars = append(pars, chartInfo.ChartInfoId)
+			if e = seriesOb.RemoveByCondition(cond, pars); e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = fmt.Sprintf("删除图表关联图例失败, %v", e)
+				return
+			}
+		}
+		if e = data_manage.RemoveMultiConfigChartMappingByChartInfoId(chartInfo.ChartInfoId); e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = fmt.Sprintf("删除图表多图配置关联失败, %v", e)
+			return
+		}
+
+		var condition string
+		var pars []interface{}
+		condition += " AND chart_classify_id=? AND source = ? "
+		pars = append(pars, chartInfo.ChartClassifyId, source)
+
+		condition += " AND chart_info_id>? ORDER BY create_time ASC LIMIT 1 "
+		pars = append(pars, req.ChartInfoId)
+
+		nextItem, err := data_manage.GetChartInfoByCondition(condition, pars)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
+			return
+		}
+
+		if nextItem != nil {
+			resp.UniqueCode = nextItem.UniqueCode
+			resp.ChartInfoId = nextItem.ChartInfoId
+		} else {
+			var condition string
+			var pars []interface{}
+
+			condition += " AND level=1 "
+			//pars = append(pars, chartInfo.ChartClassifyId)
+
+			condition += " AND chart_classify_id>? ORDER BY chart_classify_id ASC LIMIT 1 "
+			pars = append(pars, chartInfo.ChartClassifyId)
+
+			classifyItem, err := data_manage.GetChartClassifyByCondition(condition, pars)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "删除失败"
+				br.ErrMsg = "获取下一级图库分类信息失败,Err:" + err.Error()
+				return
+			}
+			if classifyItem != nil {
+				nextItem, err = data_manage.GetNextChartInfo(chartInfo.ChartClassifyId)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "删除失败"
+					br.ErrMsg = "获取下一级图库信息失败,Err:" + err.Error()
+					return
+				}
+				if nextItem != nil {
+					resp.UniqueCode = nextItem.UniqueCode
+					resp.ChartInfoId = nextItem.ChartInfoId
+				}
+			}
+		}
+
+		//新增操作日志
+		{
+			chartLog := new(data_manage.ChartInfoLog)
+			chartLog.ChartName = chartInfo.ChartName
+			chartLog.ChartInfoId = req.ChartInfoId
+			chartLog.ChartClassifyId = chartInfo.ChartClassifyId
+			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)
+		}
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// ChartClassifyMove
+// @Title 分类移动接口
+// @Description 分类移动接口
+// @Success 200 {object} data_manage.MoveChartClassifyReq
+// @router /warehouse/classify/move [post]
+func (this *WarehouseClassifyController) 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 = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.MoveChartClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyId <= 0 && req.ChartInfoId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "请选择拖动目标,分类目录或者指标"
+		return
+	}
+
+	err, errMsg := data.MoveChartClassify(req, sysUser, utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+// ClassifyTree
+// @Title 多层分类列表树
+// @Description 多层分类列表树
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /warehouse/classify/tree [get]
+func (this *WarehouseClassifyController) ClassifyTree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	allList, err := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取所有分类失败, Err:" + err.Error()
+		return
+	}
+	nodeAll := make([]*data_manage.ChartClassifyItems, 0)
+
+	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
+		}
+
+		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
+		}
+
+		nodeAll = data.GetChartClassifyTreeRecursive(allList, 0)
+		//根据sort值排序
+		sort.Slice(nodeAll, func(i, j int) bool {
+			return nodeAll[i].Sort < nodeAll[j].Sort
+		})
+	}
+
+	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.Data = resp
+}

+ 19 - 0
controllers/user_login.go

@@ -1044,12 +1044,29 @@ func (this *UserLoginController) BaseInfo() {
 		return
 	}
 
+	logoCnMini, e := models.GetBusinessConfByKey("LogoCNMini")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
+		return
+	}
+
+	logoEnMini, e := models.GetBusinessConfByKey("LogoENMini")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
+		return
+	}
+
+
 	type BaseInfoResp struct {
 		Icp      *models.BusinessConf `description:"Icp信息"`
 		ETATitle *models.BusinessConf `description:"eta系统名称"`
 		TabName  *models.BusinessConf `description:"tab页名称"`
 		LogoCn   *models.BusinessConf `description:"中文logo"`
 		LogoEn   *models.BusinessConf `description:"英文logo"`
+		LogoCnMini   *models.BusinessConf `description:"中文logoMini"`
+		LogoEnMini   *models.BusinessConf `description:"英文logoMini"`
 	}
 
 	resp := BaseInfoResp{
@@ -1058,6 +1075,8 @@ func (this *UserLoginController) BaseInfo() {
 		TabName:  tabName,
 		LogoCn:   logoCn,
 		LogoEn:   logoEn,
+		LogoCnMini:   logoCnMini,
+		LogoEnMini:   logoEnMini,
 	}
 
 	br.Data = resp

+ 5 - 0
go.mod

@@ -88,6 +88,8 @@ require (
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/hhrutter/lzw v1.0.0 // indirect
+	github.com/hhrutter/tiff v1.0.1 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -96,6 +98,7 @@ require (
 	github.com/leodido/go-urn v1.2.0 // indirect
 	github.com/magiconair/properties v1.8.1 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mattn/go-runewidth v0.0.15 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
 	github.com/minio/sha256-simd v1.0.1 // indirect
@@ -105,6 +108,7 @@ require (
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
+	github.com/pdfcpu/pdfcpu v0.8.0 // indirect
 	github.com/pelletier/go-toml v1.9.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.16.0 // indirect
@@ -113,6 +117,7 @@ require (
 	github.com/prometheus/procfs v0.10.1 // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/rs/xid v1.5.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect

+ 11 - 0
go.sum

@@ -319,6 +319,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
+github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
+github.com/hhrutter/tiff v1.0.1 h1:MIus8caHU5U6823gx7C6jrfoEvfSTGtEFRiM8/LOzC0=
+github.com/hhrutter/tiff v1.0.1/go.mod h1:zU/dNgDm0cMIa8y8YwcYBeuEEveI4B0owqHyiPpJPHc=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
@@ -376,6 +380,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -443,6 +449,8 @@ github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:Ff
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pdfcpu/pdfcpu v0.8.0 h1:SuEB4uVsPFz1nb802r38YpFpj9TtZh/oB0bGG34IRZw=
+github.com/pdfcpu/pdfcpu v0.8.0/go.mod h1:jj03y/KKrwigt5xCi8t7px2mATcKuOzkIOoCX62yMho=
 github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.9.2 h1:7NiByeVF4jKSG1lDF3X8LTIkq2/bu+1uYbIm1eS5tzk=
@@ -496,6 +504,9 @@ github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK
 github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
 github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=

+ 80 - 0
models/common_classify.go

@@ -0,0 +1,80 @@
+package models
+
+import "time"
+
+// CommonClassify 通用分类
+type CommonClassify struct {
+	ClassifyId   int       `description:"分类ID"`
+	ClassifyName string    `description:"分类名称"`
+	ParentId     int       `description:"父级ID"`
+	RootId       int       `description:"顶级ID"`
+	Level        int       `description:"层级"`
+	LevelPath    string    `description:"层级路径"`
+	Sort         int       `description:"排序"`
+	CreateTime   time.Time `description:"创建时间"`
+	ModifyTime   time.Time `description:"修改时间"`
+}
+
+// CommonClassifyCols 通用分类基本字段
+type CommonClassifyCols struct {
+	ClassifyId   string `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     string `description:"父级id"`
+	RootId       string `description:"顶级id"`
+	Level        string `description:"层级"`
+	LevelPath    string `description:"层级路径"`
+	Sort         string `description:"排序字段,越小越靠前,默认值:10"`
+	CreateTime   string `description:"创建时间"`
+	ModifyTime   string `description:"修改时间"`
+}
+
+// CommonClassifyObj 通用分类对象
+type CommonClassifyObj struct {
+	ObjectId   int       `description:"对象ID"`
+	ClassifyId int       `description:"分类ID"`
+	Sort       int       `description:"排序"`
+	CreateTime time.Time `description:"创建时间"`
+	ModifyTime time.Time `description:"修改时间"`
+}
+
+// CommonClassifyObjCols 通用分类对象基本字段
+type CommonClassifyObjCols struct {
+	ObjectId   string `description:"对象ID"`
+	ClassifyId string `description:"分类ID"`
+	Sort       string `description:"排序"`
+	CreateTime string `description:"创建时间"`
+	ModifyTime string `description:"修改时间"`
+}
+
+// CommonClassifyMoveReq 移动分类
+type CommonClassifyMoveReq struct {
+	ClassifyId       int `description:"分类ID"`
+	ParentClassifyId int `description:"父级分类ID"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类ID"`
+	NextClassifyId   int `description:"下一个兄弟节点分类ID"`
+	ObjectId         int `description:"对象ID(指标/图表..), 如果对象ID>0则移动对象, 否则认为移动分类"`
+	PrevObjectId     int `description:"上一个对象ID"`
+	NextObjectId     int `description:"下一个对象ID"`
+}
+
+// ExtraPermissionClassifyStrategy 是一个带有额外权限校验的装饰器
+//type ExtraPermissionClassifyStrategy struct {
+//	BaseClassifyStrategy
+//}
+
+// UpdateCommonClassify 覆盖基础策略的UpdateClassify方法,并添加额外的权限校验
+//func (s *ExtraPermissionClassifyStrategy) UpdateCommonClassify(classify *CommonClassify) error {
+//	// 额外的权限校验
+//	if !checkExtraPermission(classify) {
+//		return fmt.Errorf("无操作权限")
+//	}
+//
+//	// 调用基础策略的UpdateClassify方法
+//	return s.BaseClassifyStrategy.UpdateCommonClassify(classify)
+//}
+//
+//// checkExtraPermission 进行额外的权限校验
+//func checkExtraPermission(classify *CommonClassify) bool {
+//	// 实现额外权限校验逻辑
+//	return true
+//}

+ 36 - 16
models/data_manage/base_from_eia_stero.go

@@ -2,9 +2,10 @@ package data_manage
 
 import (
 	"eta/eta_api/utils"
+	"time"
+
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
-	"time"
 )
 
 // BaseFromEiaSteoIndex EiaSteo指标
@@ -33,20 +34,11 @@ type BaseFromEiaSteoIndexItem struct {
 	BaseFromEiaSteoIndexId    int    `orm:"column(base_from_eia_steo_index_id);pk"`
 	BaseFromEiaSteoClassifyId int    `description:"指标分类id"`
 	IndexCode                 string `description:"指标编码"`
-	IndexName                 string `description:"指标名称(中文名称)"`
-	IndexNameOriginal         string `description:"指标名称(原始名称)"`
-	Frequency                 string `description:"频度"`
-	Level                     int    `description:"指标层级(原始层级)"`
-	Unit                      string `description:"单位"`
-	Super                     string `description:"我也不知道是个啥,反正先存起来,万一用到了呢"`
-	Precision                 int    `description:"精度,我也不知道会不会用到,反正先存起来,万一用到了呢"`
-	LastHistorical            string `description:"最近的历史记录,我也不知道会不会用到,反正先存起来,万一用到了呢"`
-	Description               string `description:"备注信息,我也不知道会不会用到,反正先存起来,万一用到了呢"`
-	IsMappable                int    `description:"是否可映射,我也不知道会不会用到,反正先存起来,万一用到了呢"`
-	StartDate                 string `description:"开始日期"`
-	EndDate                   string `description:"结束日期"`
-	ModifyTime                string `description:"最新更新时间"`
-	CreateTime                string `description:"创建时间"`
+	IndexName                 string `description:"指标名称"`
+	EdbInfoId                 int    `description:"eta指标库的id"`
+	EdbUniqueCode             string `description:"指标库唯一编码"`
+	EdbClassifyId             int    `description:"指标库分类ID"`
+	EdbExist                  int    `description:"指标库是否已添加:0-否;1-是"`
 }
 
 // BaseFromEiaSteoClassify EiaSteo分类
@@ -54,10 +46,21 @@ type BaseFromEiaSteoClassify struct {
 	BaseFromEiaSteoClassifyId int       `orm:"column(base_from_eia_steo_classify_id);pk"`
 	ClassifyName              string    `description:"分类名称(中文名称)"`
 	ClassifyNameOriginal      string    `description:"分类名称(原始名称)"`
+	ParentId                  int       `description:"父级id"`
+	Level                     int       `description:"层级"`
 	ModifyTime                time.Time `description:"最新更新时间"`
 	CreateTime                time.Time `description:"创建时间"`
 }
 
+type BaseFromEiaSteoClassifyView struct {
+	BaseFromEiaSteoClassifyId int                            `orm:"column(base_from_eia_steo_classify_id);pk"`
+	ClassifyName              string                         `description:"分类名称(中文名称)"`
+	ClassifyNameOriginal      string                         `description:"分类名称(原始名称)"`
+	ParentId                  int                            `description:"父级id"`
+	Level                     int                            `description:"层级"`
+	Child                     []*BaseFromEiaSteoClassifyView `description:"子级分类列表"`
+}
+
 // BaseFromEiaSteoData EiaSteo数据表
 type BaseFromEiaSteoData struct {
 	BaseFromEiaSteoDataId  int       `orm:"column(base_from_eia_steo_data_id);pk"`
@@ -81,18 +84,35 @@ type BaseFromEiaSteoDataItem struct {
 }
 
 // GetEiaSteoClassifyList 获取分类列表
-func GetEiaSteoClassifyList() (items []*BaseFromEiaSteoClassify, err error) {
+func GetEiaSteoClassifyList() (items []*BaseFromEiaSteoClassifyView, err error) {
 	sql := ` SELECT * FROM base_from_eia_steo_classify  ORDER BY base_from_eia_steo_classify_id ASC `
 	o := orm.NewOrmUsingDB("data")
 	o.Raw(sql).QueryRows(&items)
 	return
 }
 
+// GetEiaSteoClassifyById 根据分类id获取分类
+func GetEiaSteoClassifyById(classifyId int) (item *BaseFromEiaSteoClassify, err error) {
+	sql := ` SELECT * FROM base_from_eia_steo_classify WHERE base_from_eia_steo_classify_id=? `
+	o := orm.NewOrmUsingDB("data")
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// GetChildEiaSteoClassifyById 获取子分类列表
+func GetChildEiaSteoClassifyById(classifyId int) (items []*BaseFromEiaSteoClassify, err error) {
+	sql := ` SELECT * FROM base_from_eia_steo_classify WHERE parent_id=? `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
 type EiaSteoIndexListResp struct {
 	BaseFromEiaSteoIndexId    int                `orm:"column(base_from_eia_steo_index_id);pk"`
 	BaseFromEiaSteoClassifyId int                `description:"指标分类id"`
 	IndexCode                 string             `description:"指标编码"`
 	IndexName                 string             `description:"指标名称(中文名称)"`
+	EdbInfoId                 int                `description:"指标库id"`
 	Unit                      string             `description:"单位"`
 	Frequency                 string             `description:"频度"`
 	StartDate                 string             `description:"开始日期"`

+ 141 - 0
models/data_manage/base_from_fenwei.go

@@ -31,6 +31,7 @@ type BaseFromFenweiIndexList struct {
 	ModifyTime    string
 	DataList      []*BaseFromFenweiData
 	Paging        *paging.PagingItem `description:"分页数据"`
+	EdbInfoId     int                `description:"指标库主键id"`
 }
 
 type FenweiSingleDataResp struct {
@@ -43,6 +44,7 @@ type FenweiSingleDataResp struct {
 	CreateTime    string
 	ModifyTime    string
 	Data          []*FenweiSingleData
+	EdbInfoId     int `description:"指标库主键id"`
 }
 
 type FenweiSingleData struct {
@@ -50,6 +52,45 @@ type FenweiSingleData struct {
 	DataTime string `orm:"column(data_time)" description:"值"`
 }
 
+// BaseFromFenWeiIndexBatchAddCheckReq 校验编码是否存在请求参数
+type BaseFromFenWeiIndexBatchAddCheckReq struct {
+	IndexCodes []string `form:"IndexCodes" description:"指标编码列表"`
+}
+
+// IndexCheckData 校验编码是否存在
+type FenWeiIndexCheckData struct {
+	IndexCode  string `orm:"column(index_code)" description:"指标编码"`
+	IndexName  string `orm:"column(index_name)" description:"指标名称"`
+	Frequency  string `orm:"column(frequency)" description:"频度"`
+	Unit       string `orm:"column(unit)" description:"单位"`
+	EdbInfoId  int    `json:"edb_info_id" description:"指标库主键id"`
+	UniqueCode string `json:"unique_code" description:"指标库唯一编码"`
+	ClassifyId int    `json:"classify_id" description:"分类id"`
+}
+
+// NameCheckResult 校验名称是否存在
+type FenWeiNameCheckResult struct {
+	IndexCode string `from:"EdbCode" description:"edb编码"`
+	IndexName string `from:"EdbName" description:"edb名称"`
+	Exist     bool
+}
+
+// FenWeiIndexAddReq 指标添加vo
+type FenWeiIndexAddReq struct {
+	EdbCode       string `description:"指标编码"`
+	EdbName       string `description:"指标名称"`
+	Frequency     string `description:"频度"`
+	Unit          string `description:"单位"`
+	ClassifyId    int    `description:"分类ID"`
+	AdminId       int    `description:"管理员ID"`
+	AdminRealName string `description:"管理员名称"`
+}
+
+type BaseFromFenWeiIndexPage struct {
+	List   []*BaseFromFenweiIndex `description:"指标列表"`
+	Paging *paging.PagingItem     `description:"分页数据"`
+}
+
 func GetFenweiIndex(condition string, pars interface{}) (items []*BaseFromFenweiIndexList, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM base_from_fenwei_index WHERE 1=1  `
@@ -142,3 +183,103 @@ func GetBaseFromFenweiIndexByIndexCode(indexCode string) (list *BaseFromFenweiIn
 	err = o.Raw(sql, indexCode).QueryRow(&list)
 	return
 }
+
+// GetFenWeiIndexFrequency 获取指标频度
+func GetFenWeiIndexFrequency(classifyId int) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency 
+        FROM base_from_fenwei_index 
+        WHERE frequency is not null`
+
+	// 如果 classifyId > 0,则添加该条件
+	if classifyId > 0 {
+		sql += ` AND classify_id = ?`
+	}
+
+	sql += ` ORDER BY FIELD(frequency, '日度', '周度', '月度', '季度', '半年度', '年度')`
+
+	o := orm.NewOrmUsingDB("data")
+	if classifyId > 0 {
+		_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	} else {
+		_, err = o.Raw(sql).QueryRows(&items)
+	}
+
+	return items, err
+}
+
+// GetFenWeiIndexByCodeAndClassify 根据指标编码和分类查询 indexCode非必传
+func GetFenWeiIndexByCodeAndClassify(indexCode string, classifyId int, frequency *string) (items []*BaseFromFenweiIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// SQL 查询语句
+	sql := `SELECT a.index_code, a.index_name, a.frequency, a.unit, MAX(b.modify_time) AS modify_time
+			FROM base_from_fenwei_index AS a
+			INNER JOIN base_from_fenwei_data AS b ON a.fenwei_index_id = b.fenwei_index_id
+			WHERE 1=1`
+
+	var params []interface{}
+
+	if classifyId != 0 {
+		sql += ` AND a.classify_id = ?`
+		params = append(params, classifyId)
+	}
+
+	// 如果 indexCode 不为空,增加过滤条件
+	if indexCode != "" {
+		sql += ` AND a.index_code = ?`
+		params = append(params, indexCode)
+	}
+
+	if frequency != nil {
+		sql += ` AND a.frequency = ?`
+		params = append(params, *frequency)
+	}
+
+	sql += ` GROUP BY a.index_code, a.index_name, a.frequency, a.unit`
+
+	_, err = o.Raw(sql, params...).QueryRows(&items)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// GetBaseFromFenWeiDataByIndexCode 根据指标编码查询
+func GetBaseFromFenWeiDataByIndexCode(indexCode string) (items []*BaseFromFenweiData, err error) {
+	sql := `SELECT * FROM base_from_fenwei_data WHERE index_code=? ORDER BY data_time desc`
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+// GetFenWeiDataListByIndexCodes 根据指标编码查询
+func GetFenWeiDataListByIndexCodes(IndexCodes string) (items []string, err error) {
+	sql := ` SELECT data_time FROM base_from_fenwei_data WHERE index_code IN(` + IndexCodes + `)  GROUP BY data_time DESC `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetFenWeiIndexInfoPage 分页查询指标信息
+func GetFenWeiIndexInfoPage(condition string, pars []interface{}) (items []*BaseFromFenweiIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_fenwei_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY fenwei_index_id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+
+}
+
+// GetFenWeiIndexInfoCount 查询指标信息总数
+func GetFenWeiIndexInfoCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM base_from_fenwei_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}

+ 13 - 0
models/data_manage/base_from_fenwei_classify.go

@@ -53,3 +53,16 @@ func (y *BaseFromFenweiClassify) Format2Item(origin *BaseFromFenweiClassify) (it
 	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
 	return
 }
+
+// GetFenweiClassifyItemByClassifyId 根据分类id查询分类信息
+func GetFenweiClassifyItemByClassifyId(classifyId int) (item *BaseFromFenweiClassifyItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_fenwei_classify WHERE classify_id = ?`
+	var origin BaseFromFenweiClassify
+	err = o.Raw(sql, classifyId).QueryRow(&origin)
+	if err != nil {
+		return
+	}
+	item = origin.Format2Item(&origin)
+	return
+}

+ 48 - 0
models/data_manage/base_from_ly_classify.go

@@ -0,0 +1,48 @@
+// Package data_manage
+// @Author gmy 2024/8/7 9:26:00
+package data_manage
+
+import "github.com/beego/beego/v2/client/orm"
+
+type BaseFromLyClassify struct {
+	BaseFromLyClassifyId int    `orm:"column(base_from_ly_classify_id);pk" description:"分类ID"`
+	CreateTime           string `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime           string `orm:"column(modify_time)" description:"修改时间"`
+	ClassifyName         string `orm:"column(classify_name)" description:"分类名称"`
+	ParentId             int    `orm:"column(parent_id)" description:"上级id"`
+	Sort                 int    `orm:"column(sort)" description:"排序字段,越小越靠前"`
+	ClassifyNameEn       string `orm:"column(classify_name_en)" description:"英文分类名称"`
+}
+
+type BaseFromLyClassifyAndIndexInfo struct {
+	BaseFromLyClassifyId int    `orm:"column(base_from_ly_classify_id);pk" description:"分类ID"`
+	CreateTime           string `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime           string `orm:"column(modify_time)" description:"修改时间"`
+	ClassifyName         string `orm:"column(classify_name)" description:"分类名称"`
+	ParentId             int    `orm:"column(parent_id)" description:"上级id"`
+	Sort                 int    `orm:"column(sort)" description:"排序字段,越小越靠前"`
+	ClassifyNameEn       string `orm:"column(classify_name_en)" description:"英文分类名称"`
+	IndexId              int    `orm:"column(base_from_ly_index_id)" description:"指标id"`
+	IndexCode            string `orm:"column(index_code)" description:"指标编码"`
+	IndexName            string `orm:"column(index_name)" description:"指标名称"`
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromLyClassify))
+}
+
+// GetAllLyClassify 查询所有分类
+func GetAllLyClassify() (items []*BaseFromLyClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_ly_classify ORDER BY sort asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetLyClassifyById 根据分类id查询
+func GetLyClassifyById(classifyId int) (item *BaseFromLyClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_ly_classify WHERE base_from_ly_classify_id=?`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}

+ 108 - 0
models/data_manage/base_from_ly_data.go

@@ -0,0 +1,108 @@
+// Package data_manage
+// @Author gmy 2024/8/7 9:50:00
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+)
+
+type BaseFromLyData struct {
+	BaseFromLyDataId  int     `orm:"column(base_from_ly_data_id);pk" description:"数据ID"`
+	CreateTime        string  `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime        string  `orm:"column(modify_time)" description:"修改时间"`
+	BaseFromLyIndexId int     `orm:"column(base_from_ly_index_id)" description:"指标id"`
+	IndexCode         string  `orm:"column(index_code)" description:"指标编码"`
+	DataTime          string  `orm:"column(data_time)" description:"数据日期"`
+	Value             float64 `orm:"column(value)" description:"数据值"`
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromLyData))
+}
+
+type BaseFromLyDataPage struct {
+	List   []*BaseFromLyData  `description:"指标数据列表"`
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+// GetLyDataCountByIndexId 获取指标数据总数
+func GetLyDataCountByIndexId(indexId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT count(*) FROM base_from_ly_data WHERE base_from_ly_index_id=?`
+	err = o.Raw(sql, indexId).QueryRow(&count)
+	return
+}
+
+// GetLyDataPageByIndexId 获取指标数据分页列表
+func GetLyDataPageByIndexId(indexId int, startSize, pageSize int) (items []*BaseFromLyData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_ly_data WHERE base_from_ly_index_id=? ORDER BY data_time desc LIMIT ?,?`
+	_, err = o.Raw(sql, indexId, (startSize-1)*pageSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetBaseFromLyDataByIndexCode 根据指标编码查询
+func GetBaseFromLyDataByIndexCode(indexCode string) (items []*BaseFromLyData, err error) {
+	sql := `SELECT * FROM base_from_ly_data WHERE index_code=? ORDER BY data_time desc`
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+func GetLyDataListByIndexCodes(IndexCodes string) (items []string, err error) {
+	sql := ` SELECT data_time FROM base_from_ly_data WHERE index_code IN(` + IndexCodes + `)  GROUP BY data_time DESC `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetLyLastUpdateTimeLastByIndexCode 根据指标编码查询 返回ModifyTime最后一条数据
+func GetLyLastUpdateTimeLastByIndexCode(indexCodes []string) (items []*BaseFromLyData, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 将 indexCodes 切片转换为逗号分隔的字符串
+	placeholders := strings.Repeat("?,", len(indexCodes)-1) + "?"
+
+	// 构造 SQL 查询
+	sql := `SELECT index_code, MAX(modify_time) AS modify_time
+            FROM base_from_ly_data
+            WHERE index_code IN (` + placeholders + `)
+            GROUP BY index_code`
+
+	// 执行 SQL 查询
+	_, err = o.Raw(sql, indexCodes).QueryRows(&items)
+	if err != nil {
+		return nil, err
+	}
+	return items, nil
+}
+
+// GetLyLastDataTimeByIndexCode 根据指标编码查询 返回data_time最后一条数据的value
+func GetLyLastDataTimeByIndexCode(indexCodes []string) (items []*BaseFromLyData, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 将 indexCodes 切片转换为逗号分隔的字符串
+	placeholders := strings.Repeat("?,", len(indexCodes)-1) + "?"
+
+	// 构造 SQL 查询
+	sql := `
+       SELECT t1.*
+       FROM base_from_ly_data t1
+       INNER JOIN (
+           SELECT index_code, MAX(data_time) AS data_time
+			FROM base_from_ly_data
+			WHERE index_code IN (` + placeholders + `)
+			GROUP BY index_code
+       ) t2 
+       ON t1.index_code = t2.index_code AND t1.data_time = t2.data_time
+   `
+
+	// 执行 SQL 查询
+	_, err = o.Raw(sql, indexCodes).QueryRows(&items)
+	if err != nil {
+		return nil, err
+	}
+	return items, nil
+}

+ 244 - 0
models/data_manage/base_from_ly_index.go

@@ -0,0 +1,244 @@
+// Package data_manage
+// @Author gmy 2024/8/7 9:38:00
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type BaseFromLyIndex struct {
+	BaseFromLyIndexId    int    `orm:"column(base_from_ly_index_id);pk" description:"指标ID"`
+	CreateTime           string `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime           string `orm:"column(modify_time)" description:"修改时间"`
+	BaseFromLyClassifyId int    `orm:"column(base_from_ly_classify_id)" description:"原始数据指标分类id"`
+	IndexCode            string `orm:"column(index_code)" description:"指标编码"`
+	IndexName            string `orm:"column(index_name)" description:"指标名称"`
+	Frequency            string `orm:"column(frequency)" description:"频度"`
+	Unit                 string `orm:"column(unit)" description:"单位"`
+	EdbExist             int    `orm:"column(edb_exist)" description:"指标库是否已添加:0-否;1-是"`
+}
+
+// 在 init 函数中注册模型
+func init() {
+	orm.RegisterModel(new(BaseFromLyIndex))
+}
+
+type BaseFromLyIndexPage struct {
+	List   []*BaseFromLyIndexAndData `description:"指标列表"`
+	Paging *paging.PagingItem        `description:"分页数据"`
+}
+
+type BaseFromLyIndexBatchAddCheckReq struct {
+	IndexCodes []string `form:"IndexCodes" description:"指标编码列表"`
+}
+
+type BaseFromLyIndexNameCheck struct {
+	IndexCode string `from:"IndexCode" description:"指标编码"`
+	IndexName string `from:"IndexName" description:"指标名称"`
+}
+
+type NameCheckResult struct {
+	IndexCode string `from:"EdbCode" description:"edb编码"`
+	IndexName string `from:"EdbName" description:"edb名称"`
+	Exist     bool
+}
+
+type BaseFromLyIndexAndData struct {
+	BaseFromLyIndexId    int     `orm:"column(base_from_ly_index_id);pk" description:"指标ID"`
+	CreateTime           string  `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime           string  `orm:"column(modify_time)" description:"修改时间"`
+	BaseFromLyClassifyId int     `orm:"column(base_from_ly_classify_id)" description:"原始数据指标分类id"`
+	IndexCode            string  `orm:"column(index_code)" description:"指标编码"`
+	IndexName            string  `orm:"column(index_name)" description:"指标名称"`
+	Frequency            string  `orm:"column(frequency)" description:"频度"`
+	Unit                 string  `orm:"column(unit)" description:"单位"`
+	EdbExist             int     `orm:"column(edb_exist)" description:"指标库是否已添加:0-否;1-是"`
+	ModifyTimeMax        string  `json:"modify_time_max" description:"最后修改时间"`
+	Value                float64 `orm:"column(value)" description:"数据值"`
+}
+
+type IndexCheckData struct {
+	IndexCode  string `orm:"column(index_code)" description:"指标编码"`
+	IndexName  string `orm:"column(index_name)" description:"指标名称"`
+	Frequency  string `orm:"column(frequency)" description:"频度"`
+	Unit       string `orm:"column(unit)" description:"单位"`
+	EdbInfoId  int    `json:"edb_info_id" description:"指标库主键id"`
+	UniqueCode string `json:"unique_code" description:"指标库唯一编码"`
+	ClassifyId int    `json:"classify_id" description:"分类id"`
+}
+
+// GetLyIndexByClassifyIds 通过分类ids查询指标列表
+func GetLyIndexByClassifyIds(classifyIds []int) (items []*BaseFromLyIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 创建查询条件
+	qs := o.QueryTable("base_from_ly_index")
+
+	// 使用 Filter 进行查询
+	_, err = qs.Filter("base_from_ly_classify_id__in", classifyIds).All(&items)
+
+	return
+}
+
+// GetLyIndexCount 获取指标总数
+func GetLyIndexCount(classifyId string, searchParam string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 构建 SQL 查询语句
+	sql := `SELECT COUNT(*) FROM base_from_ly_index WHERE 1=1`
+
+	var params []interface{}
+
+	// 添加 classifyId 过滤条件
+	if classifyId != "" {
+		sql += ` AND base_from_ly_classify_id = ?`
+		params = append(params, classifyId)
+	}
+
+	// 添加搜索条件
+	if searchParam != "" {
+		sql += ` AND (index_name LIKE ? OR index_code = ?)`
+		params = append(params, "%"+searchParam+"%", searchParam)
+	}
+
+	// 执行查询
+	err = o.Raw(sql, params...).QueryRow(&count)
+	if err != nil {
+		return 0, err
+	}
+	return count, nil
+}
+
+// GetLyIndexPage 获取指标列表
+func GetLyIndexPage(classifyId string, searchParam string, currentIndex, pageSize int) (items []*BaseFromLyIndexAndData, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 构建 SQL 查询语句
+	sql := `SELECT * FROM base_from_ly_index WHERE 1=1`
+
+	var params []interface{}
+
+	// 添加 classifyId 过滤条件
+	if classifyId != "" {
+		sql += ` AND base_from_ly_classify_id = ?`
+		params = append(params, classifyId)
+	}
+
+	// 添加搜索条件
+	if searchParam != "" {
+		sql += ` AND (index_name LIKE ? OR index_code = ?)`
+		params = append(params, "%"+searchParam+"%", searchParam)
+	}
+
+	// 添加排序和分页条件
+	sql += ` ORDER BY base_from_ly_index_id DESC LIMIT ?, ?`
+	params = append(params, (currentIndex-1)*pageSize, pageSize)
+
+	// 执行查询
+	_, err = o.Raw(sql, params...).QueryRows(&items)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// UpdateLyIndexEdbExist 指标库标记已添加
+func UpdateLyIndexEdbExist(indexCode string, isExist int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 构建 SQL 更新语句
+	sql := `UPDATE base_from_ly_index SET edb_exist = ? WHERE index_code = ?`
+
+	// 执行 SQL 语句
+	_, err = o.Raw(sql, isExist, indexCode).Exec()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// GetLyIndexList 根据传入条件查询指标列表
+func GetLyIndexList(condition string, pars interface{}) (items []*BaseFromLyIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_ly_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += `ORDER BY base_from_ly_index_id ASC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetLyDataMaxCount 获取分类下指标最大数据量
+func GetLyDataMaxCount(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT MAX(t.num) AS count FROM (
+				SELECT COUNT(1) AS num  FROM base_from_ly_index AS a
+				INNER JOIN base_from_ly_data AS b ON a.base_from_ly_index_id=b.base_from_ly_index_id
+				WHERE a.base_from_ly_classify_id=?
+				GROUP BY a.base_from_ly_index_id
+			)AS t `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// GetLyIndexByCodeAndClassify 根据指标编码和分类查询 indexCode非必传
+func GetLyIndexByCodeAndClassify(indexCode string, classifyId int, frequency *string) (items []*BaseFromLyIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// SQL 查询语句
+	sql := `SELECT a.index_code, a.index_name, a.frequency, a.unit, MAX(b.modify_time) AS modify_time
+			FROM base_from_ly_index AS a
+			INNER JOIN base_from_ly_data AS b ON a.base_from_ly_index_id = b.base_from_ly_index_id
+			WHERE 1=1`
+
+	var params []interface{}
+
+	if classifyId != 0 {
+		sql += ` AND a.base_from_ly_classify_id = ?`
+		params = append(params, classifyId)
+	}
+
+	// 如果 indexCode 不为空,增加过滤条件
+	if indexCode != "" {
+		sql += ` AND a.index_code = ?`
+		params = append(params, indexCode)
+	}
+
+	if frequency != nil {
+		sql += ` AND a.frequency = ?`
+		params = append(params, *frequency)
+	}
+
+	sql += ` GROUP BY a.index_code, a.index_name, a.frequency, a.unit`
+
+	_, err = o.Raw(sql, params...).QueryRows(&items)
+	if err != nil {
+		return nil, err
+	}
+	return
+}
+
+// GetLyIndexFrequency 获取指标频度
+func GetLyIndexFrequency(classifyId int) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency 
+        FROM base_from_ly_index 
+        WHERE frequency IS NOT NULL`
+
+	// 如果 classifyId > 0,则添加该条件
+	if classifyId > 0 {
+		sql += ` AND base_from_ly_classify_id = ?`
+	}
+
+	sql += ` ORDER BY FIELD(frequency, '日度', '周度', '月度', '季度', '半年度', '年度')`
+
+	o := orm.NewOrmUsingDB("data")
+	if classifyId > 0 {
+		_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	} else {
+		_, err = o.Raw(sql).QueryRows(&items)
+	}
+
+	return items, err
+}

+ 14 - 2
models/data_manage/chart_info.go

@@ -59,6 +59,7 @@ type ChartInfo struct {
 	IsJoinPermission  int    `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	ForumChartInfoId  int    `description:"社区的图表ID"`
 	ChartAlias        string `description:"图表别名"`
+	DateTypeNum       int    `description:"date_type=25(N月前)时的N值,其他N值可复用此字段"`
 }
 
 type ChartInfoMore struct {
@@ -70,6 +71,10 @@ type ChartInfoMore struct {
 func AddChartInfo(item *ChartInfo) (lastId int64, err error) {
 	o := orm.NewOrmUsingDB("data")
 	lastId, err = o.Insert(item)
+	if err != nil {
+		return
+	}
+	item.ChartInfoId = int(lastId)
 	return
 }
 
@@ -174,6 +179,7 @@ type SaveChartInfoReq struct {
 	MinMaxSave       int              `description:"是否手动保存过上下限:0-否;1-是"`
 	ExtraConfig      string           `description:"图表额外配置,json数据"`
 	StartYear        int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	DateTypeNum      int              `description:"date_type=25(N月前)时的N值,其他N值可复用此字段"`
 }
 
 type ChartSaveItem struct {
@@ -196,6 +202,7 @@ type ChartSaveItem struct {
 	ConvertValue      float64 `description:"数据转换值"`
 	ConvertUnit       string  `description:"数据转换单位"`
 	ConvertEnUnit     string  `description:"数据转换单位"`
+	UniqueFlag        string  `description:"唯一标识"`
 }
 
 func DeleteChartInfoAndData(chartInfoId int) (err error) {
@@ -263,6 +270,7 @@ type EditChartInfoReq struct {
 	Unit                 string                  `description:"中文单位名称"`
 	UnitEn               string                  `description:"英文单位名称"`
 	ChartAlias           string                  `description:"图表别名"`
+	DateTypeNum          int                     `description:"date_type=25(N月前)时的N值,其他N值可复用此字段"`
 }
 
 type EditFutureGoodChartInfoReq struct {
@@ -728,6 +736,7 @@ type ChartEdbInfoMapping struct {
 	ConvertEnUnit       string  `description:"数据转换单位"`
 	IsJoinPermission    int     `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth       bool    `description:"是否有数据权限,默认:false"`
+	UniqueFlag          string  `description:"唯一标识(与唯一编码不是一个东西)"`
 }
 
 type QuarterData struct {
@@ -1066,6 +1075,9 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 	sql += `,chart_alias = ? `
 	pars = append(pars, req.ChartAlias)
 
+	sql += `,date_type_num = ? `
+	pars = append(pars, req.DateTypeNum)
+
 	sql += `WHERE chart_info_id = ?`
 
 	pars = append(pars, req.ChartInfoId)
@@ -1417,8 +1429,6 @@ func EditChartBaseInfoAndEdbEnInfo(req *EditChartInfoBaseReq, chartItem *ChartIn
 		updateChartCols = append(updateChartCols, "ExtraConfig")
 	}
 
-
-
 	chartItem.ModifyTime = time.Now()
 	updateChartCols = append(updateChartCols, "ModifyTime")
 	_, err = to.Update(chartItem, updateChartCols...)
@@ -1502,6 +1512,7 @@ type AddChartInfoReq struct {
 	Unit                 string                  `description:"中文单位名称"`
 	UnitEn               string                  `description:"英文单位名称"`
 	ChartAlias           string                  `description:"图表别名"`
+	DateTypeNum          int                     `description:"date_type=25(N月前)时的N值,其他类型N值可复用此字段"`
 }
 
 type AddFutureGoodChartInfoReq struct {
@@ -1958,6 +1969,7 @@ type ChartInfoView struct {
 	HaveOperaAuth     bool            `description:"是否有数据权限,默认:false"`
 	ForumChartInfoId  int             `description:"社区的图表ID"`
 	ChartAlias        string          `description:"图表别名"`
+	DateTypeNum       int             `description:"date_type=25(N月前)时的N值,其他类型N值可复用此字段"`
 }
 
 type ChartViewButton struct {

+ 438 - 0
models/data_manage/chart_info_range_analysis.go

@@ -0,0 +1,438 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strconv"
+	"strings"
+	"time"
+)
+
+type ChartRangeAnalysisExtraConf struct {
+	EdbInfoMode           int                                `description:"指标模式 0: 单指标,1: 多指标"`
+	DateRangeType         int                                `description:"区间划分类型 0:智能划分,1:手工划分,2:跨年划分"`
+	AutoDateConf          ChartRangeAnalysisAutoDateConf     `description:"智能划分时间区间配置"`
+	ManualDateConf        []ChartRangeAnalysisManualDateConf `description:"手工划分时间区间配置"`
+	YearDateConf          ChartRangeAnalysisYearDateConf     `description:"跨年划分时间区间配置"`
+	CalculateType         int                                `description:"计算类型 0: 区间均值,1: 区间累计值,2:区间涨幅,3:区间年化增长率,4:区间最大值,5:区间最小值"`
+	UnNormalDataDealType  int                                `description:"异常值处理配置 0:不处理,1:剔除,2替换"`
+	UnNormalDataConf      ChartRangeAnalysisDeleteDataConf
+	DataConvertType       int                               `description:"数据转换类型 0不转, 1乘 2除 3对数"`
+	DataConvertConf       ChartRangeAnalysisDataConvertConf `description:"数据转换详情"`
+	SeriesName            string                            `description:"指标系列名称"`
+	EdbInfoType           int                               `description:"指标类型:0普通指标,1预测指标"`
+	MultipleGraphConfigId int                               `description:"配置ID"`
+}
+
+type ChartRangeAnalysisAutoDateChangeConf struct {
+	BaseDateType int `description:"基准日期类型:0指标日期,1系统日期"`
+	MoveForward  int `description:"前移的期数"`
+	DateChange   []*EdbDataDateChangeConf
+}
+
+type EdbDataDateChangeConf struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
+type ChartRangeAnalysisDeleteDataConf struct {
+	Formula      string  `description:"比较符号:=、>、<、>=、<="`
+	Value        float64 `description:"比较的值"`
+	ReplaceValue float64 `description:"替换的值"`
+}
+
+type ChartRangeAnalysisDataConvertConf struct {
+	Value  float64 `description:"数据转换值"`
+	Unit   string  `description:"数据转换单位"`
+	EnUnit string  `description:"数据转换单位"`
+}
+
+type ChartRangeAnalysisManualDateConf struct { //手工划分
+	StartDate string `description:"开始日期"`
+	EndDate   string `description:"结束日期"`
+}
+
+type ChartRangeAnalysisAutoDateConf struct { //智能划分
+	IsAutoStartDate int                                  `description:"起始日期是否是动态设置:0固定,1动态"`
+	StartDate       string                               `description:"固定模式下的起始日期"`
+	EndDate         string                               `description:"固定模式下的截止日期"`
+	IsAutoEndDate   int                                  `description:"截止日期是否是动态设置:0固定,1动态"`
+	StartDateConf   ChartRangeAnalysisAutoDateChangeConf `description:"动态起始日期配置"`
+	EndDateConf     ChartRangeAnalysisAutoDateChangeConf `description:"动态截止日期配置"`
+}
+
+type ChartRangeAnalysisYearDateConf struct {
+	StartDay string `description:"开始日"`
+	EndDay   string `description:"结束日"`
+}
+
+// ChartRangeAnalysisPreviewReq 相关性图表请求体
+type ChartRangeAnalysisPreviewReq struct {
+	ExtraConfig      ChartRangeAnalysisExtraConf
+	ChartEdbInfoList []*ChartSaveItem `description:"指标及配置信息"`
+	DateType         int              `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间,6:起始日期至今 20:最近N年"`
+	StartDate        string           `description:"自定义开始日期"`
+	EndDate          string           `description:"自定义结束日期"`
+	StartYear        int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+}
+
+type ChartRangeAnalysisManualDateConfList []ChartRangeAnalysisManualDateConf
+
+func (a ChartRangeAnalysisManualDateConfList) Len() int      { return len(a) }
+func (a ChartRangeAnalysisManualDateConfList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ChartRangeAnalysisManualDateConfList) Less(i, j int) bool {
+	return a[i].StartDate < a[j].StartDate
+}
+
+type ChartRangeAnalysisDataResp struct { //图表详情返回值
+	*ChartRangeAnalysisExtraConf
+	SeriesId     int `description:"指标系列ID"`
+	ConfigEdbNum int `description:"生成的指标数"`
+}
+
+type ChartRangeAnalysisDateDataItem struct {
+	StartDate time.Time
+	EndDate   time.Time
+	DataList  []*EdbDataList
+}
+
+// CreateRangeChartAndEdb 新增区间计算图表
+func CreateRangeChartAndEdb(chartInfo *ChartInfo, edbMappingList []*ChartEdbMapping) (chartInfoId int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+		} else {
+			_ = tx.Commit()
+		}
+	}()
+
+	// 新增图表信息
+	newId, err := tx.Insert(chartInfo)
+	if err != nil {
+		return
+	}
+	// 指标mapping
+	chartInfo.ChartInfoId = int(newId)
+	chartInfoId = int(newId)
+	if len(edbMappingList) > 0 {
+		for i := range edbMappingList {
+			edbMappingList[i].ChartInfoId = chartInfoId
+		}
+		_, err = tx.InsertMulti(len(edbMappingList), edbMappingList)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// EditRangeChartInfoAndMapping 修改区间计算图表的 图表与指标 的关系
+func EditRangeChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr, lang string, calendar string, dateType, disabled int, barChartConf string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	var pars []interface{}
+	pars = append(pars, req.ChartName)
+	pars = append(pars, req.ChartNameEn)
+	pars = append(pars, edbInfoIdStr)
+	pars = append(pars, req.ChartType)
+	pars = append(pars, req.ChartClassifyId)
+	pars = append(pars, disabled)
+	pars = append(pars, barChartConf)
+	pars = append(pars, req.ExtraConfig)
+	pars = append(pars, req.StartYear)
+	pars = append(pars, req.ChartThemeId)
+	pars = append(pars, req.SourcesFrom)
+	pars = append(pars, req.Instructions)
+	pars = append(pars, req.MarkersLines)
+	pars = append(pars, req.MarkersAreas)
+	pars = append(pars, req.Unit)
+	pars = append(pars, req.UnitEn)
+
+	sql := ` UPDATE  chart_info
+			SET
+			  chart_name =?,
+			  chart_name_en =?,
+              edb_info_ids=?,
+			  chart_type=?,
+			  chart_classify_id = ?,
+			  modify_time = NOW(),
+              disabled = ?,
+              bar_config = ?,
+              extra_config = ?, 
+ 			  start_year = ?,
+ 			  chart_theme_id = ?,
+ 			  sources_from = ?,
+ 			  instructions = ?,
+ 			  markers_lines = ?,
+ 			  markers_areas = ?,
+ 			  unit = ?,
+ 			  unit_en = ?
+			`
+	if calendar != "" {
+		sql += `,calendar = ? `
+		pars = append(pars, calendar)
+	}
+	if dateType > 0 {
+		sql += `,date_type = ? `
+		pars = append(pars, dateType)
+	}
+
+	sql += `,start_date = ? `
+	pars = append(pars, req.StartDate)
+
+	sql += `,end_date = ? `
+	pars = append(pars, req.EndDate)
+
+	sql += `,season_start_date = ? `
+	pars = append(pars, req.StartDate)
+
+	sql += `,season_end_date = ? `
+	pars = append(pars, req.EndDate)
+
+	sql += `,left_min = ? `
+	pars = append(pars, req.LeftMin)
+
+	sql += `,left_max = ? `
+	pars = append(pars, req.LeftMax)
+
+	sql += `,right_min = ? `
+	pars = append(pars, req.RightMin)
+
+	sql += `,right_max = ? `
+	pars = append(pars, req.RightMax)
+
+	sql += `,right2_min = ? `
+	pars = append(pars, req.Right2Min)
+
+	sql += `,right2_max = ? `
+	pars = append(pars, req.Right2Max)
+
+	sql += `,min_max_save = ? `
+	pars = append(pars, req.MinMaxSave)
+
+	sql += `WHERE chart_info_id = ?`
+
+	pars = append(pars, req.ChartInfoId)
+	_, err = to.Raw(sql, pars).Exec()
+	if err != nil {
+		fmt.Println("UPDATE  chart_info Err:", err.Error())
+		return err
+	}
+	chartEdbMappingIdList := make([]string, 0)
+	for _, v := range req.ChartEdbInfoList {
+		// 查询该指标是否存在,如果存在的话,那么就去修改,否则新增
+		var tmpChartEdbMapping *ChartEdbMapping
+		csql := `SELECT *  FROM chart_edb_mapping WHERE chart_info_id=? AND edb_info_id=? AND source = ? `
+		err = to.Raw(csql, req.ChartInfoId, v.EdbInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS).QueryRow(&tmpChartEdbMapping)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			fmt.Println("QueryRow Err:", err.Error())
+			return err
+		}
+		if tmpChartEdbMapping != nil {
+			chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(tmpChartEdbMapping.ChartEdbMappingId))
+			tmpChartEdbMapping.ModifyTime = time.Now()
+			//tmpChartEdbMapping.MaxData = v.MaxData
+			//tmpChartEdbMapping.MinData = v.MinData
+			//tmpChartEdbMapping.IsOrder = v.IsOrder
+			tmpChartEdbMapping.EdbAliasName = v.EdbAliasName
+			tmpChartEdbMapping.IsAxis = v.IsAxis
+			//tmpChartEdbMapping.EdbInfoType = v.EdbInfoType
+			//tmpChartEdbMapping.LeadValue = v.LeadValue
+			//tmpChartEdbMapping.LeadUnit = v.LeadUnit
+			//tmpChartEdbMapping.ChartStyle = v.ChartStyle
+			//tmpChartEdbMapping.ChartColor = v.ChartColor
+			//tmpChartEdbMapping.PredictChartColor = v.PredictChartColor
+			//tmpChartEdbMapping.ChartWidth = v.ChartWidth
+			_, err = to.Update(tmpChartEdbMapping, "ModifyTime", "IsAxis", "EdbAliasName")
+			if err != nil {
+				fmt.Println("chart_edb_mapping Err:" + err.Error())
+				return err
+			}
+		} else {
+			mapItem := new(ChartEdbMapping)
+			mapItem.ChartInfoId = req.ChartInfoId
+			mapItem.EdbInfoId = v.EdbInfoId
+			mapItem.EdbAliasName = v.EdbAliasName
+			mapItem.IsAxis = v.IsAxis
+			mapItem.CreateTime = time.Now()
+			mapItem.ModifyTime = time.Now()
+			timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+			mapItem.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp + "_" + strconv.Itoa(v.EdbInfoId))
+			//mapItem.MaxData = v.MaxData
+			//mapItem.MinData = v.MinData
+			//mapItem.IsOrder = true
+			mapItem.EdbInfoType = 1
+			//mapItem.LeadValue = v.LeadValue
+			//mapItem.LeadUnit = v.LeadUnit
+			//mapItem.ChartStyle = v.ChartStyle
+			//mapItem.ChartColor = v.ChartColor
+			//mapItem.PredictChartColor = v.PredictChartColor
+			//mapItem.ChartWidth = v.ChartWidth
+			mapItem.Source = utils.CHART_SOURCE_RANGE_ANALYSIS
+			tmpId, err := to.Insert(mapItem)
+			if err != nil {
+				fmt.Println("AddChartEdbMapping Err:" + err.Error())
+				return err
+			}
+			mapItem.ChartEdbMappingId = int(tmpId)
+			chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(mapItem.ChartEdbMappingId))
+		}
+	}
+	if len(chartEdbMappingIdList) > 0 {
+		chartEdbMappingIdStr := strings.Join(chartEdbMappingIdList, ",")
+		dsql := `DELETE FROM chart_edb_mapping WHERE chart_info_id=? AND chart_edb_mapping_id NOT IN(` + chartEdbMappingIdStr + `)`
+		_, err = to.Raw(dsql, req.ChartInfoId).Exec()
+		if err != nil {
+			fmt.Println("delete err:" + err.Error())
+			return err
+		}
+	}
+
+	return
+}
+
+// EditRangeChartInfo 修改区间计算图表基本信息
+func EditRangeChartInfo(req *SaveChartRangeReq) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	var pars []interface{}
+	pars = append(pars, req.StartYear)
+
+	sql := ` UPDATE  chart_info
+			SET
+			  modify_time = NOW(),
+ 			  start_year = ?
+			`
+	if req.DateType > 0 {
+		sql += `,date_type = ? `
+		pars = append(pars, req.DateType)
+	}
+
+	sql += `,start_date = ? `
+	pars = append(pars, req.StartDate)
+
+	sql += `,end_date = ? `
+	pars = append(pars, req.EndDate)
+
+	sql += `,left_min = ? `
+	pars = append(pars, req.LeftMin)
+
+	sql += `,left_max = ? `
+	pars = append(pars, req.LeftMax)
+
+	sql += `,right_min = ? `
+	pars = append(pars, req.RightMin)
+
+	sql += `,right_max = ? `
+	pars = append(pars, req.RightMax)
+
+	sql += `,right2_min = ? `
+	pars = append(pars, req.Right2Min)
+
+	sql += `,right2_max = ? `
+	pars = append(pars, req.Right2Max)
+
+	sql += `,min_max_save = ? `
+	pars = append(pars, req.MinMaxSave)
+
+	sql += `WHERE chart_info_id = ?`
+
+	pars = append(pars, req.ChartInfoId)
+	_, err = to.Raw(sql, pars).Exec()
+	if err != nil {
+		fmt.Println("UPDATE  chart_info Err:", err.Error())
+		return err
+	}
+
+	return
+}
+
+// SaveChartRangeAnalysisEdbReq 指标保存请求
+type SaveChartRangeAnalysisEdbReq struct {
+	EdbInfoList           []*CalculateEdbInfoItem `description:"指标列表"`
+	ExtraConfig           string
+	MultipleGraphConfigId int  `description:"配置id"`
+	IsSaveAs              bool `description:"是否另存为,true的话,就是另存为,不会建立与配置的关系"`
+	EdbInfoType           int  `description:"指标类型"`
+}
+
+// ChartRangeAnalysisConfigEdbResp 指标列表
+type ChartRangeAnalysisConfigEdbResp struct {
+	EdbInfoList []*ChartRangeAnalysisConfigEdbItem `description:"指标列表"`
+}
+
+type ChartRangeAnalysisConfigEdbItem struct {
+	EdbInfoId     int    `description:"指标id"`
+	EdbName       string `description:"指标名称"`
+	EdbNameEn     string `description:"指标名称"`
+	Frequency     string `description:"频度"`
+	Unit          string `description:"单位"`
+	UnitEn        string `description:"单位"`
+	ClassifyId    int    `description:"分类id"`
+	FromEdbInfoId int    `description:"计算来源指标id"`
+	EdbTypeInfo   int    `description:"指标类型,0普通指标,1预测指标"`
+}
+
+type SortEdbDataList []*EdbDataList
+
+func (m SortEdbDataList) Len() int {
+	return len(m)
+}
+
+func (m SortEdbDataList) Less(i, j int) bool {
+	return m[i].DataTime > m[j].DataTime
+}
+
+func (m SortEdbDataList) Swap(i, j int) {
+	m[i], m[j] = m[j], m[i]
+}
+
+type EditChartRangeBaseReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	ChartName   string `description:"英文图表名称"`
+}
+type SaveChartRangeReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	DateType    int    `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间,6:起始日期至今,20:N年至今"`
+	StartDate   string `description:"自定义开始日期"`
+	EndDate     string `description:"自定义结束日期"`
+	Calendar    string `description:"公历/农历"`
+	LeftMin     string `description:"图表左侧最小值"`
+	LeftMax     string `description:"图表左侧最大值"`
+	RightMin    string `description:"图表右侧最小值"`
+	RightMax    string `description:"图表右侧最大值"`
+	Right2Min   string `description:"图表右侧最小值"`
+	Right2Max   string `description:"图表右侧最大值"`
+	MinMaxSave  int    `description:"是否手动保存过上下限:0-否;1-是"`
+	StartYear   int    `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+}

+ 20 - 0
models/data_manage/chart_series.go

@@ -24,6 +24,7 @@ type ChartSeries struct {
 	IsOrder       bool      `description:"true:正序,false:逆序"`
 	CreateTime    time.Time `description:"创建时间"`
 	ModifyTime    time.Time `description:"修改时间"`
+	UniqueFlag    string    `description:"唯一标识"`
 }
 
 func (c *ChartSeries) TableName() string {
@@ -372,3 +373,22 @@ func (s ChartSectionSeriesValSortDesc) Less(i, j int) bool {
 func (s ChartSectionSeriesValSortDesc) Swap(i, j int) {
 	s[i], s[j] = s[j], s[i]
 }
+
+func (c *ChartSeries) CreateMulti(items []*ChartSeries) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(utils.MultiAddNum, items)
+	return
+}
+
+func (m *ChartSeries) 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
+}

+ 4 - 0
models/data_manage/edb_data_base.go

@@ -179,6 +179,10 @@ func GetEdbDataTableName(source, subSource int) (tableName string) {
 		tableName = "edb_data_sci99"
 	case utils.DATA_SOURCE_SCI_HQ:
 		tableName = "edb_data_sci_hq"
+	case utils.DATA_SOURCE_LY: // 粮油商务网->86
+		tableName = "edb_data_ly"
+	case utils.DATA_SOURCE_TRADE_ANALYSIS: // 持仓分析->92
+		tableName = "edb_data_trade_analysis"
 	default:
 		edbSource := EdbSourceIdMap[source]
 		if edbSource != nil {

+ 6 - 0
models/data_manage/edb_info.go

@@ -73,6 +73,7 @@ type EdbInfoFullClassify struct {
 	CorrelationStr string `description:"相关性系数字符串"`
 	ClassifyList   []*EdbClassifyIdItems
 	HaveOperaAuth  bool `description:"是否有数据权限,默认:false"`
+	IsSupplierStop int  `description:"是否供应商停更:1:停更,0:未停更"`
 }
 
 func AddEdbInfo(item *EdbInfo) (lastId int64, err error) {
@@ -227,6 +228,10 @@ type MysteelChemicalDataBatchAddCheckReq struct {
 	IndexCodes []string `form:"IndexCodes" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
 }
 
+type EiaSteoDataBatchAddCheckReq struct {
+	IndexCodes []string `description:"指标编码"`
+}
+
 type AddEdbInfoReq struct {
 	Source     int    `description:"来源id"`
 	EdbCode    string `description:"指标编码"`
@@ -448,6 +453,7 @@ type EdbInfoList struct {
 	NoUpdate         int8                    `description:"是否停止更新,0:继续更新;1:停止更新"`
 	IsJoinPermission int                     `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth    bool                    `description:"是否有数据权限,默认:false"`
+	IsSupplierStop   int                     `description:"是否供应商停更:1:停更,0:未停更"`
 }
 
 type EdbDataInsertConfigItem struct {

+ 18 - 17
models/data_manage/edb_info_calculate.go

@@ -255,23 +255,24 @@ type EdbInfoCalculateBatchSaveReqByEdbLib struct {
 
 // EdbInfoCalculateBatchEditReqByEdbLib 编辑计算指标的请求参数
 type EdbInfoCalculateBatchEditReqByEdbLib struct {
-	EdbName       string `description:"指标名称"`
-	Frequency     string `description:"频度"`
-	Unit          string `description:"单位"`
-	ClassifyId    int    `description:"分类id"`
-	AdminId       int    `description:"操作人id"`
-	AdminName     string `description:"操作人姓名"`
-	Formula       string `description:"N值"`
-	EdbInfoId     int    `description:"编辑指标id"`
-	FromEdbInfoId int    `description:"计算来源指标id"`
-	Source        int    `description:"来源:1:同花顺,2:wind,3:彭博,4:指标运算,5:累计值转月,6:同比值,7:同差值,8:N数值移动平均计算,12:环比值,13:环差值,14:变频"`
-	MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
-	MoveFrequency string `description:"移动频度:天/周/月/季/年"`
-	Calendar      string `description:"公历/农历"`
-	EdbInfoIdArr  []EdbInfoFromTag
-	Data          interface{} `description:"数据列"`
-	Extra         string      `description:"指标的额外配置"`
-	EmptyType     int         `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	EdbName          string `description:"指标名称"`
+	Frequency        string `description:"频度"`
+	Unit             string `description:"单位"`
+	ClassifyId       int    `description:"分类id"`
+	AdminId          int    `description:"操作人id"`
+	AdminName        string `description:"操作人姓名"`
+	Formula          string `description:"N值"`
+	EdbInfoId        int    `description:"编辑指标id"`
+	FromEdbInfoId    int    `description:"计算来源指标id"`
+	Source           int    `description:"来源:1:同花顺,2:wind,3:彭博,4:指标运算,5:累计值转月,6:同比值,7:同差值,8:N数值移动平均计算,12:环比值,13:环差值,14:变频"`
+	MoveType         int    `description:"移动方式:1:领先(默认),2:滞后"`
+	MoveFrequency    string `description:"移动频度:天/周/月/季/年"`
+	Calendar         string `description:"公历/农历"`
+	EdbInfoIdArr     []EdbInfoFromTag
+	Data             interface{} `description:"数据列"`
+	Extra            string      `description:"指标的额外配置"`
+	EmptyType        int         `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	CalculateFormula string      `description:"计算公式"`
 }
 
 func GetEdbInfoCalculateMap(edbInfoId, source int) (list []*EdbInfo, err error) {

+ 16 - 5
models/data_manage/edb_info_relation.go

@@ -236,6 +236,7 @@ type BaseRelationEdbInfo struct {
 	SysUserRealName string `description:"创建人姓名"`
 	Frequency       string `description:"频度"`
 	IsStop          int    `description:"是否停更:1:停更,0:未停更"`
+	IsSupplierStop  int    `description:"是否供应商停更:1:停更,0:未停更"`
 	RelationNum     int    `description:"引用次数"`
 	RelationTime    string `description:"引用时间"`
 }
@@ -261,11 +262,16 @@ type BaseRelationEdbInfoDetailResp struct {
 }
 
 // 查询指标引用列表
-func GetEdbInfoRelationList(condition string, pars []interface{}, orderBy string, startSize, pageSize int) (total int, items []*BaseRelationEdbInfo, err error) {
+func GetEdbInfoRelationList(condition string, pars []interface{}, addFieldStr, joinTableStr, 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 `
+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  `
+
+	if joinTableStr != "" {
+		totalSql += joinTableStr
+	}
+	totalSql += ` WHERE 1=1 `
 	if condition != "" {
 		totalSql += condition
 	}
@@ -274,10 +280,15 @@ SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as re
 		return
 	}
 
+	fieldStr := ` 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 ` + addFieldStr
 	// 列表数据
-	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
- `
+	sql := ` SELECT ` + fieldStr + ` 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  `
+	if joinTableStr != "" {
+		sql += joinTableStr
+	}
+	sql += ` WHERE 1=1 `
+
 	if condition != "" {
 		sql += condition
 	}

+ 82 - 0
models/data_manage/excel/excel_info_rule_mapping.go

@@ -0,0 +1,82 @@
+package excel
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ExcelInfoRuleMapping struct {
+	ExcelInfoRuleMappingId int       `orm:"pk" description:"主键"`
+	ExcelInfoId            int       `description:"Excel信息ID"`
+	RuleType               int       `description:"规则类型"`
+	LeftValue              string    `description:"左值"`
+	LeftValueBack          string    `description:"左值后端存储"`
+	LeftValueType          int       `description:"左值类型"`
+	RightValue             string    `description:"右值"`
+	RightValueBack         string    `description:"右值后端存储"`
+	RightValueType         int       `description:"右值类型"`
+	FontColor              string    `description:"字体颜色"`
+	BackgroundColor        string    `description:"背景颜色"`
+	Remark                 string    `description:"预设颜色说明"`
+	RemarkEn               string    `description:"预设颜色英文说明"`
+	Scope                  string    `description:"作用范围"`
+	ScopeCoord             string    `description:"作用范围坐标"`
+	ScopeShow              string    `description:"作用范围坐标前端显示"`
+	CreateTime             time.Time `description:"创建时间"`
+}
+
+type ExcelInfoRuleMappingView struct {
+	ExcelInfoRuleMappingId int    `orm:"pk" description:"主键"`
+	ExcelInfoId            int    `description:"Excel信息ID"`
+	RuleType               int    `description:"规则类型"`
+	LeftValue              string `description:"左值"`
+	LeftValueBack          string `description:"左值后端存储" json:"-"`
+	LeftValueType          int    `description:"左值类型"`
+	RightValue             string `description:"右值"`
+	RightValueBack         string `description:"右值后端存储" json:"-"`
+	RightValueType         int    `description:"右值类型"`
+	FontColor              string `description:"字体颜色"`
+	BackgroundColor        string `description:"背景颜色"`
+	Remark                 string `description:"预设颜色说明"`
+	RemarkEn               string `description:"预设颜色英文说明"`
+	Scope                  string `description:"作用范围"`
+	ScopeCoord             string `description:"作用范围坐标"`
+	ScopeShow              string `description:"作用范围坐标前端显示"`
+	CreateTime             string `description:"创建时间"`
+}
+
+func (e *ExcelInfoRuleMapping) Insert() (insertId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	insertId, err = o.Insert(e)
+	return
+}
+
+func (e *ExcelInfoRuleMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(e, cols...)
+	return
+}
+
+// GetExcelRuleMappingByExcelInfoId 根据excelInfoId获取规则映射信息
+func GetExcelRuleMappingByExcelInfoId(id int) (items []*ExcelInfoRuleMappingView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_info_rule_mapping WHERE excel_info_id = ? ORDER BY create_time ASC`
+	_, err = o.Raw(sql, id).QueryRows(&items)
+	return
+}
+
+// GetExcelRuleMappingById 根据主键Id获取规则映射信息
+func GetExcelRuleMappingById(id int) (item *ExcelInfoRuleMappingView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM excel_info_rule_mapping WHERE excel_info_rule_mapping_id = ?`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func DeleteExcelRuleMappingById(id int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM excel_info_rule_mapping WHERE excel_info_rule_mapping_id = ?`
+	_, err = o.Raw(sql, id).Exec()
+	return
+}

+ 111 - 1
models/data_manage/excel/request/excel_info.go

@@ -1,5 +1,7 @@
 package request
 
+import "encoding/json"
+
 // MoveExcelInfoReq 移动excel表格请求
 type MoveExcelInfoReq struct {
 	ExcelClassifyId int `description:"excel表格分类id"`
@@ -61,7 +63,16 @@ type CalculateReq struct {
 
 // GetOtherEdbDateDataReq 获取其他指标的指定日期的数据
 type GetOtherEdbDateDataReq struct {
-	EdbInfoId int      `description:"指标id"`
+	EdbInfoId   int            `description:"指标id"`
+	SortType    string         `description:"如何排序,是正序还是倒序,枚举值:asc 正序,desc 倒序,不传默认倒序"`
+	DateList    []string       `description:"日期列表,从小到大"`
+	DateDecimal map[string]int `description:"日期行的小数位数映射"`
+}
+
+type GetBatchEdbDateDataReq struct {
+	EdbInfoId []int    `description:"指标id"`
+	Num       int      `description:"默认期数"`
+	Decimal   []int    `description:"指标上的小数位数"`
 	SortType  string   `description:"如何排序,是正序还是倒序,枚举值:asc 正序,desc 倒序,不传默认倒序"`
 	DateList  []string `description:"日期列表,从小到大"`
 }
@@ -71,6 +82,7 @@ type GetFutureDateDataReq struct {
 	EdbInfoIdList []int  `description:"指标id列表,从左至右,从上到下的顺序"`
 	DateType      int    `description:"日期类型,1:期数,2:截止日期"`
 	Num           int    `description:"需要添加的期数"`
+	Decimal       []int  `description:"小数位数, 从左至右,从上到下的顺序"`
 	StartDate     string `description:"开始日期"`
 	EndDate       string `description:"结束日期"`
 }
@@ -79,6 +91,7 @@ type GetFutureDateDataReq struct {
 type GetHistoryDateDataReq struct {
 	EdbInfoIdList []int  `description:"指标id列表,从左至右,从上到下的顺序"`
 	Num           int    `description:"需要添加的期数"`
+	Decimal       []int  `description:"小数位数, 从左至右,从上到下的顺序"`
 	EndDate       string `description:"结束日期"`
 }
 
@@ -96,6 +109,12 @@ type TableDataReq struct {
 	Sort          int               `description:"日期排序,1:倒序,2:正序"`
 	Data          []EdbInfoData     `description:"数据列表"`
 	TextRowData   [][]ManualDataReq `description:"文本列表"`
+	DecimalConfig []DecimalConfig   `description:"小数位数配置"`
+}
+type DecimalConfig struct {
+	Row     string `description:"行上的索引, 目前仅保存指标的日期"`
+	Col     int    `description:"列上的索引, 目前仅保存edbInfoId"`
+	Decimal int    `description:"小数位数, 从左至右,从上到下的顺序"`
 }
 
 // EdbInfoData 自定义表格的数据
@@ -108,12 +127,54 @@ type EdbInfoData struct {
 	Frequency        string          `description:"频度"`
 	Unit             string          `description:"单位"`
 	UnitEn           string          `description:"英文单位"`
+	Decimal          int             `description:"小数位数"`
+	ClassifyId       int             `description:"所属分类" json:"-"`
+	IsJoinPermission int             `description:"是否加入权限管控,0:不加入;1:加入;默认:0" json:"-"`
+	HaveOperaAuth    bool            `description:"是否有数据权限,默认:false"`
+	Data             []ManualDataReq `description:"单元格数据列表"`
+}
+type EdbInfoDataTemp struct {
+	EdbInfoId        int             `description:"指标ID"`
+	Tag              string          `description:"标签"`
+	EdbName          string          `description:"指标名称"`
+	EdbNameEn        string          `description:"英文指标名称"`
+	EdbAliasName     string          `description:"指标别名"`
+	Frequency        string          `description:"频度"`
+	Unit             string          `description:"单位"`
+	UnitEn           string          `description:"英文单位"`
+	Decimal          *int            `description:"小数位数"`
 	ClassifyId       int             `description:"所属分类" json:"-"`
 	IsJoinPermission int             `description:"是否加入权限管控,0:不加入;1:加入;默认:0" json:"-"`
 	HaveOperaAuth    bool            `description:"是否有数据权限,默认:false"`
 	Data             []ManualDataReq `description:"单元格数据列表"`
 }
 
+func (e *EdbInfoData) UnmarshalJSON(data []byte) error {
+	var alias EdbInfoDataTemp
+
+	if err := json.Unmarshal(data, &alias); err != nil {
+		return err
+	}
+	if alias.Decimal != nil {
+		e.Decimal = *alias.Decimal
+	} else {
+		e.Decimal = -1
+	}
+	e.EdbInfoId = alias.EdbInfoId
+	e.Tag = alias.Tag
+	e.EdbName = alias.EdbName
+	e.EdbNameEn = alias.EdbNameEn
+	e.EdbAliasName = alias.EdbAliasName
+	e.Frequency = alias.Frequency
+	e.Unit = alias.Unit
+	e.UnitEn = alias.UnitEn
+	e.ClassifyId = alias.ClassifyId
+	e.IsJoinPermission = alias.IsJoinPermission
+	e.HaveOperaAuth = alias.HaveOperaAuth
+	e.Data = alias.Data
+	return nil
+}
+
 // ManualDataReq 自定义表格的单元格数据
 type ManualDataReq struct {
 	DataType            int               `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算,5:预测值"`
@@ -121,9 +182,40 @@ type ManualDataReq struct {
 	DataTimeType        int               `description:"日期类型,1:实际日期;2:未来日期"`
 	ShowValue           string            `description:"展示值"`
 	Value               string            `description:"实际值(计算公式)"`
+	Decimal             int               `description:"小数位数"`
 	RelationEdbInfoList []RelationEdbInfo `description:"关联指标(计算公式中关联的指标,用于计算的时候去匹配)"`
 }
 
+type ManualDataTemp struct {
+	DataType            int               `description:"数据类型,1:普通的,2:插值法,3:手动输入,4:公式计算,5:预测值"`
+	DataTime            string            `description:"所属日期"`
+	DataTimeType        int               `description:"日期类型,1:实际日期;2:未来日期"`
+	ShowValue           string            `description:"展示值"`
+	Value               string            `description:"实际值(计算公式)"`
+	Decimal             *int              `description:"小数位数"`
+	RelationEdbInfoList []RelationEdbInfo `description:"关联指标(计算公式中关联的指标,用于计算的时候去匹配)"`
+}
+
+func (m *ManualDataReq) UnmarshalJSON(data []byte) error {
+	var alias ManualDataTemp
+
+	if err := json.Unmarshal(data, &alias); err != nil {
+		return err
+	}
+	if alias.Decimal != nil {
+		m.Decimal = *alias.Decimal
+	} else {
+		m.Decimal = -1
+	}
+	m.DataType = alias.DataType
+	m.DataTime = alias.DataTime
+	m.DataTimeType = alias.DataTimeType
+	m.ShowValue = alias.ShowValue
+	m.Value = alias.Value
+	m.RelationEdbInfoList = alias.RelationEdbInfoList
+	return nil
+}
+
 // RelationEdbInfo 自定义表格中单元格的关联指标
 type RelationEdbInfo struct {
 	Tag string `description:"指标标签"`
@@ -160,3 +252,21 @@ type ShareExcelInfoReq struct {
 	ViewUserIds []int `description:"查看权限用户IDs"`
 	EditUserIds []int `description:"编辑权限用户IDs"`
 }
+
+type ExcelRuleMappingReq struct {
+	ExcelRuleMappingId int    `description:"规则映射ID"`
+	ExcelInfoId        int    `description:"ETA表格ID"`
+	RuleType           int    `description:"规则类型:1-大于,2-小于,3-介于,4-等于,5-发生日期"`
+	LeftValue          string `description:"条件值"`
+	LeftValueType      int    `description:"条件值的类型,1:数值,2:坐标"`
+	RightValue         string `description:"条件值, 用于介于的条件"`
+	RightValueType     int    `description:"条件值的类型,1:数值,2:坐标"`
+	FontColor          string `description:"字体颜色"`
+	BackgroundColor    string `description:"背景颜色"`
+	Remark             string `description:"预设的单元格样式名称"`
+	Scope              string `description:"应用选区"`
+}
+
+type DeleteExcelRuleMappingReq struct {
+	ExcelRuleMappingId int `description:"规则映射ID"`
+}

+ 7 - 2
models/data_manage/excel/request/mixed_table.go

@@ -133,8 +133,13 @@ type MixDateCalculateTagReq struct {
 
 // MixCellShowStyle 混合表格 单元格样式展示计算
 type MixCellShowStyle struct {
-	Pn int    `description:"小数点位数增加或减少,正数表述增加,负数表示减少" json:"pn"`
-	Nt string `description:"变换类型:number 小数点位数改变,percent百分比," json:"nt"`
+	Pn              int         `description:"小数点位数增加或减少,正数表述增加,负数表示减少" json:"pn"`
+	Nt              string      `description:"变换类型:number 小数点位数改变,percent百分比," json:"nt"`
+	GlObj           interface{} `description:"公式对象:1:数值,2:百分比,3:文本" json:"glObj"`
+	Decimal         *int        `description:"小数点位数"`
+	Last            string      `description:"起始操作:nt|decimal"`
+	Color           string      `description:"颜色值,#RRG" json:"color"`
+	BackgroundColor string      `description:"背景颜色值,#RRG" json:"background-color"`
 }
 
 type DateDataBeforeAfterReq struct {

+ 7 - 1
models/data_manage/excel/response/excel_info.go

@@ -4,8 +4,9 @@ import (
 	excel2 "eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/services/excel"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // AddExcelInfoResp 添加excel表格的返回
@@ -49,6 +50,7 @@ type TableCellResp struct {
 type TableDataItem struct {
 	EdbInfoId     int                     `description:"指标id"`
 	Data          []request.ManualDataReq `description:"数据列表"`
+	Decimal       int                     `description:"小数位数"`
 	ExcelSource   string                  `description:"表格来源str"`
 	ExcelSourceEn string                  `description:"表格来源(英文)"`
 }
@@ -118,3 +120,7 @@ type ShareExcelInfoDetail struct {
 	ViewUserIds []int `description:"查看权限用户IDs"`
 	EditUserIds []int `description:"编辑权限用户IDs"`
 }
+
+type ExcelRuleListResp struct {
+	List []*excel2.ExcelInfoRuleMappingView
+}

+ 62 - 0
models/data_manage/factor_edb_series.go

@@ -360,6 +360,68 @@ func (a FactorEdbSeriesCorrelationMatrixOrder) Less(i, j int) bool {
 	return a[i].XData > a[j].XData
 }
 
+// RemoveSeriesAndMappingByFactorEdbSeriesId 删除系列和指标关联
+func (m *FactorEdbSeries) RemoveSeriesAndMappingByFactorEdbSeriesId(factorEdbSeriesChartMapping *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()
+	}()
+	factorEdbSeriesId := factorEdbSeriesChartMapping.FactorEdbSeriesId
+	err = factorEdbSeriesChartMapping.Remove()
+	if err != nil {
+		err = fmt.Errorf("factorEdbSeriesChartMapping.delete err: %v", err)
+		return
+	}
+	if factorEdbSeriesId == 0 {
+		return
+	}
+
+	// 清除原指标关联
+	seriesOb := new(FactorEdbSeries)
+	cond := fmt.Sprintf("%s = ?", seriesOb.Cols().PrimaryId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, factorEdbSeriesId)
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, seriesOb.TableName(), cond)
+	_, e = tx.Raw(sql, pars).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove FactorEdbSeries err: %v", e)
+		return
+	}
+
+	// 清除原指标关联
+	mappingOb := new(FactorEdbSeriesMapping)
+	cond1 := fmt.Sprintf("%s = ?", mappingOb.Cols().FactorEdbSeriesId)
+	pars1 := make([]interface{}, 0)
+	pars1 = append(pars1, factorEdbSeriesId)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s`, mappingOb.TableName(), cond1)
+	_, e = tx.Raw(sql, pars1).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+	dataOb := new(FactorEdbSeriesCalculateDataQjjs)
+	//删除原指标数据
+	cond2 := fmt.Sprintf("%s = ?", dataOb.Cols().FactorEdbSeriesId)
+	pars2 := make([]interface{}, 0)
+	pars2 = append(pars2, factorEdbSeriesId)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s`, dataOb.TableName(), cond2)
+	_, e = tx.Raw(sql, pars2).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+	return
+}
+
 // CalculateCorrelationMatrixPars 计算相关性矩阵参数
 type CalculateCorrelationMatrixPars struct {
 	BaseEdbInfoId int               `description:"标的指标ID"`

+ 210 - 0
models/data_manage/factor_edb_series_calculate_data_qjjs.go

@@ -0,0 +1,210 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// FactorEdbSeriesCalculateDataQjjs 因子指标系列-区间计算数据表
+type FactorEdbSeriesCalculateDataQjjs 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 *FactorEdbSeriesCalculateDataQjjs) TableName() string {
+	return "factor_edb_series_calculate_data_qjjs"
+}
+
+type FactorEdbSeriesCalculateDataQjjsCols struct {
+	PrimaryId         string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbCode           string
+	DataTime          string
+	Value             string
+	CreateTime        string
+	ModifyTime        string
+	DataTimestamp     string
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Cols() FactorEdbSeriesCalculateDataQjjsCols {
+	return FactorEdbSeriesCalculateDataQjjsCols{
+		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 *FactorEdbSeriesCalculateDataQjjs) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesCalculateDataId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) CreateMulti(items []*FactorEdbSeriesCalculateDataQjjs) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(500, items)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) 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 *FactorEdbSeriesCalculateDataQjjs) 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 *FactorEdbSeriesCalculateDataQjjs) 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 *FactorEdbSeriesCalculateDataQjjs) GetItemById(id int) (item *FactorEdbSeriesCalculateDataQjjs, 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 *FactorEdbSeriesCalculateDataQjjs) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesCalculateDataQjjs, 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 *FactorEdbSeriesCalculateDataQjjs) 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 *FactorEdbSeriesCalculateDataQjjs) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesCalculateDataQjjs, 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 *FactorEdbSeriesCalculateDataQjjs) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesCalculateDataQjjs, 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
+}
+
+// FactorEdbSeriesCalculateDataQjjsItem 因子指标系列-计算数据信息
+type FactorEdbSeriesCalculateDataQjjsItem struct {
+	DataId            int     `description:"数据ID"`
+	FactorEdbSeriesId int     `description:"因子指标系列ID"`
+	EdbInfoId         int     `description:"指标ID"`
+	EdbCode           string  `description:"指标编码"`
+	DataTime          string  `description:"数据日期"`
+	Value             float64 `description:"数据值"`
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) Format2Item() (item *FactorEdbSeriesCalculateDataQjjsItem) {
+	item = new(FactorEdbSeriesCalculateDataQjjsItem)
+	item.DataId = m.FactorEdbSeriesCalculateDataId
+	item.FactorEdbSeriesId = m.FactorEdbSeriesId
+	item.EdbInfoId = m.EdbInfoId
+	item.EdbCode = m.EdbCode
+	return
+}
+
+// TransEdbSeriesCalculateDataQjjs2EdbDataList 转换数据格式
+func TransEdbSeriesCalculateDataQjjs2EdbDataList(items []*FactorEdbSeriesCalculateDataQjjs) (list []*EdbDataList) {
+	list = make([]*EdbDataList, 0)
+	for _, v := range items {
+		list = append(list, &EdbDataList{
+			DataTime: v.DataTime.Format(utils.FormatDate),
+			Value:    v.Value,
+		})
+	}
+	return
+}
+
+func (m *FactorEdbSeriesCalculateDataQjjs) GetEdbDataList(seriesId int, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
+	var pars []interface{}
+	sql := `SELECT factor_edb_series_calculate_data_id as edb_data_id  ,edb_info_id,data_time,value,data_timestamp FROM %s WHERE edb_info_id=? and factor_edb_series_id=? `
+	pars = append(pars, edbInfoId, seriesId)
+	if startDate != "" {
+		sql += ` AND data_time>=? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		sql += ` AND data_time<=? `
+		pars = append(pars, endDate)
+	}
+
+	sql += ` ORDER BY data_time ASC `
+	sql = fmt.Sprintf(sql, m.TableName())
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}

+ 8 - 0
models/data_manage/factor_edb_series_chart_mapping.go

@@ -10,6 +10,7 @@ import (
 
 const (
 	FactorEdbSeriesChartCalculateTypeCorrelation = 1 // 相关性计算
+	FactorEdbSeriesChartCalculateTypeRange       = 2 // 	区间计算
 )
 
 // FactorEdbSeriesChartMapping 因子指标系列-图表关联
@@ -120,6 +121,13 @@ func (m *FactorEdbSeriesChartMapping) GetItemById(id int) (item *FactorEdbSeries
 	return
 }
 
+func (m *FactorEdbSeriesChartMapping) GetItemByChartInfoId(id int) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().ChartInfoId)
+	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 := ``

+ 7 - 0
models/data_manage/factor_edb_series_mapping.go

@@ -165,3 +165,10 @@ func (m *FactorEdbSeriesMapping) Format2Item() (item *FactorEdbSeriesMappingItem
 	item.EdbCode = m.EdbCode
 	return
 }
+
+func (m *FactorEdbSeriesMapping) GetItemBySeriesId(seriesId int) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().FactorEdbSeriesId)
+	_, err = o.Raw(sql, seriesId).QueryRows(&items)
+	return
+}

+ 7 - 0
models/data_manage/multiple_graph_config_chart_mapping.go

@@ -196,3 +196,10 @@ func ReplaceMultipleGraphConfigChartEdb(oldEdbInfo, newEdbInfo *EdbInfo) (replac
 	}
 	return
 }
+
+func RemoveMultiConfigChartMappingByChartInfoId(chartInfoId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM multiple_graph_config_chart_mapping WHERE chart_info_id = ?`
+	_, err = o.Raw(sql, chartInfoId).Exec()
+	return
+}

+ 52 - 1
models/data_manage/multiple_graph_config_edb_mapping.go

@@ -1,6 +1,7 @@
 package data_manage
 
 import (
+	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -10,7 +11,7 @@ type MultipleGraphConfigEdbMapping struct {
 	Id                    int       `orm:"column(id);pk"`
 	MultipleGraphConfigId int       `description:"多图配置id"`
 	EdbInfoId             int       `description:"指标id"`
-	Source                int       `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;"`
+	Source                int       `description:"来源,1:曲线图,2:相关性图;3:滚动相关性图1;4:滚动相关性图2;10:区间计算指标"`
 	ModifyTime            time.Time `description:"最近一次修改时间"`
 	CreateTime            time.Time `description:"添加时间"`
 }
@@ -51,3 +52,53 @@ func GetMultipleGraphConfigEdbMappingListById(configId int) (items []*MultipleGr
 
 	return
 }
+
+func GetMultipleGraphConfigEdbMappingListByIdAndSource(configId, source int) (items []*MultipleGraphConfigEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? AND source = ? `
+	_, err = o.Raw(sql, configId, source).QueryRows(&items)
+
+	return
+}
+
+// AddMultipleGraphConfigEdbMappingList 新增多图配置
+func AddMultipleGraphConfigEdbMappingList(items []*MultipleGraphConfigEdbMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func DeleteMultipleGraphConfigEdbMappingByEdbIds(configId, source int, edbIds []int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? AND source = ? AND edb_info_id IN (` + utils.GetOrmInReplace(len(edbIds)) + `) `
+	_, err = o.Raw(sql, configId, source, edbIds).Exec()
+	return
+}
+
+func DeleteMultipleGraphConfigByChartInfoId(chartInfoId, configId, source int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	sql := ` DELETE FROM multiple_graph_config WHERE multiple_graph_config_id = ?`
+	_, err = to.Raw(sql, configId).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` DELETE FROM multiple_graph_config_chart_mapping WHERE chart_info_id=? and multiple_graph_config_id = ? and	source=?`
+	_, err = to.Raw(sql, chartInfoId, configId, source).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` DELETE FROM  multiple_graph_config_edb_mapping WHERE multiple_graph_config_id = ? and	source=? `
+	_, err = to.Raw(sql, configId, source).Exec()
+	return
+}

+ 23 - 0
models/data_manage/mysteel_chemical_index.go

@@ -34,6 +34,7 @@ type BaseFromMysteelChemicalIndex struct {
 	TerminalCode                      string    `description:"终端编码"`
 	IsStop                            int       `description:"是否停更:1:停更,0:未停更"`
 	EndValue                          float64   `description:"指标的最新值"`
+	IsSupplierStop                    int       `description:"是否供应商停更:1:停更,0:未停更"`
 }
 
 type BaseFromMysteelChemicalIndexItem struct {
@@ -190,6 +191,7 @@ type MysteelChemicalList struct {
 	EdbInfoId                         int                `description:"指标库的id"`
 	UpdateTime                        string             `orm:"column(modify_time)"`
 	IsStop                            int                `description:"是否停更:1:停更,0:未停更"`
+	IsSupplierStop                    int                `description:"是否供应商停更:1:停更,0:未停更"`
 	Paging                            *paging.PagingItem `description:"分页数据"`
 	DataList                          []*MysteelChemicalData
 }
@@ -698,3 +700,24 @@ func ModifyMysteelChemicalUpdateStatusByEdbInfoIds(edbInfoIds []int, isStop int,
 
 	return
 }
+
+// GetNotIsSupplierStopIndexByCodeList
+// @Description: 获取未被供应商停更的指标
+// @author: Roc
+// @datetime 2024-08-28 18:15:03
+// @param codeList []string
+// @param isStop int
+// @return items []*BaseFromMysteelChemicalIndex
+// @return err error
+func GetNotIsSupplierStopIndexByCodeList(codeList []string, isStop int) (items []*BaseFromMysteelChemicalIndex, err error) {
+	num := len(codeList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_mysteel_chemical_index WHERE is_supplier_stop = ? AND index_code in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, isStop, codeList).QueryRows(&items)
+
+	return
+}

+ 122 - 0
models/data_manage/trade_analysis/base_from_trade_exchange.go

@@ -0,0 +1,122 @@
+package trade_analysis
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromTradeExchange 交易所表
+type BaseFromTradeExchange struct {
+	BaseFromTradeExchangeId int       `orm:"column(base_from_trade_exchange_id);pk"`
+	Exchange                string    `description:"交易所标识"`
+	ExchangeName            string    `description:"交易所名称"`
+	ExchangeNameEn          string    `description:"交易所英文名称"`
+	Sort                    int       `description:"排序"`
+	AnalysisState           int       `description:"持仓分析状态:0-隐藏;1-显示"`
+	CreateTime              time.Time `description:"创建时间"`
+	ModifyTime              time.Time `description:"修改时间"`
+}
+
+func (m *BaseFromTradeExchange) TableName() string {
+	return "base_from_trade_exchange"
+}
+
+type BaseFromTradeExchangeCols struct {
+	PrimaryId      string
+	Exchange       string
+	ExchangeName   string
+	ExchangeNameEn string
+	Sort           string
+	AnalysisState  string
+	CreateTime     string
+	ModifyTime     string
+}
+
+func (m *BaseFromTradeExchange) Cols() BaseFromTradeExchangeCols {
+	return BaseFromTradeExchangeCols{
+		PrimaryId:      "base_from_trade_exchange_id",
+		Exchange:       "exchange",
+		ExchangeName:   "exchange_name",
+		ExchangeNameEn: "exchange_name_en",
+		Sort:           "sort",
+		AnalysisState:  "analysis_state",
+		CreateTime:     "create_time",
+		ModifyTime:     "modify_time",
+	}
+}
+
+func (m *BaseFromTradeExchange) GetItemById(id int) (item *BaseFromTradeExchange, 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 *BaseFromTradeExchange) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromTradeExchange, 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 *BaseFromTradeExchange) 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 *BaseFromTradeExchange) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromTradeExchange, 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 *BaseFromTradeExchange) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromTradeExchange, 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
+}
+
+// BaseFromTradeExchangeItem 交易所信息
+type BaseFromTradeExchangeItem struct {
+	ExchangeId     int    `description:"交易所ID"`
+	Exchange       string `description:"交易所标识"`
+	ExchangeName   string `description:"交易所名称"`
+	ExchangeNameEn string `description:"交易所英文名称"`
+	Sort           int    `description:"排序"`
+}
+
+func (m *BaseFromTradeExchange) Format2Item() (item *BaseFromTradeExchangeItem) {
+	item = new(BaseFromTradeExchangeItem)
+	item.ExchangeId = m.BaseFromTradeExchangeId
+	item.Exchange = m.Exchange
+	item.ExchangeName = m.ExchangeName
+	item.ExchangeNameEn = m.ExchangeNameEn
+	item.Sort = m.Sort
+	return
+}

+ 20 - 0
models/data_manage/trade_analysis/request/warehouse.go

@@ -0,0 +1,20 @@
+package request
+
+import (
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
+)
+
+// WarehousePreviewReq 建仓图表预览
+type WarehousePreviewReq struct {
+	ChartInfoId  int                                      `description:"图表ID"`
+	ExtraConfig  *tradeAnalysisModel.WarehouseExtraConfig `description:"建仓过程参数"`
+	ChartsConfig []tradeAnalysisModel.WarehouseChartPars  `description:"图表配置(日期配置等)"`
+}
+
+// WarehouseEdbSaveReq 建仓指标保存
+type WarehouseEdbSaveReq struct {
+	MultipleGraphConfigId int                                        `description:"配置ID"`
+	ExtraConfig           *tradeAnalysisModel.WarehouseExtraConfig   `description:"图表配置"`
+	IsSaveAs              bool                                       `description:"是否另存为, true表示另存为, 不会建立与配置的关系"`
+	EdbInfoList           []*tradeAnalysisModel.WarehouseEdbSaveItem `description:"指标列表"`
+}

+ 17 - 0
models/data_manage/trade_analysis/response/warehouse.go

@@ -0,0 +1,17 @@
+package response
+
+import (
+	"eta/eta_api/models/data_manage"
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
+)
+
+type WarehouseChartDetailResp struct {
+	MultipleGraphConfigId int                                `description:"多图配置ID"`
+	WarehouseCharts       []*data_manage.ChartInfoDetailResp `description:"建仓多图信息"`
+}
+
+// WarehouseEdbSaveResp 建仓指标保存响应
+type WarehouseEdbSaveResp struct {
+	Success []*tradeAnalysisModel.WarehouseEdbSaveRespItem `description:"操作成功的指标"`
+	Fail    []*tradeAnalysisModel.WarehouseEdbSaveRespItem `description:"操作失败的指标"`
+}

+ 372 - 0
models/data_manage/trade_analysis/trade_analysis.go

@@ -1,6 +1,8 @@
 package trade_analysis
 
 import (
+	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
 )
@@ -159,3 +161,373 @@ func GetTradePositionTop(exchange string, classifyName, classifyType, dataTime s
 
 	return
 }
+
+type OriginTradeData struct {
+	Rank         int       `description:"排名"`
+	CompanyName  string    `description:"期货公司名称"`
+	Val          int       `description:"持仓量"`
+	ValChange    int       `description:"持仓增减"`
+	DataTime     time.Time `description:"数据日期"`
+	ClassifyName string    `description:"品种名称"`
+	ClassifyType string    `description:"合约代码"`
+	ValType      int       `description:"数据类型: 1-多单; 2-空单"`
+}
+
+// GetTradeDataByClassifyAndCompany 根据品种和公司名称获取持仓数据
+func GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, companies []string) (items []*OriginTradeData, err error) {
+	if exchange == "" {
+		err = fmt.Errorf("数据表名称有误")
+		return
+	}
+	if len(contracts) == 0 || len(companies) == 0 {
+		return
+	}
+	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
+	sql := `SELECT
+			rank,
+			buy_short_name AS company_name,
+			buy_value AS val,
+			buy_change AS val_change,
+			classify_name,
+			classify_type,
+			data_time,
+			1 AS val_type 
+		FROM
+			%s 
+		WHERE
+			classify_name = ? AND classify_type IN (%s) AND buy_short_name IN (%s)
+		UNION ALL
+		(
+		SELECT
+			rank,
+			sold_short_name,
+			sold_value,
+			sold_change,
+			classify_name,
+			classify_type,
+			data_time,
+			2 AS val_type 
+		FROM
+			%s 
+		WHERE
+			classify_name = ? AND classify_type IN (%s) AND sold_short_name IN (%s)
+		)`
+	sql = fmt.Sprintf(sql, tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)), tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)))
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, classifyName, contracts, companies, classifyName, contracts, companies).QueryRows(&items)
+	return
+}
+
+// GetTradeZhengzhouDataByClassifyAndCompany 郑商所-根据品种和公司名称获取持仓数据
+func GetTradeZhengzhouDataByClassifyAndCompany(exchange string, contracts, companies []string) (items []*OriginTradeData, err error) {
+	if exchange == "" {
+		err = fmt.Errorf("数据表名称有误")
+		return
+	}
+	if len(contracts) == 0 || len(companies) == 0 {
+		return
+	}
+	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
+	sql := `SELECT
+			rank,
+			buy_short_name AS company_name,
+			buy_value AS val,
+			buy_change AS val_change,
+			classify_name AS classify_type,
+			data_time,
+			1 AS val_type 
+		FROM
+			%s 
+		WHERE
+			classify_name IN (%s) AND buy_short_name IN (%s)
+		UNION ALL
+		(
+		SELECT
+			rank,
+			sold_short_name,
+			sold_value,
+			sold_change,
+			classify_name AS classify_type,
+			data_time,
+			2 AS val_type 
+		FROM
+			%s 
+		WHERE
+			classify_name IN (%s) AND sold_short_name IN (%s)
+		)`
+	sql = fmt.Sprintf(sql, tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)), tableName, utils.GetOrmInReplace(len(contracts)), utils.GetOrmInReplace(len(companies)))
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, contracts, companies, contracts, companies).QueryRows(&items)
+	return
+}
+
+// ContractCompanyTradeData [合约-期货公司]持仓数据
+type ContractCompanyTradeData struct {
+	CompanyName  string                          `description:"期货公司名称"`
+	ClassifyType string                          `description:"合约代码"`
+	StartDate    time.Time                       `description:"数据开始日期"`
+	EndDate      time.Time                       `description:"数据结束日期"`
+	DataList     []*ContractCompanyTradeDataList `description:"数据序列"`
+}
+
+const (
+	TradeDataTypeNull      = 0 // 无值
+	TradeDataTypeOrigin    = 1 // 原始值
+	TradeDataTypeCalculate = 2 // 推算值
+
+	WarehouseBuyChartType     = 1 // 多单图
+	WarehouseSoldChartType    = 2 // 空单图
+	WarehousePureBuyChartType = 3 // 净多单图
+
+	WarehouseDefaultUnit      = "手"
+	WarehouseDefaultFrequency = "日度"
+
+	GuangZhouTopCompanyAliasName = "日成交持仓排名" // 广期所TOP20对应的公司名称
+	GuangZhouSeatNameBuy         = "持买单量"    // 广期所指标名称中的多单名称
+	GuangZhouSeatNameSold        = "持卖单量"    // 广期所指标名称中的空单名称
+	GuangZhouTopSeatNameBuy      = "持买单量总计"  // 广期所指标名称中的TOP20多单名称
+	GuangZhouTopSeatNameSold     = "持卖单量总计"  // 广期所指标名称中的TOP20空单名称
+)
+
+const (
+	TradeExchangeZhengzhou = "zhengzhou"
+	TradeExchangeGuangzhou = "guangzhou"
+)
+
+var WarehouseTypeSuffixNames = map[int]string{
+	WarehouseBuyChartType:     "席位多单",
+	WarehouseSoldChartType:    "席位空单",
+	WarehousePureBuyChartType: "席位净多单",
+}
+
+// GuangzhouSeatNameValType 广期所数据名称对应的席位方向
+var GuangzhouSeatNameValType = map[string]int{
+	GuangZhouSeatNameBuy:     1,
+	GuangZhouSeatNameSold:    2,
+	GuangZhouTopSeatNameBuy:  1,
+	GuangZhouTopSeatNameSold: 2,
+}
+
+// ContractCompanyTradeDataList [合约-期货公司]持仓数据详情
+type ContractCompanyTradeDataList struct {
+	Date              time.Time `description:"数据日期"`
+	BuyVal            int       `description:"多单持仓量"`
+	BuyValType        int       `description:"多单数据类型: 0-无值; 1-原始值; 2-推算值"`
+	BuyChange         int       `description:"多单持仓增减"`
+	BuyChangeType     int       `description:"多单持仓增减类型: 0-无值; 1-原始值; 2-推算值"`
+	SoldVal           int       `description:"空单持仓量"`
+	SoldValType       int       `description:"空单数据类型: 0-无值; 1-原始值; 2-推算值"`
+	SoldChange        int       `description:"空单持仓增减"`
+	SoldChangeType    int       `description:"空单持仓增减类型: 0-无值; 1-原始值; 2-推算值"`
+	PureBuyVal        int       `description:"净多单持仓量"`
+	PureBuyValType    int       `description:"净多单数据类型: 0-无值; 1-原始值; 2-推算值"`
+	PureBuyChange     int       `description:"净多单持仓增减"`
+	PureBuyChangeType int       `description:"净多单持仓增减类型: 0-无值; 1-原始值; 2-推算值"`
+}
+
+// GetLastTradeDataByClassify 获取[合约]末位多空单数据
+func GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*OriginTradeData, err error) {
+	if exchange == "" {
+		err = fmt.Errorf("数据表名称有误")
+		return
+	}
+	if len(contracts) == 0 {
+		return
+	}
+	contractReplacer := utils.GetOrmInReplace(len(contracts))
+
+	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
+	sql := `SELECT 
+			tpt.rank,
+			tpt.buy_short_name AS company_name,
+			tpt.buy_value AS val,
+			tpt.buy_change AS val_change,
+			tpt.classify_name,
+			tpt.classify_type,
+			tpt.data_time,
+			1 AS val_type
+		FROM 
+			%s tpt
+		JOIN 
+			(
+				SELECT
+					data_time, classify_type, MAX(rank) AS max_rank
+				FROM 
+					%s
+				WHERE 
+					classify_name = ? AND classify_type IN (%s) AND buy_short_name <> ''
+				GROUP BY 
+					data_time,
+					classify_type
+			) sub
+		ON
+			tpt.data_time = sub.data_time AND tpt.classify_type = sub.classify_type AND tpt.rank = sub.max_rank
+		WHERE 
+			tpt.classify_name = ? AND tpt.classify_type IN (%s)
+		UNION ALL
+		(
+		SELECT 
+			tpt.rank, tpt.sold_short_name, tpt.sold_value, tpt.sold_change, tpt.classify_name, tpt.classify_type, tpt.data_time, 2 AS val_type
+		FROM 
+			%s tpt
+		JOIN 
+			(
+				SELECT 
+					data_time, classify_type, MAX(rank) AS max_rank
+				FROM 
+					%s
+				WHERE 
+					classify_name = ? AND classify_type IN (%s) AND sold_short_name <> ''
+				GROUP BY 
+					data_time, classify_type
+			) sub
+		ON 
+			tpt.data_time = sub.data_time AND tpt.classify_type = sub.classify_type AND tpt.rank = sub.max_rank
+		WHERE 
+			tpt.classify_name = ? AND tpt.classify_type IN (%s)
+		)`
+	sql = fmt.Sprintf(sql, tableName, tableName, contractReplacer, contractReplacer, tableName, tableName, contractReplacer, contractReplacer)
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, classifyName, contracts, classifyName, contracts, classifyName, contracts, classifyName, contracts).QueryRows(&items)
+	return
+}
+
+// GetLastTradeZhengzhouDataByClassify 郑商所-获取[合约]末位多空单数据
+func GetLastTradeZhengzhouDataByClassify(exchange string, contracts []string) (items []*OriginTradeData, err error) {
+	if exchange == "" {
+		err = fmt.Errorf("数据表名称有误")
+		return
+	}
+	if len(contracts) == 0 {
+		return
+	}
+	contractReplacer := utils.GetOrmInReplace(len(contracts))
+
+	tableName := fmt.Sprintf("base_from_trade_%s_index", exchange)
+	sql := `SELECT 
+			tpt.rank,
+			tpt.buy_short_name AS company_name,
+			tpt.buy_value AS val,
+			tpt.buy_change AS val_change,
+  			tpt.classify_name AS classify_type,
+			tpt.data_time,
+			1 AS val_type
+		FROM 
+			%s tpt
+		JOIN 
+			(
+				SELECT
+					data_time, classify_name, MAX(rank) AS max_rank
+				FROM 
+					%s
+				WHERE 
+					classify_name IN (%s) AND buy_short_name <> ''
+				GROUP BY 
+					data_time,
+					classify_name
+			) sub
+		ON
+			tpt.data_time = sub.data_time AND tpt.classify_name = sub.classify_name AND tpt.rank = sub.max_rank
+		WHERE 
+			tpt.classify_name IN (%s)
+		UNION ALL
+		(
+		SELECT 
+			tpt.rank, tpt.sold_short_name, tpt.sold_value, tpt.sold_change, tpt.classify_name AS classify_type, tpt.data_time, 2 AS val_type
+		FROM 
+			%s tpt
+		JOIN 
+			(
+				SELECT 
+					data_time, classify_name, MAX(rank) AS max_rank
+				FROM 
+					%s
+				WHERE 
+					classify_name IN (%s) AND sold_short_name <> ''
+				GROUP BY 
+					data_time, classify_name
+			) sub
+		ON 
+			tpt.data_time = sub.data_time AND tpt.classify_name = sub.classify_name AND tpt.rank = sub.max_rank
+		WHERE 
+			tpt.classify_name IN (%s)
+		)`
+	sql = fmt.Sprintf(sql, tableName, tableName, contractReplacer, contractReplacer, tableName, tableName, contractReplacer, contractReplacer)
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, contracts, contracts, contracts, contracts).QueryRows(&items)
+	return
+}
+
+type BaseFromTradeGuangzhouIndex struct {
+	BaseFromTradeGuangzhouIndexId    int       `orm:"column(base_from_trade_guangzhou_index_id);pk"`
+	BaseFromTradeGuangzhouClassifyId int       `description:"分类id"`
+	IndexCode                        string    `description:"指标编码"`
+	IndexName                        string    `description:"指标名称"`
+	Frequency                        string    `description:"频率"`
+	Unit                             string    `description:"单位"`
+	StartDate                        string    `description:"开始日期"`
+	EndDate                          string    `description:"结束日期"`
+	CreateTime                       time.Time `description:"创建日期"`
+	ModifyTime                       time.Time `description:"修改日期"`
+}
+
+func GetBaseFromTradeGuangzhouIndexByClassifyId(classifyId int) (list []*BaseFromTradeGuangzhouIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_trade_guangzhou_index WHERE base_from_trade_guangzhou_classify_id = ?`
+	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	return
+}
+
+type BaseFromTradeGuangzhouData struct {
+	BaseFromTradeGuangzhouDataId  int       `orm:"column(base_from_trade_guangzhou_data_id);pk"`
+	BaseFromTradeGuangzhouIndexId int       `description:"指标id"`
+	IndexCode                     string    `description:"指标编码"`
+	DataTime                      time.Time `description:"数据日期"`
+	Value                         float64   `description:"数据值"`
+	QtySub                        float64   `description:"增减"`
+	CreateTime                    time.Time `description:"创建日期"`
+	ModifyTime                    time.Time `description:"修改日期"`
+}
+
+// GetBaseFromTradeGuangzhouDataByIndexIds 获取指标数据
+func GetBaseFromTradeGuangzhouDataByIndexIds(indexIds []int) (list []*BaseFromTradeGuangzhouData, err error) {
+	if len(indexIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM base_from_trade_guangzhou_data WHERE base_from_trade_guangzhou_index_id IN (%s) ORDER BY base_from_trade_guangzhou_index_id`, utils.GetOrmInReplace(len(indexIds)))
+	_, err = o.Raw(sql, indexIds).QueryRows(&list)
+	return
+}
+
+// GetBaseFromTradeGuangzhouMinDataByIndexIds 获取指标中的末位数据
+func GetBaseFromTradeGuangzhouMinDataByIndexIds(indexIds []int) (list []*BaseFromTradeGuangzhouData, err error) {
+	indexLen := len(indexIds)
+	if indexLen == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT 
+			t1.data_time,
+			t1.min_value AS value
+		FROM 
+			(
+				SELECT 
+					data_time,
+					MIN(value) AS min_value
+				FROM 
+					base_from_trade_guangzhou_data
+				WHERE 
+					base_from_trade_guangzhou_index_id IN (%s)
+				GROUP BY 
+					data_time
+			) t1
+		JOIN 
+			base_from_trade_guangzhou_data t2
+		ON 
+			t1.data_time = t2.data_time AND t1.min_value = t2.value AND t2.base_from_trade_guangzhou_index_id IN (%s)
+		GROUP BY 
+			t1.data_time`, utils.GetOrmInReplace(indexLen), utils.GetOrmInReplace(indexLen))
+	_, err = o.Raw(sql, indexIds, indexIds).QueryRows(&list)
+	return
+}

+ 76 - 0
models/data_manage/trade_analysis/trade_classify.go

@@ -1,7 +1,9 @@
 package trade_analysis
 
 import (
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
+	"strings"
 	"time"
 )
 
@@ -53,3 +55,77 @@ func GetClassifyTypeByClassifyName(exchange, classifyName string) (item *TradeCl
 
 	return
 }
+
+func (m *BaseFromTradeClassify) TableName() string {
+	return "base_from_trade_classify"
+}
+
+type BaseFromTradeClassifyCols struct {
+	PrimaryId    string
+	Exchange     string
+	ClassifyName string
+	ClassifyType string
+	LatestDate   string
+	CreateTime   string
+	ModifyTime   string
+}
+
+func (m *BaseFromTradeClassify) Cols() BaseFromTradeClassifyCols {
+	return BaseFromTradeClassifyCols{
+		PrimaryId:    "id",
+		Exchange:     "exchange",
+		ClassifyName: "classify_name",
+		ClassifyType: "classify_type",
+		LatestDate:   "latest_date",
+		CreateTime:   "create_time",
+		ModifyTime:   "modify_time",
+	}
+}
+
+func (m *BaseFromTradeClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromTradeClassify, 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
+}
+
+// GetClassifyItemsByCondition 获取品种信息
+func (m *BaseFromTradeClassify) GetClassifyItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromTradeClassify, 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 GROUP BY %s %s`, fields, m.TableName(), condition, m.Cols().ClassifyName, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// BaseFromTradeClassifyItem 交易所品种信息
+type BaseFromTradeClassifyItem struct {
+	ClassifyName string `description:"品种"`
+	Exchange     string `description:"交易所标识"`
+	ExchangeName string `description:"交易所名称"`
+}
+
+// BaseFromTradeContractItem 交易所合约信息
+type BaseFromTradeContractItem struct {
+	ClassifyName string `description:"品种"`
+	Exchange     string `description:"交易所标识"`
+	ExchangeName string `description:"交易所名称"`
+	ClassifyType string `description:"合约"`
+	LatestDate   string `description:"最近数据的日期"`
+	CreateTime   string `description:"创建时间"`
+}

+ 129 - 0
models/data_manage/trade_analysis/trade_futures_company.go

@@ -0,0 +1,129 @@
+package trade_analysis
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const TradeFuturesCompanyTop20 = "TOP20"
+
+// TradeFuturesCompany 期货公司表
+type TradeFuturesCompany struct {
+	TradeFuturesCompanyId int       `orm:"column(trade_futures_company_id);pk"`
+	CompanyName           string    `description:"标准公司名称"`
+	ZhengzhouName         string    `description:"郑商所下的名称"`
+	DalianName            string    `description:"大商所下的名称"`
+	ShanghaiName          string    `description:"上期所下的名称"`
+	IneName               string    `description:"上期能源下的名称"`
+	GuangzhouName         string    `description:"广期所下的名称"`
+	CffexName             string    `description:"中金所下的名称"`
+	Sort                  int       `description:"排序"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+func (m *TradeFuturesCompany) TableName() string {
+	return "trade_futures_company"
+}
+
+type TradeFuturesCompanyCols struct {
+	PrimaryId     string
+	CompanyName   string
+	ZhengzhouName string
+	DalianName    string
+	ShanghaiName  string
+	IneName       string
+	GuangzhouName string
+	CffexName     string
+	Sort          string
+	CreateTime    string
+	ModifyTime    string
+}
+
+func (m *TradeFuturesCompany) Cols() TradeFuturesCompanyCols {
+	return TradeFuturesCompanyCols{
+		PrimaryId:     "trade_futures_company_id",
+		CompanyName:   "company_name",
+		ZhengzhouName: "zhengzhou_name",
+		DalianName:    "dalian_name",
+		ShanghaiName:  "shanghai_name",
+		IneName:       "ine_name",
+		GuangzhouName: "guangzhou_name",
+		CffexName:     "cffex_name",
+		Sort:          "sort",
+		CreateTime:    "create_time",
+		ModifyTime:    "modify_time",
+	}
+}
+
+func (m *TradeFuturesCompany) GetItemById(id int) (item *TradeFuturesCompany, 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 *TradeFuturesCompany) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *TradeFuturesCompany, 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 *TradeFuturesCompany) 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 *TradeFuturesCompany) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*TradeFuturesCompany, 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 *TradeFuturesCompany) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*TradeFuturesCompany, 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
+}
+
+// TradeFuturesCompanyItem 期货公司信息
+type TradeFuturesCompanyItem struct {
+	CompanyId   int    `description:"期货公司ID"`
+	CompanyName string `description:"标准公司名称"`
+	Sort        int    `description:"排序"`
+}
+
+func (m *TradeFuturesCompany) Format2Item() (item *TradeFuturesCompanyItem) {
+	item = new(TradeFuturesCompanyItem)
+	item.CompanyId = m.TradeFuturesCompanyId
+	item.CompanyName = m.CompanyName
+	item.Sort = m.Sort
+	return
+}

+ 103 - 0
models/data_manage/trade_analysis/warehouse.go

@@ -0,0 +1,103 @@
+package trade_analysis
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// WarehouseExtraConfig 建仓图表配置
+type WarehouseExtraConfig struct {
+	MultipleGraphConfigId int      `description:"多图配置ID"`
+	WarehouseChartType    int      `description:"图表类型: 1-多单图; 2-空单图; 3-净多单图"`
+	Exchange              string   `description:"交易所标识"`
+	ClassifyName          string   `description:"品种名称"`
+	Contracts             []string `description:"合约代码"`
+	Companies             []string `description:"期货公司, 不超过5个"`
+	PredictRatio          float64  `description:"预估参数, 0-1之间"`
+}
+
+// WarehouseChartPars 建仓单表配置
+type WarehouseChartPars struct {
+	WarehouseChartType int    `description:"图表类型: 1-多单图; 2-空单图; 3-净多单图"`
+	DateType           int    `description:"日期类型"`
+	DateTypeNum        int    `description:"日期类型=25(N月)时的N值"`
+	StartDate          string `description:"自定义开始日期"`
+	EndDate            string `description:"自定义结束日期"`
+	//ChartThemeId       int                               `description:"图表主题ID"`
+	ChartEdbInfoList []*data_manage.ChartSaveItem `description:"指标及配置信息"`
+	//SourcesFrom        *data_manage.ChartInfoSourcesFrom `description:"图表来源"`
+}
+
+// WarehouseChartDataResp 图表详情返回信息
+type WarehouseChartDataResp struct {
+	WarehouseExtraConfig
+	MultiEdbMappings []*WarehouseEdbSaveItem
+}
+
+// WarehouseEdbSaveItem 建仓指标保存
+type WarehouseEdbSaveItem struct {
+	EdbInfoId  int    `description:"指标ID"`
+	EdbName    string `description:"指标名称"`
+	Unit       string `description:"单位"`
+	Frequency  string `description:"频度"`
+	ClassifyId int    `description:"指标库分类ID"`
+	UniqueFlag string `description:"唯一标识"`
+	//ExtraConfig string `description:"配置信息-JSON"`
+}
+
+type WarehouseEdbSaveRespItem struct {
+	WarehouseEdbSaveItem
+	Tips   string `description:"提示信息"`
+	ErrMsg string `description:"错误信息"`
+}
+
+// CreateWarehouseChart 新增建仓图表
+func CreateWarehouseChart(chartInfo *data_manage.ChartInfo, seriesList []*data_manage.ChartSeries, multiChartMapping *data_manage.MultipleGraphConfigChartMapping) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %s", e.Error())
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 新增图表
+	id, e := tx.Insert(chartInfo)
+	if e != nil {
+		err = fmt.Errorf("insert chart err: %v", e)
+		return
+	}
+	newId := int(id)
+	chartInfo.ChartInfoId = newId
+
+	// 新增图例
+	if len(seriesList) > 0 {
+		for _, s := range seriesList {
+			s.ChartInfoId = newId
+		}
+		_, e = tx.InsertMulti(utils.MultiAddNum, seriesList)
+		if e != nil {
+			err = fmt.Errorf("insert multi series err: %v", e)
+			return
+		}
+	}
+
+	// 图表关联
+	if multiChartMapping != nil {
+		multiChartMapping.ChartInfoId = newId
+		_, e = tx.Insert(multiChartMapping)
+		if e != nil {
+			err = fmt.Errorf("insert multi chart mapping err: %v", e)
+			return
+		}
+	}
+	return
+}

+ 340 - 0
models/data_manage/trade_analysis/warehouse_process_classify.go

@@ -0,0 +1,340 @@
+package trade_analysis
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// WareHouseProcessClassify 建仓过程分类表
+type WareHouseProcessClassify struct {
+	WareHouseProcessClassifyId int       `orm:"column(warehouse_process_classify_id);pk"`
+	ClassifyName               string    `description:"分类名称"`
+	ClassifyNameEn             string    `description:"英文分类名称"`
+	ParentId                   int       `description:"父级ID"`
+	SysUserId                  int       `description:"创建人ID"`
+	SysUserRealName            string    `description:"创建人姓名"`
+	Level                      int       `description:"层级"`
+	Sort                       int       `description:"排序"`
+	RootId                     int       `description:"顶级分类ID"`
+	LevelPath                  string    `description:"层级路径"`
+	UniqueCode                 string    `description:"唯一编码"`
+	CreateTime                 time.Time `description:"创建时间"`
+	ModifyTime                 time.Time `description:"修改时间"`
+}
+
+func (m *WareHouseProcessClassify) TableName() string {
+	return "warehouse_process_classify"
+}
+
+type WareHouseProcessClassifyCols struct {
+	PrimaryId       string
+	ClassifyName    string
+	ClassifyNameEn  string
+	ParentId        string
+	SysUserId       string
+	SysUserRealName string
+	Level           string
+	Sort            string
+	RootId          string
+	LevelPath       string
+	UniqueCode      string
+	CreateTime      string
+	ModifyTime      string
+}
+
+func (m *WareHouseProcessClassify) Cols() WareHouseProcessClassifyCols {
+	return WareHouseProcessClassifyCols{
+		PrimaryId:       "warehouse_process_classify_id",
+		ClassifyName:    "classify_name",
+		ClassifyNameEn:  "classify_name_en",
+		ParentId:        "parent_id",
+		SysUserId:       "sys_user_id",
+		SysUserRealName: "sys_user_real_name",
+		Level:           "level",
+		Sort:            "sort",
+		RootId:          "root_id",
+		LevelPath:       "level_path",
+		UniqueCode:      "unique_code",
+		CreateTime:      "create_time",
+		ModifyTime:      "modify_time",
+	}
+}
+
+func (m *WareHouseProcessClassify) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.WareHouseProcessClassifyId = int(id)
+	return
+}
+
+func (m *WareHouseProcessClassify) CreateMulti(items []*WareHouseProcessClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *WareHouseProcessClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *WareHouseProcessClassify) 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.WareHouseProcessClassifyId).Exec()
+	return
+}
+
+func (m *WareHouseProcessClassify) 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 *WareHouseProcessClassify) 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 *WareHouseProcessClassify) GetItemById(id int) (item *WareHouseProcessClassify, 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 *WareHouseProcessClassify) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *WareHouseProcessClassify, 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 *WareHouseProcessClassify) 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 *WareHouseProcessClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*WareHouseProcessClassify, 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 *WareHouseProcessClassify) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*WareHouseProcessClassify, 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
+}
+
+// WareHouseProcessClassifyItem 建仓过程分类信息
+type WareHouseProcessClassifyItem struct {
+	ClassifyId     int                             `description:"分类ID"`
+	ClassifyName   string                          `description:"分类名称"`
+	ClassifyNameEn string                          `description:"英文分类名称"`
+	ParentId       int                             `description:"父级ID"`
+	Level          int                             `description:"层级"`
+	Sort           int                             `description:"排序"`
+	LevelPath      string                          `description:"层级路径"`
+	UniqueCode     string                          `description:"唯一编码"`
+	Children       []*WareHouseProcessClassifyItem `description:"子分类"`
+}
+
+func (m *WareHouseProcessClassify) Format2Item() (item *WareHouseProcessClassifyItem) {
+	item = new(WareHouseProcessClassifyItem)
+	item.ClassifyId = m.WareHouseProcessClassifyId
+	item.ClassifyName = m.ClassifyName
+	item.ClassifyNameEn = m.ClassifyNameEn
+	item.ParentId = m.ParentId
+	item.Level = m.Level
+	item.Sort = m.Sort
+	item.LevelPath = m.LevelPath
+	item.UniqueCode = m.UniqueCode
+	item.Children = make([]*WareHouseProcessClassifyItem, 0)
+	return
+}
+
+// ------------------------------------------------ 通用分类 ------------------------------------------------
+
+// GetCommonClassifyCols 通用分类字段映射
+func (m *WareHouseProcessClassify) GetCommonClassifyCols() models.CommonClassifyCols {
+	return models.CommonClassifyCols{
+		ClassifyId:   m.Cols().PrimaryId,
+		ClassifyName: m.Cols().ClassifyName,
+		ParentId:     m.Cols().ParentId,
+		Sort:         m.Cols().ParentId,
+		RootId:       m.Cols().RootId,
+		Level:        m.Cols().Level,
+		LevelPath:    m.Cols().LevelPath,
+		CreateTime:   m.Cols().CreateTime,
+		ModifyTime:   m.Cols().ModifyTime,
+	}
+}
+
+// GetCommonClassifyById 获取通用分类
+func (m *WareHouseProcessClassify) GetCommonClassifyById(classifyId int) (commonClassify *models.CommonClassify, err error) {
+	item, e := m.GetItemById(classifyId)
+	if e != nil {
+		err = e
+		return
+	}
+	commonClassify = new(models.CommonClassify)
+	commonClassify.ClassifyId = item.WareHouseProcessClassifyId
+	commonClassify.ClassifyName = item.ClassifyName
+	commonClassify.ParentId = item.ParentId
+	commonClassify.RootId = item.RootId
+	commonClassify.Level = item.Level
+	commonClassify.LevelPath = item.LevelPath
+	commonClassify.Sort = item.Sort
+	commonClassify.CreateTime = item.CreateTime
+	commonClassify.ModifyTime = item.ModifyTime
+	return
+}
+
+// GetClassifyByParentIdAndName 实现获取分类信息的方法
+func (m *WareHouseProcessClassify) GetClassifyByParentIdAndName(parentId int, name string, excludeId int) (*models.CommonClassify, error) {
+	// 实现获取分类信息的逻辑
+	return nil, nil
+}
+
+// UpdateCommonClassify 实现更新分类信息的方法
+func (m *WareHouseProcessClassify) UpdateCommonClassify(classify *models.CommonClassify, updateCols []string) (err error) {
+	return
+}
+
+func (m *WareHouseProcessClassify) UpdateClassifyChildByParentId(classifyIds []int, rootId int, stepLevel int) (err error) {
+	//	o := orm.NewOrmUsingDB("data")
+	//	var pars []interface{}
+	//	pars = append(pars, rootId, levelStep)
+	//	pars = append(pars, classifyIds)
+	//	// 更新相关联的二级分类的parentId,和classify_name_second
+	//	sql := `update edb_classify
+	//SET root_id = ?, level = level+?
+	//where classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	//	_, err = o.Raw(sql, pars).Exec()
+	//	if err != nil {
+	//		return
+	//	}
+	return
+}
+
+// GetClassifySortMaxByParentId 实现获取分类排序的方法
+func (m *WareHouseProcessClassify) GetClassifySortMaxByParentId(parentId int) (sortMax int, err error) {
+	//	o := orm.NewOrmUsingDB("data")
+	//	sql := `SELECT Max(sort) AS sort FROM edb_classify WHERE parent_id=? AND classify_type=? `
+	//	err = o.Raw(sql, parentId, classifyType).QueryRow(&sort)
+	//	return
+	return
+}
+
+func (m *WareHouseProcessClassify) GetFirstClassifyByParentId(parentId int) (item *models.CommonClassify, err error) {
+	//o := orm.NewOrmUsingDB("data")
+	//sql := ` SELECT * FROM edb_classify WHERE parent_id=? order by sort asc,classify_id asc limit 1`
+	//err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// SetClassifySortByParentId 实现设置分类排序的方法
+func (m *WareHouseProcessClassify) SetClassifySortByParentId(parentId, classifyId, sort int, sortUpdate string) (err error) {
+	//o := orm.NewOrmUsingDB("data")
+	//sql := ` update edb_classify set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? AND classify_type = ? `
+	//if classifyId > 0 {
+	//	sql += ` or ( classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	//}
+	//_, err = o.Raw(sql, parentId, nowSort, classifyType).Exec()
+	return
+}
+
+// GetCommonClassifyObjCols 通用分类对象字段映射
+func (m *WareHouseProcessClassify) GetCommonClassifyObjCols() models.CommonClassifyObjCols {
+	// TODO: 完善
+	return models.CommonClassifyObjCols{
+		ObjectId:   m.Cols().ClassifyName,
+		ClassifyId: m.Cols().PrimaryId,
+		Sort:       m.Cols().ParentId,
+	}
+}
+
+func (m *WareHouseProcessClassify) GetObjectById(objectId int) (*models.CommonClassifyObj, error) {
+	// 实现获取分类信息的逻辑
+	return nil, nil
+}
+
+// GetObjectSortMaxByClassifyId 获取分类下最大排序
+func (m *WareHouseProcessClassify) GetObjectSortMaxByClassifyId(classifyId int) (sortMax int, err error) {
+	//	o := orm.NewOrmUsingDB("data")
+	//	sql := `SELECT Max(sort) AS sort FROM edb_info WHERE classify_id=? `
+	//	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	//	return
+	return
+}
+
+func (m *WareHouseProcessClassify) GetFirstObjectByClassifyId(classifyId int) (item *models.CommonClassifyObj, err error) {
+	//o := orm.NewOrmUsingDB("data")
+	//sql := ` SELECT * FROM edb_info WHERE classify_id=? order by sort asc,edb_info_id asc limit 1`
+	//err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func (m *WareHouseProcessClassify) SetObjectSortByClassifyId(classifyId, sort, prevObjectId int, sortUpdate string) (err error) {
+	//o := orm.NewOrmUsingDB("data")
+	//sql := ` update edb_info set sort = ` + updateSort + ` WHERE classify_id=?`
+	//if prevEdbInfoId > 0 {
+	//	sql += ` AND ( sort > ? or ( edb_info_id > ` + fmt.Sprint(prevEdbInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	//} else {
+	//	sql += ` AND ( sort > ? )`
+	//}
+	//_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// UpdateCommonClassifyObj 更新通用分类对象
+func (m *WareHouseProcessClassify) UpdateCommonClassifyObj(object *models.CommonClassifyObj, updateCols []string) (err error) {
+	return
+}
+
+// ------------------------------------------------ 通用分类 ------------------------------------------------

+ 15 - 13
models/db.go

@@ -506,15 +506,16 @@ func initChartFramework() {
 // initExcel 初始化EXCEL
 func initExcel() {
 	orm.RegisterModel(
-		new(excel.ExcelClassify),   //ETA excel表格分类
-		new(excel.ExcelInfo),       //ETA excel表格
-		new(excel.ExcelDraft),      //ETA excel表格草稿
-		new(excel.ExcelSheet),      //ETA excel sheet
-		new(excel.ExcelSheetData),  //ETA excel sheet data
-		new(excel.ExcelEdbMapping), //ETA excel 与 指标 的关系表
-		new(excel.ExcelWorker),     // 平衡表协作人表格
-		new(excel.ExcelChartEdb),   // 平衡表做图指标
-		new(excel.ExcelChartData),  // 平衡表作图数据
+		new(excel.ExcelClassify),        //ETA excel表格分类
+		new(excel.ExcelInfo),            //ETA excel表格
+		new(excel.ExcelDraft),           //ETA excel表格草稿
+		new(excel.ExcelSheet),           //ETA excel sheet
+		new(excel.ExcelSheetData),       //ETA excel sheet data
+		new(excel.ExcelEdbMapping),      //ETA excel 与 指标 的关系表
+		new(excel.ExcelWorker),          // 平衡表协作人表格
+		new(excel.ExcelChartEdb),        // 平衡表做图指标
+		new(excel.ExcelChartData),       // 平衡表作图数据
+		new(excel.ExcelInfoRuleMapping), //表格的管理规则
 	)
 }
 
@@ -627,10 +628,11 @@ func initFeCalendar() {
 // initFactorEdbSeries 因子指标系列数据表
 func initFactorEdbSeries() {
 	orm.RegisterModel(
-		new(data_manage.FactorEdbSeries),              // 因子指标系列
-		new(data_manage.FactorEdbSeriesChartMapping),  // 因子指标系列-图表关联
-		new(data_manage.FactorEdbSeriesMapping),       // 因子指标系列-指标计算数据
-		new(data_manage.FactorEdbSeriesCalculateData), // 因子指标系列-指标关联
+		new(data_manage.FactorEdbSeries),                  // 因子指标系列
+		new(data_manage.FactorEdbSeriesChartMapping),      // 因子指标系列-图表关联
+		new(data_manage.FactorEdbSeriesMapping),           // 因子指标系列-指标计算数据
+		new(data_manage.FactorEdbSeriesCalculateData),     // 因子指标系列-指标关联
+		new(data_manage.FactorEdbSeriesCalculateDataQjjs), // 因子指标系列-区间计算数据
 	)
 }
 

+ 33 - 0
models/manual_edb.go

@@ -86,6 +86,7 @@ type EdbInfoListItem struct {
 	StartDate    string  `description:"数据开始日期"`
 	EndDate      string  `description:"数据结束日期"`
 	LatestValue  float64 `description:"指标最新值"`
+	NextDateTime string  `description:"下期时间"`
 }
 
 // EdbListResp 指标数据结构体
@@ -120,6 +121,38 @@ func GetEdbInfoList(condition string, pars []interface{}, startSize, pageSize in
 	return
 }
 
+// GetEdbInfoSortList
+// @Description: 根据条件获取指标列表 排序
+// @author: gmy
+// @datetime 2024-09-05 15:25:05
+// @param condition string
+// @param pars []interface{}
+// @return items []*Edbinfo
+// @return err error
+func GetEdbInfoSortList(condition string, pars []interface{}, startSize, pageSize int, orderField, orderType string) (items []*EdbInfoListItem, err error) {
+	o := orm.NewOrmUsingDB("edb")
+	sql := `SELECT a.* FROM edbinfo AS a  WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	if orderField == "" {
+		orderField = "end_date"
+	}
+	if orderType == "" {
+		orderType = "DESC"
+	}
+	sql += ` ORDER BY a.` + orderField + ` ` + orderType
+
+	if pageSize > 0 {
+		sql += ` LIMIT ?,? `
+		pars = append(pars, startSize, pageSize)
+	}
+
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+
+	return
+}
+
 // GetCountEdbInfoList
 // @Description: 根据条件获取指标数量
 // @author: Roc

+ 31 - 20
models/target.go

@@ -72,6 +72,15 @@ type Edbdata struct {
 	ModifyTime time.Time `orm:"column(modify_time)" description:"修改时间"`
 }
 
+type EdbDataNextDateTime struct {
+	TradeCode  string    `orm:"column(TRADE_CODE);pk" description:"指标编码"`
+	Dt         string    `orm:"column(DT)" description:"日期"`
+	Close      string    `orm:"column(CLOSE)" description:"值"`
+	ModifyTime time.Time `orm:"column(modify_time)" description:"修改时间"`
+
+	NextDateTime string `description:"下期日期"`
+}
+
 func GetDataInfo(tradeCode, creteDate string) (item *Edbdata, err error) {
 	sql := " SELECT * FROM edbdata WHERE TRADE_CODE=? AND DT=? "
 	o := orm.NewOrmUsingDB("edb")
@@ -532,6 +541,7 @@ type EdbdataImportResp struct {
 	Msg          string
 	SuccessCount int
 	FailCount    int
+	IndexCount   int `description:"指标数"`
 }
 
 func GetFailList(sysUserId int) (items []*EdbdataImportFail, err error) {
@@ -1264,26 +1274,27 @@ func GetLzFrequency(productName string) (items []*int, err error) {
 
 // EdbInfoItem
 type EdbInfoItem struct {
-	TradeCode    string     `orm:"column(TRADE_CODE);pk" description:"指标code"`
-	SecName      string     `orm:"column(SEC_NAME);" description:"指标名称"`
-	Unit         string     `orm:"column(UNIT);" description:"单位"`
-	Remark       string     `orm:"column(REMARK);" description:"备注"`
-	Frequency    string     `description:"频度"`
-	ClassifyId   int        `description:"分类id"`
-	ClassifyName string     `description:"分类名称"`
-	CreateDate   string     `description:"创建时间"`
-	UserId       int        `description:"录入用户id"`
-	UserName     string     `description:"录入用户名称"`
-	NoticeTime   string     `description:"通知时间"`
-	Mobile       string     `description:"录入者手机号"`
-	ModifyDate   string     `description:"待更新日期"`
-	ModifyTime   string     `description:"最近一次更新时间"`
-	Status       string     `description:"状态:未完成/完成"`
-	IsJoinEdb    int8       `description:"指标库是否已添加:0-否;1-是"`
-	StartDate    string     `description:"数据开始日期"`
-	EndDate      string     `description:"数据结束日期"`
-	LatestValue  float64    `description:"指标最新值"`
-	DataList     []*Edbdata `description:"指标数据列表"`
+	TradeCode        string                 `orm:"column(TRADE_CODE);pk" description:"指标code"`
+	SecName          string                 `orm:"column(SEC_NAME);" description:"指标名称"`
+	Unit             string                 `orm:"column(UNIT);" description:"单位"`
+	Remark           string                 `orm:"column(REMARK);" description:"备注"`
+	Frequency        string                 `description:"频度"`
+	ClassifyId       int                    `description:"分类id"`
+	ClassifyName     string                 `description:"分类名称"`
+	CreateDate       string                 `description:"创建时间"`
+	UserId           int                    `description:"录入用户id"`
+	UserName         string                 `description:"录入用户名称"`
+	NoticeTime       string                 `description:"通知时间"`
+	Mobile           string                 `description:"录入者手机号"`
+	ModifyDate       string                 `description:"待更新日期"`
+	ModifyTime       string                 `description:"最近一次更新时间"`
+	Status           string                 `description:"状态:未完成/完成"`
+	IsJoinEdb        int8                   `description:"指标库是否已添加:0-否;1-是"`
+	StartDate        string                 `description:"数据开始日期"`
+	EndDate          string                 `description:"数据结束日期"`
+	LatestValue      float64                `description:"指标最新值"`
+	DataList         []*Edbdata             `description:"指标数据列表"`
+	NextDataTimeList []*EdbDataNextDateTime `description:"下期数据时间列表"`
 }
 
 // GetTargetItemList 获取指标列表数据

+ 594 - 0
routers/commentsRouter.go

@@ -1276,6 +1276,60 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "AddExcelRule",
+            Router: `/excel_info/rule/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "DeleteExcelRule",
+            Router: `/excel_info/rule/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetExcelRuleDetail",
+            Router: `/excel_info/rule/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "EditExcelRule",
+            Router: `/excel_info/rule/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetExcelRuleList",
+            Router: `/excel_info/rule/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
+        beego.ControllerComments{
+            Method: "GetBatchEdbData",
+            Router: `/excel_info/table/batch_edb_data_list`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/excel:ExcelInfoController"],
         beego.ControllerComments{
             Method: "BatchRefresh",
@@ -2059,6 +2113,195 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/chart_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "BaseInfoEdit",
+            Router: `/chart_info/base/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Copy",
+            Router: `/chart_info/copy`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/chart_info/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "DetailFromUniqueCode",
+            Router: `/chart_info/detail/from_unique_code`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/chart_info/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/chart_info/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Preview",
+            Router: `/chart_info/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/chart_info/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoSave",
+            Router: `/chart_info/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/chart_info/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultipleGraphConfigEdbList",
+            Router: `/edb/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartChartInfoController"],
+        beego.ControllerComments{
+            Method: "MultipleGraphConfigSaveEdb",
+            Router: `/edb/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "AddChartClassify",
+            Router: `/chart_classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassify",
+            Router: `/chart_classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassifyCheck",
+            Router: `/chart_classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "EditChartClassify",
+            Router: `/chart_classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyItems",
+            Router: `/chart_classify/items`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyList",
+            Router: `/chart_classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyMove",
+            Router: `/chart_classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/range_analysis:RangeChartClassifyController"],
+        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/supply_analysis:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/supply_analysis:VarietyController"],
         beego.ControllerComments{
             Method: "Add",
@@ -2257,6 +2500,60 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyClassifyController"],
+        beego.ControllerComments{
+            Method: "LyClassifyList",
+            Router: `/ly/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexAdd",
+            Router: `/ly/index/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexAddValidate",
+            Router: `/ly/index/add/validate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexDataExport",
+            Router: `/ly/index/data/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexDataList",
+            Router: `/ly/index/data/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexList",
+            Router: `/ly/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromNationalStatisticsController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromNationalStatisticsController"],
         beego.ControllerComments{
             Method: "ClassifyList",
@@ -4219,6 +4516,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: "EiaSteoBatchAdd",
+            Router: `/eia_steo/batch_add`,
+            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: "EiaSteoBatchSearch",
+            Router: `/eia_steo/batch_search`,
+            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: "EiaSteoClassify",
@@ -4237,6 +4552,33 @@ 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: "EiaSteoAdd",
+            Router: `/eia_steo/edb_info/add`,
+            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: "EiaSteoAddCheck",
+            Router: `/eia_steo/edb_info/add_check`,
+            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: "EiaSteoNameCheck",
+            Router: `/eia_steo/name_check`,
+            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: "EiaSteoSearchList",
@@ -4318,6 +4660,42 @@ 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: "GetFenWeiFrequencyList",
+            Router: `/fenwei/frequency/list`,
+            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: "GetFenWeiIndexInfo",
+            Router: `/fenwei/get/index/info`,
+            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: "FenWeiIndexAdd",
+            Router: `/fenwei/index/add`,
+            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: "FenWeiIndexAddValidate",
+            Router: `/fenwei/index/add/validate`,
+            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: "FenweiIndexData",
@@ -4327,6 +4705,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: "FenWeiIndexDataExport",
+            Router: `/fenwei/index/data/export`,
+            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: "FenweiSearchList",
@@ -7801,6 +8188,42 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeClassifyList",
+            Router: `/classify_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeFuturesCompanyList",
+            Router: `/company_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeContractList",
+            Router: `/contract_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeExchangeList",
+            Router: `/exchange_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
         beego.ControllerComments{
             Method: "GetPositionTop",
@@ -7810,6 +8233,177 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "AddChartClassify",
+            Router: `/warehouse/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassify",
+            Router: `/warehouse/classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassifyCheck",
+            Router: `/warehouse/classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "EditChartClassify",
+            Router: `/warehouse/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyItems",
+            Router: `/warehouse/classify/items`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyList",
+            Router: `/warehouse/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyMove",
+            Router: `/warehouse/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/warehouse/classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/warehouse/chart/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Copy",
+            Router: `/warehouse/chart/copy`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/warehouse/chart/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/warehouse/chart/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/warehouse/chart/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Preview",
+            Router: `/warehouse/chart/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/warehouse/chart/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "ChartInfoSave",
+            Router: `/warehouse/chart/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/warehouse/chart/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "EdbSave",
+            Router: `/warehouse/edb/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "EdbSaveCheck",
+            Router: `/warehouse/edb/save_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:BannerController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:BannerController"],
         beego.ControllerComments{
             Method: "Upload",

+ 11 - 0
routers/router.go

@@ -18,6 +18,7 @@ import (
 	future_good2 "eta/eta_api/controllers/data_manage/future_good"
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
+	"eta/eta_api/controllers/data_manage/range_analysis"
 	"eta/eta_api/controllers/data_manage/supply_analysis"
 	"eta/eta_api/controllers/data_source"
 	"eta/eta_api/controllers/data_stat"
@@ -176,6 +177,8 @@ func init() {
 				&data_manage.EdbInfoRelationController{},
 				&data_manage.FactorEdbSeriesController{},
 				&data_manage.SciHqDataController{},
+				&data_manage.BaseFromLyClassifyController{},
+				&data_manage.BaseFromLyIndexController{},
 			),
 		),
 		web.NSNamespace("/my_chart",
@@ -286,6 +289,8 @@ func init() {
 		web.NSNamespace("/trade_analysis",
 			web.NSInclude(
 				&trade_analysis.TradeAnalysisController{},
+				&trade_analysis.WarehouseClassifyController{},
+				&trade_analysis.WarehouseController{},
 			),
 		),
 		web.NSNamespace("/custom_analysis",
@@ -380,6 +385,12 @@ func init() {
 				&fe_calendar.FeCalendarMatterController{},
 			),
 		),
+		web.NSNamespace("/range_analysis",
+			web.NSInclude(
+				&range_analysis.RangeChartClassifyController{},
+				&range_analysis.RangeChartChartInfoController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 6 - 0
services/data/base_edb_lib.go

@@ -76,6 +76,8 @@ func AddEdbData(source int, edbCode, frequency string) (resp *models.BaseRespons
 		urlStr = "sci99/add"
 	case utils.DATA_SOURCE_SCI_HQ:
 		urlStr = "sci_hq/add"
+	case utils.DATA_SOURCE_LY:
+		urlStr = "ly/add"
 	default:
 		edbSource := data_manage.EdbSourceIdMap[source]
 		if edbSource != nil {
@@ -274,6 +276,10 @@ func RefreshEdbData(edbInfoId, source, subSource int, edbCode, startDate string)
 		urlStr = "sci99/refresh"
 	case utils.DATA_SOURCE_SCI_HQ:
 		urlStr = "sci_hq/refresh"
+	case utils.DATA_SOURCE_LY:
+		urlStr = "ly/refresh"
+	case utils.DATA_SOURCE_TRADE_ANALYSIS:
+		urlStr = "trade_analysis/edb/refresh"
 	default:
 		edbSource := data_manage.EdbSourceIdMap[source]
 		if edbSource != nil {

+ 79 - 0
services/data/base_from_eia_steo.go

@@ -0,0 +1,79 @@
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+func GetClassifyALLById(classifyId int) (items []*data_manage.BaseFromEiaSteoClassify, err error) {
+	classify, err := data_manage.GetEiaSteoClassifyById(classifyId)
+	if err != nil {
+		return
+	}
+	if classify.Level == 1 {
+		childClassify, er := data_manage.GetChildEiaSteoClassifyById(classifyId)
+		if er != nil {
+			err = er
+			return
+		}
+		items = append(items, childClassify...)
+	}
+	// 兼容之前部分指标在一级分类下的情况
+	if len(items) == 0 {
+		items = append(items, classify)
+	}
+	return
+}
+
+type EiaSteoIndexSource2EdbReq struct {
+	EdbCode       string
+	EdbName       string
+	Frequency     string
+	Unit          string
+	ClassifyId    int
+	AdminId       int
+	AdminRealName string
+}
+
+// EiaSteoIndexSource2Edb 新增eiaSteo数据源到指标库
+func EiaSteoIndexSource2Edb(req EiaSteoIndexSource2EdbReq, lang string) (edb *data_manage.EdbInfo, err error, errMsg string, skip bool) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("EiaSteoIndexSource2Edb新增失败, Err: %s", err.Error())
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_EIA_STEO
+
+	// 是否新增过指标
+	exist, e := data_manage.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取指标是否存在失败, err: %s", e.Error())
+		return
+	}
+	if exist != nil {
+		skip = true
+		return
+	}
+
+	// 开始结束时间
+	var startDate, endDate string
+
+	// 新增指标库
+	edbInfo, e, msg, _ := EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, startDate, endDate, req.AdminId, req.AdminRealName, lang)
+	if e != nil {
+		errMsg = msg
+		err = fmt.Errorf("EdbInfo: 新增指标失败, err: %s", e.Error())
+		return
+	}
+
+	edb = edbInfo
+
+	return
+}

+ 162 - 0
services/data/base_from_fenwei_index_service.go

@@ -0,0 +1,162 @@
+// Package data
+// @Author gmy 2024/8/22 9:44:00
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+// FenWeiIndexAddValidate 指标添加校验
+func FenWeiIndexAddValidate(indexCodes []string) (*[]data_manage.FenWeiIndexCheckData, error) {
+	// 根据指标编码获取指标库 指标信息
+	edbInfos, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_FENWEI, indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	var respList []data_manage.FenWeiIndexCheckData
+	if len(edbInfos) > 0 {
+		for _, ebdInfo := range edbInfos {
+			respList = append(respList, data_manage.FenWeiIndexCheckData{
+				IndexCode:  ebdInfo.EdbCode,
+				IndexName:  ebdInfo.EdbName,
+				Unit:       ebdInfo.Unit,
+				Frequency:  ebdInfo.Frequency,
+				EdbInfoId:  ebdInfo.EdbInfoId,
+				ClassifyId: ebdInfo.ClassifyId,
+				UniqueCode: ebdInfo.UniqueCode,
+			})
+		}
+	}
+	return &respList, nil
+}
+
+// FenWeiIndexNameCheck 指标名称校验
+func FenWeiIndexNameCheck(indexNames []string, resp []*data_manage.FenWeiNameCheckResult) ([]*data_manage.FenWeiNameCheckResult, error) {
+	// 重名校验
+	edbList, e := data_manage.GetEdbInfoByNameArr(indexNames, utils.EDB_INFO_TYPE)
+	if e != nil {
+		return nil, e
+	}
+	nameExists := make(map[string]bool)
+	for _, edbInfo := range edbList {
+		nameExists[edbInfo.EdbName] = true
+	}
+	if len(nameExists) > 0 {
+		for _, v := range resp {
+			v.Exist = nameExists[v.IndexName]
+		}
+	}
+	return resp, nil
+}
+
+// FenWeiIndexAdd 批量添加指标
+func FenWeiIndexAdd(req data_manage.FenWeiIndexAddReq, lang string) (edb *data_manage.EdbInfo, err error, errMsg string, skip bool) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("BloombergIndexSource2Edb新增失败, Err: %s", err.Error())
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_FENWEI
+
+	// 是否已有指标数据
+	dataList, e := data_manage.GetEdbDataAllByEdbCode(req.EdbCode, source, utils.DATA_SUB_SOURCE_EDB, utils.EDB_DATA_LIMIT)
+	if e != nil {
+		err = fmt.Errorf("获取指标数据失败, Err: %s", e.Error())
+		return
+	}
+
+	// 新增指标数据
+	if len(dataList) == 0 {
+		res, e := AddEdbData(source, req.EdbCode, req.Frequency)
+		if e != nil {
+			err = fmt.Errorf("index_lib: 新增指标数据失败, Err: %s", e.Error())
+			return
+		}
+		if res == nil {
+			err = fmt.Errorf("index_lib: 新增指标数据失败, res nil")
+			return
+		}
+		if res.Ret != 200 {
+			err = fmt.Errorf("index_lib: 新增指标数据失败, Ret: %d", res.Ret)
+			return
+		}
+	}
+
+	// 是否新增过指标
+	exist, e := data_manage.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取指标是否存在失败, err: %s", e.Error())
+		return
+	}
+	if exist != nil {
+		skip = true
+		return
+	}
+
+	// 开始结束时间
+	var startDate, endDate string
+	minMax, e := data_manage.GetEdbInfoMaxAndMinInfo(source, utils.DATA_SUB_SOURCE_EDB, req.EdbCode)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("MinMax: 获取指标极值失败, err: %s", e.Error())
+		return
+	}
+	if minMax != nil {
+		startDate = minMax.MinDate
+		endDate = minMax.MaxDate
+	}
+
+	// 新增指标到指标库
+	edbInfo, e, msg, _ := EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, startDate, endDate, req.AdminId, req.AdminRealName, lang)
+	if e != nil {
+		errMsg = msg
+		err = fmt.Errorf("EdbInfo: 新增指标失败, err: %s", e.Error())
+		return
+	}
+	edb = edbInfo
+
+	// EdbInfoAdd方法已经新增es,这里不需要再新增???
+	// 新增es
+	// go AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+	return
+}
+
+// GetFenWeiIndexInfo 获取指标信息-分页
+func GetFenWeiIndexInfo(keyWord string, classifyIdList []string, frequencyList []string) (fenWeiIndexInfoList []*data_manage.BaseFromFenweiIndex, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if keyWord != "" {
+		condition += ` AND CONCAT(index_name,index_code) LIKE '%` + keyWord + `%'`
+	}
+	if len(classifyIdList) > 0 {
+		condition += ` AND classify_id IN (`
+		for _, v := range classifyIdList {
+			condition += `?,`
+			pars = append(pars, v)
+		}
+		condition = condition[:len(condition)-1] + `)`
+	}
+	if len(frequencyList) > 0 {
+		condition += ` AND frequency IN (`
+		for _, v := range frequencyList {
+			condition += `?,`
+			pars = append(pars, v)
+		}
+		condition = condition[:len(condition)-1] + `)`
+	}
+
+	fenWeiIndexInfoList, err = data_manage.GetFenWeiIndexInfoPage(condition, pars)
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 47 - 0
services/data/base_from_ly_classify_service.go

@@ -0,0 +1,47 @@
+// Package data
+// @Author gmy 2024/8/12 16:09:00
+package data
+
+import "eta/eta_api/models/data_manage"
+
+// LyClassifyList 获取分类列表 包含 分类下的指标名称
+func LyClassifyList() ([]*data_manage.BaseFromLyClassifyAndIndexInfo, error) {
+	// step_1 获取分类列表
+	lyClassify, err := data_manage.GetAllLyClassify()
+	if err != nil {
+		return nil, err
+	}
+	// step_2 分类id列表
+	classifyIds := make([]int, 0)
+	for _, v := range lyClassify {
+		classifyIds = append(classifyIds, v.BaseFromLyClassifyId)
+	}
+	// step_3 获取分类下的指标
+	lyIndices, err := data_manage.GetLyIndexByClassifyIds(classifyIds)
+	if err != nil {
+		return nil, err
+	}
+	// step_4 封装返回
+	lyClassifyAndIndexInfos := make([]*data_manage.BaseFromLyClassifyAndIndexInfo, 0)
+	for _, classify := range lyClassify {
+		lyClassifyAndIndexInfo := &data_manage.BaseFromLyClassifyAndIndexInfo{
+			BaseFromLyClassifyId: classify.BaseFromLyClassifyId,
+			CreateTime:           classify.CreateTime,
+			ModifyTime:           classify.ModifyTime,
+			ClassifyName:         classify.ClassifyName,
+			ParentId:             classify.ParentId,
+			Sort:                 classify.Sort,
+			ClassifyNameEn:       classify.ClassifyNameEn,
+		}
+		for _, lyIndex := range lyIndices {
+			if classify.BaseFromLyClassifyId == lyIndex.BaseFromLyClassifyId {
+				lyClassifyAndIndexInfo.IndexId = lyIndex.BaseFromLyIndexId
+				lyClassifyAndIndexInfo.IndexCode = lyIndex.IndexCode
+				lyClassifyAndIndexInfo.IndexName = lyIndex.IndexName
+			}
+		}
+		lyClassifyAndIndexInfos = append(lyClassifyAndIndexInfos, lyClassifyAndIndexInfo)
+	}
+
+	return lyClassifyAndIndexInfos, nil
+}

+ 249 - 0
services/data/base_from_ly_index_service.go

@@ -0,0 +1,249 @@
+// Package data
+// @Author gmy 2024/8/12 16:44:00
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/core/logs"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// GetIndexPage 获取指标分页列表
+func GetIndexPage(classifyId string, searchParam string, currentIndex, pageSize int) (*data_manage.BaseFromLyIndexPage, error) {
+	count, err := data_manage.GetLyIndexCount(classifyId, searchParam)
+	if err != nil {
+		return nil, err
+	}
+	lyIndexPage := data_manage.BaseFromLyIndexPage{}
+	page := paging.GetPaging(currentIndex, pageSize, count)
+
+	if count <= 0 {
+		lyIndexPage.Paging = page
+		return &lyIndexPage, nil
+	}
+
+	lyIndexList, err := data_manage.GetLyIndexPage(classifyId, searchParam, currentIndex, pageSize)
+	if err != nil {
+		return nil, err
+	}
+
+	// 获取指标编码列表
+	var indexCodes []string
+	for _, v := range lyIndexList {
+		indexCodes = append(indexCodes, v.IndexCode)
+	}
+
+	// 获取指标数据
+	lyIndexData, err := data_manage.GetLyLastUpdateTimeLastByIndexCode(indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	// 将lyIndexData转换为map
+	lyIndexDataMap := make(map[string]*data_manage.BaseFromLyData)
+	for _, v := range lyIndexData {
+		lyIndexDataMap[v.IndexCode] = v
+	}
+
+	lyData, err := data_manage.GetLyLastDataTimeByIndexCode(indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	// 将lyData转换为map
+	lyDataMap := make(map[string]*data_manage.BaseFromLyData)
+	for _, v := range lyData {
+		lyDataMap[v.IndexCode] = v
+	}
+
+	// 查询指标库是否已添加  不做另外补偿维护index表中的edb_exist字段,直接去查,因为维护会改变指标库指标添加代码结构
+	edbInfoList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_LY, indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	edbInfoMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range edbInfoList {
+		edbInfoMap[v.EdbCode] = v
+	}
+
+	if len(lyIndexData) > 0 {
+		for _, v := range lyIndexList {
+			if lyIndexDataMap[v.IndexCode] != nil {
+				toYmd := utils.TimeFormatToYmd(lyIndexDataMap[v.IndexCode].ModifyTime)
+				v.ModifyTimeMax = toYmd
+			}
+			if lyDataMap[v.IndexCode] != nil {
+				v.Value = lyDataMap[v.IndexCode].Value
+			}
+			if edbInfoMap[v.IndexCode] != nil {
+				v.EdbExist = utils.IS_YES
+			}
+		}
+	}
+
+	lyIndexPage.List = lyIndexList
+	lyIndexPage.Paging = page
+
+	return &lyIndexPage, nil
+}
+
+// GetIndexDataPage 获取指标分页数据列表
+func GetIndexDataPage(indexId, startSize, pageSize int) (*data_manage.BaseFromLyDataPage, error) {
+	count, err := data_manage.GetLyDataCountByIndexId(indexId)
+	if err != nil {
+		return nil, err
+	}
+	lyDataPage := data_manage.BaseFromLyDataPage{}
+	page := paging.GetPaging(startSize, pageSize, count)
+
+	if count <= 0 {
+		lyDataPage.Paging = page
+		return &lyDataPage, nil
+	}
+
+	dataList, err := data_manage.GetLyDataPageByIndexId(indexId, startSize, pageSize)
+	if err != nil {
+		return nil, err
+	}
+	lyDataPage.List = dataList
+	lyDataPage.Paging = page
+
+	return &lyDataPage, nil
+}
+
+// LyIndexAddValidate 指标添加校验
+func LyIndexAddValidate(indexCodes []string) (*[]data_manage.IndexCheckData, error) {
+	// 根据指标编码获取指标库 指标信息
+	edbInfos, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_LY, indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	var respList []data_manage.IndexCheckData
+	if len(edbInfos) > 0 {
+		for _, ebdInfo := range edbInfos {
+			respList = append(respList, data_manage.IndexCheckData{
+				IndexCode:  ebdInfo.EdbCode,
+				IndexName:  ebdInfo.EdbName,
+				Unit:       ebdInfo.Unit,
+				Frequency:  ebdInfo.Frequency,
+				EdbInfoId:  ebdInfo.EdbInfoId,
+				ClassifyId: ebdInfo.ClassifyId,
+				UniqueCode: ebdInfo.UniqueCode,
+			})
+		}
+	}
+	return &respList, nil
+}
+
+// LyIndexNameCheck 指标名称校验
+func LyIndexNameCheck(indexNames []string, resp []*data_manage.NameCheckResult) ([]*data_manage.NameCheckResult, error) {
+	// 重名校验
+	edbList, e := data_manage.GetEdbInfoByNameArr(indexNames, utils.EDB_INFO_TYPE)
+	if e != nil {
+		return nil, e
+	}
+	nameExists := make(map[string]bool)
+	for _, edbInfo := range edbList {
+		nameExists[edbInfo.EdbName] = true
+	}
+	if len(nameExists) > 0 {
+		for _, v := range resp {
+			v.Exist = nameExists[v.IndexName]
+		}
+	}
+	return resp, nil
+}
+
+type LyIndexAddReq struct {
+	EdbCode       string `description:"指标编码"`
+	EdbName       string `description:"指标名称"`
+	Frequency     string `description:"频度"`
+	Unit          string `description:"单位"`
+	ClassifyId    int    `description:"分类ID"`
+	AdminId       int    `description:"管理员ID"`
+	AdminRealName string `description:"管理员名称"`
+}
+
+// LyIndexAdd 批量添加指标
+func LyIndexAdd(req LyIndexAddReq, lang string) (edb *data_manage.EdbInfo, err error, errMsg string, skip bool) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("BloombergIndexSource2Edb新增失败, Err: %s", err.Error())
+			logs.Info(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_LY
+
+	// 是否已有指标数据
+	dataList, e := data_manage.GetEdbDataAllByEdbCode(req.EdbCode, source, utils.DATA_SUB_SOURCE_EDB, utils.EDB_DATA_LIMIT)
+	if e != nil {
+		err = fmt.Errorf("获取指标数据失败, Err: %s", e.Error())
+		return
+	}
+
+	// 新增指标数据
+	if len(dataList) == 0 {
+		res, e := AddEdbData(source, req.EdbCode, req.Frequency)
+		if e != nil {
+			err = fmt.Errorf("index_lib: 新增指标数据失败, Err: %s", e.Error())
+			return
+		}
+		if res == nil {
+			err = fmt.Errorf("index_lib: 新增指标数据失败, res nil")
+			return
+		}
+		if res.Ret != 200 {
+			err = fmt.Errorf("index_lib: 新增指标数据失败, Ret: %d", res.Ret)
+			return
+		}
+	}
+
+	// 是否新增过指标
+	exist, e := data_manage.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取指标是否存在失败, err: %s", e.Error())
+		return
+	}
+	if exist != nil {
+		skip = true
+		return
+	}
+
+	// 开始结束时间
+	var startDate, endDate string
+	minMax, e := data_manage.GetEdbInfoMaxAndMinInfo(source, utils.DATA_SUB_SOURCE_EDB, req.EdbCode)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("MinMax: 获取指标极值失败, err: %s", e.Error())
+		return
+	}
+	if minMax != nil {
+		startDate = minMax.MinDate
+		endDate = minMax.MaxDate
+	}
+
+	// 新增指标到指标库
+	edbInfo, e, msg, _ := EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, startDate, endDate, req.AdminId, req.AdminRealName, lang)
+	if e != nil {
+		errMsg = msg
+		err = fmt.Errorf("EdbInfo: 新增指标失败, err: %s", e.Error())
+		return
+	}
+	edb = edbInfo
+
+	// 标记原始指标为已添加
+	/*err = data_manage.UpdateLyIndexEdbExist(req.EdbCode, utils.IS_YES)
+	if err != nil {
+		err = fmt.Errorf("BaseIndex: 标记已添加指标库失败, err: %s", err.Error())
+		return
+	}*/
+
+	// EdbInfoAdd方法已经新增es,这里不需要再新增???
+	// 新增es
+	// go AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+	return
+}

+ 485 - 0
services/data/common_classify.go

@@ -0,0 +1,485 @@
+package data
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func CommonClassifyMove(req models.CommonClassifyMoveReq, strategy CommonClassifyStrategy) (tips string, err error) {
+	ctx := NewCommonClassifyCtx(strategy)
+
+	var (
+		classify       *models.CommonClassify
+		parentClassify *models.CommonClassify
+		prevClassify   *models.CommonClassify
+		nextClassify   *models.CommonClassify
+		object         *models.CommonClassifyObj
+		prevObject     *models.CommonClassifyObj
+		nextObject     *models.CommonClassifyObj
+		sortPrev       int
+		sortNext       int
+	)
+
+	// 获取父级分类
+	if req.ParentClassifyId > 0 {
+		c, e := ctx.GetCommonClassifyById(req.ParentClassifyId)
+		if e != nil {
+			err = fmt.Errorf("获取上级分类失败, %v", e)
+			return
+		}
+		parentClassify = c
+	}
+
+	// 兄弟节点
+	if req.PrevClassifyId > 0 {
+		c, e := ctx.GetCommonClassifyById(req.PrevClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				tips = "上一个分类不存在, 请刷新页面"
+				return
+			}
+			err = fmt.Errorf("获取上一个分类失败, %v", e)
+			return
+		}
+		prevClassify = c
+		sortPrev = prevClassify.Sort
+	} else if req.PrevObjectId > 0 {
+		obj, e := ctx.GetObjectById(req.PrevObjectId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				tips = "上一个移动对象不存在, 请刷新页面"
+				return
+			}
+			err = fmt.Errorf("获取上一个移动对象失败, %v", e)
+			return
+		}
+		prevObject = obj
+		sortPrev = prevObject.Sort
+	}
+	if req.NextClassifyId > 0 {
+		c, e := ctx.GetCommonClassifyById(req.NextClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				tips = "下一个分类不存在, 请刷新页面"
+				return
+			}
+			err = fmt.Errorf("获取下一个分类失败, %v", e)
+			return
+		}
+		nextClassify = c
+		sortNext = nextClassify.Sort
+	} else if req.NextObjectId > 0 {
+		obj, e := ctx.GetObjectById(req.NextObjectId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				tips = "下一个移动对象不存在, 请刷新页面"
+				return
+			}
+			err = fmt.Errorf("获取下一个移动对象失败, %v", e)
+			return
+		}
+		nextObject = obj
+		sortNext = nextObject.Sort
+	}
+
+	// 移动分类
+	if req.ObjectId == 0 {
+		c, e := ctx.GetCommonClassifyById(req.ClassifyId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				tips = "当前分类不存在, 请刷新页面"
+				return
+			}
+			err = fmt.Errorf("获取当前分类失败, %v", e)
+			return
+		}
+		classify = c
+		return moveCommonClassify(ctx, req, classify, parentClassify, prevClassify, nextClassify, prevObject, nextObject, sortPrev, sortNext)
+	}
+
+	// 移动对象
+	if req.ObjectId > 0 {
+		obj, e := ctx.GetObjectById(req.ObjectId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				tips = "移动对象不存在, 请刷新页面"
+				return
+			}
+			err = fmt.Errorf("获取移动对象失败, %v", e)
+			return
+		}
+		if req.ParentClassifyId <= 0 {
+			tips = "移动对象必须挂在分类下"
+			return
+		}
+		object = obj
+
+		// TODO:对象的不同实现, 如指标校验权限
+
+		return moveCommonClassifyObj(ctx, req, prevClassify, nextClassify, object, prevObject, nextObject, sortPrev, sortNext)
+	}
+	return
+}
+
+func moveCommonClassify(ctx *CommonClassifyCtx, req models.CommonClassifyMoveReq, classify, parentClassify, prevClassify, nextClassify *models.CommonClassify, prevObject, nextObject *models.CommonClassifyObj, sortPrev, sortNext int) (tips string, err error) {
+	// 校验层级以及父级分类下同名分类
+	if req.ParentClassifyId > 0 && parentClassify.Level == 6 {
+		tips = "最高只支持添加6级分类"
+		return
+	}
+	exists, e := ctx.GetClassifyByParentIdAndName(req.ParentClassifyId, classify.ClassifyName, classify.ClassifyId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取父级分类下的同名分类失败, %v", e)
+		return
+	}
+	if exists != nil {
+		tips = "当前父级分类下存在相同名称"
+		return
+	}
+
+	// TODO:分类的非通用实现, 如指标分类需校验权限, 可以采用适配器模式兼容进来
+
+	var classifyChildIds []int
+	var classifyUpdateCols []string
+	originParentId := classify.ParentId
+	originLevel := classify.Level
+
+	// 需更新子层级分类ID
+	if originParentId != req.ParentClassifyId {
+		// TODO:此处如果要兼容以前的分类表, 那么要提出来处理
+		levelPathArr := strings.Split(classify.LevelPath, ",")
+		for _, p := range levelPathArr {
+			d, _ := strconv.Atoi(p)
+			if d > 0 {
+				classifyChildIds = append(classifyChildIds, d)
+			}
+		}
+	}
+
+	colsMapping := ctx.GetCommonClassifyCols() // 分类表实际的字段映射
+
+	// 判断上级ID是否一致, 不一致的话需要移动该分类层级
+	if classify.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+		if classify.Level != parentClassify.Level+1 { //禁止层级调整
+			tips = "不支持目录层级变更"
+			return
+		}
+		classify.ParentId = parentClassify.ClassifyId
+		classify.RootId = parentClassify.RootId
+		classify.Level = parentClassify.Level + 1
+		classify.ModifyTime = time.Now()
+		classifyUpdateCols = append(classifyUpdateCols, colsMapping.ParentId, colsMapping.RootId, colsMapping.Level, colsMapping.ModifyTime)
+	}
+	if classify.ParentId != req.ParentClassifyId && req.ParentClassifyId == 0 {
+		tips = "不支持目录层级变更"
+		return
+	}
+
+	if sortPrev > 0 {
+		// 移动至两个兄弟之间
+		if sortNext > 0 {
+			// 如果上一个兄弟与下一个兄弟的排序权重是一致的, 那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2, 自己变成上一个兄弟的排序权重+1
+			if sortPrev == sortNext || sortPrev == classify.Sort {
+				sortUpdate := `sort + 2`
+				if prevClassify != nil {
+					if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, prevClassify.ClassifyId, prevClassify.Sort, sortUpdate); e != nil {
+						err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, prevClassify.ClassifyId, prevClassify.Sort, sortUpdate, e)
+						return
+					}
+				} else {
+					if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, 0, sortPrev, sortUpdate); e != nil {
+						err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, sortPrev, sortUpdate, e)
+						return
+					}
+				}
+
+				if prevObject != nil {
+					if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate); e != nil {
+						err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate, e)
+						return
+					}
+				} else {
+					if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, 0, sortUpdate); e != nil {
+						err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, 0, sortUpdate, e)
+						return
+					}
+				}
+			} else {
+				// 如果下一个兄弟的排序权重正好是上个兄弟节点的下一层, 那么需要再加一层
+				if sortNext-sortPrev == 1 {
+					sortUpdate := `sort + 1`
+					if prevClassify != nil {
+						if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, prevClassify.ClassifyId, sortPrev, sortUpdate); e != nil {
+							err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, prevClassify.ClassifyId, sortPrev, sortUpdate, e)
+							return
+						}
+					} else {
+						if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, 0, sortPrev, sortUpdate); e != nil {
+							err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, sortPrev, sortUpdate, e)
+							return
+						}
+					}
+
+					if prevObject != nil {
+						if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate); e != nil {
+							err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate, e)
+							return
+						}
+					} else {
+						if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, 0, sortUpdate); e != nil {
+							err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, 0, sortUpdate, e)
+							return
+						}
+					}
+				}
+			}
+		}
+
+		classify.Sort = sortPrev + 1
+		classify.ModifyTime = time.Now()
+		classifyUpdateCols = append(classifyUpdateCols, colsMapping.Sort, colsMapping.ModifyTime)
+	} else if prevClassify == nil && nextClassify == nil && prevObject == nil && nextObject == nil && req.ParentClassifyId > 0 {
+		// 处理只拖动到目录里, 默认放到目录底部的情况
+		m, e := GetCommonClassifySortMaxByParentId(req.ParentClassifyId, ctx)
+		if e != nil {
+			err = fmt.Errorf("GetCommonClassifySortMaxByParentId, %v", e)
+			return
+		}
+		classify.Sort = m + 1
+		classify.ModifyTime = time.Now()
+		classifyUpdateCols = append(classifyUpdateCols, colsMapping.Sort, colsMapping.ModifyTime)
+	} else {
+		// 拖动到父级分类的第一位
+		firstClassify, e := ctx.GetFirstClassifyByParentId(req.ParentClassifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			//tips = "移动失败"
+			err = fmt.Errorf("GetFirstClassifyByParentId, %v", e)
+			return
+		}
+
+		// 如果该分类下存在其他分类, 且第一个其他分类的排序等于0, 那么需要调整排序
+		if firstClassify != nil && firstClassify.Sort == 0 {
+			sortUpdate := ` sort + 1 `
+			if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, firstClassify.ClassifyId-1, 0, sortUpdate); e != nil {
+				err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, firstClassify.ClassifyId-1, 0, sortUpdate, e)
+				return
+			}
+			// 该分类下的所有指标也需要+1
+			if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, 0, 0, sortUpdate); e != nil {
+				err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, 0, sortUpdate, e)
+				return
+			}
+		} else {
+			// 如果该分类下存在指标, 且第一个指标的排序等于0, 那么需要调整排序
+			firstObject, e := ctx.GetFirstObjectByClassifyId(req.ParentClassifyId)
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				err = fmt.Errorf("GetFirstObjectByClassifyId, %v", e)
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstObject != nil && firstObject.Sort == 0 {
+				sortUpdate := ` sort + 1 `
+				if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, 0, firstObject.ObjectId-1, sortUpdate); e != nil {
+					err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, firstObject.ObjectId-1, sortUpdate, e)
+					return
+				}
+				if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, 0, 0, sortUpdate); e != nil {
+					err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, 0, sortUpdate, e)
+					return
+				}
+			}
+		}
+
+		classify.Sort = 0 // 那就是排在第一位
+		classify.ModifyTime = time.Now()
+		classifyUpdateCols = append(classifyUpdateCols, colsMapping.Sort, colsMapping.ModifyTime)
+	}
+
+	// 更新分类
+	if len(classifyUpdateCols) > 0 {
+		if e = ctx.UpdateCommonClassify(classify, classifyUpdateCols); e != nil {
+			err = fmt.Errorf("UpdateCommonClassify, %v", e)
+			return
+		}
+
+		// 更新对应分类的root_id和层级
+		if originParentId != req.ParentClassifyId {
+			if len(classifyChildIds) > 0 {
+				stepLevel := classify.Level - originLevel
+				if e = ctx.UpdateClassifyChildByParentId(classifyChildIds, classify.RootId, stepLevel); e != nil {
+					err = fmt.Errorf("UpdateClassifyChildByParentId, parentId: %d, classifyId: %d, stepLevel: %d, err: %v", req.ParentClassifyId, classify.ClassifyId, stepLevel, e)
+					return
+				}
+			}
+		}
+	}
+	return
+}
+
+func moveCommonClassifyObj(ctx *CommonClassifyCtx, req models.CommonClassifyMoveReq, prevClassify, nextClassify *models.CommonClassify, object, prevObject, nextObject *models.CommonClassifyObj, sortPrev, sortNext int) (tips string, err error) {
+	var objUpdateCols []string
+	colsMapping := ctx.GetCommonClassifyObjCols() // 分类对象表实际的字段映射
+
+	// 分类变更的情况
+	if object.ClassifyId != req.ParentClassifyId {
+		object.ClassifyId = req.ParentClassifyId
+		object.ModifyTime = time.Now()
+		objUpdateCols = append(objUpdateCols, colsMapping.ClassifyId, colsMapping.ModifyTime)
+	}
+
+	if sortPrev > 0 {
+		// 移动至两个兄弟之间
+		if sortNext > 0 {
+			// 如果上一个兄弟与下一个兄弟的排序权重是一致的, 那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2, 自己变成上一个兄弟的排序权重+1
+			if sortPrev == sortNext || sortPrev == object.Sort {
+				sortUpdate := `sort + 2`
+
+				if prevClassify != nil {
+					if e := ctx.SetClassifySortByParentId(req.ParentClassifyId, prevClassify.ClassifyId, prevClassify.Sort, sortUpdate); e != nil {
+						err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, prevClassify.ClassifyId, prevClassify.Sort, sortUpdate, e)
+						return
+					}
+				} else {
+					if e := ctx.SetClassifySortByParentId(req.ParentClassifyId, 0, sortPrev, sortUpdate); e != nil {
+						err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, sortPrev, sortUpdate, e)
+						return
+					}
+				}
+
+				if prevObject != nil {
+					if e := ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate); e != nil {
+						err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate, e)
+						return
+					}
+				} else {
+					if e := ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, 0, sortUpdate); e != nil {
+						err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, 0, sortUpdate, e)
+						return
+					}
+				}
+			} else {
+				// 如果下一个兄弟的排序权重正好是上个兄弟节点的下一层, 那么需要再加一层
+				if sortNext-sortPrev == 1 {
+					sortUpdate := `sort + 1`
+
+					if prevClassify != nil {
+						if e := ctx.SetClassifySortByParentId(req.ParentClassifyId, prevClassify.ClassifyId, sortPrev, sortUpdate); e != nil {
+							err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, prevClassify.ClassifyId, sortPrev, sortUpdate, e)
+							return
+						}
+					} else {
+						if e := ctx.SetClassifySortByParentId(req.ParentClassifyId, 0, sortPrev, sortUpdate); e != nil {
+							err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, sortPrev, sortUpdate, e)
+							return
+						}
+					}
+
+					if prevObject != nil {
+						if e := ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate); e != nil {
+							err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, prevObject.ObjectId, sortUpdate, e)
+							return
+						}
+					} else {
+						if e := ctx.SetObjectSortByClassifyId(req.ParentClassifyId, sortPrev, 0, sortUpdate); e != nil {
+							err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, sortPrev, 0, sortUpdate, e)
+							return
+						}
+					}
+				}
+			}
+		}
+
+		object.Sort = sortPrev + 1
+		object.ModifyTime = time.Now()
+		objUpdateCols = append(objUpdateCols, colsMapping.Sort, colsMapping.ModifyTime)
+	} else if prevClassify == nil && nextClassify == nil && prevObject == nil && nextObject == nil && req.ParentClassifyId > 0 {
+		// 处理只拖动到目录里, 默认放到目录底部的情况
+		m, e := GetCommonClassifySortMaxByParentId(req.ParentClassifyId, ctx)
+		if e != nil {
+			err = fmt.Errorf("GetCommonClassifySortMaxByParentId, %v", e)
+			return
+		}
+		object.Sort = m + 1 //那就是排在组内最后一位
+		object.ModifyTime = time.Now()
+		objUpdateCols = append(objUpdateCols, colsMapping.Sort, colsMapping.ModifyTime)
+	} else {
+		// 拖动到父级分类的第一位
+		firstClassify, e := ctx.GetFirstClassifyByParentId(req.ParentClassifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("GetFirstClassifyByParentId, %v", e)
+			return
+		}
+
+		// 如果该分类下存在其他分类, 且第一个其他分类的排序等于0, 那么需要调整排序
+		if firstClassify != nil && firstClassify.Sort == 0 {
+			sortUpdate := ` sort + 1 `
+			if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, firstClassify.ClassifyId-1, 0, sortUpdate); e != nil {
+				err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, firstClassify.ClassifyId-1, 0, sortUpdate, e)
+				return
+			}
+			// 该分类下的所有指标也需要+1
+			if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, 0, 0, sortUpdate); e != nil {
+				err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, 0, sortUpdate, e)
+				return
+			}
+		} else {
+			// 如果该分类下存在对象, 且第一个对象的排序等于0, 那么需要调整排序
+			firstObject, e := ctx.GetFirstObjectByClassifyId(req.ParentClassifyId)
+			if e != nil && e.Error() != utils.ErrNoRow() {
+				err = fmt.Errorf("GetFirstObjectByClassifyId, %v", e)
+				return
+			}
+
+			// 如果该分类下存在其他分类, 且第一个其他分类的排序等于0, 那么需要调整排序
+			if firstObject != nil && firstObject.Sort == 0 {
+				sortUpdate := ` sort + 1 `
+				if e = ctx.SetObjectSortByClassifyId(req.ParentClassifyId, 0, firstObject.ObjectId-1, sortUpdate); e != nil {
+					err = fmt.Errorf("SetObjectSortByClassifyId, classifyId: %d, sort: %d, objectId: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, firstObject.ObjectId-1, sortUpdate, e)
+					return
+				}
+				if e = ctx.SetClassifySortByParentId(req.ParentClassifyId, 0, 0, sortUpdate); e != nil {
+					err = fmt.Errorf("SetClassifySortByParentId, parentId: %d, classifyId: %d, sort: %d, sortUpdate: %s, err: %v", req.ParentClassifyId, 0, 0, sortUpdate, e)
+					return
+				}
+			}
+		}
+
+		object.Sort = 0 // 那就是排在第一位
+		object.ModifyTime = time.Now()
+		objUpdateCols = append(objUpdateCols, colsMapping.Sort, colsMapping.ModifyTime)
+	}
+
+	// 更新分类对象
+	if len(objUpdateCols) > 0 {
+		if e := ctx.UpdateCommonClassifyObj(object, objUpdateCols); e != nil {
+			err = fmt.Errorf("UpdateCommonClassifyObj, %v", e)
+			return
+		}
+	}
+	return
+}
+
+// GetCommonClassifySortMaxByParentId 获取分类下最大排序
+func GetCommonClassifySortMaxByParentId(parentId int, ctx *CommonClassifyCtx) (sortMax int, err error) {
+	// 比对分类和对象的最大排序
+	classifyMax, e := ctx.GetClassifySortMaxByParentId(parentId)
+	if e != nil {
+		err = fmt.Errorf("GetClassifySortMaxByParentId, %v", e)
+		return
+	}
+	sortMax = classifyMax
+	objectMax, e := ctx.GetObjectSortMaxByClassifyId(parentId)
+	if e != nil {
+		err = fmt.Errorf("GetObjectSortMaxByClassifyId, %v", e)
+		return
+	}
+	if sortMax < objectMax {
+		sortMax = objectMax
+	}
+	return
+}

+ 106 - 0
services/data/common_classify_ctx.go

@@ -0,0 +1,106 @@
+package data
+
+import (
+	"eta/eta_api/models"
+)
+
+// CommonClassifyStrategy 通用分类策略接口
+type CommonClassifyStrategy interface {
+	GetCommonClassifyCols() models.CommonClassifyCols
+	GetCommonClassifyById(classifyId int) (*models.CommonClassify, error)
+	GetClassifyByParentIdAndName(parentId int, name string, excludeId int) (*models.CommonClassify, error)
+	GetClassifySortMaxByParentId(parentId int) (int, error)
+	GetFirstClassifyByParentId(parentId int) (*models.CommonClassify, error)
+
+	SetClassifySortByParentId(parentId, classifyId, sort int, sortUpdate string) error
+	UpdateCommonClassify(classify *models.CommonClassify, updateCols []string) error
+	UpdateClassifyChildByParentId(classifyIds []int, rootId int, stepLevel int) error
+
+	GetCommonClassifyObjCols() models.CommonClassifyObjCols
+	GetObjectById(objectId int) (*models.CommonClassifyObj, error)
+	GetObjectSortMaxByClassifyId(classifyId int) (int, error)
+	GetFirstObjectByClassifyId(classifyId int) (*models.CommonClassifyObj, error)
+
+	SetObjectSortByClassifyId(classifyId, sort, prevObjectId int, sortUpdate string) error
+	UpdateCommonClassifyObj(object *models.CommonClassifyObj, updateCols []string) (err error)
+}
+
+// CommonClassifyCtx 通用分类上下文
+type CommonClassifyCtx struct {
+	strategy CommonClassifyStrategy
+}
+
+// NewCommonClassifyCtx New一个通用分类上下文
+func NewCommonClassifyCtx(strategy CommonClassifyStrategy) *CommonClassifyCtx {
+	return &CommonClassifyCtx{strategy: strategy}
+}
+
+// GetCommonClassifyCols 通用分类字段映射
+func (c *CommonClassifyCtx) GetCommonClassifyCols() models.CommonClassifyCols {
+	return c.strategy.GetCommonClassifyCols()
+}
+
+// GetCommonClassifyById 通过策略获取分类信息
+func (c *CommonClassifyCtx) GetCommonClassifyById(classifyId int) (*models.CommonClassify, error) {
+	return c.strategy.GetCommonClassifyById(classifyId)
+}
+
+// GetClassifyByParentIdAndName 通过策略获取分类信息
+func (c *CommonClassifyCtx) GetClassifyByParentIdAndName(parentId int, name string, excludeId int) (*models.CommonClassify, error) {
+	return c.strategy.GetClassifyByParentIdAndName(parentId, name, excludeId)
+}
+
+// GetClassifySortMaxByParentId 获取父级分类下最大排序
+func (c *CommonClassifyCtx) GetClassifySortMaxByParentId(parentId int) (int, error) {
+	return c.strategy.GetClassifySortMaxByParentId(parentId)
+}
+
+// GetFirstClassifyByParentId 获取父级分类下首个分类
+func (c *CommonClassifyCtx) GetFirstClassifyByParentId(parentId int) (*models.CommonClassify, error) {
+	return c.strategy.GetFirstClassifyByParentId(parentId)
+}
+
+// SetClassifySortByParentId 通过父级ID更新分类排序
+func (c *CommonClassifyCtx) SetClassifySortByParentId(parentId, classifyId, sort int, sortUpdate string) error {
+	return c.strategy.SetClassifySortByParentId(parentId, classifyId, sort, sortUpdate)
+}
+
+// UpdateCommonClassify 更新通用分类
+func (c *CommonClassifyCtx) UpdateCommonClassify(classify *models.CommonClassify, updateCols []string) error {
+	return c.strategy.UpdateCommonClassify(classify, updateCols)
+}
+
+// UpdateClassifyChildByParentId 更新分类子节点RootId
+func (c *CommonClassifyCtx) UpdateClassifyChildByParentId(classifyIds []int, rootId int, stepLevel int) error {
+	return c.strategy.UpdateClassifyChildByParentId(classifyIds, rootId, stepLevel)
+}
+
+// GetCommonClassifyObjCols 通用分类对象字段映射
+func (c *CommonClassifyCtx) GetCommonClassifyObjCols() models.CommonClassifyObjCols {
+	return c.strategy.GetCommonClassifyObjCols()
+}
+
+// GetObjectById 获取分类对象
+func (c *CommonClassifyCtx) GetObjectById(objectId int) (*models.CommonClassifyObj, error) {
+	return c.strategy.GetObjectById(objectId)
+}
+
+// GetObjectSortMaxByClassifyId 获取分类下最大排序
+func (c *CommonClassifyCtx) GetObjectSortMaxByClassifyId(classifyId int) (int, error) {
+	return c.strategy.GetObjectSortMaxByClassifyId(classifyId)
+}
+
+// GetFirstObjectByClassifyId 获取分类下首个对象
+func (c *CommonClassifyCtx) GetFirstObjectByClassifyId(classifyId int) (*models.CommonClassifyObj, error) {
+	return c.strategy.GetFirstObjectByClassifyId(classifyId)
+}
+
+// SetObjectSortByClassifyId 通过分类ID更新对象排序
+func (c *CommonClassifyCtx) SetObjectSortByClassifyId(classifyId, sort, prevObjectId int, sortUpdate string) error {
+	return c.strategy.SetObjectSortByClassifyId(classifyId, sort, prevObjectId, sortUpdate)
+}
+
+// UpdateCommonClassifyObj 更新分类对象
+func (c *CommonClassifyCtx) UpdateCommonClassifyObj(object *models.CommonClassifyObj, updateCols []string) (err error) {
+	return c.strategy.UpdateCommonClassifyObj(object, updateCols)
+}

+ 24 - 0
services/data/edb_info.go

@@ -1833,6 +1833,8 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 		utils.DATA_SOURCE_FUBAO:               "富宝数据",
 		utils.DATA_SOURCE_GFEX:                "广期所",
 		utils.DATA_SOURCE_SCI_HQ:              "卓创红期",
+		utils.DATA_SOURCE_LY:                  "粮油商务网",
+		utils.DATA_SOURCE_TRADE_ANALYSIS:      "持仓分析",
 	}
 
 	sourceName, ok := sourceNameMap[source]
@@ -2966,3 +2968,25 @@ func handleByDelEdbInfo(edbInfo *data_manage.EdbInfo) {
 		_ = models.UpdateManualIsJoinEdbStatus(edbInfo.EdbCode, 0)
 	}
 }
+
+// GetIsSupplierStop
+// @Description: 获取是否供应商停更
+// @author: Roc
+// @datetime 2024-08-27 10:28:19
+// @param source int
+// @param edbCode string
+// @return isSupplierStop int
+func GetIsSupplierStop(source int, edbCode string) (isSupplierStop int) {
+	switch source {
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL:
+		item, err := data_manage.GetBaseFromMysteelChemicalIndexByCode(edbCode)
+		if err != nil {
+			return
+		}
+		isSupplierStop = item.IsSupplierStop
+	}
+
+	return
+}
+
+//IsSupplierStop  int    `description:"是否供应商停更:1:停更,0:未停更"`

+ 33 - 5
services/data/edb_info_relation.go

@@ -357,12 +357,18 @@ func GetEdbRelationList(source, edbType int, classifyId, sysUserId, frequency, k
 	list = make([]*data_manage.BaseRelationEdbInfo, 0)
 
 	isStop := -1
-	if status == `暂停` {
+	switch status {
+	case `暂停`:
 		isStop = 1
-	} else if status == "启用" {
+	case `启用`:
 		isStop = 0
+	case `供应商停用`:
+		isStop = 3
 	}
 
+	// 关联表语句
+	var addFieldStr, joinTableStr string
+
 	switch source {
 	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL, utils.DATA_SOURCE_WIND:
 		condition += ` AND e.source = ? `
@@ -373,10 +379,32 @@ func GetEdbRelationList(source, edbType int, classifyId, sysUserId, frequency, k
 		condition += ` AND e.edb_type = ? AND e.edb_info_type = 0`
 		pars = append(pars, edbType)
 	}
-
-	if isStop >= 0 {
+	switch isStop {
+	case -1:
+		// 供应商停用
+		if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			joinTableStr = ` LEFT JOIN base_from_mysteel_chemical_index z ON e.edb_code = z.index_code `
+			addFieldStr = ` ,z.is_supplier_stop `
+		}
+	case 0, 1:
 		condition += " AND e.no_update = ? "
 		pars = append(pars, isStop)
+
+		// 供应商停用
+		if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			condition += " AND z.is_supplier_stop = ? "
+			pars = append(pars, 0)
+			joinTableStr = ` LEFT JOIN base_from_mysteel_chemical_index z ON e.edb_code = z.index_code `
+			addFieldStr = ` ,z.is_supplier_stop `
+		}
+	case 3:
+		// 供应商停用
+		if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			condition += " AND z.is_supplier_stop = ? "
+			pars = append(pars, 1)
+			joinTableStr = ` LEFT JOIN base_from_mysteel_chemical_index z ON e.edb_code = z.index_code `
+			addFieldStr = ` ,z.is_supplier_stop `
+		}
 	}
 
 	if classifyId != `` {
@@ -421,7 +449,7 @@ func GetEdbRelationList(source, edbType int, classifyId, sysUserId, frequency, k
 		sortStr = fmt.Sprintf("%s %s,e.edb_info_id desc ", sortParam, sortType)
 	}
 
-	total, list, err = data_manage.GetEdbInfoRelationList(condition, pars, sortStr, startSize, pageSize)
+	total, list, err = data_manage.GetEdbInfoRelationList(condition, pars, addFieldStr, joinTableStr, sortStr, startSize, pageSize)
 
 	return
 }

+ 384 - 3
services/data/excel/excel_info.go

@@ -14,7 +14,10 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"sort"
+	"strings"
 	"time"
+
+	"github.com/xuri/excelize/v2"
 )
 
 // GetExcelDetailInfoByExcelInfoId 根据表格id获取表格详情
@@ -123,6 +126,7 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 			err = errors.New("获取最新的表格数据失败,Err:" + tmpErr.Error())
 			return
 		}
+		result = SetExcelByDecimalConfig(result, tableDataConfig.DecimalConfig)
 
 		if len(result.EdbInfoIdList) > 0 {
 			classifyIdList := make([]int, 0)
@@ -182,6 +186,39 @@ func formatExcelInfo2Detail(excelInfo *excel.ExcelInfo, sysUserId int, lang stri
 	return
 }
 
+// SetExcelByDecimalConfig 设置表格的小数位
+func SetExcelByDecimalConfig(tableData request.TableDataReq, config []request.DecimalConfig) request.TableDataReq {
+	excelData := tableData.Data
+	edbInfoIndex := make(map[int]int)
+	dateIndex := make(map[string]int)
+	for i, v := range excelData {
+		edbInfoIndex[v.EdbInfoId] = i
+	}
+	for i, v := range excelData[0].Data {
+		dateIndex[v.DataTime] = i
+	}
+	for _, conf := range config {
+		if conf.Col > 0 {
+			if v, ok := edbInfoIndex[conf.Col]; ok {
+				excelData[v].Decimal = conf.Decimal
+				for i := 0; i < len(excelData[v].Data); i++ {
+					excelData[v].Data[i].Decimal = conf.Decimal
+				}
+			}
+		}
+		if conf.Row != "" {
+			if v, ok := dateIndex[conf.Row]; ok {
+				for i := 0; i < len(excelData); i++ {
+					excelData[i].Data[v].Decimal = conf.Decimal
+				}
+			}
+		}
+	}
+	tableData.Data = excelData
+	tableData.DecimalConfig = config
+	return tableData
+}
+
 // GetExcelInfoOpButton 获取ETA表格的操作权限
 func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int, haveOperaAuth bool) (button excel.ExcelInfoDetailButton) {
 	// 如果没有数据权限,那么直接返回
@@ -213,8 +250,9 @@ func GetExcelInfoOpButton(sysUser *system.Admin, belongUserId, source int, haveO
 }
 
 // GetFirstEdbDataList 获取第一列的数据
-func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList []string) (resultDataList []request.ManualDataReq, err error) {
+func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList []string, decimal int) (resultDataList []request.ManualDataReq, err error) {
 	var dataList []*data_manage.EdbDataList
+	resultDataList = make([]request.ManualDataReq, 0)
 	switch edbInfo.EdbInfoType {
 	case 0:
 		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
@@ -286,6 +324,7 @@ func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList [
 			DataTime:     dataList[lenData-i].DataTime,
 			ShowValue:    fmt.Sprint(dataList[lenData-i].Value),
 			Value:        fmt.Sprint(dataList[lenData-i].Value),
+			Decimal:      decimal,
 			DataTimeType: 1,
 		})
 	}
@@ -293,8 +332,160 @@ func GetFirstEdbDataList(edbInfo *data_manage.EdbInfo, num int, manualDateList [
 	return
 }
 
+// PadFirstEdbDataList 补齐第一列的数据
+func PadFirstEdbDataList(resultDataList []request.ManualDataReq, dateList []string, sortType string, decimal int) []request.ManualDataReq {
+	originDataNum := len(resultDataList)
+	requsetDateNum := len(dateList)
+	if originDataNum >= requsetDateNum {
+		return resultDataList
+	}
+	padNum := requsetDateNum - originDataNum
+	if sortType == "asc" {
+		for i := 0; i < padNum; i++ {
+			resultDataList = append(resultDataList, request.ManualDataReq{
+				DataType:     0,
+				DataTime:     dateList[originDataNum+i],
+				Decimal:      decimal,
+				ShowValue:    ``,
+				Value:        ``,
+				DataTimeType: 1,
+			})
+		}
+	} else {
+		var tmpDateList []request.ManualDataReq
+		for i := padNum - 1; i <= 0; i-- {
+			tmpDateList = append(tmpDateList, request.ManualDataReq{
+				DataType:     0,
+				DataTime:     dateList[originDataNum+i],
+				Decimal:      decimal,
+				ShowValue:    ``,
+				Value:        ``,
+				DataTimeType: 1,
+			})
+			resultDataList = append(tmpDateList, resultDataList...)
+		}
+
+	}
+	return resultDataList
+}
+
+// GetOtherEdbDataListFollowDate 获取其他列的数据,并设置日期的小数位数
+func GetOtherEdbDataListFollowDate(edbInfo *data_manage.EdbInfo, dateList []string, dateDecimal map[string]int) (resultDataList []request.ManualDataReq, err error) {
+	lenDate := len(dateList)
+	if lenDate <= 0 {
+		return
+	}
+	sortDateList := dateList
+	baseDateList := utils.StrArr{}
+	baseDateList = append(baseDateList, sortDateList...)
+	sort.Sort(baseDateList)
+	sortDateList = append([]string{}, baseDateList...)
+
+	endDateTime, err := time.ParseInLocation(utils.FormatDate, sortDateList[0], time.Local)
+	if err != nil {
+		return
+	}
+
+	firstDateTime, err := time.ParseInLocation(utils.FormatDate, sortDateList[lenDate-1], time.Local)
+	if err != nil {
+		return
+	}
+
+	var dataList []*data_manage.EdbDataList
+	switch edbInfo.EdbInfoType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, ``, ``)
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfo.EdbInfoId, ``, ``, false)
+	default:
+		err = errors.New(fmt.Sprint("获取失败,指标类型异常", edbInfo.EdbInfoType))
+	}
+	if err != nil {
+		return
+	}
+
+	// 获取日期内的数据(包含开始日期前一个日期,以及 结束日期后一个日期,目的为了做空日期时的 插值法兼容)
+	baseDataList := make([]*data_manage.EdbDataList, 0)
+	var lastData *data_manage.EdbDataList
+	var isInsert bool
+	for _, data := range dataList {
+		tmpDate := data.DataTime
+		tmpDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDate, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		if tmpDateTime.Before(firstDateTime) {
+			lastData = data
+			continue
+		}
+
+		// 如果是第一次写入数据
+		if !isInsert && lastData != nil {
+			baseDataList = append(baseDataList, lastData)
+		}
+
+		if tmpDateTime.After(endDateTime) {
+			baseDataList = append(baseDataList, data)
+			break
+		}
+		baseDataList = append(baseDataList, data)
+		isInsert = true
+	}
+
+	// 实际数据的日期map
+	realValMap := make(map[string]string)
+	for _, v := range baseDataList {
+		realValMap[v.DataTime] = v.DataTime
+	}
+
+	// 插值法处理
+	handleDataMap := make(map[string]float64)
+	err = data.HandleDataByLinearRegression(baseDataList, handleDataMap)
+	if err != nil {
+		return
+	}
+
+	latestDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfo.LatestDate, time.Local)
+
+	// 对于不存在的数据做补充
+	for _, date := range sortDateList {
+		dataType := 1
+		if _, ok := realValMap[date]; !ok {
+			dataType = 2
+		} else {
+			dataTime, _ := time.ParseInLocation(utils.FormatDate, date, time.Local)
+			// 如果是预测指标,且当前值的日期,晚于实际日期,那么是预测值
+			if edbInfo.EdbInfoType == 1 && dataTime.After(latestDateTime) {
+				dataType = 5
+			}
+		}
+		var value, showValue string
+		if tmpVal, ok := handleDataMap[date]; ok {
+			value = fmt.Sprint(tmpVal)
+			showValue = value
+		} else {
+			dataType = 3
+		}
+		tmpData := request.ManualDataReq{
+			DataType:  dataType,
+			DataTime:  date,
+			ShowValue: showValue,
+			Value:     value,
+		}
+		if v, ok := dateDecimal[date]; ok {
+			tmpData.Decimal = v
+		} else {
+			tmpData.Decimal = -1
+		}
+		resultDataList = append(resultDataList, tmpData)
+	}
+
+	return
+}
+
 // GetOtherEdbDataList 获取其他列的数据
-func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resultDataList []request.ManualDataReq, err error) {
+func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string, decimal int) (resultDataList []request.ManualDataReq, err error) {
 	lenDate := len(dateList)
 	if lenDate <= 0 {
 		return
@@ -394,6 +585,7 @@ func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resul
 		resultDataList = append(resultDataList, request.ManualDataReq{
 			DataType:  dataType,
 			DataTime:  date,
+			Decimal:   decimal,
 			ShowValue: showValue,
 			Value:     value,
 		})
@@ -403,7 +595,7 @@ func GetOtherEdbDataList(edbInfo *data_manage.EdbInfo, dateList []string) (resul
 }
 
 // GetFirstHistoryEdbDataList 获取指标的历史的数据
-func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate string) (resultDataList []request.ManualDataReq, err error) {
+func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate string, decimal int) (resultDataList []request.ManualDataReq, err error) {
 	endDateTime, err := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
 	if err != nil {
 		return
@@ -452,6 +644,7 @@ func GetFirstHistoryEdbDataList(edbInfo *data_manage.EdbInfo, num int, endDate s
 		resultDataList = append(resultDataList, request.ManualDataReq{
 			DataType:  dataType,
 			DataTime:  dataList[lenData-i].DataTime,
+			Decimal:   decimal,
 			ShowValue: fmt.Sprint(dataList[lenData-i].Value),
 			Value:     fmt.Sprint(dataList[lenData-i].Value),
 		})
@@ -614,3 +807,191 @@ func GetCustomAnalysisOpButton(sysUser *system.Admin, belongUserId int, permissi
 	}
 	return
 }
+
+func parseExcelScopeCoord(scopeList []string) (scopeCoord string, err error) {
+	if len(scopeList) == 2 {
+		x1, y1, er := excelize.CellNameToCoordinates(scopeList[0])
+		if er != nil {
+			return "", er
+		}
+		x2, y2, er := excelize.CellNameToCoordinates(scopeList[1])
+		if er != nil {
+			return "", er
+		}
+		scopeCoord = fmt.Sprintf("%d,%d,%d,%d", x1, y1, x2, y2)
+	}
+	if len(scopeList) == 1 {
+		x1, y1, er := excelize.CellNameToCoordinates(scopeList[0])
+		if er != nil {
+			return "", er
+		}
+		scopeCoord = fmt.Sprintf("%d,%d", x1, y1)
+	}
+	return
+}
+
+func ExcelRuleFormat(req *request.ExcelRuleMappingReq, lang string) (res *excel.ExcelInfoRuleMapping, err error) {
+	res = new(excel.ExcelInfoRuleMapping)
+	if req.RuleType == 5 {
+		switch req.LeftValue {
+		// 今天
+		case "today":
+			res.LeftValueBack = req.LeftValue
+		// 明天
+		case "tomorrow":
+			res.LeftValueBack = req.LeftValue
+		// 最近7天
+		case "last7days":
+			res.LeftValueBack = req.LeftValue
+		// 上周
+		case "lastweek":
+			res.LeftValueBack = req.LeftValue
+		// 本周
+		case "thisweek":
+			res.LeftValueBack = req.LeftValue
+		// 下周
+		case "nextweek":
+			res.LeftValueBack = req.LeftValue
+		// 上月
+		case "lastmonth":
+			res.LeftValueBack = req.LeftValue
+		// 本月
+		case "thismonth":
+			res.LeftValueBack = req.LeftValue
+		// 下月
+		case "nextmonth":
+			res.LeftValueBack = req.LeftValue
+		default:
+			err = errors.New("发生日期规则错误")
+			return
+		}
+	}
+	// 格式化条件值
+	switch req.LeftValueType {
+	// 坐标
+	case 2:
+		x, y, err := excelize.CellNameToCoordinates(req.LeftValue)
+		if err != nil {
+			return nil, err
+		}
+		res.LeftValue = req.LeftValue
+		res.LeftValueBack = fmt.Sprintf("%d,%d", x, y)
+	default:
+		res.LeftValue = req.LeftValue
+		res.LeftValueBack = req.LeftValue
+	}
+	switch req.RightValueType {
+	// 坐标
+	case 2:
+		x, y, err := excelize.CellNameToCoordinates(req.RightValue)
+		if err != nil {
+			return nil, err
+		}
+		res.RightValue = req.RightValue
+		res.RightValueBack = fmt.Sprintf("%d,%d", x, y)
+	default:
+		res.RightValue = req.RightValue
+		res.RightValueBack = req.RightValue
+	}
+	if res.LeftValueBack == "" {
+		res.LeftValueBack = req.LeftValue
+	}
+	if res.RightValueBack == "" {
+		res.RightValueBack = req.RightValue
+	}
+	res.RuleType = req.RuleType
+	res.ExcelInfoId = req.ExcelInfoId
+	res.ExcelInfoRuleMappingId = req.ExcelRuleMappingId
+	res.LeftValueType = req.LeftValueType
+	res.RightValueType = req.RightValueType
+	res.FontColor = req.FontColor
+	res.BackgroundColor = req.BackgroundColor
+	res.Remark = req.Remark
+	res.RemarkEn = req.Remark
+	res.ScopeShow = req.Scope
+	scopeList := strings.Split(req.Scope, ":")
+	res.ScopeCoord, err = parseExcelScopeCoord(scopeList)
+	return
+}
+
+func AddExcelRule(req *request.ExcelRuleMappingReq, lang string) (err error) {
+	excelRule, err := ExcelRuleFormat(req, lang)
+	if err != nil {
+		return
+	}
+	if excelRule.ExcelInfoRuleMappingId != 0 {
+		return errors.New("规则已存在")
+	}
+	excelRule.CreateTime = time.Now()
+	_, err = excelRule.Insert()
+	return
+}
+
+func ModifyExcelRule(req *request.ExcelRuleMappingReq, lang string) (err error) {
+	excelInfo, err := excel.GetExcelRuleMappingById(req.ExcelRuleMappingId)
+	if err != nil {
+		return
+	}
+	editExcelInfo, err := ExcelRuleFormat(req, lang)
+	if err != nil {
+		return
+	}
+	var updateCols []string
+	if excelInfo.LeftValue != editExcelInfo.LeftValue {
+		updateCols = append(updateCols, "LeftValue")
+		updateCols = append(updateCols, "LeftValueBack")
+	}
+	if excelInfo.LeftValueType != editExcelInfo.LeftValueType {
+		updateCols = append(updateCols, "LeftValueType")
+	}
+	if excelInfo.RightValue != editExcelInfo.RightValue {
+		updateCols = append(updateCols, "RightValue")
+		updateCols = append(updateCols, "RightValueBack")
+	}
+	if excelInfo.RightValueType != editExcelInfo.RightValueType {
+		updateCols = append(updateCols, "RightValueType")
+	}
+	if excelInfo.FontColor != editExcelInfo.FontColor {
+		updateCols = append(updateCols, "FontColor")
+	}
+	if excelInfo.BackgroundColor != editExcelInfo.BackgroundColor {
+		updateCols = append(updateCols, "BackgroundColor")
+	}
+	if excelInfo.Remark != editExcelInfo.Remark {
+		updateCols = append(updateCols, "Remark")
+	}
+	if excelInfo.RemarkEn != editExcelInfo.RemarkEn {
+		updateCols = append(updateCols, "RemarkEn")
+	}
+	if excelInfo.ScopeShow != editExcelInfo.ScopeShow {
+		updateCols = append(updateCols, "ScopeCoord")
+		updateCols = append(updateCols, "ScopeShow")
+	}
+	if len(updateCols) > 0 {
+		err = editExcelInfo.Update(updateCols)
+	}
+
+	return
+}
+
+// GetExcelRuleList 获取规则列表
+func GetExcelRuleList(excelInfoId int) (resp *response.ExcelRuleListResp, err error) {
+	resp = new(response.ExcelRuleListResp)
+	excelInfoList, err := excel.GetExcelRuleMappingByExcelInfoId(excelInfoId)
+	if err != nil {
+		return
+	}
+	resp.List = excelInfoList
+	return
+}
+
+// GetExcelRuleDetail 获取规则列表
+func GetExcelRuleDetail(excelInfoMappingId int) (resp *excel.ExcelInfoRuleMappingView, err error) {
+	resp = new(excel.ExcelInfoRuleMappingView)
+	excelInfoDetail, err := excel.GetExcelRuleMappingById(excelInfoMappingId)
+	if err != nil {
+		return
+	}
+	resp = excelInfoDetail
+	return
+}

+ 26 - 2
services/data/excel/mixed_table.go

@@ -8,12 +8,13 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
 )
 
 // BaseCalculate
@@ -1192,6 +1193,21 @@ func handleMixCellShowStyle(showStyleList []string, calculateCellMap map[string]
 			}
 			_, e := strconv.ParseFloat(val, 64) // 将字符串转换成float类型
 			if e != nil {                       // 如果没有错误发生则返回true,说明该字符串是一个合法的数字
+				var styleConf request.MixCellShowStyle
+				err = json.Unmarshal([]byte(cell.ShowStyle), &styleConf)
+				if err != nil {
+					err = fmt.Errorf("日期计算配置json解析失败失败: %s, Err:%s", config, err.Error())
+					return
+				}
+				// 前端传过来的json中有可能有glObj,需要去掉
+				if styleConf.GlObj != nil {
+					styleConf.GlObj = nil
+					tmpStyleConf, err := json.Marshal(styleConf)
+					if err == nil {
+						cell.ShowStyle = string(tmpStyleConf)
+					}
+				}
+				config[cellPosition.Column][cellPosition.Row] = cell
 				continue
 			}
 			var styleConf request.MixCellShowStyle
@@ -1206,6 +1222,14 @@ func handleMixCellShowStyle(showStyleList []string, calculateCellMap map[string]
 			} else {
 				cell.ShowFormatValue = cell.ShowValue
 			}
+			// 前端传过来的json中有可能有glObj,需要去掉
+			if styleConf.GlObj != nil {
+				styleConf.GlObj = nil
+				tmpStyleConf, err := json.Marshal(styleConf)
+				if err == nil {
+					cell.ShowStyle = string(tmpStyleConf)
+				}
+			}
 			config[cellPosition.Column][cellPosition.Row] = cell
 		}
 	}

+ 11 - 4
services/data/excel/time_table.go

@@ -7,12 +7,13 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/shopspring/decimal"
-	"github.com/yidane/formula"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
 )
 
 // TableDataConfig
@@ -26,6 +27,7 @@ type TableDataConfig struct {
 	ManualDate       []string                  `description:"手动配置的日期(未来的日期)"`
 	TableEdbInfoList []TableEdbInfo            `description:"表格内指标信息"`
 	TextRowData      [][]request.ManualDataReq `description:"文本列表"`
+	DecimalConfig    []request.DecimalConfig   `description:"小数位数配置"`
 }
 
 // TableEdbInfo
@@ -39,6 +41,7 @@ type TableEdbInfo struct {
 	Frequency    string `description:"频度"`
 	Unit         string `description:"单位"`
 	UnitEn       string `description:"英文单位"`
+	Decimal      int    `description:"小数位数"`
 }
 
 // ManualData
@@ -101,7 +104,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 		err = errors.New("找不到A列指标")
 		return
 	}
-	baseFirstEdbInfoDataList, err := GetFirstEdbDataList(firstEdbInfo, tableDataConfig.Num, manualDateList)
+	baseFirstEdbInfoDataList, err := GetFirstEdbDataList(firstEdbInfo, tableDataConfig.Num, manualDateList, -1)
 	if err != nil {
 		return
 	}
@@ -166,7 +169,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 			err = errors.New("找不到A列指标")
 			return
 		}
-		otherDataList, tmpErr := GetOtherEdbDataList(tmpEdbInfo, dateList)
+		otherDataList, tmpErr := GetOtherEdbDataList(tmpEdbInfo, dateList, -1)
 		if tmpErr != nil {
 			err = tmpErr
 			return
@@ -249,6 +252,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 			EdbNameEn:    tableEdbInfo.EdbNameEn,
 			EdbAliasName: tableEdbInfo.EdbAliasName,
 			Frequency:    tableEdbInfo.Frequency,
+			Decimal:      tableEdbInfo.Decimal,
 			Unit:         tableEdbInfo.Unit,
 			UnitEn:       tableEdbInfo.UnitEn,
 			Data:         manualDataReqList,
@@ -277,6 +281,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 				manualDataReqList = append(manualDataReqList, request.ManualDataReq{
 					DataType:            3,
 					DataTime:            tmpDateTimeStr,
+					Decimal:             tableEdbInfo.Decimal,
 					DataTimeType:        dataTimeType,
 					ShowValue:           "",
 					Value:               "",
@@ -290,6 +295,7 @@ func GetDataByTableDataConfig(tableDataConfig TableDataConfig) (resultResp reque
 				manualDataReqList = append(manualDataReqList, request.ManualDataReq{
 					DataType:            3,
 					DataTime:            tmpDateTimeStr,
+					Decimal:             tableEdbInfo.Decimal,
 					DataTimeType:        dataTimeType,
 					ShowValue:           "",
 					Value:               "",
@@ -374,6 +380,7 @@ func GetTableDataConfig(reqData request.TableDataReq) (tableDataConfig TableData
 			Frequency:    v.Frequency,
 			Unit:         v.Unit,
 			UnitEn:       v.UnitEn,
+			Decimal:      v.Decimal,
 		}
 		tableEdbInfoList = append(tableEdbInfoList, tmpTableEdbInfo)
 

+ 33 - 1
services/data/manual.go

@@ -226,7 +226,7 @@ func ManualIndexSource2Edb(req ManualIndexSource2EdbReq, lang string) (edb *data
 // @return failCount int
 // @return err error
 // @return errMsg string
-func ImportManualData(path string, sysUser *system.Admin) (successCount, failCount int, err error, errMsg string) {
+func ImportManualData(path string, sysUser *system.Admin) (successCount, failCount int, indexCount int, err error, errMsg string) {
 	// 错误信息
 	errMsgList := make([]string, 0)
 	// 操作记录
@@ -516,6 +516,9 @@ func ImportManualData(path string, sysUser *system.Admin) (successCount, failCou
 				failDataList = append(failDataList, failItem)
 				continue
 			}
+			// 新增指标记录数
+			indexCount++
+
 			tmpTarget, tmpErr := models.GetTargetBySecName(v.IndexName)
 			target = tmpTarget
 			targetMap[v.IndexName] = target
@@ -545,6 +548,35 @@ func ImportManualData(path string, sysUser *system.Admin) (successCount, failCou
 
 		//更新指标信息
 		{
+			if v.Frequency == "" {
+				failItem := &models.EdbdataImportFail{
+					ClassifyName: v.ClassName,
+					CreateDate:   tmpDate,
+					SecName:      v.IndexName,
+					Close:        tmpValue,
+					Remark:       "更新指标失败,频度字段为空",
+					SysUserId:    fmt.Sprint(sysUser.AdminId),
+					Frequency:    v.Frequency,
+					Unit:         v.Unit,
+				}
+				failDataList = append(failDataList, failItem)
+				continue
+			}
+			if v.Unit == "" {
+				failItem := &models.EdbdataImportFail{
+					ClassifyName: v.ClassName,
+					CreateDate:   tmpDate,
+					SecName:      v.IndexName,
+					Close:        tmpValue,
+					Remark:       "更新指标失败,单位字段为空",
+					SysUserId:    fmt.Sprint(sysUser.AdminId),
+					Frequency:    v.Frequency,
+					Unit:         v.Unit,
+				}
+				failDataList = append(failDataList, failItem)
+				continue
+			}
+
 			updateCols := make([]string, 0)
 			//更新指标分类
 			if target.ClassifyId <= 0 && classify.ClassifyId > 0 {

+ 2247 - 0
services/data/range_analysis/chart_info.go

@@ -0,0 +1,2247 @@
+package range_analysis
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// GetAutoCalculateDateDataList 获取当前时间相关的区间作为计算依据
+func GetAutoCalculateDateDataList(currentDate string, dataList []*data_manage.EdbDataList, req *data_manage.ChartRangeAnalysisExtraConf) (newDataList []*data_manage.EdbDataList, err error) {
+	currentDateTime, _ := time.ParseInLocation(utils.FormatDate, currentDate, time.Local)
+	switch req.DateRangeType {
+	case 0:
+		// 智能划分得到一个开始日期,和结束日期
+		var startDateTime time.Time
+		if req.AutoDateConf.IsAutoStartDate == 0 { //固定设置
+			startDate := req.AutoDateConf.StartDate
+			if startDate == "" {
+				startDate = "2020-01-01"
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		} else {
+			startConf := req.AutoDateConf.StartDateConf
+			startDate := ""
+			if startConf.BaseDateType == 0 { //
+				startDate = currentDate
+			} else if startConf.BaseDateType == 1 {
+				startDate = time.Now().Format(utils.FormatDate)
+			}
+			if startConf.MoveForward > 0 {
+				startDate = GetEdbDateByMoveForward(startDate, startConf.MoveForward, dataList)
+			}
+			if len(startConf.DateChange) > 0 {
+				startDate, err = HandleEdbDateChange(startDate, startConf.DateChange)
+				if err != nil {
+					err = fmt.Errorf("智能划分开始日期处理失败:%s", err.Error())
+					return
+				}
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		}
+		var calStartTime, calEndTime time.Time
+		if currentDateTime.Before(startDateTime) {
+			calStartTime = currentDateTime
+			calEndTime = startDateTime
+		} else {
+			calStartTime = startDateTime
+			calEndTime = currentDateTime
+		}
+		// 根据日期,获取数据
+		for _, vv := range dataList {
+			dataTimeT, _ := time.ParseInLocation(utils.FormatDate, vv.DataTime, time.Local)
+			if (dataTimeT.After(calStartTime) && dataTimeT.Before(calEndTime)) ||
+				dataTimeT.Equal(calStartTime) ||
+				dataTimeT.Equal(calEndTime) {
+				newDataList = append(newDataList, vv)
+			}
+		}
+	}
+	return
+}
+
+// HandleDataByCalculateType 根据计算公式处理数据
+func HandleDataByCalculateType(originList []*data_manage.ChartRangeAnalysisDateDataItem, originDataList []*data_manage.EdbDataList, req *data_manage.ChartRangeAnalysisExtraConf) (newList []*data_manage.EdbDataList, err error) {
+	if len(originList) == 0 {
+		return
+	}
+	calculateType := req.CalculateType
+	switch calculateType {
+	case 0: //均值
+		var sum float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					sum = 0
+					//计算的数据返回需要重新确定
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for _, vv := range calDataList {
+						sum += vv.Value
+					}
+					val := sum / float64(len(calDataList))
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				sum = 0
+				for k, v := range item.DataList {
+					sum += v.Value
+					val := sum / float64(k+1)
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		}
+
+	case 1: //累计值
+		var sum float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				sum = 0
+				for _, v := range item.DataList {
+					sum = 0
+					//计算的数据返回需要重新确定
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for _, vv := range calDataList {
+						sum += vv.Value
+					}
+					val := sum
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				sum = 0
+				for _, v := range item.DataList {
+					sum += v.Value
+					val := sum
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		}
+
+	case 2: //涨幅
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					var baseVal float64
+					//计算的数据返回需要重新确定
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					if len(calDataList) == 0 {
+						continue
+					}
+					baseVal = calDataList[0].Value
+					baseDate := calDataList[0].DataTime
+					if baseVal == 0 {
+						continue
+					}
+					if v.DataTime == baseDate {
+						continue
+					}
+
+					val := (v.Value - baseVal) / baseVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				if len(item.DataList) == 0 {
+					break
+				}
+				baseVal := item.DataList[0].Value
+				baseDate := item.DataList[0].DataTime
+				if baseVal == 0 {
+					break
+				}
+				for _, v := range item.DataList {
+					if v.DataTime == baseDate {
+						continue
+					}
+					val := (v.Value - baseVal) / baseVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{
+						DataTime:      v.DataTime,
+						Value:         val,
+						DataTimestamp: v.DataTimestamp,
+					})
+				}
+			}
+		}
+	case 3: //复合增长率
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					var baseVal float64
+					var baseDate string
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					if len(calDataList) == 0 {
+						continue
+					}
+					baseVal = calDataList[0].Value
+					baseDate = calDataList[0].DataTime
+					if v.DataTime == baseDate {
+						continue
+					}
+					if baseVal == 0 {
+						continue
+					}
+
+					baseDateT, e := time.ParseInLocation(utils.FormatDate, baseDate, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					tmpT, e := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					// 计算两个日期相差的天数
+					diff := tmpT.Sub(baseDateT).Hours() / 24 / 365
+					val := v.Value / baseVal
+					val = math.Pow(val, 1/diff) - 1
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				if len(item.DataList) == 0 {
+					break
+				}
+				baseVal := item.DataList[0].Value
+				baseDate := item.DataList[0].DataTime
+				if baseVal == 0 {
+					break
+				}
+				for _, v := range item.DataList {
+					if v.DataTime == baseDate {
+						continue
+					}
+					baseDateT, e := time.ParseInLocation(utils.FormatDate, baseDate, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					tmpT, e := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+					if e != nil {
+						err = fmt.Errorf("time.ParseInLocation err: %v", e)
+						return
+					}
+					// 计算两个日期相差的天数
+					diff := tmpT.Sub(baseDateT).Hours() / 24 / 365
+					val := v.Value / baseVal
+					val = math.Pow(val, 1/diff) - 1
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		}
+	case 4: //最大值
+		var maxVal float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for kk, vv := range calDataList {
+						if kk == 0 {
+							maxVal = vv.Value
+						}
+						if vv.Value > maxVal {
+							maxVal = vv.Value
+						}
+					}
+
+					val := maxVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				for k, v := range item.DataList {
+					if k == 0 {
+						maxVal = v.Value
+					}
+					if v.Value > maxVal {
+						maxVal = v.Value
+					}
+					val := maxVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		}
+	case 5: //最小值
+		var minVal float64
+		if req.DateRangeType == 0 && req.AutoDateConf.IsAutoStartDate > 0 {
+			for _, item := range originList {
+				for _, v := range item.DataList {
+					calDataList, e := GetAutoCalculateDateDataList(v.DataTime, originDataList, req)
+					if e != nil {
+						err = fmt.Errorf("获取区间数据失败:%s", e.Error())
+						return
+					}
+					for kk, vv := range calDataList {
+						if kk == 0 {
+							minVal = vv.Value
+						}
+						if vv.Value < minVal {
+							minVal = vv.Value
+						}
+					}
+					val := minVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		} else {
+			for _, item := range originList {
+				for k, v := range item.DataList {
+					if k == 0 {
+						minVal = v.Value
+					}
+					if v.Value < minVal {
+						minVal = v.Value
+					}
+					val := minVal
+					val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+					newList = append(newList, &data_manage.EdbDataList{DataTime: v.DataTime, Value: val, DataTimestamp: v.DataTimestamp})
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// GetChartEdbInfoFormat 区间计算图表-获取指标信息
+func GetChartEdbInfoFormat(chartInfoId int, edbInfoMappingList []*data_manage.ChartEdbInfoMapping) (edbList []*data_manage.ChartEdbInfoMapping, err error) {
+	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
+	for _, edbInfoMapping := range edbInfoMappingList {
+		if edbInfoMapping == nil {
+			err = fmt.Errorf("指标信息有误")
+			return
+		}
+
+		edbInfoMapping.FrequencyEn = data.GetFrequencyEn(edbInfoMapping.Frequency)
+		if edbInfoMapping.Unit == `无` {
+			edbInfoMapping.Unit = ``
+		}
+		if chartInfoId <= 0 {
+			edbInfoMapping.IsAxis = 1
+			edbInfoMapping.LeadValue = 0
+			edbInfoMapping.LeadUnit = ""
+			edbInfoMapping.ChartEdbMappingId = 0
+			edbInfoMapping.ChartInfoId = 0
+			edbInfoMapping.IsOrder = false
+			edbInfoMapping.EdbInfoType = 1
+			edbInfoMapping.ChartStyle = ""
+			edbInfoMapping.ChartColor = ""
+			edbInfoMapping.ChartWidth = 0
+		} else {
+			edbInfoMapping.LeadUnitEn = data.GetLeadUnitEn(edbInfoMapping.LeadUnit)
+		}
+		edbList = append(edbList, edbInfoMapping)
+	}
+	return
+}
+
+// GetChartDataByEdbInfoList 区间计算图表-根据指标信息获取x轴和y轴
+func GetChartDataByEdbInfoList(chartInfoId int, dateType, startYear int, startDate, endDate string, edbInfoMappingList []*data_manage.ChartEdbInfoMapping, req *data_manage.ChartRangeAnalysisExtraConf) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, dataResp data_manage.ChartRangeAnalysisDataResp, err error) {
+	if chartInfoId > 0 && req.EdbInfoMode == 1 {
+		edbList, xEdbIdValue, dataResp, err = GetChartDataByEdbInfoListBySeries(chartInfoId, dateType, startYear, startDate, endDate, edbInfoMappingList, req)
+		return
+	}
+	for _, edbInfoMapping := range edbInfoMappingList {
+		edbInfoMapping, err = getChartDataByEdbInfo(edbInfoMapping, req)
+		if err != nil {
+			return
+		}
+		edbInfoMapping.ConvertUnit = edbInfoMapping.Unit
+		edbInfoMapping.ConvertEnUnit = edbInfoMapping.UnitEn
+		if req.CalculateType == 2 || req.CalculateType == 3 {
+			edbInfoMapping.ConvertUnit = "无"
+			edbInfoMapping.ConvertEnUnit = "无"
+		}
+		if req.DataConvertType > 0 && req.DataConvertConf.Unit != "" {
+			edbInfoMapping.ConvertUnit = req.DataConvertConf.Unit
+			edbInfoMapping.ConvertEnUnit = req.DataConvertConf.Unit
+		}
+		dataList := edbInfoMapping.DataList.([]*data_manage.EdbDataList)
+		// 处理上下限
+		var maxData, minData float64
+		if len(dataList) > 0 {
+			maxData = dataList[0].Value
+			minData = dataList[0].Value
+			for _, v := range dataList {
+				if v.Value > maxData {
+					maxData = v.Value
+				}
+				if v.Value < minData {
+					minData = v.Value
+				}
+			}
+		}
+		edbInfoMapping.MaxData = maxData
+		edbInfoMapping.MinData = minData
+		xEdbIdValue = append(xEdbIdValue, edbInfoMapping.EdbInfoId)
+	}
+	//根据时间类型来筛选最终的数据
+	yearMax := 0
+	if dateType == utils.DateTypeNYears {
+		for _, v := range edbInfoMappingList {
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			if len(dataList) > 0 {
+				latestDate := dataList[len(dataList)-1].DataTime
+				if latestDate != "" {
+					lastDateT, tErr := time.Parse(utils.FormatDate, latestDate)
+					if tErr != nil {
+						err = fmt.Errorf("获取图表日期信息失败,Err:" + tErr.Error())
+						return
+					}
+					if lastDateT.Year() > yearMax {
+						yearMax = lastDateT.Year()
+					}
+				}
+			}
+		}
+	}
+	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
+
+	if startDate != "" {
+		for k, v := range edbInfoMappingList {
+			var maxData, minData float64
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			if len(dataList) == 0 {
+				newDataList = dataList
+			} else {
+				firstFlag := true
+				for _, d := range dataList {
+					if endDate != "" && d.DataTime > endDate {
+						continue
+					}
+					if d.DataTime < startDate {
+						continue
+					}
+					newDataList = append(newDataList, d)
+					if firstFlag {
+						maxData = d.Value
+						minData = d.Value
+						firstFlag = false
+					} else {
+						if d.Value > maxData {
+							maxData = d.Value
+						}
+						if d.Value < minData {
+							minData = d.Value
+						}
+					}
+				}
+			}
+			edbInfoMappingList[k].DataList = newDataList
+			edbInfoMappingList[k].MinData = minData
+			edbInfoMappingList[k].MaxData = maxData
+		}
+	}
+	dataResp = data_manage.ChartRangeAnalysisDataResp{ChartRangeAnalysisExtraConf: req}
+	if req.MultipleGraphConfigId > 0 {
+		//判断MultipleGraphConfigId和图表关系是否正确
+		multipleGraphConfigEdbMappingList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+			return
+		}
+		if len(multipleGraphConfigEdbMappingList) > 0 {
+			// 查询指标是否已存在,如果已经被删除了,则返回0值
+			edbIds := make([]int, 0)
+			for _, v := range multipleGraphConfigEdbMappingList {
+				edbIds = append(edbIds, v.EdbInfoId)
+			}
+			var condition string
+			var pars []interface{}
+			condition = " AND edb_info_id in (" + utils.GetOrmInReplace(len(edbIds)) + ") "
+			pars = append(pars, edbIds)
+			count, e := data_manage.GetEdbInfoCountByCondition(condition, pars)
+			if e != nil {
+				err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+				return
+			}
+			if count > 0 {
+				// 查询是否已经生成指标
+				dataResp.ConfigEdbNum = len(multipleGraphConfigEdbMappingList)
+			}
+		}
+	}
+	edbList, err = GetChartEdbInfoFormat(chartInfoId, edbInfoMappingList)
+	if err != nil {
+		err = fmt.Errorf("获取区间计算图表, 完善指标信息失败, Err:" + err.Error())
+		return
+	}
+	return
+}
+
+func GetChartDataByEdbInfoListBySeries(chartInfoId int, dateType, startYear int, startDate, endDate string, edbInfoMappingList []*data_manage.ChartEdbInfoMapping, req *data_manage.ChartRangeAnalysisExtraConf) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, dataResp data_manage.ChartRangeAnalysisDataResp, err error) {
+	//查询seriesId
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesMappingItem, err := chartSeriesOb.GetItemByChartInfoId(chartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = fmt.Errorf("图表关联关系不存在")
+			return
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	}
+	//根据seriesId查询数据
+	//并把数据放到dataList中
+	for _, edbInfoMapping := range edbInfoMappingList {
+		dataOb := new(data_manage.FactorEdbSeriesCalculateDataQjjs)
+		dataList, e := dataOb.GetEdbDataList(seriesMappingItem.FactorEdbSeriesId, edbInfoMapping.EdbInfoId, startDate, endDate)
+		if e != nil {
+			err = e
+			return
+		}
+		edbInfoMapping.ConvertUnit = edbInfoMapping.Unit
+		edbInfoMapping.ConvertEnUnit = edbInfoMapping.UnitEn
+		if req.CalculateType == 2 || req.CalculateType == 3 {
+			edbInfoMapping.ConvertUnit = "无"
+			edbInfoMapping.ConvertEnUnit = "无"
+		}
+		if req.DataConvertType > 0 && req.DataConvertConf.Unit != "" {
+			edbInfoMapping.ConvertUnit = req.DataConvertConf.Unit
+			edbInfoMapping.ConvertEnUnit = req.DataConvertConf.Unit
+		}
+		edbInfoMapping.DataList = dataList
+		// 处理上下限
+		var maxData, minData float64
+		if len(dataList) > 0 {
+			maxData = dataList[0].Value
+			minData = dataList[0].Value
+			for _, v := range dataList {
+				if v.Value > maxData {
+					maxData = v.Value
+				}
+				if v.Value < minData {
+					minData = v.Value
+				}
+			}
+		}
+		edbInfoMapping.MaxData = maxData
+		edbInfoMapping.MinData = minData
+		xEdbIdValue = append(xEdbIdValue, edbInfoMapping.EdbInfoId)
+	}
+	yearMax := 0
+	if dateType == utils.DateTypeNYears {
+		for _, v := range edbInfoMappingList {
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			latestDate := dataList[len(dataList)-1].DataTime
+			if latestDate != "" {
+				lastDateT, tErr := time.Parse(utils.FormatDate, latestDate)
+				if tErr != nil {
+					err = fmt.Errorf("获取图表日期信息失败,Err:" + tErr.Error())
+					return
+				}
+				if lastDateT.Year() > yearMax {
+					yearMax = lastDateT.Year()
+				}
+			}
+		}
+	}
+	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
+
+	if startDate != "" {
+		for k, v := range edbInfoMappingList {
+			var maxData, minData float64
+			dataList := v.DataList.([]*data_manage.EdbDataList)
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			if len(dataList) == 0 {
+				newDataList = dataList
+			} else {
+				firstFlag := true
+				for _, d := range dataList {
+					if endDate != "" && d.DataTime > endDate {
+						continue
+					}
+					if d.DataTime < startDate {
+						continue
+					}
+					newDataList = append(newDataList, d)
+					if firstFlag {
+						maxData = d.Value
+						minData = d.Value
+						firstFlag = false
+					} else {
+						if d.Value > maxData {
+							maxData = d.Value
+						}
+						if d.Value < minData {
+							minData = d.Value
+						}
+					}
+				}
+			}
+			edbInfoMappingList[k].DataList = newDataList
+			edbInfoMappingList[k].MinData = minData
+			edbInfoMappingList[k].MaxData = maxData
+		}
+	}
+	dataResp = data_manage.ChartRangeAnalysisDataResp{ChartRangeAnalysisExtraConf: req, SeriesId: seriesMappingItem.FactorEdbSeriesId}
+	// 查询配置关联关系
+	if req.MultipleGraphConfigId > 0 {
+		multipleGraphConfigEdbMappingList, e := data_manage.GetMultipleGraphConfigEdbMappingListByIdAndSource(req.MultipleGraphConfigId, utils.CHART_SOURCE_RANGE_ANALYSIS)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+			return
+		}
+		if len(multipleGraphConfigEdbMappingList) > 0 {
+			// 查询指标是否已存在,如果已经被删除了,则返回0值
+			edbIds := make([]int, 0)
+			for _, v := range multipleGraphConfigEdbMappingList {
+				edbIds = append(edbIds, v.EdbInfoId)
+			}
+			var condition string
+			var pars []interface{}
+			condition = " and edb_info_id in (" + utils.GetOrmInReplace(len(edbIds)) + ") "
+			pars = append(pars, edbIds)
+			count, e := data_manage.GetEdbInfoCountByCondition(condition, pars)
+			if e != nil {
+				err = fmt.Errorf("获取区间计算图表, 指标信息失败, Err:" + e.Error())
+				return
+			}
+			if count > 0 {
+				// 查询是否已经生成指标
+				dataResp.ConfigEdbNum = len(multipleGraphConfigEdbMappingList)
+			}
+		}
+	}
+
+	edbList, err = GetChartEdbInfoFormat(chartInfoId, edbInfoMappingList)
+	if err != nil {
+		err = fmt.Errorf("获取区间计算图表, 完善指标信息失败, Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// getChartDataByEdbInfo 区间计算图表-根据指标信息获取x轴和y轴
+func getChartDataByEdbInfo(edbInfoMapping *data_manage.ChartEdbInfoMapping, req *data_manage.ChartRangeAnalysisExtraConf) (newEdbInfoMapping *data_manage.ChartEdbInfoMapping, err error) {
+	newEdbInfoMapping = edbInfoMapping
+	// 指标的开始日期和结束日期
+	edbStartDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfoMapping.StartDate, time.Local)
+	//edbStartDate := edbStartDateTime.AddDate(0, 0, 1).Format(utils.FormatDate)
+	edbEndDateTime, _ := time.ParseInLocation(utils.FormatDate, edbInfoMapping.EndDate, time.Local)
+	edbEndDate := edbEndDateTime.Format(utils.FormatDate)
+
+	// 获取时间基准指标在时间区间内的值
+	dataList := make([]*data_manage.EdbDataList, 0)
+	switch edbInfoMapping.EdbInfoCategoryType {
+	case 0:
+		dataList, err = data_manage.GetEdbDataList(edbInfoMapping.Source, edbInfoMapping.SubSource, edbInfoMapping.EdbInfoId, "", "")
+	case 1:
+		_, dataList, _, _, err, _ = data.GetPredictDataListByPredictEdbInfoId(edbInfoMapping.EdbInfoId, "", "", false)
+	default:
+		err = errors.New("指标base类型异常")
+		return
+	}
+	/*var dataList data_manage.SortEdbDataList
+	dataList = dataListTmp
+	ascDataList := dataListTmp
+	sort.Sort(dataList)*/
+	dateList := make([]*data_manage.ChartRangeAnalysisDateDataItem, 0)
+	switch req.DateRangeType {
+	case 0:
+		// 智能划分得到一个开始日期,和结束日期
+		var startDateTime, endDateTime time.Time
+		startDateTime = edbStartDateTime
+		if req.AutoDateConf.IsAutoStartDate == 0 { //固定设置
+			startDate := req.AutoDateConf.StartDate
+			if startDate == "" {
+				startDate = "2020-01-01"
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		} /*else {
+			startConf := req.AutoDateConf.StartDateConf
+			startDate := ""
+			if startConf.BaseDateType == 0 { //
+				startDate = edbEndDate
+			} else if startConf.BaseDateType == 1 {
+				startDate = time.Now().Format(utils.FormatDate)
+			}
+			if startConf.MoveForward > 0 {
+				startDate = GetEdbDateByMoveForward(startConf.MoveForward, dataList)
+			}
+			if len(startConf.DateChange) > 0 {
+				startDate, err = HandleEdbDateChange(startDate, startConf.DateChange)
+				if err != nil {
+					err = fmt.Errorf("智能划分开始日期处理失败:%s", err.Error())
+					return
+				}
+			}
+			startDateTime, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+		}*/
+
+		if req.AutoDateConf.IsAutoEndDate == 0 { //固定设置
+			endDate := req.AutoDateConf.EndDate
+			if endDate == "" {
+				err = fmt.Errorf("智能划分截止日期处理失败:请输入截止日期")
+				return
+			}
+			// todo 如果截止日期比指标日期还要大,则用指标的最新日期
+			endDateTime, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+		} else {
+			endConf := req.AutoDateConf.EndDateConf
+			endDate := edbEndDate
+			if endConf.MoveForward > 0 {
+				endDate = GetEdbDateByMoveForward(endDate, endConf.MoveForward, dataList)
+			}
+			if len(endConf.DateChange) > 0 {
+				endDate, err = HandleEdbDateChange(endDate, endConf.DateChange)
+				if err != nil {
+					err = fmt.Errorf("智能划分结束日期处理失败:%s", err.Error())
+					return
+				}
+			}
+			endDateTime, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+		}
+
+		dateList = append(dateList, &data_manage.ChartRangeAnalysisDateDataItem{
+			StartDate: startDateTime,
+			EndDate:   endDateTime})
+	case 1:
+		// 手工划分得到多个开始日期和结束日期(已排序)
+		for _, v := range req.ManualDateConf {
+			startDateT, _ := time.ParseInLocation(utils.FormatDate, v.StartDate, time.Local)
+			endDateT, _ := time.ParseInLocation(utils.FormatDate, v.EndDate, time.Local)
+			tmp := &data_manage.ChartRangeAnalysisDateDataItem{
+				StartDate: startDateT,
+				EndDate:   endDateT,
+			}
+			dateList = append(dateList, tmp)
+		}
+	case 2:
+		// 跨年划分得到多个开始日期和结束日期
+		startYear := edbStartDateTime.Year()
+		endYear := edbEndDateTime.Year()
+		startDay := req.YearDateConf.StartDay
+		endDay := req.YearDateConf.EndDay
+		for year := startYear; year <= endYear; year++ {
+			startDate := fmt.Sprintf("%d-%s", year, startDay)
+			endDate := fmt.Sprintf("%d-%s", year+1, endDay)
+			startDateTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+			endDateTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+			if startDateTime.Before(edbStartDateTime) {
+				break
+			}
+
+			tmp := &data_manage.ChartRangeAnalysisDateDataItem{
+				StartDate: startDateTime,
+				EndDate:   endDateTime,
+			}
+			dateList = append(dateList, tmp)
+		}
+	}
+	// 根据日期,获取数据
+	for _, v := range dateList {
+		for _, vv := range dataList {
+			dataTimeT, _ := time.ParseInLocation(utils.FormatDate, vv.DataTime, time.Local)
+			if dataTimeT.After(v.StartDate) && dataTimeT.Before(v.EndDate) ||
+				dataTimeT.Equal(v.StartDate) ||
+				dataTimeT.Equal(v.EndDate) {
+				v.DataList = append(v.DataList, vv)
+			}
+		}
+	}
+	// 根据时间区间类型来获取数据的计算窗口,然后再拼接成整段数据
+	newDataList, err := HandleDataByCalculateType(dateList, dataList, req)
+	if err != nil {
+		return
+	}
+	if req.UnNormalDataDealType > 0 {
+		switch req.UnNormalDataDealType { //0:不处理,1:剔除,2替换
+		case 1:
+			dealDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range newDataList {
+				if !utils.CompareFloatByOpStrings(req.UnNormalDataConf.Formula, v.Value, req.UnNormalDataConf.Value) {
+					dealDataList = append(dealDataList, v)
+				}
+			}
+		case 2:
+			for i, v := range newDataList {
+				if utils.CompareFloatByOpStrings(req.UnNormalDataConf.Formula, v.Value, req.UnNormalDataConf.Value) {
+					newDataList[i].Value = req.UnNormalDataConf.ReplaceValue
+				}
+			}
+		}
+	}
+
+	if req.DataConvertType > 0 {
+		// 数据转换类型 0不转, 1乘 2除 3对数
+		switch req.DataConvertType {
+		case 1:
+			for i, v := range newDataList {
+				val := v.Value * req.DataConvertConf.Value
+				val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+				newDataList[i].Value = val
+			}
+			//item.MaxData = item.MaxData * v.ConvertValue
+			//item.MinData = item.MinData * v.ConvertValue
+		case 2:
+			for i, v := range newDataList {
+				val := v.Value / req.DataConvertConf.Value
+				val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+				newDataList[i].Value = val
+			}
+			//item.MaxData = item.MaxData / v.ConvertValue
+			//item.MinData = item.MinData / v.ConvertValue
+		case 3:
+			for i, v := range newDataList {
+				if v.Value <= 0 {
+					err = errors.New("数据中含有负数或0,无法对数运算")
+					return
+				}
+				val := math.Log(v.Value) / math.Log(req.DataConvertConf.Value)
+				val, _ = decimal.NewFromFloat(val).Round(4).Float64()
+				newDataList[i].Value = val
+			}
+			//item.MaxData = math.Log(item.MaxData) / math.Log(v.ConvertValue)
+			//item.MinData = math.Log(item.MinData) / math.Log(v.ConvertValue)
+		}
+	}
+	newEdbInfoMapping.DataList = newDataList
+	//时间处理
+
+	return
+}
+
+// RollingCorrelationChartDataResp 滚动区间计算图表数据
+type RollingCorrelationChartDataResp struct {
+	MaxData             float64
+	MinData             float64
+	LatestDate          string `description:"真实数据的最后日期"`
+	EdbInfoCategoryType int
+	ChartColor          string
+	ChartStyle          string
+	PredictChartColor   string
+	ChartType           int
+	ChartWidth          int
+	EdbName             string
+	EdbNameEn           string
+	Unit                string
+	UnitEn              string
+	IsAxis              int
+	DataList            []data_manage.EdbDataList
+}
+
+// ChartInfoRefresh 图表刷新
+func ChartInfoRefresh(chartInfoId int, uniqueCode string) (isAsync bool, err error) {
+	var errMsg string
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("CorrelationChartInfoRefresh: %s", errMsg)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+	var chartInfo *data_manage.ChartInfo
+	if chartInfoId > 0 {
+		chartInfo, err = data_manage.GetChartInfoById(chartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "图表已被删除,请刷新页面"
+				err = errors.New(errMsg)
+				return
+			}
+			errMsg = "获取图表信息失败"
+			err = errors.New("获取图表信息失败,Err:" + err.Error())
+			return
+		}
+	} else {
+		chartInfo, err = data_manage.GetChartInfoByUniqueCode(uniqueCode)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "图表已被删除,请刷新页面"
+				err = errors.New(errMsg)
+				return
+			}
+			errMsg = "获取图表信息失败"
+			err = errors.New("获取图表信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	// 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
+	}
+
+	// todo 重新计算
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesMappingItem, err := chartSeriesOb.GetItemByChartInfoId(chartInfo.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	} else {
+		_, e = FactorEdbStepCalculateRange(seriesMappingItem.FactorEdbSeriesId, edbIds, extraConfig, true)
+		if e != nil {
+			err = fmt.Errorf("计算因子指标失败, Err: " + e.Error())
+			return
+		}
+	}
+	// 4.清除图表缓存
+	key := utils.HZ_CHART_LIB_DETAIL + uniqueCode
+	_ = utils.Rc.Delete(key)
+
+	return
+}
+
+// AddChartInfo 添加图表
+func AddChartInfo(req data_manage.AddChartInfoReq, source int, sysUser *system.Admin, lang string) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartName == "" {
+		errMsg = "请填写图表名称!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if req.ChartClassifyId <= 0 {
+		errMsg = "分类参数错误!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if req.ExtraConfig == `` {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg)
+		return
+	}
+	err = json.Unmarshal([]byte(req.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+
+	if len(req.ChartEdbInfoList) <= 0 {
+		errMsg = "请选择指标"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if len(req.ChartEdbInfoList) > 100 {
+		errMsg = "添加指标总数量不得超过100"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	err, errMsg, isSendEmail = CheckChartRangeExtraConfig(extraConfig)
+	if err != nil {
+		return
+	}
+
+	chartClassify, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if chartClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	var edbInfoIdArr []int
+
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoId := v.EdbInfoId
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(edbInfoId)
+		if tmpErr != nil {
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "指标不存在!"
+				err = errors.New("指标不存在,edbInfoId:" + strconv.Itoa(edbInfoId))
+				return
+			} else {
+				errMsg = "获取指标信息失败!"
+				err = errors.New("获取图表的指标信息失败,Err:" + tmpErr.Error())
+				return
+			}
+		}
+
+		if edbInfo == nil {
+			errMsg = "指标已被删除,请重新选择!"
+			err = errors.New("指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+			return
+		} else {
+			if edbInfo.EdbInfoId <= 0 {
+				errMsg = "指标已被删除,请重新选择!"
+				err = errors.New("指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+				return
+			}
+		}
+
+		edbInfoIdArr = append(edbInfoIdArr, edbInfoId)
+		edbInfo.EdbNameSource = edbInfo.EdbName
+	}
+
+	sort.Ints(edbInfoIdArr)
+	var edbInfoIdArrStr []string
+	for _, v := range edbInfoIdArr {
+		edbInfoIdArrStr = append(edbInfoIdArrStr, strconv.Itoa(v))
+	}
+	edbInfoIdStr := strings.Join(edbInfoIdArrStr, ",")
+	var chartInfoId int
+
+	// 判断图表是否存在
+	{
+		var condition string
+		var pars []interface{}
+
+		switch lang {
+		case utils.EnLangVersion:
+			condition += " AND chart_name_en = ? AND source = ? "
+		default:
+			condition += " AND chart_name=? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, source)
+		count, tmpErr := data_manage.GetChartInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断图表名称是否存在失败"
+			err = errors.New("判断图表名称是否存在失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		if count > 0 {
+			errMsg = "图表已存在,请重新填写"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	disableVal := data.CheckIsDisableChart(edbInfoIdArr)
+
+	chartInfo = new(data_manage.ChartInfo)
+	chartInfo.ChartName = req.ChartName
+	chartInfo.ChartNameEn = req.ChartName
+	chartInfo.EdbInfoIds = edbInfoIdStr
+	chartInfo.ChartClassifyId = req.ChartClassifyId
+	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 = 1 // 区间计算图
+	chartInfo.Calendar = "公历"
+	chartInfo.DateType = req.DateType
+	chartInfo.StartDate = req.StartDate
+	chartInfo.EndDate = req.EndDate
+	chartInfo.StartYear = req.StartYear
+	chartInfo.SeasonStartDate = req.StartDate
+	chartInfo.SeasonEndDate = req.EndDate
+	chartInfo.LeftMin = req.LeftMin
+	chartInfo.LeftMax = req.LeftMax
+	chartInfo.RightMin = req.RightMin
+	chartInfo.RightMax = req.RightMax
+	chartInfo.Disabled = disableVal
+	chartInfo.Source = source
+	chartInfo.ChartThemeId = req.ChartThemeId
+	chartInfo.SourcesFrom = req.SourcesFrom
+	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)
+	for _, v := range req.ChartEdbInfoList {
+		// todo 指标名称修改
+		mapItem := new(data_manage.ChartEdbMapping)
+		mapItem.EdbInfoId = v.EdbInfoId
+		mapItem.EdbAliasName = v.EdbAliasName
+		mapItem.IsAxis = v.IsAxis
+		mapItem.CreateTime = time.Now()
+		mapItem.ModifyTime = time.Now()
+		edbTimestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		mapItem.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + edbTimestamp + "_" + strconv.Itoa(v.EdbInfoId))
+		mapItem.IsOrder = false
+		mapItem.EdbInfoType = 1
+		mapItem.Source = utils.CHART_SOURCE_RANGE_ANALYSIS
+		mapList = append(mapList, mapItem)
+
+	}
+
+	// 新增图表和指标mapping
+	chartInfoId, e := data_manage.CreateRangeChartAndEdb(chartInfo, mapList)
+	if e != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增区间计算图表失败, Err: " + e.Error())
+		return
+	}
+	// 把系列和图表绑定
+	if extraConfig.EdbInfoMode == 1 {
+		// 新增指标系列
+		err = AddSeries(edbInfoIdArr, chartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS, extraConfig, req.ExtraConfig)
+		if err != nil {
+			errMsg = "操作失败"
+			err = errors.New("新增区间计算图表失败, Err: " + err.Error())
+			return
+		}
+		//todo 如果保存失败是否要删除
+	}
+	/*//添加配置信息
+	multipleGraphConfig := &data_manage.MultipleGraphConfig{
+		//MultipleGraphConfigId: 0,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	err = data_manage.AddMultipleGraphConfig(multipleGraphConfig)
+	if err != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增区间计算图表配置失败, Err: " + err.Error())
+		return
+	}*/
+	if extraConfig.MultipleGraphConfigId > 0 {
+		multipleGraphConfigChartMapping := &data_manage.MultipleGraphConfigChartMapping{
+			//Id:                    0,
+			MultipleGraphConfigId: extraConfig.MultipleGraphConfigId,
+			ChartInfoId:           chartInfo.ChartInfoId,
+			Source:                utils.CHART_SOURCE_RANGE_ANALYSIS,
+			ModifyTime:            time.Now(),
+			CreateTime:            time.Now(),
+		}
+		err = data_manage.AddMultipleGraphConfigChartMapping(multipleGraphConfigChartMapping)
+		if err != nil {
+			errMsg = "操作失败"
+			err = errors.New("新增区间计算图表和配置关联关系失败, Err: " + err.Error())
+			return
+		}
+	}
+
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartInfoId)
+
+	return
+}
+
+// EditChartInfo 编辑图表
+func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin, lang string) (chartItem *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+
+	chartItem, err = data_manage.GetChartInfoById(req.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "图表已被删除,请刷新页面"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取图表信息失败"
+		err = errors.New("获取图表信息失败,Err:" + err.Error())
+		return
+	}
+
+	if chartItem.Source != utils.CHART_SOURCE_RANGE_ANALYSIS {
+		errMsg = "该图不是区间计算图表!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	req.ChartName = strings.Trim(req.ChartName, " ")
+	if req.ChartClassifyId <= 0 {
+		errMsg = "分类参数错误!"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// 区间计算图表配置校验
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	if req.ExtraConfig == `` {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg)
+		return
+	}
+	err = json.Unmarshal([]byte(req.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg)
+		return
+	}
+
+	if len(req.ChartEdbInfoList) <= 0 {
+		errMsg = "请选择指标"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	extraConfig.SeriesName = strings.TrimSpace(extraConfig.SeriesName)
+	if len(req.ChartEdbInfoList) > 100 {
+		errMsg = "添加指标总数量不得超过100"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	err, errMsg, isSendEmail = CheckChartRangeExtraConfig(extraConfig)
+	if err != nil {
+		return
+	}
+	chartClassify, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		errMsg = "获取分类信息失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	if chartClassify == nil {
+		errMsg = "分类不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	// 图表操作权限
+	ok := data.CheckOpChartPermission(sysUser, chartItem.SysUserId, true)
+	if !ok {
+		errMsg = "没有该图表的操作权限"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	var edbInfoIdArr []int
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoId := v.EdbInfoId
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(edbInfoId)
+		if tmpErr != nil {
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "图表不存在!"
+				err = errors.New("图表指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+				return
+			} else {
+				errMsg = "获取图表信息失败!"
+				err = errors.New("获取图表的指标信息失败,Err:" + tmpErr.Error())
+				return
+			}
+		}
+
+		if edbInfo == nil {
+			errMsg = "指标不存在!"
+			err = errors.New("指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoId))
+			return
+		}
+		edbInfoIdArr = append(edbInfoIdArr, edbInfoId)
+	}
+
+	sort.Ints(edbInfoIdArr)
+	var edbInfoIdArrStr []string
+	for _, v := range edbInfoIdArr {
+		edbInfoIdArrStr = append(edbInfoIdArrStr, strconv.Itoa(v))
+	}
+	edbInfoIdStr := strings.Join(edbInfoIdArrStr, ",")
+
+	//判断图表是否存在
+	{
+		var condition string
+		var pars []interface{}
+		condition += " AND chart_info_id <> ? "
+		pars = append(pars, req.ChartInfoId)
+		switch lang {
+		case utils.EnLangVersion:
+			condition += " AND chart_name_en = ? AND source = ? "
+		default:
+			condition += " AND chart_name=? AND source = ? "
+		}
+		pars = append(pars, req.ChartName, chartItem.Source)
+		count, tmpErr := data_manage.GetChartInfoCountByCondition(condition, pars)
+		if tmpErr != nil {
+			errMsg = "判断图表名称是否存在失败"
+			err = errors.New("判断图表名称是否存在失败,Err:" + tmpErr.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "图表已存在,请重新填写"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+	// 修改图表与指标mapping
+	req.ChartType = 1
+	dateType := req.DateType
+	err = data_manage.EditRangeChartInfoAndMapping(&req, edbInfoIdStr, lang, "公历", dateType, 0, ``)
+	if err != nil {
+		errMsg = "保存失败"
+		err = errors.New("保存失败,Err:" + err.Error())
+		return
+	}
+
+	addSeries := false
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesMappingItem, err := chartSeriesOb.GetItemByChartInfoId(chartItem.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+			addSeries = true
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	}
+	if extraConfig.EdbInfoMode == 1 {
+		if addSeries {
+			// 新增指标系列
+			err = AddSeries(edbInfoIdArr, chartItem.ChartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS, extraConfig, req.ExtraConfig)
+			if err != nil {
+				errMsg = "操作失败"
+				err = errors.New("新增区间计算图表失败, Err: " + err.Error())
+				return
+			}
+		} else {
+			err = EditSeries(seriesMappingItem, edbInfoIdArr, extraConfig, req.ExtraConfig, true)
+			if err != nil {
+				errMsg = "保存失败"
+				err = errors.New("保存失败,Err:" + err.Error())
+				return
+			}
+		}
+		// todo 编辑失败处理
+	} else if extraConfig.EdbInfoMode == 0 {
+		if !addSeries {
+			// 删除相关的系列
+			factorSeriesOb := new(data_manage.FactorEdbSeries)
+			err = factorSeriesOb.RemoveSeriesAndMappingByFactorEdbSeriesId(seriesMappingItem)
+			if err != nil {
+				errMsg = "保存失败"
+				err = errors.New("保存失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	resp := new(data_manage.AddChartInfoResp)
+	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数据
+	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+
+	return
+}
+
+// CopyChartInfo 复制图表
+func CopyChartInfo(classifyId int, chartName string, oldChartInfo *data_manage.ChartInfo, sysUser *system.Admin, lang string) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	var extraConfig data_manage.ChartRangeAnalysisExtraConf
+	err = json.Unmarshal([]byte(oldChartInfo.ExtraConfig), &extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+
+	//新增配置,并绑定图表
+	multipleGraphConfig := &data_manage.MultipleGraphConfig{
+		//MultipleGraphConfigId: 0,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	err = data_manage.AddMultipleGraphConfig(multipleGraphConfig)
+	if err != nil {
+		errMsg = "新增区间计算图表配置失败"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+	extraConfig.MultipleGraphConfigId = multipleGraphConfig.MultipleGraphConfigId
+	newExtraConfigBytes, err := json.Marshal(extraConfig)
+	if err != nil {
+		errMsg = "配置信息错误"
+		err = errors.New(errMsg + ", Err: " + err.Error())
+		return
+	}
+	newExtraConfig := string(newExtraConfigBytes)
+	chartInfo = &data_manage.ChartInfo{
+		ChartInfoId:     0,
+		ChartName:       chartName,
+		ChartClassifyId: classifyId,
+		SysUserId:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+		UniqueCode:      utils.MD5(utils.CHART_PREFIX + "_" + timestamp),
+		CreateTime:      time.Now(),
+		ModifyTime:      time.Now(),
+		DateType:        oldChartInfo.DateType,
+		StartDate:       oldChartInfo.StartDate,
+		EndDate:         oldChartInfo.EndDate,
+		IsSetName:       oldChartInfo.IsSetName,
+		EdbInfoIds:      oldChartInfo.EdbInfoIds,
+		ChartType:       oldChartInfo.ChartType,
+		Calendar:        oldChartInfo.Calendar,
+		SeasonStartDate: oldChartInfo.SeasonStartDate,
+		SeasonEndDate:   oldChartInfo.SeasonEndDate,
+		ChartImage:      oldChartInfo.ChartImage,
+		BarConfig:       oldChartInfo.BarConfig,
+		//Sort:     sort,
+		LeftMin:           oldChartInfo.LeftMin,
+		LeftMax:           oldChartInfo.LeftMax,
+		RightMin:          oldChartInfo.RightMin,
+		RightMax:          oldChartInfo.RightMax,
+		Right2Min:         oldChartInfo.Right2Min,
+		Right2Max:         oldChartInfo.Right2Max,
+		Disabled:          oldChartInfo.Disabled,
+		Source:            oldChartInfo.Source,
+		ExtraConfig:       newExtraConfig,
+		SeasonExtraConfig: oldChartInfo.SeasonExtraConfig,
+		StartYear:         oldChartInfo.StartYear,
+		Unit:              oldChartInfo.Unit,
+		UnitEn:            oldChartInfo.UnitEn,
+		ChartThemeId:      oldChartInfo.ChartThemeId,
+		SourcesFrom:       oldChartInfo.SourcesFrom,
+		Instructions:      oldChartInfo.Instructions,
+		MarkersLines:      oldChartInfo.MarkersLines,
+		MarkersAreas:      oldChartInfo.MarkersAreas,
+	}
+	newId, err := data_manage.AddChartInfo(chartInfo)
+	if err != nil {
+		err = fmt.Errorf("保存失败,Err:%s", err.Error())
+		return
+	}
+	chartInfo.ChartInfoId = int(newId)
+	edbInfoMappingList, err := data_manage.GetChartMappingList(oldChartInfo.ChartInfoId)
+	if err != nil {
+		err = fmt.Errorf("获取图表,指标信息失败,Err:" + err.Error())
+		return
+	}
+	// 添加图表与指标的关联关系
+	edbInfoIdArr := make([]int, 0)
+	{
+		mapList := make([]*data_manage.ChartEdbMapping, 0)
+		for _, v := range edbInfoMappingList {
+			edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+			timestamp = strconv.FormatInt(time.Now().UnixNano(), 10)
+			mapItem := &data_manage.ChartEdbMapping{
+				//ChartEdbMappingId: 0,
+				ChartInfoId:   chartInfo.ChartInfoId,
+				EdbInfoId:     v.EdbInfoId,
+				CreateTime:    time.Now(),
+				ModifyTime:    time.Now(),
+				UniqueCode:    utils.MD5(utils.CHART_PREFIX + "_" + timestamp),
+				MaxData:       v.MaxData,
+				MinData:       v.MinData,
+				IsOrder:       v.IsOrder,
+				IsAxis:        v.IsAxis,
+				EdbInfoType:   v.EdbInfoType,
+				LeadValue:     v.LeadValue,
+				LeadUnit:      v.LeadUnit,
+				ChartStyle:    v.ChartStyle,
+				ChartColor:    v.ChartColor,
+				ChartWidth:    v.ChartWidth,
+				Source:        v.Source,
+				EdbAliasName:  v.EdbAliasName,
+				IsConvert:     v.IsConvert,
+				ConvertType:   v.ConvertType,
+				ConvertValue:  v.ConvertValue,
+				ConvertUnit:   v.ConvertEnUnit,
+				ConvertEnUnit: v.ConvertEnUnit,
+			}
+			mapList = append(mapList, mapItem)
+		}
+		err = data_manage.AddChartEdbMapping(mapList)
+		if err != nil {
+			err = fmt.Errorf("保存失败,Err:%s", err.Error())
+			return
+		}
+	}
+	// 添加系列和图表映射
+	chartSeriesOb := new(data_manage.FactorEdbSeriesChartMapping)
+	_, err = chartSeriesOb.GetItemByChartInfoId(oldChartInfo.ChartInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		} else {
+			err = fmt.Errorf("获取图表关联失败, Err: " + err.Error())
+			return
+		}
+	} else {
+		// 新增指标系列
+		// 区间计算图表配置校验
+
+		err = AddSeries(edbInfoIdArr, chartInfo.ChartInfoId, utils.CHART_SOURCE_RANGE_ANALYSIS, extraConfig, chartInfo.ExtraConfig)
+		if err != nil {
+			errMsg = "操作失败"
+			err = errors.New("新增区间计算图表失败, Err: " + err.Error())
+			return
+		}
+	}
+
+	multipleGraphConfigChartMapping := &data_manage.MultipleGraphConfigChartMapping{
+		//Id:                    0,
+		MultipleGraphConfigId: extraConfig.MultipleGraphConfigId,
+		ChartInfoId:           chartInfo.ChartInfoId,
+		Source:                utils.CHART_SOURCE_RANGE_ANALYSIS,
+		ModifyTime:            time.Now(),
+		CreateTime:            time.Now(),
+	}
+	err = data_manage.AddMultipleGraphConfigChartMapping(multipleGraphConfigChartMapping)
+	if err != nil {
+		errMsg = "操作失败"
+		err = errors.New("新增区间计算图表和配置关联关系失败, Err: " + err.Error())
+		return
+	}
+
+	//添加es数据
+	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
+
+	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
+}*/
+
+func GetEdbDateByMoveForward(startDate string, moveForward int, edbDataList []*data_manage.EdbDataList) (date string) {
+	// 根据日期进行排序
+	index := 0
+	length := len(edbDataList)
+	for i := length - 1; i >= 0; i-- {
+		item := edbDataList[i]
+		if item.DataTime == startDate {
+			index += 1
+			continue
+		}
+		if index >= moveForward {
+			date = item.DataTime
+			break
+		}
+		if index > 0 {
+			index += 1
+			date = item.DataTime
+		}
+	}
+	return
+}
+
+// HandleEdbDateChange 处理日期变换
+func HandleEdbDateChange(date string, dateChange []*data_manage.EdbDataDateChangeConf) (newDate string, err error) {
+	newDate = date
+	if newDate != "" {
+		if len(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 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, _ = utils.HandleSystemAppointDateT(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
+}
+
+// 添加指标系列和数据
+func AddSeries(edbInfoIds []int, chartInfoId, chartInfoSource int, extraConf data_manage.ChartRangeAnalysisExtraConf, calculatesJson string) (err error) {
+	edbArr, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("获取指标列表失败, Err: " + e.Error())
+		return
+	}
+	if len(edbArr) == 0 {
+		err = fmt.Errorf("获取指标列表失败, 指标不存在")
+		return
+	}
+	edbInfoType := edbArr[0].EdbInfoType
+	// 新增指标系列
+	seriesItem := new(data_manage.FactorEdbSeries)
+	seriesItem.SeriesName = extraConf.SeriesName
+	seriesItem.EdbInfoType = edbInfoType
+	seriesItem.CreateTime = time.Now().Local()
+	seriesItem.ModifyTime = time.Now().Local()
+	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 {
+		err = fmt.Errorf("新增因子指标系列失败, Err: " + e.Error())
+		return
+	}
+
+	// 图表关联-此处添加的chart_info_id=0
+	seriesChartMapping := new(data_manage.FactorEdbSeriesChartMapping)
+	seriesChartMapping.CalculateType = data_manage.FactorEdbSeriesChartCalculateTypeRange
+	//新增图表和指标的映射关系
+	seriesChartMapping.CalculateData = ""
+	seriesChartMapping.FactorEdbSeriesId = seriesId
+	seriesChartMapping.ChartInfoId = chartInfoId
+	seriesChartMapping.Source = chartInfoSource
+	seriesChartMapping.CreateTime = time.Now().Local()
+	seriesChartMapping.ModifyTime = time.Now().Local()
+
+	if e = seriesChartMapping.Create(); e != nil {
+		err = fmt.Errorf("新增图表关联失败, Err: " + e.Error())
+		return
+	}
+
+	// todo 计算指标数据并存储
+	_, e = FactorEdbStepCalculateRange(seriesId, edbInfoIds, extraConf, false)
+	if e != nil {
+		err = fmt.Errorf("计算因子指标失败, 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 {
+		err = fmt.Errorf("更新因子指标系列计算状态失败, Err: " + e.Error())
+		return
+	}
+	return
+}
+
+func EditSeries(seriesMapping *data_manage.FactorEdbSeriesChartMapping, edbInfoIds []int, extraConf data_manage.ChartRangeAnalysisExtraConf, calculatesJson string, recalculate bool) (err error) {
+	seriesOb := new(data_manage.FactorEdbSeries)
+	seriesItem, e := seriesOb.GetItemById(seriesMapping.FactorEdbSeriesId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			err = fmt.Errorf("因子指标系列不存在, Err: " + e.Error())
+			return
+		}
+		err = fmt.Errorf("获取因子指标系列失败, Err: " + e.Error())
+		return
+	}
+
+	edbArr, e := data_manage.GetEdbInfoByIdList(edbInfoIds)
+	if e != nil {
+		err = fmt.Errorf("获取指标列表失败, Err: " + e.Error())
+		return
+	}
+	if len(edbArr) == 0 {
+		err = fmt.Errorf("指标列表为空")
+		return
+	}
+	var calculateResp data_manage.FactorEdbSeriesStepCalculateResp
+	calculateResp.SeriesId = seriesItem.FactorEdbSeriesId
+
+	// 如果不需要进行重新计算(比如只改了系列名称)那么只更新指标系列
+	seriesItem.SeriesName = extraConf.SeriesName
+	seriesItem.EdbInfoType = edbArr[0].EdbInfoType
+	seriesItem.ModifyTime = time.Now().Local()
+	updateCols := []string{seriesOb.Cols().SeriesName, seriesOb.Cols().EdbInfoType, seriesOb.Cols().ModifyTime}
+	if !recalculate {
+		if e = seriesItem.Update(updateCols); e != nil {
+			err = fmt.Errorf("更新因子指标系列失败, Err: " + e.Error())
+			return
+		}
+		return
+	}
+
+	// 更新系列信息和指标关联
+	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 {
+		err = fmt.Errorf("更新因子指标系列信息失败, Err: %s", e.Error())
+		return
+	}
+
+	// todo 重新计算
+	_, e = FactorEdbStepCalculateRange(seriesItem.FactorEdbSeriesId, edbInfoIds, extraConf, false)
+	if e != nil {
+		err = fmt.Errorf("计算因子指标失败, 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 {
+		err = fmt.Errorf("更新因子指标系列计算状态失败, Err: %s", e.Error())
+		return
+	}
+	return
+}
+
+// FactorEdbStepCalculateRange 因子指标-区间计算
+func FactorEdbStepCalculateRange(seriesId int, edbArr []int, extraConf data_manage.ChartRangeAnalysisExtraConf, recalculate bool) (calculateResp data_manage.FactorEdbSeriesStepCalculateResp, err error) {
+	// todo 如果指标已保存,则用指标数据还是图表指标数据?
+	// 获取图表x轴y轴
+	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)
+		}*/
+	}()
+
+	edbInfoMappingList, e := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbArr)
+	if e != nil {
+		err = fmt.Errorf("获取区间计算图表, A指标mapping信息失败, Err:%v", e)
+		return
+	}
+
+	_, _, _, err = GetChartDataByEdbInfoList(0, 0, 0, "", "", edbInfoMappingList, &extraConf)
+	if err != nil {
+		err = fmt.Errorf("获取图表数据失败, ErrMsg: %v", err)
+		return
+	}
+
+	// 重新计算-先清除原数据
+	calculateDataOb := new(data_manage.FactorEdbSeriesCalculateDataQjjs)
+
+	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
+	}
+
+	// 计算成功的保存结果
+	dataArr := make([]*data_manage.FactorEdbSeriesCalculateDataQjjs, 0)
+	for _, v := range edbInfoMappingList {
+		dataList := v.DataList.([]*data_manage.EdbDataList)
+		for _, dataItem := range dataList {
+			dataTime, _ := time.ParseInLocation(utils.FormatDate, dataItem.DataTime, time.Local)
+			dataArr = append(dataArr, &data_manage.FactorEdbSeriesCalculateDataQjjs{
+				FactorEdbSeriesId: seriesId,
+				EdbInfoId:         v.EdbInfoId,
+				EdbCode:           v.EdbCode,
+				DataTime:          dataTime,
+				Value:             dataItem.Value,
+				CreateTime:        time.Now().Local(),
+				ModifyTime:        time.Now().Local(),
+				DataTimestamp:     dataItem.DataTimestamp,
+			})
+		}
+	}
+	if len(dataArr) == 0 {
+		err = fmt.Errorf("计算结果无数据, seriesId: %d", seriesId)
+		return
+	}
+	if e = calculateDataOb.CreateMulti(dataArr); e != nil {
+		err = fmt.Errorf("保存计算结果失败, seriesId: %d, err: %v, ", seriesId, e)
+		return
+	}
+	return
+}
+
+func CheckChartRangeExtraConfig(extraConfig data_manage.ChartRangeAnalysisExtraConf) (err error, errMsg string, isSendEmail bool) {
+	extraConfig.SeriesName = strings.TrimSpace(extraConfig.SeriesName)
+	if extraConfig.SeriesName == "" && extraConfig.EdbInfoMode == 1 {
+		errMsg = "请输入指标系列名称"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	if extraConfig.CalculateType > 5 || extraConfig.CalculateType < 0 {
+		errMsg = "计算方式参数错误"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	switch extraConfig.DateRangeType {
+	case 0:
+	case 1:
+		if len(extraConfig.ManualDateConf) == 0 {
+			errMsg = "请选择时间区间"
+			err = errors.New(errMsg)
+			return
+		}
+		// 先按开始时间排序
+		sort.Sort(data_manage.ChartRangeAnalysisManualDateConfList(extraConfig.ManualDateConf))
+		// 校验日期
+		// 1.如果截止时间小于指标的截止日期,需要重置为指标的截止日期
+		// 2.时间区间不能重叠
+		for i := 1; i < len(extraConfig.ManualDateConf); i++ {
+			start1, e := time.Parse(utils.FormatDate, extraConfig.ManualDateConf[i-1].EndDate)
+			if e != nil {
+				err = e
+				errMsg = "截止日期格式有误"
+				return
+			}
+			start2, e := time.Parse(utils.FormatDate, extraConfig.ManualDateConf[i].EndDate)
+			if e != nil {
+				err = e
+				errMsg = "截止日期格式有误"
+				return
+			}
+
+			start3, e := time.Parse(utils.FormatDate, extraConfig.ManualDateConf[i].StartDate)
+			if e != nil {
+				err = e
+				errMsg = "截止日期格式有误"
+				return
+			}
+			// 如果当前区间的开始时间小于等于前一个区间的结束时间,则存在重叠
+			if !start2.After(start1) || start3.Before(start1) {
+				errMsg = "日期区间存在重叠"
+				return
+			}
+		}
+		//如果截止时间大于指标的截止日期,需要重置为指标的截止日期
+	case 2:
+		if extraConfig.YearDateConf.StartDay == "" || extraConfig.YearDateConf.EndDay == "" {
+			errMsg = "请选择时间区间"
+			return
+		}
+		if _, e := time.Parse(utils.FormatMonthDay, extraConfig.YearDateConf.StartDay); e != nil {
+			errMsg = "开始日期格式有误"
+			return
+		}
+		if _, e := time.Parse(utils.FormatMonthDay, extraConfig.YearDateConf.EndDay); e != nil {
+			errMsg = "结束日期格式有误"
+			return
+		}
+	}
+	return
+}

+ 13 - 6
services/data/trade_analysis/trade_analysis.go

@@ -75,11 +75,11 @@ func GetClassifyName(lang string) (list trade_analysis.TradeClassifyNameListSort
 	currDate := time.Now().Format(utils.FormatDate)
 	for k, v := range exchanges {
 		tmp := trade_analysis.TradeClassifyNameList{
-			Exchange: v,
+			Exchange:   v,
 			ExchangeEn: exchangesEn[v],
-			Items:    nil,
-			Sort:     exchangesSortMap[k],
-			CurrDate: currDate,
+			Items:      nil,
+			Sort:       exchangesSortMap[k],
+			CurrDate:   currDate,
 		}
 		nameList, ok := classifyExchangeMap[k]
 		if !ok {
@@ -101,7 +101,7 @@ func GetClassifyName(lang string) (list trade_analysis.TradeClassifyNameListSort
 		if len(nameList) > 0 {
 			if k == "zhengzhou" {
 				for _, item := range nameList {
-					classifyName := getZhengzhouClassifyName(item.ClassifyName)
+					classifyName := GetZhengzhouClassifyName(item.ClassifyName)
 					tmpItemItem := trade_analysis.TradeClassifyNameListItemItem{
 						ClassifyType: item.ClassifyName,
 					}
@@ -135,7 +135,7 @@ func GetClassifyName(lang string) (list trade_analysis.TradeClassifyNameListSort
 	return
 }
 
-func getZhengzhouClassifyName(code string) (name string) {
+func GetZhengzhouClassifyName(code string) (name string) {
 	if strings.HasPrefix(code, "PTA") {
 		name = "PTA"
 		return
@@ -240,6 +240,13 @@ func getZhengzhouClassifyName(code string) (name string) {
 		name = "烧碱"
 		return
 	}
+	if strings.HasPrefix(code, "PR") {
+		name = "瓶片"
+		return
+	}
+	if name == "" {
+		utils.FileLog.Info(fmt.Sprintf("郑商所-合约暂未归类: %s", code))
+	}
 	return
 }
 

+ 647 - 0
services/data/trade_analysis/trade_analysis_data.go

@@ -0,0 +1,647 @@
+package trade_analysis
+
+import (
+	"eta/eta_api/models/data_manage"
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strings"
+	"time"
+)
+
+// FormatCompanyTradeData2EdbMappings [公司-合约加总]转为指标数据
+func FormatCompanyTradeData2EdbMappings(companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, tradeType, dateType, dateTypeNum int, startDate, endDate string, chartEdbList []*data_manage.ChartSaveItem) (edbMappings []*data_manage.ChartEdbInfoMapping, chartName string, err error) {
+	edbMappings = make([]*data_manage.ChartEdbInfoMapping, 0)
+	if dateType <= 0 {
+		dateType = utils.DateTypeOneMonth
+	}
+
+	// 期货公司名称作为标识进行匹配
+	edbMap := make(map[string]*data_manage.ChartSaveItem)
+	if len(chartEdbList) > 0 {
+		for _, v := range chartEdbList {
+			edbMap[v.UniqueFlag] = v
+		}
+	}
+
+	for k, v := range companyTradeData {
+		mapping := new(data_manage.ChartEdbInfoMapping)
+		mapping.EdbName = v.CompanyName
+		mapping.EdbNameEn = v.CompanyName
+		mapping.EdbAliasName = v.CompanyName
+		mapping.EdbAliasNameEn = v.CompanyName
+		mapping.Frequency = "日度"
+		mapping.FrequencyEn = data.GetFrequencyEn(mapping.Frequency)
+		mapping.SourceName = utils.SourceNameTradeAnalysis
+		mapping.Source = utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS
+		mapping.IsAxis = 1
+		mapping.EdbInfoType = 1
+		mapping.StartDate = v.StartDate.Format(utils.FormatDate)
+		mapping.EndDate = v.EndDate.Format(utils.FormatDate)
+		mapping.ConvertUnit = tradeAnalysisModel.WarehouseDefaultUnit // 固定单位
+		mapping.UniqueFlag = v.CompanyName                            // 期货公司名称作为每条曲线的唯一标识
+
+		// 有配置那么取配置中的图例名称和左右轴
+		edbConf := edbMap[mapping.UniqueFlag]
+		if edbConf != nil {
+			mapping.EdbName = edbConf.EdbAliasName
+			mapping.EdbNameEn = edbConf.EdbAliasName
+			mapping.EdbAliasName = edbConf.EdbAliasName
+			mapping.EdbAliasNameEn = edbConf.EdbAliasName
+			mapping.IsAxis = edbConf.IsAxis
+		}
+
+		// 根据参数取日期范围
+		var startTime, endTime time.Time
+		if dateType > 0 {
+			st, ed := utils.GetDateByDateTypeV2(dateType, startDate, endDate, dateTypeNum, 0)
+			if st != "" {
+				startTime, _ = time.ParseInLocation(utils.FormatDate, st, time.Local)
+			}
+			if startTime.IsZero() {
+				startTime = v.StartDate
+			}
+			if ed != "" {
+				endTime, _ = time.ParseInLocation(utils.FormatDate, ed, time.Local)
+			}
+			if endTime.IsZero() {
+				endTime = v.EndDate
+			}
+		}
+
+		// 指标数据和最值
+		edbData := make([]*data_manage.EdbDataList, 0)
+		var minData, maxData float64
+		var setMinMax bool
+		for _, dv := range v.DataList {
+			if dv.Date.Before(startTime) || dv.Date.After(endTime) {
+				continue
+			}
+
+			// 交易方向
+			var (
+				val    float64
+				hasVal bool
+			)
+			if tradeType == tradeAnalysisModel.WarehouseBuyChartType {
+				if dv.BuyValType == tradeAnalysisModel.TradeDataTypeNull {
+					continue
+				}
+				hasVal = true
+				val = float64(dv.BuyVal)
+			}
+			if tradeType == tradeAnalysisModel.WarehouseSoldChartType {
+				if dv.SoldValType == tradeAnalysisModel.TradeDataTypeNull {
+					continue
+				}
+				hasVal = true
+				val = float64(dv.SoldVal)
+			}
+			if tradeType == tradeAnalysisModel.WarehousePureBuyChartType {
+				if dv.PureBuyValType == tradeAnalysisModel.TradeDataTypeNull {
+					continue
+				}
+				hasVal = true
+				val = float64(dv.PureBuyVal)
+			}
+			if !hasVal {
+				continue
+			}
+
+			if !setMinMax {
+				minData = val
+				maxData = val
+				setMinMax = true
+			}
+			if val < minData {
+				minData = val
+			}
+			if val > maxData {
+				maxData = val
+			}
+			edbData = append(edbData, &data_manage.EdbDataList{
+				DataTime:      dv.Date.Format(utils.FormatDate),
+				DataTimestamp: dv.Date.UnixNano() / 1e6,
+				Value:         val,
+			})
+		}
+		mapping.MinData = minData
+		mapping.MaxData = maxData
+		mapping.DataList = edbData
+		edbMappings = append(edbMappings, mapping)
+
+		// 图表默认名称
+		if k == 0 {
+			chartName += strings.ReplaceAll(v.ClassifyType, ",", "")
+		}
+		chartName += v.CompanyName
+	}
+
+	// 图表名称后缀
+	chartName += tradeAnalysisModel.WarehouseTypeSuffixNames[tradeType]
+	return
+}
+
+func GetOriginTradeData(exchange, classifyName string, contracts, companies []string, predictRatio float64) (companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, err error) {
+	// 各原始数据表期货公司名称不一致
+	companyMap := make(map[string]string)
+	{
+		ob := new(tradeAnalysisModel.TradeFuturesCompany)
+		list, e := ob.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取期货公司名称失败: %v", e)
+			return
+		}
+		switch exchange {
+		case "zhengzhou":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.ZhengzhouName
+			}
+		case "dalian":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.DalianName
+			}
+		case "shanghai":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.ShanghaiName
+			}
+		case "cffex":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.CffexName
+			}
+		case "ine":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.IneName
+			}
+		case "guangzhou":
+			for _, v := range list {
+				companyMap[v.CompanyName] = v.GuangzhouName
+			}
+		}
+	}
+	var queryCompanies []string
+	for _, v := range companies {
+		// TOP20用空名称去查询
+		if v == tradeAnalysisModel.TradeFuturesCompanyTop20 {
+			queryCompanies = append(queryCompanies, "")
+			continue
+		}
+		companyName, ok := companyMap[v]
+		if !ok {
+			utils.FileLog.Info(fmt.Sprintf("交易所%s公司名称映射不存在: %s", exchange, v))
+			continue
+		}
+		queryCompanies = append(queryCompanies, companyName)
+	}
+
+	// 郑商所/广期所查询方式不一样
+	var tradeAnalysis TradeAnalysisInterface
+	switch exchange {
+	case tradeAnalysisModel.TradeExchangeZhengzhou:
+		tradeAnalysis = &ZhengzhouTradeAnalysis{}
+	case tradeAnalysisModel.TradeExchangeGuangzhou:
+		tradeAnalysis = &GuangzhouTradeAnalysis{}
+	default:
+		tradeAnalysis = &BaseTradeAnalysis{}
+	}
+
+	// 获取多单/空单原始数据
+	originList, e := tradeAnalysis.GetTradeDataByClassifyAndCompany(exchange, classifyName, contracts, queryCompanies)
+	if e != nil {
+		err = fmt.Errorf("获取多空单原始数据失败, %v", e)
+		return
+	}
+
+	keyItems := make(map[string]*tradeAnalysisModel.ContractCompanyTradeData)
+	keyDateData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeDataList)
+	keyDateDataExist := make(map[string]bool)
+	for _, v := range originList {
+		// TOP20对应数据库中的空名称
+		companyName := v.CompanyName
+		if companyName == "" {
+			companyName = tradeAnalysisModel.TradeFuturesCompanyTop20
+		}
+
+		k := fmt.Sprintf("%s-%s", v.ClassifyType, companyName)
+		if keyItems[k] == nil {
+			keyItems[k] = new(tradeAnalysisModel.ContractCompanyTradeData)
+			keyItems[k].CompanyName = companyName
+			keyItems[k].ClassifyType = v.ClassifyType
+			keyItems[k].DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+		}
+
+		kd := fmt.Sprintf("%s-%s", k, v.DataTime.Format(utils.FormatDate))
+		if keyDateData[kd] == nil {
+			keyDateData[kd] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+			keyDateData[kd].Date = v.DataTime
+		}
+		if v.ValType == 1 {
+			keyDateData[kd].BuyVal = v.Val
+			keyDateData[kd].BuyValType = tradeAnalysisModel.TradeDataTypeOrigin
+			keyDateData[kd].BuyChange = v.ValChange
+			keyDateData[kd].BuyChangeType = tradeAnalysisModel.TradeDataTypeOrigin
+		}
+		if v.ValType == 2 {
+			keyDateData[kd].SoldVal = v.Val
+			keyDateData[kd].SoldValType = tradeAnalysisModel.TradeDataTypeOrigin
+			keyDateData[kd].SoldChange = v.ValChange
+			keyDateData[kd].SoldChangeType = tradeAnalysisModel.TradeDataTypeOrigin
+		}
+		if !keyDateDataExist[kd] {
+			keyItems[k].DataList = append(keyItems[k].DataList, keyDateData[kd])
+			keyDateDataExist[kd] = true
+		}
+	}
+
+	// 获取[合约]每日的末位多空单
+	contractLastBuyDateVal := make(map[string]map[time.Time]int)
+	contractLastSoldDateVal := make(map[string]map[time.Time]int)
+	{
+		lastOriginList, e := tradeAnalysis.GetLastTradeDataByClassify(exchange, classifyName, contracts)
+		if e != nil {
+			err = fmt.Errorf("获取末位多空单原始数据失败, %v", e)
+			return
+		}
+		for _, v := range lastOriginList {
+			if v.ValType == 1 {
+				if contractLastBuyDateVal[v.ClassifyType] == nil {
+					contractLastBuyDateVal[v.ClassifyType] = make(map[time.Time]int)
+				}
+				contractLastBuyDateVal[v.ClassifyType][v.DataTime] = v.Val
+				continue
+			}
+			if contractLastSoldDateVal[v.ClassifyType] == nil {
+				contractLastSoldDateVal[v.ClassifyType] = make(map[time.Time]int)
+			}
+			contractLastSoldDateVal[v.ClassifyType][v.DataTime] = v.Val
+		}
+	}
+
+	// 填充[合约-公司]预估数据, 并根据[公司-多合约]分组, [公司]算作一个指标, 指标值为[多个合约]的计算加总
+	companyContracts := make(map[string][]*tradeAnalysisModel.ContractCompanyTradeData)
+	for _, v := range keyItems {
+		td, fd, ed, e := PredictingTradeData(v.DataList, contractLastBuyDateVal[v.ClassifyType], contractLastSoldDateVal[v.ClassifyType], predictRatio)
+		if e != nil {
+			err = fmt.Errorf("数据补全失败, %v", e)
+			return
+		}
+		v.DataList = td
+		v.StartDate = fd
+		v.EndDate = ed
+
+		if companyContracts[v.CompanyName] == nil {
+			companyContracts[v.CompanyName] = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+		}
+		companyContracts[v.CompanyName] = append(companyContracts[v.CompanyName], v)
+	}
+
+	// 以[公司]为组, 计算合约加总
+	mussyTradeData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeData)
+	for k, v := range companyContracts {
+		companyData := new(tradeAnalysisModel.ContractCompanyTradeData)
+		companyData.CompanyName = k
+		companyData.DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+		contractArr := make([]string, 0)
+
+		// 合约加总
+		sumDateData := make(map[time.Time]*tradeAnalysisModel.ContractCompanyTradeDataList)
+		for _, vv := range v {
+			contractArr = append(contractArr, vv.ClassifyType)
+			for _, dv := range vv.DataList {
+				if sumDateData[dv.Date] == nil {
+					sumDateData[dv.Date] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+					sumDateData[dv.Date].Date = dv.Date
+				}
+				// 数据类型以第一个非零值为准, 只处理多空和净多, 变化就不管了
+				if sumDateData[dv.Date].BuyValType == tradeAnalysisModel.TradeDataTypeNull && dv.BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].BuyValType = dv.BuyValType
+				}
+				if sumDateData[dv.Date].BuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.BuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].BuyValType = dv.BuyValType
+				}
+				if dv.BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].BuyVal += dv.BuyVal
+				}
+				// 空单
+				if sumDateData[dv.Date].SoldValType == tradeAnalysisModel.TradeDataTypeNull && dv.SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].SoldValType = dv.SoldValType
+				}
+				if sumDateData[dv.Date].SoldValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.SoldValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].SoldValType = dv.SoldValType
+				}
+				if dv.SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].SoldVal += dv.SoldVal
+				}
+				// 净多单
+				if sumDateData[dv.Date].PureBuyValType == tradeAnalysisModel.TradeDataTypeNull && dv.PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].PureBuyValType = dv.PureBuyValType
+				}
+				if sumDateData[dv.Date].PureBuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dv.PureBuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					sumDateData[dv.Date].PureBuyValType = dv.PureBuyValType
+				}
+				if dv.PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+					sumDateData[dv.Date].PureBuyVal += dv.PureBuyVal
+				}
+			}
+
+			// 多个合约比对开始结束时间
+			if companyData.StartDate.IsZero() {
+				companyData.StartDate = vv.StartDate
+			}
+			if vv.StartDate.Before(companyData.StartDate) {
+				companyData.StartDate = vv.StartDate
+			}
+			if companyData.EndDate.IsZero() {
+				companyData.EndDate = vv.EndDate
+			}
+			if vv.EndDate.Before(companyData.EndDate) {
+				companyData.EndDate = vv.EndDate
+			}
+		}
+		for _, sv := range sumDateData {
+			companyData.DataList = append(companyData.DataList, sv)
+		}
+		sort.Slice(companyData.DataList, func(i, j int) bool {
+			return companyData.DataList[i].Date.Before(companyData.DataList[j].Date)
+		})
+		companyData.ClassifyType = strings.Join(contractArr, ",")
+		mussyTradeData[k] = companyData
+	}
+
+	// 数据根据公司排序, 不然会随机乱
+	companyTradeData = make([]*tradeAnalysisModel.ContractCompanyTradeData, 0)
+	for _, v := range companies {
+		// 没数据也需要加进去, 不然edbList会少
+		if mussyTradeData[v] == nil {
+			companyData := new(tradeAnalysisModel.ContractCompanyTradeData)
+			companyData.CompanyName = v
+			companyData.DataList = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+			companyTradeData = append(companyTradeData, companyData)
+			continue
+		}
+		companyTradeData = append(companyTradeData, mussyTradeData[v])
+	}
+	return
+}
+
+// PredictingTradeData 根据数据库中的多空数据填充预估数据
+func PredictingTradeData(originData []*tradeAnalysisModel.ContractCompanyTradeDataList, lastBuyDateVal, lastSoldDateVal map[time.Time]int, predictRatio float64) (newData []*tradeAnalysisModel.ContractCompanyTradeDataList, firstDate, endDate time.Time, err error) {
+	// 测试用的验证数据
+	//lastBuyDateVal, lastSoldDateVal = make(map[time.Time]int), make(map[time.Time]int)
+	//lastBuyDateVal[time.Date(2024, 7, 16, 0, 0, 0, 0, time.Local)] = 4602
+	//lastBuyDateVal[time.Date(2024, 7, 17, 0, 0, 0, 0, time.Local)] = 5116
+	//lastBuyDateVal[time.Date(2024, 7, 18, 0, 0, 0, 0, time.Local)] = 5130
+	//lastBuyDateVal[time.Date(2024, 7, 19, 0, 0, 0, 0, time.Local)] = 5354
+	//lastBuyDateVal[time.Date(2024, 7, 22, 0, 0, 0, 0, time.Local)] = 5916
+	//lastBuyDateVal[time.Date(2024, 7, 23, 0, 0, 0, 0, time.Local)] = 6524
+	//lastBuyDateVal[time.Date(2024, 7, 26, 0, 0, 0, 0, time.Local)] = 6575
+	//lastBuyDateVal[time.Date(2024, 7, 29, 0, 0, 0, 0, time.Local)] = 7461
+	//lastBuyDateVal[time.Date(2024, 7, 30, 0, 0, 0, 0, time.Local)] = 8488
+	//
+	//lastSoldDateVal[time.Date(2024, 7, 11, 0, 0, 0, 0, time.Local)] = 5467
+	//lastSoldDateVal[time.Date(2024, 7, 12, 0, 0, 0, 0, time.Local)] = 5248
+	//lastSoldDateVal[time.Date(2024, 7, 15, 0, 0, 0, 0, time.Local)] = 5102
+	//lastSoldDateVal[time.Date(2024, 7, 16, 0, 0, 0, 0, time.Local)] = 4771
+	//lastSoldDateVal[time.Date(2024, 7, 23, 0, 0, 0, 0, time.Local)] = 5989
+	//lastSoldDateVal[time.Date(2024, 7, 26, 0, 0, 0, 0, time.Local)] = 6745
+	//lastSoldDateVal[time.Date(2024, 7, 30, 0, 0, 0, 0, time.Local)] = 7272
+	//
+	//originData = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+	//originData = append(originData, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 10, 0, 0, 0, 0, time.Local),
+	//	BuyVal:         14324,
+	//	BuyValType:     tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:      -1107,
+	//	BuyChangeType:  tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldVal:        0,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeNull,
+	//	SoldChange:     0,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeNull,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:          time.Date(2024, 7, 11, 0, 0, 0, 0, time.Local),
+	//	BuyVal:        14280,
+	//	BuyValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:     -44,
+	//	BuyChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:          time.Date(2024, 7, 12, 0, 0, 0, 0, time.Local),
+	//	BuyVal:        14214,
+	//	BuyValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:     -66,
+	//	BuyChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:          time.Date(2024, 7, 15, 0, 0, 0, 0, time.Local),
+	//	BuyVal:        14269,
+	//	BuyValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	BuyChange:     55,
+	//	BuyChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 17, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        5254,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     708,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 18, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        6595,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     1341,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 19, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        5938,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     -657,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 22, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        6131,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     193,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//}, &tradeAnalysisModel.ContractCompanyTradeDataList{
+	//	Date:           time.Date(2024, 7, 29, 0, 0, 0, 0, time.Local),
+	//	SoldVal:        6679,
+	//	SoldValType:    tradeAnalysisModel.TradeDataTypeOrigin,
+	//	SoldChange:     312,
+	//	SoldChangeType: tradeAnalysisModel.TradeDataTypeOrigin,
+	//})
+
+	if len(originData) == 0 {
+		return
+	}
+	if predictRatio < 0 || predictRatio > 1 {
+		err = fmt.Errorf("估计参数不在0-1之间")
+		return
+	}
+	sort.Slice(originData, func(i, j int) bool {
+		return originData[i].Date.Before(originData[j].Date)
+	})
+	dateVal := make(map[time.Time]*tradeAnalysisModel.ContractCompanyTradeDataList)
+	for _, v := range originData {
+		dateVal[v.Date] = v
+	}
+
+	// 生成开始日期-1d(可能会往前面推算一天)至结束日期间的交易日, 以交易日为时间序列遍历
+	tradeDays := utils.GetTradingDays(originData[0].Date.AddDate(0, 0, -1), originData[len(originData)-1].Date)
+	for k, v := range tradeDays {
+		// T日多空均无的情况
+		//bothLast := false
+		if dateVal[v] == nil {
+			// T-1和T+1[原始数据]均无值, 那么T日无数据
+			hasPrev, hasNext := false, false
+			if k-1 >= 0 {
+				hasPrev = true
+			}
+			if k+1 <= len(tradeDays)-1 {
+				hasNext = true
+			}
+			if !hasPrev && !hasNext {
+				continue
+			}
+
+			// T+1有值, 优先从T+1推, 然后继续走下面计算净多单的逻辑
+			if hasNext {
+				nextDay := tradeDays[k+1]
+				if dateVal[nextDay] != nil {
+					// T+1有多/空及多空变化, 且是原始数据, 那么推出数据并在map中新加一日数据
+					if dateVal[nextDay].BuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dateVal[nextDay].BuyChangeType == tradeAnalysisModel.TradeDataTypeOrigin {
+						if _, ok := dateVal[v]; !ok {
+							dateVal[v] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+							dateVal[v].Date = v
+						}
+						dateVal[v].BuyVal = dateVal[nextDay].BuyVal - dateVal[nextDay].BuyChange
+						dateVal[v].BuyValType = tradeAnalysisModel.TradeDataTypeOrigin
+					}
+					if dateVal[nextDay].SoldValType == tradeAnalysisModel.TradeDataTypeOrigin && dateVal[nextDay].SoldChangeType == tradeAnalysisModel.TradeDataTypeOrigin {
+						if _, ok := dateVal[v]; !ok {
+							dateVal[v] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+							dateVal[v].Date = v
+						}
+						dateVal[v].SoldVal = dateVal[nextDay].SoldVal - dateVal[nextDay].SoldChange
+						dateVal[v].SoldValType = tradeAnalysisModel.TradeDataTypeOrigin
+					}
+				}
+			}
+
+			// T+1没推出来而T-1有值, 那么T多空均取末位, 计算净多单
+			_, has := dateVal[v]
+			if hasPrev && !has {
+				sv, sok := lastSoldDateVal[v]
+				bv, bok := lastBuyDateVal[v]
+				if !sok && !bok {
+					continue
+				}
+				dateVal[v] = new(tradeAnalysisModel.ContractCompanyTradeDataList)
+				dateVal[v].Date = v
+				if sok {
+					dateVal[v].SoldVal = int(predictRatio*float64(sv) + 0.5)
+					dateVal[v].SoldValType = tradeAnalysisModel.TradeDataTypeCalculate
+				}
+				if bok {
+					dateVal[v].BuyVal = int(predictRatio*float64(bv) + 0.5)
+					dateVal[v].BuyValType = tradeAnalysisModel.TradeDataTypeCalculate
+				}
+				if dateVal[v].BuyValType > tradeAnalysisModel.TradeDataTypeNull && dateVal[v].SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+					dateVal[v].PureBuyVal = dateVal[v].BuyVal - dateVal[v].SoldVal
+					dateVal[v].PureBuyValType = tradeAnalysisModel.TradeDataTypeCalculate
+				}
+				continue
+			}
+		}
+
+		// 多空均有的情况下计算净多单
+		if dateVal[v].BuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dateVal[v].SoldValType == tradeAnalysisModel.TradeDataTypeOrigin {
+			dateVal[v].PureBuyVal = dateVal[v].BuyVal - dateVal[v].SoldVal
+			dateVal[v].PureBuyValType = tradeAnalysisModel.TradeDataTypeOrigin // 原始值算出来的也作原始值
+		}
+
+		// 仅有多单, 空单取末位, 计算净多单
+		if dateVal[v].BuyValType == tradeAnalysisModel.TradeDataTypeOrigin && dateVal[v].SoldValType == tradeAnalysisModel.TradeDataTypeNull {
+			if sv, ok := lastSoldDateVal[v]; ok {
+				dateVal[v].SoldVal = int(predictRatio*float64(sv) + 0.5) // 估计参数*末位值, 向上取整
+				dateVal[v].SoldValType = tradeAnalysisModel.TradeDataTypeCalculate
+				dateVal[v].PureBuyVal = dateVal[v].BuyVal - dateVal[v].SoldVal
+				dateVal[v].PureBuyValType = tradeAnalysisModel.TradeDataTypeCalculate
+			}
+		}
+
+		// 仅有空单, 多单取末位, 计算净多单
+		if dateVal[v].SoldValType == tradeAnalysisModel.TradeDataTypeOrigin && dateVal[v].BuyValType == tradeAnalysisModel.TradeDataTypeNull {
+			if sv, ok := lastBuyDateVal[v]; ok {
+				dateVal[v].BuyVal = int(predictRatio*float64(sv) + 0.5)
+				dateVal[v].BuyValType = tradeAnalysisModel.TradeDataTypeCalculate
+				dateVal[v].PureBuyVal = dateVal[v].BuyVal - dateVal[v].SoldVal
+				dateVal[v].PureBuyValType = tradeAnalysisModel.TradeDataTypeCalculate
+			}
+		}
+	}
+
+	// 二次遍历, 计算与T-1的变化值
+	for k, v := range tradeDays {
+		// 无T/T-1数据, 忽略
+		if dateVal[v] == nil {
+			continue
+		}
+		if k-1 < 0 {
+			continue
+		}
+		beforeDay := tradeDays[k-1]
+		if dateVal[beforeDay] == nil {
+			continue
+		}
+
+		// 多单变化
+		if dateVal[v].BuyChangeType == tradeAnalysisModel.TradeDataTypeNull {
+			if dateVal[v].BuyValType > tradeAnalysisModel.TradeDataTypeNull && dateVal[beforeDay].BuyValType > tradeAnalysisModel.TradeDataTypeNull {
+				dateVal[v].BuyChange = dateVal[v].BuyVal - dateVal[beforeDay].BuyVal
+				// 如果当日多单或者前日多单是估计值, 那么多单变化也为估计值
+				if dateVal[v].BuyValType == tradeAnalysisModel.TradeDataTypeCalculate || dateVal[beforeDay].BuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					dateVal[v].BuyChangeType = tradeAnalysisModel.TradeDataTypeCalculate
+				}
+			}
+		}
+
+		// 空单变化
+		if dateVal[v].SoldChangeType == tradeAnalysisModel.TradeDataTypeNull {
+			if dateVal[v].SoldValType > tradeAnalysisModel.TradeDataTypeNull && dateVal[beforeDay].SoldValType > tradeAnalysisModel.TradeDataTypeNull {
+				dateVal[v].SoldChange = dateVal[v].SoldVal - dateVal[beforeDay].SoldVal
+				// 如果当日空单或者前日空单是估计值, 那么空单变化也为估计值
+				if dateVal[v].SoldValType == tradeAnalysisModel.TradeDataTypeCalculate || dateVal[beforeDay].SoldValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					dateVal[v].SoldChangeType = tradeAnalysisModel.TradeDataTypeCalculate
+				}
+			}
+		}
+
+		// 净多变化
+		if dateVal[v].PureBuyChangeType == tradeAnalysisModel.TradeDataTypeNull {
+			if dateVal[v].PureBuyValType > tradeAnalysisModel.TradeDataTypeNull && dateVal[beforeDay].PureBuyValType > tradeAnalysisModel.TradeDataTypeNull {
+				dateVal[v].PureBuyChange = dateVal[v].PureBuyVal - dateVal[beforeDay].PureBuyVal
+				dateVal[v].PureBuyChangeType = tradeAnalysisModel.TradeDataTypeOrigin
+				// 如果当日净多单或者前日净多单是估计值, 那么净多单变化也为估计值
+				if dateVal[v].PureBuyValType == tradeAnalysisModel.TradeDataTypeCalculate || dateVal[beforeDay].PureBuyValType == tradeAnalysisModel.TradeDataTypeCalculate {
+					dateVal[v].PureBuyChangeType = tradeAnalysisModel.TradeDataTypeCalculate
+				}
+			}
+		}
+	}
+
+	// 重新遍历map, 生成数据序列并排序
+	newData = make([]*tradeAnalysisModel.ContractCompanyTradeDataList, 0)
+	for _, v := range dateVal {
+		if v.BuyValType == tradeAnalysisModel.TradeDataTypeNull && v.SoldValType == tradeAnalysisModel.TradeDataTypeNull {
+			continue
+		}
+		newData = append(newData, v)
+	}
+	sort.Slice(newData, func(i, j int) bool {
+		return newData[i].Date.Before(newData[j].Date)
+	})
+	if len(newData) > 0 {
+		firstDate = newData[0].Date
+		endDate = newData[len(newData)-1].Date
+	}
+	return
+}

+ 181 - 0
services/data/trade_analysis/trade_analysis_interface.go

@@ -0,0 +1,181 @@
+package trade_analysis
+
+import (
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+// TradeAnalysisInterface 持仓分析查询接口
+type TradeAnalysisInterface interface {
+	GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) // 根据品种和公司获取原始数据
+	GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error)                       // 获取品种末位数据
+}
+
+// BaseTradeAnalysis 通用交易所
+type BaseTradeAnalysis struct{}
+
+func (b *BaseTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
+	return tradeAnalysisModel.GetTradeDataByClassifyAndCompany(exchange, classifyName, contracts, queryCompanies)
+}
+
+func (b *BaseTradeAnalysis) GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
+	return tradeAnalysisModel.GetLastTradeDataByClassify(exchange, classifyName, contracts)
+}
+
+// ZhengzhouTradeAnalysis 郑商所
+type ZhengzhouTradeAnalysis struct{}
+
+func (z *ZhengzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
+	return tradeAnalysisModel.GetTradeZhengzhouDataByClassifyAndCompany(exchange, contracts, queryCompanies)
+}
+
+func (z *ZhengzhouTradeAnalysis) GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
+	return tradeAnalysisModel.GetLastTradeZhengzhouDataByClassify(exchange, contracts)
+}
+
+// GuangzhouTradeAnalysis 广期所
+type GuangzhouTradeAnalysis struct{}
+
+func (g *GuangzhouTradeAnalysis) GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts, queryCompanies []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
+	classifyIdMap := map[string]int{"si": 7, "lc": 8}
+	classifyId := classifyIdMap[classifyName]
+	if classifyId == 0 {
+		err = fmt.Errorf("品种有误")
+		return
+	}
+
+	// TOP20
+	seatNameArr := []string{tradeAnalysisModel.GuangZhouSeatNameBuy, tradeAnalysisModel.GuangZhouSeatNameSold}
+	if utils.InArrayByStr(queryCompanies, tradeAnalysisModel.TradeFuturesCompanyTop20) {
+		seatNameArr = append(seatNameArr, tradeAnalysisModel.GuangZhouTopSeatNameBuy, tradeAnalysisModel.GuangZhouTopSeatNameSold)
+	}
+
+	// 查询品种下所有指标
+	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndexByClassifyId(classifyId)
+	if e != nil {
+		err = fmt.Errorf("获取广期所指标失败, %v", e)
+		return
+	}
+	var indexIds []int
+	indexInfo := make(map[int]*tradeAnalysisModel.OriginTradeData)
+	for _, v := range indexes {
+		// eg.永安期货_si2401_持买单量
+		nameArr := strings.Split(v.IndexName, "_")
+		if len(nameArr) != 3 {
+			continue
+		}
+		companyName := nameArr[0]
+		if nameArr[0] == tradeAnalysisModel.GuangZhouTopCompanyAliasName {
+			companyName = tradeAnalysisModel.TradeFuturesCompanyTop20
+		}
+		if !utils.InArrayByStr(seatNameArr, nameArr[2]) {
+			continue
+		}
+		if !utils.InArrayByStr(queryCompanies, companyName) {
+			continue
+		}
+		if !utils.InArrayByStr(contracts, nameArr[1]) {
+			continue
+		}
+		indexIds = append(indexIds, v.BaseFromTradeGuangzhouIndexId)
+		if indexInfo[v.BaseFromTradeGuangzhouIndexId] == nil {
+			if tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]] == 0 {
+				continue
+			}
+			indexInfo[v.BaseFromTradeGuangzhouIndexId] = new(tradeAnalysisModel.OriginTradeData)
+			indexInfo[v.BaseFromTradeGuangzhouIndexId].CompanyName = companyName
+			indexInfo[v.BaseFromTradeGuangzhouIndexId].ClassifyName = classifyName
+			indexInfo[v.BaseFromTradeGuangzhouIndexId].ClassifyType = nameArr[1]
+			indexInfo[v.BaseFromTradeGuangzhouIndexId].ValType = tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]]
+		}
+	}
+	if len(indexIds) == 0 {
+		return
+	}
+
+	// 查询指标数据
+	indexesData, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouDataByIndexIds(indexIds)
+	if e != nil {
+		err = fmt.Errorf("获取广期所指标数据失败, %v", e)
+		return
+	}
+	items = make([]*tradeAnalysisModel.OriginTradeData, 0)
+	for _, v := range indexesData {
+		info, ok := indexInfo[v.BaseFromTradeGuangzhouIndexId]
+		if !ok {
+			continue
+		}
+		items = append(items, &tradeAnalysisModel.OriginTradeData{
+			CompanyName:  info.CompanyName,
+			Val:          int(v.Value),
+			ValChange:    int(v.QtySub),
+			DataTime:     v.DataTime,
+			ClassifyName: info.ClassifyName,
+			ClassifyType: info.ClassifyType,
+			ValType:      info.ValType,
+		})
+	}
+	return
+}
+
+func (g *GuangzhouTradeAnalysis) GetLastTradeDataByClassify(exchange, classifyName string, contracts []string) (items []*tradeAnalysisModel.OriginTradeData, err error) {
+	classifyIdMap := map[string]int{"si": 7, "lc": 8}
+	classifyId := classifyIdMap[classifyName]
+	if classifyId == 0 {
+		err = fmt.Errorf("品种有误")
+		return
+	}
+	seatNameArr := []string{tradeAnalysisModel.GuangZhouSeatNameBuy, tradeAnalysisModel.GuangZhouSeatNameSold}
+
+	// 查询品种下所有指标
+	indexes, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouIndexByClassifyId(classifyId)
+	if e != nil {
+		err = fmt.Errorf("获取广期所指标失败, %v", e)
+		return
+	}
+
+	// 获取各合约下的指标
+	contractIndexIds := make(map[string][]int)
+	for _, v := range indexes {
+		// eg.永安期货_si2401_持买单量
+		nameArr := strings.Split(v.IndexName, "_")
+		if len(nameArr) != 3 {
+			continue
+		}
+		if !utils.InArrayByStr(contracts, nameArr[1]) {
+			continue
+		}
+		if !utils.InArrayByStr(seatNameArr, nameArr[2]) {
+			continue
+		}
+		if tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]] == 0 {
+			continue
+		}
+		k := fmt.Sprintf("%s-%d", nameArr[1], tradeAnalysisModel.GuangzhouSeatNameValType[nameArr[2]])
+		contractIndexIds[k] = append(contractIndexIds[k], v.BaseFromTradeGuangzhouIndexId)
+	}
+
+	// ps.如果后面如果有空可以优化一下这里, 把末位数据每天写进一张表里面
+	for k, v := range contractIndexIds {
+		keyArr := strings.Split(k, "-")
+		contract := keyArr[0]
+		valType, _ := strconv.Atoi(keyArr[1])
+		lastVales, e := tradeAnalysisModel.GetBaseFromTradeGuangzhouMinDataByIndexIds(v)
+		if e != nil {
+			err = fmt.Errorf("获取合约末位数据失败, %v", e)
+			return
+		}
+		for _, vv := range lastVales {
+			items = append(items, &tradeAnalysisModel.OriginTradeData{
+				Val:          int(vv.Value),
+				DataTime:     vv.DataTime,
+				ClassifyType: contract,
+				ValType:      valType,
+			})
+		}
+	}
+	return
+}

+ 418 - 0
services/data/trade_analysis/warehouse.go

@@ -0,0 +1,418 @@
+package trade_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/chart_theme"
+	tradeAnalysisModel "eta/eta_api/models/data_manage/trade_analysis"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// CheckWarehouseChartExtraConfig 校验持仓分析图表参数
+func CheckWarehouseChartExtraConfig(extraConfig tradeAnalysisModel.WarehouseExtraConfig) (pass bool, tips string) {
+	if extraConfig.Exchange == "" {
+		tips = "请选择交易所"
+		return
+	}
+	if extraConfig.ClassifyName == "" {
+		tips = "请选择品种"
+		return
+	}
+	if len(extraConfig.Contracts) == 0 {
+		tips = "请选择合约"
+		return
+	}
+	if len(extraConfig.Companies) == 0 {
+		tips = "请选择期货公司"
+		return
+	}
+	if len(extraConfig.Companies) > 5 {
+		tips = "最多可选5个期货公司"
+		return
+	}
+	if extraConfig.PredictRatio < 0 || extraConfig.PredictRatio > 1 {
+		tips = "请输入正确的估计参数"
+		return
+	}
+	pass = true
+	return
+}
+
+func GetWarehouseChartResp(chartView *data_manage.ChartInfoView, companyTradeData []*tradeAnalysisModel.ContractCompanyTradeData, multiEdb []*tradeAnalysisModel.WarehouseEdbSaveItem, extraConfig tradeAnalysisModel.WarehouseExtraConfig, chartConfig tradeAnalysisModel.WarehouseChartPars) (chartResp *data_manage.ChartInfoDetailResp, err error) {
+	edbMappings, defaultChartName, e := FormatCompanyTradeData2EdbMappings(companyTradeData, chartConfig.WarehouseChartType, chartConfig.DateType, chartConfig.DateTypeNum, chartConfig.StartDate, chartConfig.EndDate, chartConfig.ChartEdbInfoList)
+	if e != nil {
+		err = fmt.Errorf("多单数据转为指标失败, %v", e)
+		return
+	}
+	// chartView为空表示为预览图, 有则表示为详情图
+	var chartThemeId int
+	chartType := utils.CHART_TYPE_CURVE // 曲线图
+	if chartView == nil {
+		// 图表样式/主题
+		chartThemeType, e := chart_theme.GetChartThemeTypeByChartTypeAndSource(chartType, utils.CHART_SOURCE_DEFAULT)
+		if e != nil {
+			err = fmt.Errorf("获取图表类型失败, %v", e)
+			return
+		}
+		chartThemeId = chartThemeType.DefaultChartThemeId
+
+		chartView = new(data_manage.ChartInfoView)
+		chartView.ChartType = chartType
+		chartView.Source = utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS
+		chartView.ChartName = defaultChartName
+		chartView.ChartNameEn = defaultChartName
+	} else {
+		chartThemeId = chartView.ChartThemeId
+	}
+	chartView.DateType = chartConfig.DateType
+	chartView.DateTypeNum = chartConfig.DateTypeNum
+	chartView.StartDate = chartConfig.StartDate
+	chartView.EndDate = chartConfig.EndDate
+	chartTheme, e := data.GetChartThemeConfig(chartThemeId, utils.CHART_SOURCE_DEFAULT, chartType)
+	if e != nil {
+		err = fmt.Errorf("获取图表主题失败, %v", e)
+		return
+	}
+	chartView.ChartThemeId = chartTheme.ChartThemeId
+	chartView.ChartThemeStyle = chartTheme.Config
+
+	chartResp = new(data_manage.ChartInfoDetailResp)
+	chartResp.ChartInfo = chartView
+	chartResp.EdbInfoList = edbMappings
+	dataResp := tradeAnalysisModel.WarehouseChartDataResp{WarehouseExtraConfig: extraConfig, MultiEdbMappings: multiEdb}
+	dataResp.WarehouseChartType = chartConfig.WarehouseChartType
+	chartResp.DataResp = dataResp
+	return
+}
+
+// AddWarehouseChart 添加持仓分析图表
+func AddWarehouseChart(req data_manage.AddChartInfoReq, extraConfig tradeAnalysisModel.WarehouseExtraConfig, adminId int, adminRealName string) (chartInfo *data_manage.ChartInfo, err error) {
+	// 图表信息
+	chartInfo = new(data_manage.ChartInfo)
+	chartInfo.ChartName = req.ChartName
+	chartInfo.ChartNameEn = req.ChartName
+	chartInfo.ChartClassifyId = req.ChartClassifyId
+	chartInfo.SysUserId = adminId
+	chartInfo.SysUserRealName = adminRealName
+	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 = utils.CHART_TYPE_CURVE
+	chartInfo.Calendar = "公历"
+	chartInfo.DateType = req.DateType
+	chartInfo.StartDate = req.StartDate
+	chartInfo.EndDate = req.EndDate
+	chartInfo.SeasonStartDate = req.StartDate
+	chartInfo.SeasonEndDate = req.EndDate
+	chartInfo.LeftMin = req.LeftMin
+	chartInfo.LeftMax = req.LeftMax
+	chartInfo.RightMin = req.RightMin
+	chartInfo.RightMax = req.RightMax
+	chartInfo.Right2Min = req.Right2Min
+	chartInfo.Right2Max = req.Right2Max
+	chartInfo.Source = utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS
+	chartInfo.ChartThemeId = req.ChartThemeId
+	chartInfo.SourcesFrom = req.SourcesFrom
+	chartInfo.Instructions = req.Instructions
+	chartInfo.MarkersLines = req.MarkersLines
+	chartInfo.MarkersAreas = req.MarkersAreas
+	chartInfo.ExtraConfig = req.ExtraConfig
+	chartInfo.DateTypeNum = req.DateTypeNum
+	chartInfo.MinMaxSave = req.MinMaxSave
+
+	// 图例信息-由于持仓分析图表无指标, 图例信息就不存在chart_edb_mapping里了, 而是chart_series
+	seriesList := make([]*data_manage.ChartSeries, 0)
+	for _, v := range req.ChartEdbInfoList {
+		t := new(data_manage.ChartSeries)
+		t.SeriesName = v.EdbAliasName
+		t.SeriesNameEn = v.EdbAliasName
+		t.IsAxis = v.IsAxis
+		t.UniqueFlag = v.UniqueFlag
+		t.CreateTime = time.Now().Local()
+		t.ModifyTime = time.Now().Local()
+		seriesList = append(seriesList, t)
+	}
+
+	// 图表关联多图配置
+	multiChartMapping := new(data_manage.MultipleGraphConfigChartMapping)
+	if extraConfig.MultipleGraphConfigId > 0 {
+		multiChartMapping.MultipleGraphConfigId = extraConfig.MultipleGraphConfigId
+		multiChartMapping.Source = utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS
+		multiChartMapping.CreateTime = time.Now().Local()
+		multiChartMapping.ModifyTime = time.Now().Local()
+	}
+
+	// 新增
+	if e := tradeAnalysisModel.CreateWarehouseChart(chartInfo, seriesList, multiChartMapping); e != nil {
+		err = fmt.Errorf("新增图表失败, %v", e)
+		return
+	}
+
+	// 添加es数据
+	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
+	return
+}
+
+// EditWarehouseChart 编辑持仓分析图表
+func EditWarehouseChart(req data_manage.EditChartInfoReq) (chartItem *data_manage.ChartInfo, err error) {
+	// 更新图表
+	e := data_manage.EditChartInfoAndMapping(&req, "", "公历", req.DateType, 0, ``, make([]*data_manage.ChartSaveItem, 0), "")
+	if e != nil {
+		err = fmt.Errorf("更新图表失败, %v", e)
+		return
+	}
+	chartItem, e = data_manage.GetChartInfoById(req.ChartInfoId)
+	if e != nil {
+		err = fmt.Errorf("获取更新后的图表失败, %v", e)
+		return
+	}
+
+	// 替换原图例
+	if e = data_manage.DeleteChartSeriesAndEdbMapping(req.ChartInfoId); e != nil {
+		err = fmt.Errorf("删除原图例失败, %v", e)
+		return
+	}
+	seriesList := make([]*data_manage.ChartSeries, 0)
+	for _, v := range req.ChartEdbInfoList {
+		t := new(data_manage.ChartSeries)
+		t.SeriesName = v.EdbAliasName
+		t.SeriesNameEn = v.EdbAliasName
+		t.ChartInfoId = chartItem.ChartInfoId
+		t.IsAxis = v.IsAxis
+		t.UniqueFlag = v.UniqueFlag
+		t.CreateTime = time.Now().Local()
+		t.ModifyTime = time.Now().Local()
+		seriesList = append(seriesList, t)
+	}
+	if len(seriesList) > 0 {
+		seriesOb := new(data_manage.ChartSeries)
+		if e = seriesOb.CreateMulti(seriesList); e != nil {
+			err = fmt.Errorf("新增图例失败, %v", e)
+			return
+		}
+	}
+
+	// 更新ES
+	go func() {
+		data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
+		data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
+	}()
+	return
+}
+
+// CopyWarehouseChart 复制持仓分析图表
+func CopyWarehouseChart(classifyId int, chartName string, originChart *data_manage.ChartInfo, adminId int, adminRealName string) (chartInfo *data_manage.ChartInfo, err error) {
+	var extraConfig tradeAnalysisModel.WarehouseExtraConfig
+	if e := json.Unmarshal([]byte(originChart.ExtraConfig), &extraConfig); e != nil {
+		err = fmt.Errorf("图表配置有误, %v", e)
+		return
+	}
+
+	// 新增配置并绑定图表
+	multiConfigCopy := &data_manage.MultipleGraphConfig{
+		SysUserId:       adminId,
+		SysUserRealName: adminRealName,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	if e := data_manage.AddMultipleGraphConfig(multiConfigCopy); e != nil {
+		err = fmt.Errorf("新增持仓分析多图配置失败, %v", e)
+		return
+	}
+	extraConfig.MultipleGraphConfigId = multiConfigCopy.MultipleGraphConfigId
+	configByte, e := json.Marshal(extraConfig)
+	if e != nil {
+		err = fmt.Errorf("图表配置格式化失败, %v", e)
+		return
+	}
+
+	// 新增图表
+	chartInfo = &data_manage.ChartInfo{
+		ChartName:         chartName,
+		ChartClassifyId:   classifyId,
+		SysUserId:         adminId,
+		SysUserRealName:   adminRealName,
+		UniqueCode:        utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10)),
+		CreateTime:        time.Now(),
+		ModifyTime:        time.Now(),
+		DateType:          originChart.DateType,
+		StartDate:         originChart.StartDate,
+		EndDate:           originChart.EndDate,
+		IsSetName:         originChart.IsSetName,
+		EdbInfoIds:        originChart.EdbInfoIds,
+		ChartType:         originChart.ChartType,
+		Calendar:          originChart.Calendar,
+		SeasonStartDate:   originChart.SeasonStartDate,
+		SeasonEndDate:     originChart.SeasonEndDate,
+		ChartImage:        originChart.ChartImage,
+		BarConfig:         originChart.BarConfig,
+		LeftMin:           originChart.LeftMin,
+		LeftMax:           originChart.LeftMax,
+		RightMin:          originChart.RightMin,
+		RightMax:          originChart.RightMax,
+		Right2Min:         originChart.Right2Min,
+		Right2Max:         originChart.Right2Max,
+		Disabled:          originChart.Disabled,
+		Source:            originChart.Source,
+		ExtraConfig:       string(configByte),
+		SeasonExtraConfig: originChart.SeasonExtraConfig,
+		StartYear:         originChart.StartYear,
+		Unit:              originChart.Unit,
+		UnitEn:            originChart.UnitEn,
+		ChartThemeId:      originChart.ChartThemeId,
+		SourcesFrom:       originChart.SourcesFrom,
+		Instructions:      originChart.Instructions,
+		MarkersLines:      originChart.MarkersLines,
+		MarkersAreas:      originChart.MarkersAreas,
+		DateTypeNum:       originChart.DateTypeNum,
+	}
+
+	// 图例信息
+	seriesList, e := data_manage.GetChartSeriesByChartInfoId(originChart.ChartInfoId)
+	if e != nil {
+		err = fmt.Errorf("获取图例信息失败, %v", e)
+		return
+	}
+	seriesCopy := make([]*data_manage.ChartSeries, 0)
+	for _, v := range seriesList {
+		t := new(data_manage.ChartSeries)
+		t.SeriesName = v.SeriesName
+		t.SeriesNameEn = v.SeriesNameEn
+		t.IsAxis = v.IsAxis
+		t.UniqueFlag = v.UniqueFlag
+		t.CreateTime = time.Now().Local()
+		t.ModifyTime = time.Now().Local()
+		seriesCopy = append(seriesCopy, t)
+	}
+
+	// 新增图表-多图配置关联
+	configChartMapping := &data_manage.MultipleGraphConfigChartMapping{
+		MultipleGraphConfigId: multiConfigCopy.MultipleGraphConfigId,
+		Source:                utils.CHART_SOURCE_TRADE_ANALYSIS_PROCESS,
+		ModifyTime:            time.Now().Local(),
+		CreateTime:            time.Now().Local(),
+	}
+
+	// 新增
+	if e = tradeAnalysisModel.CreateWarehouseChart(chartInfo, seriesCopy, configChartMapping); e != nil {
+		err = fmt.Errorf("新增图表失败, %v", e)
+		return
+	}
+
+	// 新增ES
+	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
+	return
+}
+
+// CheckEdbSave 校验指标新增
+func CheckEdbSave(extraConfig tradeAnalysisModel.WarehouseExtraConfig, multiEdbList []*data_manage.MultipleGraphConfigEdbMapping, IsSaveAs bool) (newEdbList []*tradeAnalysisModel.WarehouseEdbSaveItem, removeEdbIds []int, err error) {
+	// 另存为或无关联指标时, 返回应当新增的全部指标列表
+	newEdbList = make([]*tradeAnalysisModel.WarehouseEdbSaveItem, 0)
+	suffixNames := tradeAnalysisModel.WarehouseTypeSuffixNames
+	prefix := strings.Join(extraConfig.Contracts, "")
+	if len(multiEdbList) == 0 || IsSaveAs {
+		for _, v := range extraConfig.Companies {
+			edb := new(tradeAnalysisModel.WarehouseEdbSaveItem)
+			edb.EdbName = fmt.Sprintf("%s%s%s", prefix, v, suffixNames[extraConfig.WarehouseChartType])
+			edb.Unit = tradeAnalysisModel.WarehouseDefaultUnit
+			edb.Frequency = tradeAnalysisModel.WarehouseDefaultFrequency
+			edb.UniqueFlag = v
+
+			//conf := extraConfig
+			//conf.Companies = []string{v}
+			//b, e := json.Marshal(conf)
+			//if e != nil {
+			//	err = fmt.Errorf("指标配置JSON格式化异常, %v", e)
+			//	return
+			//}
+			//edb.ExtraConfig = string(b)
+			newEdbList = append(newEdbList, edb)
+		}
+		return
+	}
+
+	// 已有关联指标
+	var edbIds []int
+	for _, v := range multiEdbList {
+		edbIds = append(edbIds, v.EdbInfoId)
+	}
+	if len(edbIds) == 0 {
+		err = fmt.Errorf("关联指标IDs异常")
+		return
+	}
+	warehouseType := extraConfig.WarehouseChartType
+
+	// 获取已关联的指标信息
+	edbList, e := data_manage.GetEdbInfoByIdList(edbIds)
+	if e != nil {
+		err = fmt.Errorf("获取指标信息失败, %v", e)
+		return
+	}
+
+	// 只需要匹配期货公司即可, 合约数不匹配那么是不需要新增指标的
+	existsMap := make(map[string]int) // [期货公司]:[指标ID]
+	for _, v := range edbList {
+		// 解析计算公式中的配置信息, 计算公式为空、解析失败的为异常需要移除绑定关系
+		if v.CalculateFormula == "" {
+			removeEdbIds = append(removeEdbIds, v.EdbInfoId)
+			continue
+		}
+		var conf tradeAnalysisModel.WarehouseExtraConfig
+		if e = json.Unmarshal([]byte(v.CalculateFormula), &conf); e != nil {
+			utils.FileLog.Info("持仓分析图表-解析指标计算公式失败, EdbInfoId: %d, Conf: %s", v.EdbInfoId, v.CalculateFormula)
+			removeEdbIds = append(removeEdbIds, v.EdbInfoId)
+			continue
+		}
+		if len(conf.Companies) != 1 {
+			utils.FileLog.Info("持仓分析图表-指标计算公式异常, EdbInfoId: %d, Conf: %s", v.EdbInfoId, v.CalculateFormula)
+			removeEdbIds = append(removeEdbIds, v.EdbInfoId)
+			continue
+		}
+		// 方向与配置中的方向不一致, 那么忽略
+		if conf.WarehouseChartType != warehouseType {
+			continue
+		}
+		existsMap[conf.Companies[0]] = v.EdbInfoId
+	}
+
+	// 配置中的, 不在已绑定中的需要新增
+	confMap := make(map[string]bool) // [期货公司]:True
+	for _, v := range extraConfig.Companies {
+		confMap[v] = true
+		if _, ok := existsMap[v]; !ok {
+			// 需要新增的指标
+			edb := new(tradeAnalysisModel.WarehouseEdbSaveItem)
+			edb.EdbName = fmt.Sprintf("%s%s%s", prefix, v, suffixNames[extraConfig.WarehouseChartType])
+			edb.Unit = tradeAnalysisModel.WarehouseDefaultUnit
+			edb.Frequency = tradeAnalysisModel.WarehouseDefaultFrequency
+			edb.UniqueFlag = v
+
+			//conf := extraConfig
+			//conf.Companies = []string{v}
+			//b, e := json.Marshal(conf)
+			//if e != nil {
+			//	err = fmt.Errorf("指标配置JSON格式化异常, %v", e)
+			//	return
+			//}
+			//edb.ExtraConfig = string(b)
+			newEdbList = append(newEdbList, edb)
+			continue
+		}
+	}
+
+	// 已绑定的, 不在配置中的需要移除绑定
+	for k, v := range existsMap {
+		if _, ok := existsMap[k]; !ok {
+			removeEdbIds = append(removeEdbIds, v)
+			continue
+		}
+	}
+	return
+}

+ 44 - 166
services/email.go

@@ -12,8 +12,8 @@ import (
 
 func SendEmailToCompany() {
 	// 获取收件人列表
-	//emailCond := " AND enabled = 1 "
-	emailCond := ""
+	emailCond := " AND enabled = 1 AND status in (1,2) "
+	//emailCond := ""
 	emailPars := make([]interface{}, 0)
 	emails, e := models.GetEnglishReportEmailList(emailCond, emailPars, "")
 	if e != nil {
@@ -28,169 +28,47 @@ func SendEmailToCompany() {
 	// TODO:这是HTML模板内容
 	template := `<!DOCTYPE html>
 <html lang="en">
-
-<head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,viewport-fit=cover">
-    <title>Horizon Insights x Fastmarkets x FGE: 2024 Macro & Commodities Outlook - Registration Open!</title>
-</head>
-
-<body style="padding:0;margin:0;background-color:#fff">
-<div id="app" style="max-width:1280px;margin:0 auto;font-size:14px;min-height:100vh;">
-    <div class="main-box" style="padding:25px 20px;">
-        <div style="line-height:1.7;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;color:#000000;"
-            class=" __aliyun_node_has_color">
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:center;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="font-size:18.0px;font-weight:bold;font-style:normal;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;color:#000000;text-decoration:underline;"
-                    class=" __aliyun_node_has_color">2024 Macro &amp; Commodities Outlook Invite</span></div>
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;color:#000000;"
-                    class=" __aliyun_node_has_color"><br></span></div>
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="font-style:normal;font-weight:400;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;color:#000000;"
-                    class=" __aliyun_node_has_color">Dear Client,</span></div>
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="font-style:normal;font-weight:400;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;color:#000000;"
-                    class=" __aliyun_node_has_color"><br></span></div>
-            <div style="clear:both;">
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><span
-                        style="font-style:normal;font-weight:400;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;color:#000000;"
-                        class=" __aliyun_node_has_color">Happy Year-End Holidays!</span></div>
-            </div>
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="font-style:normal;font-weight:400;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;color:#000000;"
-                    class=" __aliyun_node_has_color"><br></span></div>
-            <div style="clear:both;">
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><span
-                        style="color:#000000;font-style:normal;font-weight:400;text-transform:none;border:.0px;font-family:arial;margin:.0px;padding:.0px;outline:.0px;font-size:13.0px;"
-                        class=" __aliyun_node_has_color">As we usher in the new year, Horizon Insights, in collaboration
-                        with Fastmarkets and FGE, is proud to announce</span><span
-                        style="color:#000000;font-style:normal;font-weight:400;text-transform:none;font-family:arial;font-size:13.0px;"
-                        class=" __aliyun_node_has_color">&nbsp;our 2024 Macro &amp; Commodities Outlook which will be
-                        held&nbsp;</span><span
-                        style="font-weight:bold;color:#000000;font-style:normal;text-transform:none;font-family:arial;font-size:13.0px;text-decoration:none;"
-                        class=" __aliyun_node_has_color">virtually</span><span
-                        style="color:#000000;font-style:normal;font-weight:400;text-transform:none;font-family:arial;font-size:13.0px;text-decoration:none;"
-                        class=" __aliyun_node_has_color">&nbsp;on&nbsp;</span><span
-                        style="font-weight:bold;color:#000000;font-style:normal;text-transform:none;font-family:arial;font-size:13.0px;text-decoration:none;"
-                        class=" __aliyun_node_has_color">10 &amp; 11 Jan 2024</span><span
-                        style="color:#000000;font-style:normal;font-weight:400;text-transform:none;font-family:arial;font-size:13.0px;"
-                        class=" __aliyun_node_has_color">,&nbsp;</span><span
-                        style="color:#000000;font-family:arial;font-size:13.0px;font-style:normal;font-weight:bold;text-transform:none;"
-                        class=" __aliyun_node_has_color">5pm - 8pm SGT</span><span
-                        style="color:#000000;font-style:normal;font-weight:400;text-transform:none;font-family:arial;font-size:13.0px;"
-                        class=" __aliyun_node_has_color">&nbsp;(i.e.&nbsp;</span><span
-                        style="color:#000000;font-family:arial;font-size:13.0px;font-style:normal;font-weight:bold;text-transform:none;"
-                        class=" __aliyun_node_has_color">9am - 12pm BST</span><span
-                        style="color:#000000;font-style:normal;font-weight:400;text-transform:none;font-family:arial;font-size:13.0px;"
-                        class=" __aliyun_node_has_color">). Our experienced analysts from Horizon Insights together with
-                        guest speakers from our valued partners will present our views in the fields of Macro, Ferrous
-                        Metals, Base Metals, Energy, as well as Petrochemicals.</span></div>
-            </div>
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-size:13.0px;font-family:arial;"><br></span>
-            </div>
-            <div style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;"
-                class=" __aliyun_node_has_color"><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-size:13.0px;font-family:arial;">Please
-                    see below for our event schedule:</span></div>
-            <div
-                style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">
-                <br></div>
-            <div
-                style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">
-                <span
-                    style="color:#000000;font-style:normal;font-weight:400;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-size:13.0px;font-family:arial;"
-                    class=" __aliyun_node_has_color">&nbsp;&nbsp;<img style="vertical-align: bottom; margin: 0px;"
-                        height="3301"
-                        src="https://hzstatic.hzinsights.com/static/images/202312/20231228/KeDTAxIZ3UrZR5DZKKIQLdoIaOVB.png"
-                        width="890"></span></div>
-            <div
-                style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">
-                <span
-                    style="color:#000000;font-style:normal;font-weight:400;text-transform:none;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-size:13.0px;font-family:arial;"
-                    class=" __aliyun_node_has_color">&nbsp; &nbsp;</span></div>
-            <div
-                style="clear:both;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-variant-ligatures:normal;font-variant-caps:normal;text-align:start;text-indent:.0px;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">
-                <br></div>
-            <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                class=" __aliyun_node_has_color"><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;"><br></span>
-            </div>
-            <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                class=" __aliyun_node_has_color"><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;">To
-                    register, simply scan the QR code in the posters above to indicate your interest. Alternatively, you
-                    may also&nbsp;</span><span
-                    style="font-weight:bold;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;"><a
-                        href="https://forms.gle/yhPRDcri43P2QyPt8" target="_blank">register here</a></span><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;">.</span>
-            </div>
-            <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                class=" __aliyun_node_has_color"><span
-                    style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;color:#000000;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;float:none;display:inline;"
-                    class=" __aliyun_node_has_color">Should you encounter any difficulties registering, please contact
-                    Stephanie (stephanie@hzinsights.com).</span></div>
-            <div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><span
-                        style="font-weight:bold;margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;">The
-                        virtual meeting details will be disseminated to you a few days prior to the event.</span></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><br></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><span
-                        style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;">Lastly,
-                        our firm would like to wish all of you a prosperous Happy New Year! We look forward to seeing
-                        everyone on the 10th &amp; 11th.</span></div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><span
-                        style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;"><br></span>
-                </div>
-                <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;color:#000000;font-family:Tahoma,Arial,STHeiti,SimSun;font-size:14.0px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;text-align:start;text-indent:.0px;text-transform:none;text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;clear:both;"
-                    class=" __aliyun_node_has_color"><span
-                        style="margin:.0px;padding:.0px;border:.0px;outline:.0px;font-family:arial;font-size:13.0px;">Cheers!</span>
-                </div>
-            </div>
-            <div><br></div>
-            <div>
-                <div style="clear:both;"><span style="font-family:arial;font-size:13.0px;color:#000000;"
-                        class=" __aliyun_node_has_color"><br></span></div>
-                <div style="clear:both;">
-                    <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;clear:both;font-variant-caps:normal;text-align:start;text-indent:.0px;"
-                        class="x___aliyun_node_has_color"><br></div>
-                    <div style="margin:.0px;padding:.0px;border:.0px;outline:.0px;clear:both;font-variant-caps:normal;text-align:start;text-indent:.0px;"
-                        class="x___aliyun_node_has_color"></div>
-                </div>
-            </div>
-            <div><br></div>
-            <div><br></div>
-            <div><br></div>
-            <div style="line-height:20.0px;clear:both;"><br></div>
-        </div>
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <div>
+      <p>Hello,</p>
+      <p></p>
+      <p> We are pleased to invite you to the launch of our <span style="font-weight: 700;">Mon. Date with HI</span> , a weekly event where we share timely updates and expert analysis on Global Macro, Base Metal, Ferrous Metal, and Energy markets.</p>
+      <h3 style="font-weight: 700;">Event Details:</h3>
+      <ul>
+        <li>Date: August 26th</li>
+        <li>Time: 5:00 PM (Shanghai/Singapore) | 10:00 AM (London)</li>
+        <li>Platform: Zoom (the same link will be used for all future sessions)</li>
+      </ul>
+      <h3 style="font-weight: 700;">Join Zoom Meeting:</h3>
+      <a href="https://us06web.zoom.us/j/84723517075?pwd=3GHNOCHq6sAgBGXsA7qtX9r1PuA1uK.1" target="_blank">https://us06web.zoom.us/j/84723517075?pwd=3GHNOCHq6sAgBGXsA7qtX9r1PuA1uK.1</a>
+      <p>
+        <span style="font-weight: 700;">Meeting ID:</span>
+        <span>847 2351 7075</span>
+      </p>
+      <p>
+        <span style="font-weight: 700;">Passcode:</span>
+        <span>564439</span>
+      </p>
+      <p style="font-weight: 700;">Theme for Today’s Session: Diverging Expectations between Market and Policymakers</p>
+      <p>Our analysts will discuss the impact of the current macro environment on commodity markets, exploring both short-term challenges and longer-term outlooks.<br>This series will be a recurring event every Monday at the same time. </p>
+      <h3 style="font-weight: 700;">Please note:</h3>
+      <ul>
+        <li>This event will be conducted in listen-only mode. However, participants are encouraged to ask questions via the chat function during the Q&A segments.</li>
+        <li>For privacy concerns, you may change your display name upon joining the event.</li>
+      </ul>
+      <p></p>
+      <p>We look forward to your participation.</p>
+      <p></p>
+      <p>Best regards,<br>Horizon Insights</p>
+      <img style="max-width: 375px;width: 100%;" src="https://hzstatic.hzinsights.com/static/images/202408/20240826/z0qJ4cpM5mIaHrHv0BaMsNXYkLKa.jpg" alt="">
     </div>
-</div>
-</body>
-
+  </body>
 </html>`
 
 	// 推送信息
@@ -199,8 +77,8 @@ func SendEmailToCompany() {
 		r := new(EnglishReportSendEmailRequest)
 		r.EmailId = emails[i].Id
 		r.Email = strings.Replace(emails[i].Email, " ", "", -1)
-		r.Subject = "Horizon Insights x Fastmarkets x FGE: 2024 Macro & Commodities Outlook - Registration Open!" // TODO:这是主题
-		r.FromAlias = "Horizon FICC"                                                                              // TODO:这是推送人(中文)
+		r.Subject = "Invitation to Weekly Call with Horizon Insights Analysts: Mon. Date with HI" // TODO:这是主题
+		r.FromAlias = "Horizon FICC"                                                              // TODO:这是推送人(中文)
 
 		r.HtmlBody = template
 		sendData = append(sendData, r)

+ 49 - 2
services/excel/lucky_sheet.go

@@ -6,13 +6,15 @@ import (
 	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/tealeg/xlsx"
-	"github.com/xuri/excelize/v2"
+	"math"
 	"os"
 	"reflect"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/tealeg/xlsx"
+	"github.com/xuri/excelize/v2"
 )
 
 type LuckySheetDataBak struct {
@@ -1743,8 +1745,53 @@ func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hide
 					tmp.MergeCell.Column = cell.MerData.Mer.Col
 				}
 				if cell.ShowStyle != "" {
+					var styleConfig request.MixCellShowStyle
+					if err := json.Unmarshal([]byte(cell.ShowStyle), &styleConfig); err != nil {
+						utils.FileLog.Info("表格样式showStyle解析失败", err.Error())
+					}
 					showFormatValue := fmt.Sprintf("%v", cell.ShowFormatValue)
+					if styleConfig.BackgroundColor != "" {
+						tmp.Background = styleConfig.BackgroundColor
+					}
+					if styleConfig.Color != "" {
+						tmp.FontColor = styleConfig.Color
+					}
 					tmp.Monitor = showFormatValue
+					tmpShowValue, err := strconv.ParseFloat(cell.Value, 64)
+					if err == nil {
+						switch styleConfig.Last {
+						case "nt":
+							// 先进行数字的百分比计算,然后保留小数点位数
+							percent := tmpShowValue * 100
+							if styleConfig.Decimal == nil {
+								continue
+							}
+							factor := math.Pow(10, float64(*styleConfig.Decimal))
+							rounded := math.Round(percent*factor) / factor
+							tmp.Monitor = fmt.Sprintf("%g%%", rounded)
+						case "decimal":
+							// 先保留小数点位数,再进行百分比计算
+							if styleConfig.Decimal == nil {
+								continue
+							}
+							factor := math.Pow(10, float64(*styleConfig.Decimal))
+							rounded := math.Round(tmpShowValue*factor) / factor
+							if styleConfig.Nt == "percent" {
+								percent := rounded * 100
+								var precisionStr string
+								if *styleConfig.Decimal > 2 {
+									precision := *styleConfig.Decimal - 2
+									precisionStr = strconv.FormatFloat(rounded, 'f', precision, 64)
+								} else {
+									precisionStr = strconv.FormatFloat(math.Round(percent), 'f', -1, 64)
+								}
+								tmp.Monitor = fmt.Sprintf("%s%%", precisionStr)
+							} else {
+								tmp.Monitor = fmt.Sprintf("%g", rounded)
+							}
+						}
+					}
+
 				}
 				dataCol = append(dataCol, tmp)
 			}

+ 224 - 0
services/excel/lucky_sheet_table.go

@@ -1,8 +1,13 @@
 package excel
 
 import (
+	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/utils"
 	"fmt"
 	"sort"
+	"strconv"
+	"strings"
+	"time"
 )
 
 // HandleTableCell 前端d毛需要我根据合并单元格处理掉多余的单元格
@@ -99,3 +104,222 @@ func handleCellVal(tmpTableColData LuckySheetDataValue) (valueStr string) {
 	}
 	return
 }
+
+// HandleRuleToTableCell 根据管理规则渲染单元格数据
+func HandleRuleToTableCell(excelInfoId int, oldTableData TableData) (newTableData TableData, err error) {
+	newTableData = oldTableData
+	excelRuleMappingList, err := excel.GetExcelRuleMappingByExcelInfoId(excelInfoId)
+	if err != nil {
+		return
+	}
+	if len(excelRuleMappingList) == 0 {
+		return
+	}
+	tableDataList := oldTableData.TableDataList
+	excelRuleMap := make(map[int]*excel.ExcelInfoRuleMappingView)
+	for _, v := range excelRuleMappingList {
+		excelRuleMap[v.ExcelInfoRuleMappingId] = v
+	}
+	ruleScopeMap := generateRuleScopeIndexMap(excelRuleMappingList)
+	for row, scopeValues := range ruleScopeMap {
+		for col, ruleId := range scopeValues {
+			if v, ok := excelRuleMap[ruleId]; ok {
+				if len(tableDataList) > row && len(tableDataList[row]) > col {
+					// 符合管理规则要求,则进行字体和背景颜色的渲染
+					if checkCellRule(v, tableDataList[row][col].Monitor, tableDataList) {
+						tableDataList[row][col].Background = v.BackgroundColor
+						tableDataList[row][col].FontColor = v.FontColor
+					}
+				} else {
+					continue
+				}
+			} else {
+				continue
+			}
+		}
+	}
+	return
+}
+
+func getCellValueByType(value string, valueType int, tableDataList [][]LuckySheetDataValue) (float64, bool) {
+	if valueType == 2 {
+		coords := strings.Split(value, ",")
+		var coordIntArr []int
+		for _, v := range coords {
+			t, _ := strconv.Atoi(v)
+			coordIntArr = append(coordIntArr, t)
+		}
+		if len(coordIntArr) == 2 {
+			x, y := coordIntArr[0]-1, coordIntArr[1]-1
+			conditionValue, err := strconv.ParseFloat(tableDataList[y][x].Monitor, 64)
+			if err != nil {
+				return 0, false
+			}
+			return conditionValue, true
+		}
+	} else {
+		conditionValue, err := strconv.ParseFloat(value, 64)
+		if err != nil {
+			return 0, false
+		}
+		return conditionValue, true
+	}
+	return 0, false
+}
+
+func checkCellRule(ruleInfo *excel.ExcelInfoRuleMappingView, value string, tableDataList [][]LuckySheetDataValue) bool {
+	var tableValue float64
+	var tableTime time.Time
+	var err error
+
+	if ruleInfo.RuleType == 5 {
+		tableTime, err = time.ParseInLocation(utils.FormatDate, value, time.Local)
+		if err != nil {
+			return false
+		}
+	} else {
+		tableValue, err = strconv.ParseFloat(value, 64)
+		if err != nil {
+			return false
+		}
+	}
+	switch ruleInfo.RuleType {
+	// 大于
+	case 1:
+		conditionValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if tableValue > conditionValue {
+			return true
+		}
+	// 小于
+	case 2:
+		conditionValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if tableValue < conditionValue {
+			return true
+		}
+	// 介于
+	case 3:
+		leftcondValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		rightcondValue, ok := getCellValueByType(ruleInfo.RightValueBack, ruleInfo.RightValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if leftcondValue <= tableValue && tableValue <= rightcondValue {
+			return true
+		}
+	// 等于
+	case 4:
+		conditionValue, ok := getCellValueByType(ruleInfo.LeftValueBack, ruleInfo.LeftValueType, tableDataList)
+		if !ok {
+			return false
+		}
+		if tableValue == conditionValue {
+			return true
+		}
+	// 发生日期
+	case 5:
+		// 发生日期
+		var dateStart, dataEnd time.Time
+		switch ruleInfo.LeftValueBack {
+		// 今天
+		case "today":
+			// 获得今天的零点零分零秒
+			dateStart = utils.Today()
+			if tableTime == dateStart {
+				return true
+			}
+		// 明天
+		case "tomorrow":
+			dateStart = utils.Tomorrow()
+			if tableTime == dateStart {
+				return true
+			}
+		// 最近7天
+		case "last7days":
+			dateStart, dataEnd = utils.Last7Days()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 上周
+		case "lastweek":
+			dateStart, dataEnd = utils.LastWeek()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 本周
+		case "thisweek":
+			dateStart, dataEnd = utils.ThisWeek()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 下周
+		case "nextweek":
+			dateStart, dataEnd = utils.NextWeek()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 上月
+		case "lastmonth":
+			dateStart, dataEnd = utils.LastMonth()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 本月
+		case "thismonth":
+			dateStart, dataEnd = utils.ThisMonth()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		// 下月
+		case "nextmonth":
+			dateStart, dataEnd = utils.NextMonth()
+			if tableTime.After(dateStart) && tableTime.Before(dataEnd) {
+				return true
+			}
+		default:
+			return false
+		}
+
+	}
+	return false
+}
+
+func generateRuleScopeIndexMap(items []*excel.ExcelInfoRuleMappingView) (ruleScopeMap map[int]map[int]int) {
+	ruleScopeMap = make(map[int]map[int]int)
+	for _, item := range items {
+		coords := strings.Split(item.ScopeCoord, ",")
+		var coordIntArr []int
+		for _, v := range coords {
+			t, _ := strconv.Atoi(v)
+			coordIntArr = append(coordIntArr, t)
+		}
+
+		if len(coords) == 4 {
+			xmin, ymin, xmax, ymax := coordIntArr[0]-1, coordIntArr[1]-1, coordIntArr[2]-1, coordIntArr[3]-1
+			for i := ymin; i <= ymax; i++ {
+				for j := xmin; j <= xmax; j++ {
+					if _, ok := ruleScopeMap[i]; !ok {
+						ruleScopeMap[i] = make(map[int]int)
+					}
+					ruleScopeMap[i][j] = item.ExcelInfoRuleMappingId
+				}
+			}
+		}
+		if len(coords) == 2 {
+			x, y := coordIntArr[0]-1, coordIntArr[1]-1
+			if _, ok := ruleScopeMap[y]; !ok {
+				ruleScopeMap[y] = make(map[int]int)
+			}
+			ruleScopeMap[y][x] = item.ExcelInfoRuleMappingId
+		}
+	}
+	return
+}

+ 88 - 0
services/file.go

@@ -1,14 +1,21 @@
 package services
 
 import (
+	"bytes"
 	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/pdfcpu/pdfcpu/pkg/api"
+	"github.com/pdfcpu/pdfcpu/pkg/font"
+	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
+	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
+	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
 	"io"
 	"mime/multipart"
 	"os"
 	"os/exec"
+	"strings"
 	"time"
 )
 
@@ -179,3 +186,84 @@ func GetResourceUrlBySvgImg(imgData string) (resourceUrl string, err error, errM
 
 	return
 }
+
+func GeneralWaterMarkPdf(pdfPath, waterMarkStr string) (f2 *bytes.Buffer, err error) {
+	onTop := true
+	update := false
+	//fontDir := `C:\Users\123\go\src\eta\eta_api\static`
+	fontDir := `static`
+	font.UserFontDir = fontDir
+
+	ttfList := fontDir + `/SimHei.ttf`
+	err = api.InstallFonts([]string{ttfList})
+	if err != nil {
+		fmt.Println("InstallFonts err:", err)
+	}
+
+	api.DisableConfigDir()
+
+	f1, err := os.Open(pdfPath)
+	if err != nil {
+		fmt.Println("文件不存在")
+		return
+	}
+	defer func() {
+		_ = f1.Close()
+	}()
+
+	f2 = &bytes.Buffer{}
+
+	//strList := []string{waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr}
+	strList := []string{waterMarkStr, waterMarkStr, waterMarkStr, waterMarkStr}
+	//strList := []string{waterMarkStr}
+	newWaterMarkStr := strings.Join(strList, "  ")
+	wmList := make([]*model.Watermark, 0)
+	for i := types.TopLeft; i <= 9; i++ {
+		wm, tmpErr := api.TextWatermark(newWaterMarkStr, "fo:SimHei, points:48, col: 0.75 0.75 0.75, rot:45, scale:1 abs, opacity:0.3", onTop, update, types.POINTS)
+		if tmpErr != nil {
+			fmt.Println("TextWatermark err:", tmpErr)
+			return
+		}
+		//wm.Pos = types.Center
+		wm.Pos = i
+		wmList = append(wmList, wm)
+	}
+
+	err = AddWatermarks(f1, f2, nil, wmList, nil)
+
+	return
+}
+func AddWatermarks(rs io.ReadSeeker, w io.Writer, selectedPages []string, wmList []*model.Watermark, conf *model.Configuration) error {
+	if rs == nil {
+		return errors.New("pdfcpu: AddWatermarks: missing rs")
+	}
+
+	if conf == nil {
+		conf = model.NewDefaultConfiguration()
+	}
+	conf.Cmd = model.ADDWATERMARKS
+	conf.OptimizeDuplicateContentStreams = false
+
+	if len(wmList) <= 0 {
+		return errors.New("pdfcpu: missing watermark configuration")
+	}
+
+	ctx, err := api.ReadValidateAndOptimize(rs, conf)
+	if err != nil {
+		return err
+	}
+
+	var pages types.IntSet
+	pages, err = api.PagesForPageSelection(ctx.PageCount, selectedPages, true, true)
+	if err != nil {
+		return err
+	}
+
+	for _, wm := range wmList {
+		if err = pdfcpu.AddWatermarks(ctx, pages, wm); err != nil {
+			return err
+		}
+	}
+
+	return api.Write(ctx, w, conf)
+}

+ 16 - 0
services/report_v2.go

@@ -1459,3 +1459,19 @@ func GetGeneralPdfUrl(reportCode, classifyFirstName string, reportLayout int8) (
 
 	return
 }
+
+func GetReportWaterMarkPdf(reportInfo *models.Report, sysUser *system.Admin) {
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "pdf/" + dateDir
+	filePath, err := utils.DownloadFile(reportInfo.DetailPdfUrl, uploadDir)
+	if err != nil {
+		return
+	}
+	// 移除临时Pdf文件
+	defer func() {
+		_ = os.Remove(filePath)
+	}()
+
+	waterMarkStr := fmt.Sprintf("%s - %s", sysUser.RealName, sysUser.Mobile)
+	GeneralWaterMarkPdf(filePath, waterMarkStr)
+}

+ 8 - 8
services/smart_report.go

@@ -136,7 +136,7 @@ func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
 	return
 }
 
-func ReportToPdf(reportUrl, filePath string) (err error) {
+func ReportToPdf(width int, reportUrl, filePath string) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch
@@ -151,7 +151,7 @@ async def main():
     })
     page = await browser.newPage()
     await page.setViewport({
-        'width': 1920,
+        'width': %d,
         'height': 1080,
     })
     await page.goto('%s', {
@@ -186,7 +186,7 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, reportUrl, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, filePath)
 	utils.FileLog.Info("pdf pyCode: \n" + pyCode)
 	cmd := exec.Command("python3", "-c", pyCode)
 	output, e := cmd.CombinedOutput()
@@ -329,7 +329,11 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 	pdfPath := `./static/` + reportCode + ".pdf"
 	jpegPath := `./static/` + reportCode + ".jpg"
 
-	err = ReportToPdf(reportUrl, pdfPath)
+	width := 1560
+	if reportType == 3 {
+		width = 800
+	}
+	err = ReportToPdf(width, reportUrl, pdfPath)
 	if err != nil {
 		utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
 		go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
@@ -389,10 +393,6 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 
 	time.Sleep(1 * time.Minute)
 
-	width := 1200
-	if reportType == 3 {
-		width = 800
-	}
 	err = ReportToJpeg(width, reportUrl, jpegPath)
 	if err != nil {
 		utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())

+ 2 - 1
static/ErrMsgConfig.json

@@ -57,5 +57,6 @@
   "当前指标已用作预测指标,不可删除": "The current metric is used as a forecasting indicator and cannot be deleted.",
   "当前指标已添加到跨品种分析,不可删除": "The current metric has been added to cross-commodity analysis and cannot be deleted.",
   "指标已删除,请刷新页面": "Metric deleted, please refresh the page.",
-  "删除失败": "Deletion failed."
+  "删除失败": "Deletion failed.",
+  "指标数据异常,请检查": "The metric data is abnormal, please check."
 }

BIN
static/SimHei.gob


+ 229 - 0
utils/common.go

@@ -743,6 +743,11 @@ func StrTimeToTime(strTime string) time.Time {
 	return resultTime
 }
 
+// TimeToStr time.Time 转 字符串 yyyy-MM-dd
+func TimeToStr(data time.Time, format string) string {
+	return data.Format(format)
+}
+
 // 字符串类型时间转周几
 func StrDateTimeToWeek(strTime string) string {
 	var WeekDayMap = map[string]string{
@@ -881,6 +886,17 @@ func ConvertNumToCny(num float64) (str string, err error) {
 	return
 }
 
+// TimeFormatToYmd 时间格式转化 yyyy-MM-dd HH:mm:ss --> yyyy-MM-dd
+func TimeFormatToYmd(strTime string) string {
+	var Ymd string
+	var resultTime = StrTimeToTime(strTime)
+	year := resultTime.Year()
+	month := resultTime.Format("01")
+	day1 := resultTime.Day()
+	Ymd = strconv.Itoa(year) + "-" + month + "-" + strconv.Itoa(day1)
+	return Ymd
+}
+
 // GetNowWeekMonday 获取本周周一的时间
 func GetNowWeekMonday() time.Time {
 	offset := int(time.Monday - time.Now().Weekday())
@@ -1748,6 +1764,32 @@ func GetDateByDateTypeV2(dateType int, tmpStartDate, tmpEndDate string, startYea
 		baseDate, _ := time.Parse(FormatDate, fmt.Sprintf("%d-01-01", yearMax))
 		startDate = baseDate.AddDate(-startYear, 0, 0).Format(FormatDate)
 		endDate = ""
+	case DateTypeOneWeek:
+		//if startDate != "" {
+		//	st, e := time.ParseInLocation(FormatDate, startDate, time.Local)
+		//	if e != nil {
+		//		FileLog.Info(fmt.Sprintf("日期格式解析失败, %s, %v", startDate, e))
+		//		return
+		//	}
+		//	startDate = st.AddDate(0, 0, -7).Format(FormatDate)
+		//}
+		startDate = time.Now().AddDate(0, 0, -7).Format(FormatDate)
+		endDate = ""
+	case DateTypeOneMonth:
+		startDate = time.Now().AddDate(0, -1, 0).Format(FormatDate)
+		endDate = ""
+	case DateTypeTwoMonth:
+		startDate = time.Now().AddDate(0, -2, 0).Format(FormatDate)
+		endDate = ""
+	case DateTypeThreeMonth:
+		startDate = time.Now().AddDate(0, -3, 0).Format(FormatDate)
+		endDate = ""
+	case DateTypeNMonth:
+		if startYear == 0 {
+			startYear = 6
+		}
+		startDate = time.Now().AddDate(0, -startYear, 0).Format(FormatDate)
+		endDate = ""
 	}
 
 	// 兼容日期错误
@@ -2500,6 +2542,150 @@ func DateConvMysqlConvMongo(dateCon string) string {
 	return cond
 }
 
+// handleSystemAppointDateT
+// @Description: 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天)
+// @author: Roc
+// @datetime2023-10-27 09:31:35
+// @param Frequency string
+// @param Day string
+// @return date string
+// @return err error
+// @return errMsg string
+func HandleSystemAppointDateT(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(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(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(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(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(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(FormatDate)
+	default:
+		errMsg = "错误的日期频度:" + frequency
+		err = errors.New(errMsg)
+		return
+	}
+
+	return
+}
+
+func CompareFloatByOpStrings(op string, a, b float64) bool {
+	switch op {
+	case "=":
+		return a == b
+	case ">":
+		return a > b
+	case ">=":
+		return a >= b
+	case "<=":
+		return a <= b
+	case "<":
+		return a < b
+	}
+	return false
+}
 func GetDuration(filePath string) (duration string, err error) {
 	// 构建 FFmpeg 命令,使用 ffprobe 来获取媒体文件信息
 	cmd := exec.Command("ffprobe", "-i", filePath, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0")
@@ -2518,3 +2704,46 @@ func GetDuration(filePath string) (duration string, err error) {
 
 	return duration, nil
 }
+
+// GetTradingDays 获取开始时间至结束时间之间的交易日期(日度)
+func GetTradingDays(startDate, endDate time.Time) []time.Time {
+	var tradingDays []time.Time
+	for curr := startDate; !curr.After(endDate); curr = curr.AddDate(0, 0, 1) {
+		if curr.Weekday() >= time.Monday && curr.Weekday() <= time.Friday {
+			tradingDays = append(tradingDays, curr)
+		}
+	}
+	return tradingDays
+}
+
+// GenerateEdbCodeMap 当前已经生成的指标编码map(暂时不做定时数据清理了,因为数据不大,我们至少每个月会重启一次,所以暂时不做定时数据清理)
+var GenerateEdbCodeMap = map[string]bool{}
+
+// GenerateEdbCode
+// @Description:  生成指标编码
+// @author: Roc
+// @datetime 2024-06-05 09:49:53
+// @param num int
+// @param pre string 前缀
+// @return edbCode string
+// @return err error
+func GenerateEdbCode(num int, pre string) (edbCode string, err error) {
+	if num >= 10 {
+		err = errors.New("指标编码生成失败,请重新生成")
+		return
+	}
+
+	// 4位随机数
+	randStr := GetRandDigit(4)
+	// 年月日时分秒+4位随机数
+	edbCode = `C` + pre + time.Now().Format(FormatShortDateTimeUnSpace) + randStr
+
+	if _, ok := GenerateEdbCodeMap[edbCode]; ok {
+		num++
+		edbCode, err = GenerateEdbCode(num, pre)
+	}
+
+	GenerateEdbCodeMap[edbCode] = true
+
+	return
+}

+ 35 - 0
utils/constants.go

@@ -2,6 +2,7 @@ package utils
 
 import (
 	"io/fs"
+	"time"
 )
 
 // 常量定义
@@ -10,6 +11,7 @@ const (
 	FormatTimeHm               = "15:04"                   //时间格式
 	FormatDate                 = "2006-01-02"              //日期格式
 	FormatDateUnSpace          = "20060102"                //日期格式
+	ormatDateUnSpace           = "2006.01.02"              //日期格式
 	FormatDateTime             = "2006-01-02 15:04:05"     //完整时间格式
 	HlbFormatDateTime          = "2006-01-02_15:04:05.999" //完整时间格式
 	FormatDateTimeUnSpace      = "20060102150405"          //完整时间格式
@@ -173,8 +175,13 @@ const (
 	DATA_SOURCE_BLOOMBERG                            = 83       // bloomberg彭博数据
 	DATA_SOURCE_BUSINESS                             = 84       // 来源于自有数据
 	DATA_SOURCE_SCI99                                = 85       // 卓创资讯 -> 85
+	DATA_SOURCE_CALCULATE_RANGEANLYSIS               = 87       //区间计算->87
+	DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS       = 90       // 预测指标区间计算->90
 	DATA_SOURCE_SCI_HQ                               = 88       // 卓创红期->88
 	DATA_SOURCE_OILCHEM                              = 89       // 隆众资讯 -> 89
+	DATA_SOURCE_FENWEI                               = 77       // 汾渭数据->92
+	DATA_SOURCE_LY                                   = 91       // 粮油商务网
+	DATA_SOURCE_TRADE_ANALYSIS                       = 92       // 持仓分析
 )
 
 // 数据刷新频率
@@ -284,6 +291,8 @@ const (
 	CHART_SOURCE_LINE_FEATURE_FREQUENCY          = 9  // 统计特征-频率分布图表
 	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
+	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
+	CHART_SOURCE_TRADE_ANALYSIS_PROCESS          = 13 // 持仓分析-建仓过程图表
 )
 
 // 批量配置图表的位置来源
@@ -325,6 +334,12 @@ const (
 	PREDICT_EDB_INFO_TYPE = 1 //指标类型 1:预测指标
 )
 
+// 是否关系 无实义
+const (
+	IS_YES = 1
+	IS_NO  = 0
+)
+
 // FrequencyDaysMap 频度日期的map关系
 var FrequencyDaysMap = map[string]int{
 	"天": 1, "周": 7, "月": 30, "季": 90, "年": 365,
@@ -472,3 +487,23 @@ const (
 
 // MultiAddNum 批量插入的数据量
 const MultiAddNum = 500
+
+const (
+	DateTypeOneWeek    = 21 // 时间类型-近1周
+	DateTypeOneMonth   = 22 // 时间类型-近1月
+	DateTypeTwoMonth   = 23 // 时间类型-近2月
+	DateTypeThreeMonth = 24 // 时间类型-近3月
+	DateTypeNMonth     = 25 // 时间类型-近N月
+)
+
+const (
+	SourceNameTradeAnalysis = "持仓分析"
+)
+
+// 基础数据初始化日期
+var (
+	BASE_START_DATE         = `1900-01-01`                                          //基础数据开始日期
+	BASE_END_DATE           = time.Now().AddDate(4, 0, 0).Format(FormatDate)        //基础数据结束日期
+	BASE_START_DATE_UnSpace = "19000101"                                            //基础数据开始日期
+	BASE_END_DATE_UnSpace   = time.Now().AddDate(4, 0, 0).Format(FormatDateUnSpace) //基础数据结束日期
+)

+ 74 - 0
utils/time.go

@@ -0,0 +1,74 @@
+package utils
+
+import "time"
+
+// 获取今天的日期(零点零分零秒)
+func Today() time.Time {
+	now := time.Now()
+	return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+}
+
+// 获取明天的日期(零点零分零秒)
+func Tomorrow() time.Time {
+	return Today().AddDate(0, 0, 1)
+}
+
+// 获取最近7天的日期范围
+func Last7Days() (time.Time, time.Time) {
+	today := Today()
+	return today.AddDate(0, 0, -6), today.Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+}
+
+// 获取上周的日期范围
+func LastWeek() (time.Time, time.Time) {
+	start := Today().AddDate(0, 0, -7)
+	end := start.AddDate(0, 0, 6).Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+	return start, end
+}
+
+// 获取本周的日期范围
+func ThisWeek() (time.Time, time.Time) {
+	start := Today().Round(0).Local().AddDate(0, 0, 0-int(time.Now().Weekday())+1) // 0是本周的第一天,+1是因为AddDate是相对于当前时间的
+	end := start.AddDate(0, 0, 6).Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+	return start, end
+}
+
+// 获取下周的日期范围
+func NextWeek() (time.Time, time.Time) {
+	_, thisEnd := ThisWeek()
+	start := thisEnd.AddDate(0, 0, 1)
+	end := start.AddDate(0, 0, 6).Add(time.Hour * 23).Add(time.Minute * 59).Add(time.Second * 59)
+	return start, end
+}
+
+// 获取上月的日期范围
+func LastMonth() (time.Time, time.Time) {
+	today := time.Now()
+	year, month, _ := today.Date()
+	start := time.Date(year, month-1, 1, 0, 0, 0, 0, today.Location())
+	end := time.Date(year, month, 0, 23, 59, 59, 999999999, today.Location())
+	return start, end
+}
+
+// 获取本月的日期范围
+func ThisMonth() (time.Time, time.Time) {
+	today := time.Now()
+	year, month, _ := today.Date()
+	start := time.Date(year, month, 1, 0, 0, 0, 0, today.Location())
+	end := time.Date(year, month+1, 0, 23, 59, 59, 999999999, today.Location())
+	return start, end
+}
+
+// 获取下月的日期范围
+func NextMonth() (time.Time, time.Time) {
+	now := time.Now()
+	year, month, _ := now.Date()
+	nextMonth := month + 1
+	if nextMonth > 12 {
+		nextMonth = 1
+		year++
+	}
+	startOfNextMonth := time.Date(year, nextMonth, 1, 0, 0, 0, 0, now.Location())
+	endOfNextMonth := startOfNextMonth.AddDate(0, 1, -1).Add(time.Hour*23 + time.Minute*59 + time.Second*59)
+	return startOfNextMonth, endOfNextMonth
+}