Browse Source

Merge branch 'master' into feature/eta2.2.6_material

# Conflicts:
#	static/ErrMsgConfig.json
xyxie 2 months ago
parent
commit
2a4330c834
95 changed files with 14562 additions and 450 deletions
  1. 507 0
      controllers/data_manage/ai_predict_model/classify.go
  2. 940 0
      controllers/data_manage/ai_predict_model/index.go
  3. 708 0
      controllers/data_manage/base_from_rzd_index_controller.go
  4. 27 2
      controllers/data_manage/chart_info.go
  5. 3 0
      controllers/data_manage/chart_theme.go
  6. 1653 0
      controllers/data_manage/clarksons_data.go
  7. 18 1
      controllers/data_manage/correlation/correlation_chart_info.go
  8. 17 1
      controllers/data_manage/cross_variety/chart_info.go
  9. 161 3
      controllers/data_manage/edb_classify.go
  10. 189 0
      controllers/data_manage/edb_info.go
  11. 35 7
      controllers/data_manage/edb_info_relation.go
  12. 3 2
      controllers/data_manage/excel/balance_table.go
  13. 31 12
      controllers/data_manage/excel/excel_info.go
  14. 17 1
      controllers/data_manage/future_good/future_good_chart_info.go
  15. 18 1
      controllers/data_manage/line_equation/line_chart_info.go
  16. 28 1
      controllers/data_manage/line_feature/chart_info.go
  17. 18 1
      controllers/data_manage/my_chart.go
  18. 2 2
      controllers/data_manage/predict_edb_classify.go
  19. 181 70
      controllers/data_manage/predict_edb_info.go
  20. 3 0
      controllers/data_manage/range_analysis/chart_info.go
  21. 4 1
      controllers/data_manage/supply_analysis/variety_edb.go
  22. 1394 0
      controllers/data_manage/usda_fas_data.go
  23. 1 0
      controllers/report_chapter_type.go
  24. 15 1
      controllers/report_v2.go
  25. 284 0
      controllers/residual_analysis/residual_analysis_controller.go
  26. 35 20
      controllers/sandbox/sandbox.go
  27. 356 0
      models/ai_predict_model/ai_predict_model_classify.go
  28. 261 0
      models/ai_predict_model/ai_predict_model_dashboard.go
  29. 176 0
      models/ai_predict_model/ai_predict_model_dashboard_detail.go
  30. 205 0
      models/ai_predict_model/ai_predict_model_data.go
  31. 390 0
      models/ai_predict_model/ai_predict_model_index.go
  32. 233 0
      models/data_manage/base_from_clarksons_classify.go
  33. 81 0
      models/data_manage/base_from_clarksons_data.go
  34. 424 0
      models/data_manage/base_from_clarksons_index.go
  35. 60 0
      models/data_manage/base_from_rzd_classify.go
  36. 115 0
      models/data_manage/base_from_rzd_data.go
  37. 213 0
      models/data_manage/base_from_rzd_index.go
  38. 272 0
      models/data_manage/base_from_usda_fas.go
  39. 238 0
      models/data_manage/base_from_usda_fas_classify.go
  40. 9 1
      models/data_manage/chart_classify.go
  41. 26 4
      models/data_manage/data_manage_permission/edb.go
  42. 2 0
      models/data_manage/edb_classify.go
  43. 6 6
      models/data_manage/edb_data_wind.go
  44. 57 35
      models/data_manage/edb_info.go
  45. 11 11
      models/data_manage/edb_info_relation.go
  46. 45 0
      models/data_manage/excel/referenced_excel_config.go
  47. 10 10
      models/data_manage/mysteel_chemical_index.go
  48. 3 0
      models/data_manage/predict_edb_conf.go
  49. 74 0
      models/data_manage/request/clarksons_data.go
  50. 12 0
      models/data_manage/request/edb_info.go
  51. 9 7
      models/data_manage/request/predict_edb_info.go
  52. 35 0
      models/data_manage/response/clarksons_data.go
  53. 3 2
      models/data_manage/smm_data.go
  54. 9 8
      models/data_manage/trade_analysis/trade_analysis.go
  55. 18 0
      models/db.go
  56. 19 9
      models/document_manage_model/outside_report.go
  57. 7 2
      models/permission.go
  58. 8 0
      models/report_chapter.go
  59. 205 0
      models/residual_analysis_model/calculate_residual_analysis_config.go
  60. 50 0
      models/residual_analysis_model/calculate_residual_analysis_config_mapping.go
  61. 40 0
      models/residual_analysis_model/edb_data_residual_analysis.go
  62. 45 0
      models/yb/yb_poster_resource.go
  63. 558 0
      routers/commentsRouter.go
  64. 16 1
      routers/router.go
  65. 513 0
      services/ai_predict_model_classify.go
  66. 266 0
      services/ai_predict_model_index.go
  67. 2 2
      services/binlog/handler.go
  68. 6 0
      services/data/base_edb_lib.go
  69. 822 0
      services/data/base_from_clarksons.go
  70. 93 0
      services/data/base_from_rzd_classify_service.go
  71. 444 0
      services/data/base_from_rzd_index_service.go
  72. 49 0
      services/data/base_from_usda_fas.go
  73. 57 42
      services/data/chart_classify.go
  74. 6 5
      services/data/chart_info.go
  75. 3 0
      services/data/chart_info_excel_balance.go
  76. 7 0
      services/data/data_manage_permission/edb_permission.go
  77. 70 11
      services/data/edb_classify.go
  78. 7 0
      services/data/edb_info.go
  79. 19 12
      services/data/edb_info_relation.go
  80. 138 55
      services/data/predict_edb_info.go
  81. 7 2
      services/data/predict_edb_info_rule.go
  82. 4 4
      services/data/stl/stl.go
  83. 3 3
      services/data/trade_analysis/trade_analysis_data.go
  84. 9 5
      services/document_manage_service/document_manage_service.go
  85. 2 2
      services/edb_info_replace.go
  86. 23 3
      services/excel/lucky_sheet.go
  87. 46 43
      services/excel/lucky_sheet_table.go
  88. 3 3
      services/minio.go
  89. 6 1
      services/report.go
  90. 137 29
      services/report_v2.go
  91. 1048 0
      services/residual_analysis_service/residual_analysis_service.go
  92. 4 1
      static/ErrMsgConfig.json
  93. 36 1
      utils/common.go
  94. 10 4
      utils/constants.go
  95. 129 0
      utils/date_util.go

+ 507 - 0
controllers/data_manage/ai_predict_model/classify.go

@@ -0,0 +1,507 @@
+package ai_predict_model
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AiPredictModelClassifyController AI预测模型-分类
+type AiPredictModelClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 分类列表
+// @Description 分类列表
+// @Param   ParentId   query   bool   false   "父级ID"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /classify/list [get]
+func (this *AiPredictModelClassifyController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	parentId, _ := this.GetInt("ParentId")
+	resp := new(aiPredictModel.AiPredictModelClassifyListResp)
+
+	// (懒加载)仅查询直属分类
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := classifyOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", classifyOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp.AllNodes = append(resp.AllNodes, &aiPredictModel.AiPredictModelClassifyListItem{
+				NodeName:     v.ClassifyName,
+				ClassifyId:   v.AiPredictModelClassifyId,
+				ClassifyName: v.ClassifyName,
+				ParentId:     v.ParentId,
+				Level:        v.Level,
+				Sort:         v.Sort,
+				UniqueCode:   v.UniqueCode,
+			})
+		}
+	}
+
+	// 非顶级目录查询指标
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	if parentId > 0 {
+		parentClassify, e := classifyOb.GetItemById(parentId)
+		if e != nil {
+			br.Msg = "父级分类不存在, 请刷新页面"
+			return
+		}
+
+		cond := fmt.Sprintf(" AND %s = ?", indexOb.Cols().ClassifyId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := indexOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", indexOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取分类下指标失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp.AllNodes = append(resp.AllNodes, &aiPredictModel.AiPredictModelClassifyListItem{
+				NodeType:     1,
+				NodeName:     v.IndexName,
+				ClassifyId:   parentClassify.AiPredictModelClassifyId,
+				ClassifyName: parentClassify.ClassifyName,
+				IndexId:      v.AiPredictModelIndexId,
+				IndexCode:    v.IndexCode,
+				IndexName:    v.IndexName,
+				ParentId:     parentId,
+				Sort:         v.Sort,
+				UniqueCode:   v.IndexCode,
+			})
+		}
+	}
+	sort.Slice(resp.AllNodes, func(i, j int) bool {
+		return resp.AllNodes[i].Sort < resp.AllNodes[j].Sort
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body aiPredictModel.AiPredictModelClassifyAddReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /classify/add [post]
+func (this *AiPredictModelClassifyController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req aiPredictModel.AiPredictModelClassifyAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "请选择上级分类"
+		return
+	}
+	if req.Level > 5 {
+		br.Msg = "目前只支持6级目录"
+		return
+	}
+
+	// 校验分类名称
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		if this.Lang == utils.EnLangVersion {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyNameEn)
+		} else {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyName)
+		}
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ParentId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+
+	// 层级路径
+	var levelPath string
+	var rootId int
+	if req.ParentId > 0 {
+		parent, e := classifyOb.GetItemById(req.ParentId)
+		if e != nil {
+			br.Msg = "上级分类有误"
+			br.ErrMsg = fmt.Sprintf("获取上级分类失败, %v", e)
+			return
+		}
+		levelPath = parent.LevelPath
+		rootId = parent.RootId
+	}
+
+	sortMax, e := classifyOb.GetSortMax(req.ParentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类最大排序失败, %v", e)
+		return
+	}
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	classifyOb.ParentId = req.ParentId
+	classifyOb.ClassifyName = req.ClassifyName
+	classifyOb.ClassifyNameEn = req.ClassifyName
+	classifyOb.Level = req.Level + 1
+	classifyOb.Sort = sortMax + 1
+	classifyOb.SysUserId = sysUser.AdminId
+	classifyOb.SysUserRealName = sysUser.RealName
+	classifyOb.UniqueCode = utils.MD5(classifyOb.TableName() + "_" + timestamp)
+	classifyOb.CreateTime = time.Now().Local()
+	classifyOb.ModifyTime = time.Now().Local()
+	if e = classifyOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("新增分类失败, %v", e)
+		return
+	}
+	if req.ParentId > 0 {
+		// 用英文逗号拼接方便查询
+		classifyOb.LevelPath = fmt.Sprintf("%s,%d", levelPath, classifyOb.AiPredictModelClassifyId)
+		classifyOb.RootId = rootId
+	} else {
+		classifyOb.LevelPath = fmt.Sprint(classifyOb.AiPredictModelClassifyId)
+		classifyOb.RootId = classifyOb.AiPredictModelClassifyId
+	}
+	if e = classifyOb.Update([]string{classifyOb.Cols().LevelPath, classifyOb.Cols().RootId}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "操作成功"
+	br.Success = true
+}
+
+// Edit
+// @Title 修改分类
+// @Description 修改分类
+// @Param	request	body aiPredictModel.AiPredictModelClassifyEditReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /classify/edit [post]
+func (this *AiPredictModelClassifyController) 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 aiPredictModel.AiPredictModelClassifyEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	classifyItem, e := classifyOb.GetItemById(req.ClassifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+
+	// 校验分类名称
+	{
+		cond := fmt.Sprintf(" AND %s <> ?", classifyOb.Cols().PrimaryId)
+		if this.Lang == utils.EnLangVersion {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyNameEn)
+		} else {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyName)
+		}
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+	classifyItem.ClassifyName = req.ClassifyName
+	classifyItem.ClassifyNameEn = req.ClassifyName
+	classifyItem.ModifyTime = time.Now().Local()
+	updateCols := []string{classifyOb.Cols().ClassifyName, classifyOb.Cols().ClassifyNameEn, classifyOb.Cols().ModifyTime}
+	if e = classifyItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "操作成功"
+	br.Success = true
+}
+
+// RemoveCheck
+// @Title 删除校验
+// @Description 删除校验
+// @Param	request	body aiPredictModel.AiPredictModelClassifyRemoveReq true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /classify/remove_check [post]
+func (this *AiPredictModelClassifyController) RemoveCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req aiPredictModel.AiPredictModelClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 && req.IndexId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	var deleteStatus int
+	var tipsMsg string
+	// 删除分类
+	if req.ClassifyId > 0 && req.IndexId == 0 {
+		count, err := aiPredictModel.GetAiPredictModelIndexCountByClassifyId(req.ClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联图表不可删除"
+		}
+	}
+
+	if deleteStatus != 1 && req.IndexId == 0 {
+		classifyCount, err := aiPredictModel.GetAiPredictModelClassifyCountByClassifyId(req.ClassifyId)
+		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
+}
+
+// Remove
+// @Title 删除分类/标的
+// @Description 删除分类/标的
+// @Param	request	body aiPredictModel.AiPredictModelClassifyRemoveReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /classify/remove [post]
+func (this *AiPredictModelClassifyController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req aiPredictModel.AiPredictModelClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 && req.IndexId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	// 删除分类
+	if req.ClassifyId > 0 && req.IndexId == 0 {
+		count, err := aiPredictModel.GetAiPredictModelIndexCountByClassifyId(req.ClassifyId)
+		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 = aiPredictModel.RemoveAiPredictModelClassify(req.ClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	// 删除标的
+	if req.IndexId > 0 {
+		indexOb := new(aiPredictModel.AiPredictModelIndex)
+		_, e := indexOb.GetItemById(req.IndexId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Ret = 200
+				br.Msg = "删除成功"
+				br.Success = true
+				return
+			}
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取标的信息失败, %v", e)
+			return
+		}
+
+		// 删除标的及数据
+		if e = indexOb.RemoveIndexAndData(req.IndexId); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("删除标的及数据失败, %v", e)
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// Move
+// @Title 移动
+// @Description 移动
+// @Success 200 {object} aiPredictModel.AiPredictModelClassifyMoveReq
+// @router /classify/move [post]
+func (this *AiPredictModelClassifyController) Move() {
+	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 aiPredictModel.AiPredictModelClassifyMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId <= 0 && req.ItemId <= 0 {
+		br.Msg = "请选择分类或指标"
+		return
+	}
+
+	err, errMsg := services.AiPredictModelMoveClassify(req, sysUser)
+	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 = "移动成功"
+}

+ 940 - 0
controllers/data_manage/ai_predict_model/index.go

@@ -0,0 +1,940 @@
+package ai_predict_model
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndexController AI预测模型标的
+type AiPredictModelIndexController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 标的列表
+// @Description 标的列表
+// @Param   PageSize   query   int   true   "每页数据条数"
+// @Param   CurrentIndex   query   int   true   "当前页页码,从1开始"
+// @Param   ClassifyId   query   int   false   "分类id"
+// @Param   Keyword   query   string   false   "搜索关键词"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /index/list [get]
+func (this *AiPredictModelIndexController) 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
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	classifyId, _ := this.GetInt("ClassifyId")
+	keyword := this.GetString("KeyWord")
+	keyword = strings.TrimSpace(keyword)
+	resp := new(aiPredictModel.AiPredictModelIndexPageListResp)
+
+	// 分页
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	// 分类
+	classifyIdName := make(map[int]string)
+	{
+		classifyOb := new(aiPredictModel.AiPredictModelClassify)
+		list, e := classifyOb.GetItemsByCondition("", make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			classifyIdName[v.AiPredictModelClassifyId] = v.ClassifyName
+		}
+	}
+
+	// 筛选条件
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	var cond string
+	var pars []interface{}
+	{
+		if classifyId > 0 {
+			cond += fmt.Sprintf(" AND %s = ?", indexOb.Cols().ClassifyId)
+			pars = append(pars, classifyId)
+		}
+		if keyword != "" {
+			cond += fmt.Sprintf(" AND %s LIKE ?", indexOb.Cols().IndexName)
+			pars = append(pars, fmt.Sprint("%", keyword, "%"))
+		}
+	}
+
+	// 获取列表
+	total, e := indexOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的总数失败, %v", e)
+		return
+	}
+	list, e := indexOb.GetPageItemsByCondition(cond, pars, []string{}, "", startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取分页列表失败, %v", e)
+		return
+	}
+	pageList := make([]*aiPredictModel.AiPredictModelIndexItem, 0)
+	for _, v := range list {
+		t := v.Format2Item()
+		t.ClassifyName = classifyIdName[v.ClassifyId]
+		pageList = append(pageList, t)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp.Paging = page
+	resp.List = pageList
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Import
+// @Title 导入标的和数据
+// @Description 导入标的和数据
+// @Param   IndexFile   query   file   true   "标的文件"
+// @Success 200 Ret=200 录入成功
+// @router /index/import [post]
+func (this *AiPredictModelIndexController) Import() {
+	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
+	}
+
+	file, _, e := this.GetFile("IndexFile")
+	if e != nil {
+		br.Msg = "导入失败"
+		br.ErrMsg = fmt.Sprintf("获取文件失败, %v", e)
+		return
+	}
+	path := "./static/ai_predict_model_temp_" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	defer func() {
+		_ = file.Close()
+		_ = os.Remove(path)
+	}()
+	if e = this.SaveToFile("IndexFile", path); e != nil {
+		br.Msg = "导入失败"
+		br.ErrMsg = fmt.Sprintf("保存文件失败, %v", e)
+		return
+	}
+	xlFile, e := xlsx.OpenFile(path)
+	if e != nil {
+		br.Msg = "导入失败"
+		br.ErrMsg = fmt.Sprintf("打开excel文件失败, %v", e)
+		return
+	}
+
+	// 获取分类和用户,遍历时校验
+	classifyNameId := make(map[string]int)
+	adminNameId := make(map[string]int)
+	{
+		classifyOb := new(aiPredictModel.AiPredictModelClassify)
+		classifyCond := fmt.Sprintf(` AND %s = ?`, classifyOb.Cols().ParentId)
+		classifyPars := make([]interface{}, 0)
+		classifyPars = append(classifyPars, 0) // 只取一级分类(临时过渡方案,业务端只会加一级)
+		classifies, e := classifyOb.GetItemsByCondition(classifyCond, classifyPars, []string{}, "")
+		if e != nil {
+			br.Msg = "导入失败"
+			br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+			return
+		}
+		for _, v := range classifies {
+			classifyNameId[v.ClassifyName] = v.AiPredictModelClassifyId
+		}
+
+		admins, e := system.GetSysAdminList(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "导入失败"
+			br.ErrMsg = fmt.Sprintf("获取用户失败, %v", e)
+			return
+		}
+		for _, v := range admins {
+			adminNameId[v.RealName] = v.AdminId
+		}
+	}
+
+	// 遍历sheet页
+	// 列表页:预测标的|分类|模型框架|创建人|预测日期|预测值|预测频度|方向准确率|绝对偏差
+	type ImportDataColKey struct {
+		IndexName string
+		ColKey    int
+		DataDate  time.Time
+	}
+	imports := make(map[string]*aiPredictModel.AiPredictModelImportData)
+	importsData := make(map[string]map[time.Time]*aiPredictModel.AiPredictModelData)
+	importsDailyData := make(map[string]map[time.Time]*aiPredictModel.AiPredictModelData)
+	for sheetKey, sheet := range xlFile.Sheets {
+		maxRow := sheet.MaxRow
+
+		// 列表页
+		if sheetKey == 0 {
+			for i := 0; i < maxRow; i++ {
+				// 忽略首行标题
+				if i < 1 {
+					continue
+				}
+				row := sheet.Row(i)
+				cells := row.Cells
+				if len(cells) < 9 {
+					continue
+				}
+
+				// 标的名称
+				indexName := strings.TrimSpace(cells[0].String())
+				if indexName == "" {
+					continue
+				}
+				if imports[indexName] == nil {
+					imports[indexName] = new(aiPredictModel.AiPredictModelImportData)
+					imports[indexName].Index = new(aiPredictModel.AiPredictModelIndex)
+					imports[indexName].Data = make([]*aiPredictModel.AiPredictModelData, 0)
+				}
+				imports[indexName].Index.IndexName = indexName
+				imports[indexName].Index.CreateTime = time.Now()
+				imports[indexName].Index.ModifyTime = time.Now()
+
+				// 分类
+				classifyName := strings.TrimSpace(cells[1].String())
+				if classifyNameId[classifyName] <= 0 {
+					br.Msg = fmt.Sprintf("分类:%s不存在", classifyName)
+					return
+				}
+				imports[indexName].Index.ClassifyId = classifyNameId[classifyName]
+
+				// 创建人
+				adminName := strings.TrimSpace(cells[3].String())
+				if adminNameId[adminName] <= 0 {
+					br.Msg = fmt.Sprintf("创建人:%s不存在", adminName)
+					return
+				}
+				imports[indexName].Index.SysUserId = adminNameId[adminName]
+				imports[indexName].Index.SysUserRealName = adminName
+
+				// 其余信息
+				imports[indexName].Index.ModelFramework = strings.TrimSpace(cells[2].String())
+				strDate := strings.TrimSpace(cells[4].String())
+				predictDate, _ := time.Parse("2006/01/02", strDate)
+				if predictDate.IsZero() {
+					predictDate, _ = time.Parse("01-02-06", strDate)
+					if predictDate.IsZero() {
+						predictDate, _ = time.Parse("2006/1/2", strDate)
+					}
+				}
+				imports[indexName].Index.PredictDate = predictDate
+
+				strVal := strings.TrimSpace(cells[5].String())
+				if strVal == "" {
+					continue
+				}
+				predictVal, _ := strconv.ParseFloat(strVal, 64)
+				imports[indexName].Index.PredictValue = predictVal
+				imports[indexName].Index.PredictFrequency = strings.TrimSpace(cells[6].String())
+				imports[indexName].Index.DirectionAccuracy = strings.TrimSpace(cells[7].String())
+				imports[indexName].Index.AbsoluteDeviation = strings.TrimSpace(cells[8].String())
+			}
+		}
+
+		// 月度数据页
+		if sheetKey == 1 {
+			// 每五列为一个指标的数据
+			colKeys := make(map[int]*ImportDataColKey) // 每一列对应的指标名称以及对应的字段序号
+			for i := 0; i < maxRow; i++ {
+				// 首行为指标名称
+				if i == 0 {
+					nameCol := 0
+					row := sheet.Row(i)
+					for ck, cell := range row.Cells {
+						nameCol += 1
+						if nameCol > 5 {
+							nameCol = 1
+						}
+						if nameCol == 1 {
+							// nameCol=1时为指标/数据行则为日期
+							indexName := strings.TrimSpace(cell.String())
+							if indexName == "" {
+								continue
+							}
+							importsData[indexName] = make(map[time.Time]*aiPredictModel.AiPredictModelData)
+
+							colKeys[ck] = &ImportDataColKey{
+								ColKey:    1,
+								IndexName: indexName,
+							}
+
+							// 后面四列分别对应: 实际值|预测值|方向|偏差率, 这里直接加无须考虑是否会越界
+							colKeys[ck+1] = &ImportDataColKey{
+								ColKey:    2,
+								IndexName: indexName,
+							}
+							colKeys[ck+2] = &ImportDataColKey{
+								ColKey:    3,
+								IndexName: indexName,
+							}
+							colKeys[ck+3] = &ImportDataColKey{
+								ColKey:    4,
+								IndexName: indexName,
+							}
+							colKeys[ck+4] = &ImportDataColKey{
+								ColKey:    5,
+								IndexName: indexName,
+							}
+							continue
+						}
+					}
+					continue
+				}
+
+				// 第二行为标题,跳过
+				if i == 1 {
+					continue
+				}
+
+				// 剩余为数据行
+				row := sheet.Row(i)
+				for ck, cell := range row.Cells {
+					if colKeys[ck] == nil {
+						continue
+					}
+					if colKeys[ck].IndexName == "" {
+						continue
+					}
+					switch colKeys[ck].ColKey {
+					case 1:
+						// 日期列
+						strDate := strings.TrimSpace(cell.String())
+						dataDate, _ := time.Parse("2006/01/02", strDate)
+						if dataDate.IsZero() {
+							dataDate, _ = time.Parse("01-02-06", strDate)
+							if dataDate.IsZero() {
+								dataDate, _ = time.Parse("2006/1/2", strDate)
+								if dataDate.IsZero() {
+									continue
+								}
+							}
+						}
+						colKeys[ck].DataDate = dataDate
+						colKeys[ck+1].DataDate = dataDate
+						colKeys[ck+2].DataDate = dataDate
+						colKeys[ck+3].DataDate = dataDate
+						colKeys[ck+4].DataDate = dataDate
+						importRow := imports[colKeys[ck].IndexName]
+						if importRow == nil {
+							continue
+						}
+						// 新增当前日期数据
+						importsData[colKeys[ck].IndexName][dataDate] = new(aiPredictModel.AiPredictModelData)
+						importsData[colKeys[ck].IndexName][dataDate].DataTime = dataDate
+						importsData[colKeys[ck].IndexName][dataDate].CreateTime = time.Now()
+						importsData[colKeys[ck].IndexName][dataDate].ModifyTime = time.Now()
+						importsData[colKeys[ck].IndexName][dataDate].Source = aiPredictModel.ModelDataSourceMonthly
+					case 2, 3:
+						// 实际值和预测值, 可能为空
+						dataDate := colKeys[ck].DataDate
+						if importsData[colKeys[ck].IndexName][dataDate] == nil {
+							continue
+						}
+						strVal := strings.TrimSpace(cell.String())
+						if strVal == "" {
+							continue
+						}
+						val, _ := strconv.ParseFloat(strVal, 64)
+						if colKeys[ck].ColKey == 2 {
+							importsData[colKeys[ck].IndexName][dataDate].Value.Valid = true
+							importsData[colKeys[ck].IndexName][dataDate].Value.Float64 = val
+						} else {
+							importsData[colKeys[ck].IndexName][dataDate].PredictValue.Valid = true
+							importsData[colKeys[ck].IndexName][dataDate].PredictValue.Float64 = val
+						}
+					case 4, 5:
+						// 方向/偏差率
+						dataDate := colKeys[ck].DataDate
+						if importsData[colKeys[ck].IndexName][dataDate] == nil {
+							continue
+						}
+						str := strings.TrimSpace(cell.String())
+						if str == "" {
+							continue
+						}
+						if colKeys[ck].ColKey == 4 {
+							importsData[colKeys[ck].IndexName][dataDate].Direction = str
+						} else {
+							importsData[colKeys[ck].IndexName][dataDate].DeviationRate = str
+						}
+					default:
+						continue
+					}
+				}
+			}
+		}
+
+		// 日度数据页
+		if sheetKey == 2 {
+			// 每3列为一个指标的数据
+			colKeys := make(map[int]*ImportDataColKey) // 每一列对应的指标名称以及对应的字段序号
+			for i := 0; i < maxRow; i++ {
+				// 首行为指标名称
+				if i == 0 {
+					nameCol := 0
+					row := sheet.Row(i)
+					for ck, cell := range row.Cells {
+						nameCol += 1
+						if nameCol > 3 {
+							nameCol = 1
+						}
+						if nameCol == 1 {
+							// nameCol=1时为指标/数据行则为日期
+							indexName := strings.TrimSpace(cell.String())
+							if indexName == "" {
+								continue
+							}
+							importsDailyData[indexName] = make(map[time.Time]*aiPredictModel.AiPredictModelData)
+
+							colKeys[ck] = &ImportDataColKey{
+								ColKey:    1,
+								IndexName: indexName,
+							}
+
+							// 后面两列分别对应: 实际值|预测值, 这里直接加无须考虑是否会越界
+							colKeys[ck+1] = &ImportDataColKey{
+								ColKey:    2,
+								IndexName: indexName,
+							}
+							colKeys[ck+2] = &ImportDataColKey{
+								ColKey:    3,
+								IndexName: indexName,
+							}
+							continue
+						}
+					}
+					continue
+				}
+
+				// 第二行为标题,遇到"预测值"单元格,需要取出其中的值作为预测图例名称
+				if i == 1 {
+					row := sheet.Row(i)
+					for ck, cell := range row.Cells {
+						if colKeys[ck] == nil {
+							continue
+						}
+						if colKeys[ck].IndexName == "" {
+							continue
+						}
+						if colKeys[ck].ColKey != 3 {
+							continue
+						}
+						if imports[colKeys[ck].IndexName] != nil && imports[colKeys[ck].IndexName].Index != nil {
+							var extraConfig aiPredictModel.AiPredictModelIndexExtraConfig
+							extraConfig.DailyChart.PredictLegendName = strings.TrimSpace(cell.String())
+							b, _ := json.Marshal(extraConfig)
+							imports[colKeys[ck].IndexName].Index.ExtraConfig = string(b)
+						}
+					}
+					continue
+				}
+
+				// 剩余为数据行
+				row := sheet.Row(i)
+				for ck, cell := range row.Cells {
+					if colKeys[ck] == nil {
+						continue
+					}
+					if colKeys[ck].IndexName == "" {
+						continue
+					}
+					switch colKeys[ck].ColKey {
+					case 1:
+						// 日期列
+						strDate := strings.TrimSpace(cell.String())
+						dataDate, _ := time.Parse("2006/01/02", strDate)
+						if dataDate.IsZero() {
+							dataDate, _ = time.Parse("01-02-06", strDate)
+							if dataDate.IsZero() {
+								dataDate, _ = time.Parse("2006/1/2", strDate)
+								if dataDate.IsZero() {
+									continue
+								}
+							}
+						}
+						colKeys[ck].DataDate = dataDate
+						colKeys[ck+1].DataDate = dataDate
+						colKeys[ck+2].DataDate = dataDate
+						importRow := imports[colKeys[ck].IndexName]
+						if importRow == nil {
+							continue
+						}
+						// 新增当前日期数据
+						importsDailyData[colKeys[ck].IndexName][dataDate] = new(aiPredictModel.AiPredictModelData)
+						importsDailyData[colKeys[ck].IndexName][dataDate].DataTime = dataDate
+						importsDailyData[colKeys[ck].IndexName][dataDate].CreateTime = time.Now()
+						importsDailyData[colKeys[ck].IndexName][dataDate].ModifyTime = time.Now()
+						importsDailyData[colKeys[ck].IndexName][dataDate].Source = aiPredictModel.ModelDataSourceDaily
+					case 2, 3:
+						// 实际值和预测值, 可能为空
+						dataDate := colKeys[ck].DataDate
+						if importsDailyData[colKeys[ck].IndexName][dataDate] == nil {
+							continue
+						}
+						strVal := strings.TrimSpace(cell.String())
+						if strVal == "" {
+							continue
+						}
+						val, _ := strconv.ParseFloat(strVal, 64)
+						if colKeys[ck].ColKey == 2 {
+							importsDailyData[colKeys[ck].IndexName][dataDate].Value.Valid = true
+							importsDailyData[colKeys[ck].IndexName][dataDate].Value.Float64 = val
+						} else {
+							importsDailyData[colKeys[ck].IndexName][dataDate].PredictValue.Valid = true
+							importsDailyData[colKeys[ck].IndexName][dataDate].PredictValue.Float64 = val
+						}
+					default:
+						continue
+					}
+				}
+			}
+		}
+	}
+
+	for indexName, v := range importsData {
+		if imports[indexName] == nil {
+			continue
+		}
+		for _, dateData := range v {
+			imports[indexName].Data = append(imports[indexName].Data, dateData)
+		}
+	}
+	for indexName, v := range importsDailyData {
+		if imports[indexName] == nil {
+			continue
+		}
+		for _, dateData := range v {
+			imports[indexName].Data = append(imports[indexName].Data, dateData)
+		}
+	}
+	importIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+	for _, v := range imports {
+		importIndexes = append(importIndexes, v)
+	}
+
+	// 导入指标
+	if e = services.ImportAiPredictModelIndexAndData(importIndexes); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("导入指标数据失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 标的详情
+// @Description 标的详情
+// @Param   IndexId   query   int   true   "标的ID"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /index/detail [get]
+func (this *AiPredictModelIndexController) 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
+	}
+	indexId, _ := this.GetInt("IndexId")
+	if indexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", indexId)
+		return
+	}
+	resp := new(aiPredictModel.AiPredictModelDetailResp)
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+
+	// 获取标的数据
+	monthData, dailyData := make([]*aiPredictModel.AiPredictModelData, 0), make([]*aiPredictModel.AiPredictModelData, 0)
+	{
+		tableData := make([]*aiPredictModel.AiPredictModelDataItem, 0)
+		dataOb := new(aiPredictModel.AiPredictModelData)
+		dataCond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().IndexCode)
+		dataPars := make([]interface{}, 0)
+		dataPars = append(dataPars, indexItem.IndexCode)
+		list, e := dataOb.GetItemsByCondition(dataCond, dataPars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取标的数据失败, %v", e)
+			return
+		}
+
+		// tableData取月度数据,最多显示10条
+		count, limit := 0, 10
+		for _, v := range list {
+			// 日度数据
+			if v.Source == aiPredictModel.ModelDataSourceDaily {
+				dailyData = append(dailyData, v)
+				continue
+			}
+
+			// 月度数据
+			if count < limit {
+				tableData = append(tableData, v.Format2Item())
+				count += 1
+			}
+			monthData = append(monthData, v)
+		}
+		resp.TableData = tableData
+	}
+
+	// 月度图表
+	if len(monthData) > 0 {
+		chartDetail, e := services.GetAiPredictChartDetailByData(indexItem, monthData, aiPredictModel.ModelDataSourceMonthly)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取月度图表失败, %v", e)
+			return
+		}
+		resp.ChartView = chartDetail
+	}
+
+	// 日度图表
+	if len(dailyData) > 0 {
+		dailyChartDetail, e := services.GetAiPredictChartDetailByData(indexItem, dailyData, aiPredictModel.ModelDataSourceDaily)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取日度图表失败, %v", e)
+			return
+		}
+		resp.DailyChartView = dailyChartDetail
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Save
+// @Title 保存标的
+// @Description 保存标的
+// @Param	request	body aiPredictModel.AiPredictModelIndexSaveReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /index/save [post]
+func (this *AiPredictModelIndexController) Save() {
+	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 aiPredictModel.AiPredictModelIndexSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.IndexId < 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("标的ID有误, IndexId: %d", req.IndexId)
+		return
+	}
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(req.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+
+	var extraConfig aiPredictModel.AiPredictModelIndexExtraConfig
+	if indexItem.ExtraConfig != "" {
+		if e = json.Unmarshal([]byte(indexItem.ExtraConfig), &extraConfig); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("标的配置解析失败, %v", e)
+			return
+		}
+	}
+	if req.MonthlyChart != nil {
+		extraConfig.MonthlyChart.LeftMin = req.MonthlyChart.LeftMin
+		extraConfig.MonthlyChart.LeftMax = req.MonthlyChart.LeftMax
+		extraConfig.MonthlyChart.Unit = req.MonthlyChart.Unit
+	}
+	if req.DailyChart != nil {
+		extraConfig.DailyChart.LeftMin = req.DailyChart.LeftMin
+		extraConfig.DailyChart.LeftMax = req.DailyChart.LeftMax
+		extraConfig.DailyChart.Unit = req.DailyChart.Unit
+	}
+
+	configByte, _ := json.Marshal(extraConfig)
+	indexItem.ExtraConfig = string(configByte)
+	indexItem.ModifyTime = time.Now()
+	updateCols := []string{indexOb.Cols().ExtraConfig, indexOb.Cols().ModifyTime}
+	if e = indexItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("保存标的失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "操作成功"
+	br.Success = true
+}
+
+// DashboardSave
+// @Title 保存看板
+// @Description 保存看板
+// @Param	request	body aiPredictModel.AiPredictModelDashboardSaveReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /index/dashboard/save [post]
+func (this *AiPredictModelIndexController) DashboardSave() {
+	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 aiPredictModel.AiPredictModelDashboardSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, %d", req.IndexId)
+		return
+	}
+	req.DashboardName = strings.TrimSpace(req.DashboardName)
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	_, e := indexOb.GetItemById(req.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+
+	// 获取看板
+	var isUpdate bool
+	var updateCols []string
+	dashboardItem := new(aiPredictModel.AiPredictModelDashboard)
+	if req.IndexId > 0 {
+		cond := fmt.Sprintf(" AND %s = ?", dashboardItem.Cols().AiPredictModelIndexId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.IndexId)
+		item, e := dashboardItem.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取标的看板失败, %v", e)
+			return
+		}
+		if item != nil {
+			isUpdate = true
+			dashboardItem = item
+			dashboardItem.DashboardName = req.DashboardName
+			dashboardItem.ModifyTime = time.Now()
+			updateCols = append(updateCols, dashboardItem.Cols().DashboardName, dashboardItem.Cols().ModifyTime)
+		}
+	}
+	if !isUpdate {
+		dashboardItem.AiPredictModelIndexId = req.IndexId
+		dashboardItem.DashboardName = req.DashboardName
+		dashboardItem.SysUserId = sysUser.AdminId
+		dashboardItem.SysUserRealName = sysUser.RealName
+		dashboardItem.CreateTime = time.Now()
+		dashboardItem.ModifyTime = time.Now()
+	}
+
+	// 详情
+	dashboardDetails := make([]*aiPredictModel.AiPredictModelDashboardDetail, 0)
+	for i, v := range req.List {
+		t := &aiPredictModel.AiPredictModelDashboardDetail{
+			Type:       v.Type,
+			UniqueCode: v.UniqueCode,
+			Sort:       i + 1,
+			CreateTime: time.Now(),
+			ModifyTime: time.Now(),
+		}
+		dashboardDetails = append(dashboardDetails, t)
+	}
+
+	// 保存
+	if e := dashboardItem.SaveIndexDashboard(dashboardItem, dashboardDetails, isUpdate, updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("保存标的看板失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// DashboardDetail
+// @Title 看板详情
+// @Description 看板详情
+// @Param   IndexId   query   int   true   "标的ID"
+// @Success 200 {object} aiPredictModel.AiPredictModelDashboardDetailResp
+// @router /index/dashboard/detail [get]
+func (this *AiPredictModelIndexController) DashboardDetail() {
+	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, _ := this.GetInt("IndexId")
+	resp := new(aiPredictModel.AiPredictModelDashboardDetailResp)
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+	resp.CreateUserId = indexItem.SysUserId
+	resp.CreateUserRealName = indexItem.SysUserRealName
+
+	// 获取标的看板
+	dashboardOb := new(aiPredictModel.AiPredictModelDashboard)
+	dashboardItem := new(aiPredictModel.AiPredictModelDashboardItem)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", dashboardOb.Cols().AiPredictModelIndexId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, indexId)
+		item, e := dashboardOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取标的看板失败, %v", e)
+			return
+		}
+		if item != nil {
+			dashboardItem = item.Format2Item()
+		}
+	}
+
+	// 获取看板详情
+	dashboardDetails := make([]*aiPredictModel.AiPredictModelDashboardDetailItem, 0)
+	if dashboardItem.DashboardId > 0 {
+		detailOb := new(aiPredictModel.AiPredictModelDashboardDetail)
+		cond := fmt.Sprintf(" AND %s = ?", detailOb.Cols().AiPredictModelDashboardId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, dashboardItem.DashboardId)
+		list, e := detailOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", detailOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取看板详情失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			dashboardDetails = append(dashboardDetails, v.Format2Item())
+		}
+	}
+
+	resp.AiPredictModelDashboardItem = dashboardItem
+	resp.List = dashboardDetails
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 708 - 0
controllers/data_manage/base_from_rzd_index_controller.go

@@ -0,0 +1,708 @@
+// 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"
+)
+
+// BaseFromRzdIndexController 睿姿得数据控制器
+type BaseFromRzdIndexController struct {
+	controllers.BaseAuthController
+}
+
+// RzdClassify
+// @Title 睿姿得数据分类
+// @Description 汾渭数据分类接口
+// @Success 200 {object} data_manage.BaseFromRzdClassifyResponse
+// @router /rzd/classify [get]
+func (this *BaseFromRzdIndexController) RzdClassify() {
+	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
+	}
+
+	classifies, e := data.RzdClassifyList()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取睿姿得数据分类失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = classifies
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// RzdIndexData
+// @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
+// @router /rzd/index/data [get]
+func (this *BaseFromRzdIndexController) RzdIndexData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+	frequency := this.GetString("Frequency")
+
+	resultList, err := data.RzdIndexData(classifyId, frequency, currentIndex, startSize, pageSize)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resultList
+}
+
+// RzdIndexDetail
+// @Title 获取睿姿得数据指标详情
+// @Description 获取睿姿得数据指标详情
+// @Param   IndexCode   query   string  true       "查询参数 指标id"
+// @Success 200
+// @router /rzd/index/detail [get]
+func (this *BaseFromRzdIndexController) RzdIndexDetail() {
+	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
+	}
+
+	indexCode := this.GetString("IndexCode")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	indexDetail, err := data.GetRzdIndexDetail(indexCode, currentIndex, startSize, pageSize)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexDetail
+}
+
+// RzdIndexList
+// @Title 获取睿姿得数据指标列表
+// @Description 获取睿姿得数据指标详情
+// @Param   searchParams   query   string  true       "查询参数 指标id/指标名称"
+// @Success 200
+// @router /rzd/index/list [get]
+func (this *BaseFromRzdIndexController) RzdIndexList() {
+	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
+	}
+
+	searchParams := this.GetString("SearchParams")
+
+	indexList, err := data.GetRzdIndexList(searchParams)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexList
+}
+
+// GetRzdFrequencyList
+// @Title 查询频率列表
+// @Description 查询频率列表
+// @Param   classifyId   query  int  false   "指标唯一编码"
+// @Success 200 {object} []string
+// @router /rzd/frequency/list [get]
+func (this *BaseFromRzdIndexController) GetRzdFrequencyList() {
+	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")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+	frequencyList, err := data.GetRzdIndexFrequency(classifyId)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = frequencyList
+}
+
+// RzdIndexAddValidate
+// @Title 新增加入到指标库校验
+// @Description 新增加入到指标库校验
+// @Param   req    body   data_manage.BaseFromFenWeiIndexBatchAddCheckReq     true        "请求参数"
+// @Success 200 {object} []data_manage.IndexCheckData
+// @router /rzd/index/add/validate [post]
+func (this *BaseFromRzdIndexController) RzdIndexAddValidate() {
+	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.BaseFromRzdIndexBatchAddCheckReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	// 校验指标编码是否存在
+	addValidate, err := data.RzdIndexAddValidate(req)
+	if err != nil {
+		br.Ret = 403
+		br.Success = false
+		br.Msg = err.Error()
+		return
+	}
+	br.Data = addValidate
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// RzdIndexAdd
+// @Title 指标添加到指标库
+// @Description 指标添加到指标库
+// @Param   req    body   []data_manage.AddEdbInfoReq     true        "请求参数"
+// @Success 200 string "操作成功"
+// @router /rzd/index/add [post]
+func (this *BaseFromRzdIndexController) RzdIndexAdd() {
+	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_RZD_" + 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 = "批量添加指标数量不得超过" + strconv.Itoa(30) + "个"
+		return
+	}
+
+	indexNames := make([]string, 0)
+	resp := make([]*data_manage.RzdNameCheckResult, 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.RzdNameCheckResult{
+			IndexCode: index.EdbCode,
+			IndexName: index.EdbName,
+			Exist:     false,
+		})
+	}
+
+	// 指标名称重复校验
+	nameCheck, err := data.RzdIndexNameCheck(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 rzdIndexAddReq data_manage.RzdIndexAddReq
+		rzdIndexAddReq.EdbCode = v.EdbCode
+		rzdIndexAddReq.EdbName = v.EdbName
+		rzdIndexAddReq.Frequency = v.Frequency
+		rzdIndexAddReq.Unit = v.Unit
+		rzdIndexAddReq.ClassifyId = v.ClassifyId
+		rzdIndexAddReq.AdminId = sysUser.AdminId
+		rzdIndexAddReq.AdminRealName = sysUser.RealName
+
+		// 新增指标到指标库
+		edbInfo, e, errMsg, skip := data.RzdIndexAdd(rzdIndexAddReq, 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
+}
+
+// RzdIndexDataExport
+// @Title 导出指标数据
+// @Description 导出指标数据
+// @Param  IndexCode     query   string     false        "指标编码"
+// @Param  ClassifyId     query   int     false        "分类ID"
+// @Success 200 string "操作成功"
+// @router /rzd/index/data/export [get]
+func (this *BaseFromRzdIndexController) RzdIndexDataExport() {
+	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
+	}
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+
+	downLoadFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	classifyIdList, err := data.RzdClassifyRecursiveQuery(classifyId)
+	if err != nil {
+		br.Msg = "查询分类失败"
+		br.ErrMsg = "查询分类失败"
+		return
+	}
+	classifyIdList = append(classifyIdList, classifyId)
+
+	frequencies, err := data_manage.GetRzdIndexFrequency(classifyIdList)
+	if err != nil {
+		br.Msg = "查询频度失败"
+		br.ErrMsg = "查询频度失败"
+		return
+	}
+
+	fileName := `睿姿得数据`
+	if classifyId > 0 && indexCode == "" {
+		fenWeiClassify, err := data_manage.GetRzdClassifyItemByClassifyId(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.GetRzdIndexByCodeAndClassify(indexCode, classifyIdList, 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("指标名称/Metric Nome")
+		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.BaseFromRzdData)
+		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.FormatDateString(v.ModifyTime)
+			rowModifyCell.SetValue(updateTimeStr)
+			tradeCodeList = append(tradeCodeList, v.IndexCode)
+
+			var dataList []*data_manage.BaseFromRzdData
+			dataList, err = data_manage.GetBaseFormRzdDataByIndexCode(v.IndexCode)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.ErrMsg = "GetBaseFormRzdDataByIndexCode,Err:" + err.Error()
+				br.Msg = "获取数据失败"
+				return
+			}
+			for _, item := range dataList {
+				if dataMap[item.IndexCode] == nil {
+					dataMap[item.IndexCode] = make(map[string]*data_manage.BaseFromRzdData)
+				}
+				dataMap[item.IndexCode][item.DataTime] = item
+			}
+		}
+
+		tradeCodeStr := strings.Join(tradeCodeList, "','")
+		tradeCodeStr = "'" + tradeCodeStr + "'"
+		dataTimeList, err := data_manage.GetRzdDataListByIndexCodes(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"
+}
+
+// GetRzdIndexInfo
+// @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.BaseFromRzdIndexPage
+// @router /rzd/get/index/info [get]
+func (this *BaseFromRzdIndexController) GetRzdIndexInfo() {
+	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
+	}
+
+	keyWord := this.GetString("KeyWord")
+	classifyIds := this.GetString("ClassifyIds")
+	frequencies := this.GetString("Frequencies")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var classifyIdList []string
+	var frequencyList []string
+	if classifyIds != "" {
+		classifyIdList = strings.Split(classifyIds, ",")
+	}
+	if frequencies != "" {
+		frequencyList = strings.Split(frequencies, ",")
+	}
+	indexInfoPage, err := data.GetRzdIndexInfo(keyWord, classifyIdList, frequencyList, currentIndex, startSize, pageSize)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexInfoPage
+}

+ 27 - 2
controllers/data_manage/chart_info.go

@@ -1058,6 +1058,10 @@ func (this *ChartInfoController) ChartInfoDetail() {
 			}
 		}
 	}
+	if chartType == utils.CHART_TYPE_SEASON && dateType == utils.DateTypeNYears {
+		// 季度图,的至今N年,需要特殊处理,将日期范围扩大到下一年
+		dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
 	// 开始/结束日期
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, dateMax)
 
@@ -1327,6 +1331,11 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 		mappingList[k] = v
 	}
 
+	if req.ChartType == utils.CHART_TYPE_SEASON && req.DateType == utils.DateTypeNYears {
+		// 季节性图表,要特殊处理起始日期, 最近N年
+		dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
+
 	// 开始/结束日期
 	startDate, endDate := utils.GetDateByDateTypeV2(req.DateType, req.StartDate, req.EndDate, req.StartYear, dateMax)
 	if startDate == "" {
@@ -1597,7 +1606,7 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 	var dateMax time.Time
 	if dateType == utils.DateTypeNYears {
 		for _, v := range mappingList {
-			if v.LatestDate != "" {
+			if v.LatestDate != "" && v.LatestDate != "0000-00-00" {
 				lastDateT, tErr := time.Parse(utils.FormatDate, v.LatestDate)
 				if tErr != nil {
 					br.Msg = "获取失败"
@@ -1610,6 +1619,9 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 			}
 		}
 	}
+	if dateType == utils.DateTypeNYears && chartInfo.ChartType == utils.CHART_TYPE_SEASON {
+		dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, dateMax)
 
 	if chartInfo.ChartType == 2 {
@@ -2750,6 +2762,10 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 			}
 		}
 	}
+	if chartInfo.ChartType == utils.CHART_TYPE_SEASON && dateType == utils.DateTypeNYears {
+		// 季度图,的至今N年,需要特殊处理,将日期范围扩大到下一年
+		dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, dateMax)
 
 	extraConfigStr := chartInfo.ExtraConfig //图表额外数据参数
@@ -4180,6 +4196,10 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 				}
 			}
 		}
+		if chartType == utils.CHART_TYPE_SEASON && dateType == utils.DateTypeNYears {
+			// 季节性图,最近N年需要特殊处理
+			dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+		}
 		// 开始/结束日期
 		startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, dateMax)
 
@@ -4657,7 +4677,12 @@ func (this *ChartInfoController) ChartList() {
 		}
 	}
 	if keyWord != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyWord + `%' OR chart_name_en LIKE '%` + keyWord + `%' )`
+		keyWordArr := strings.Split(keyWord, " ")
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				condition += ` AND CONCAT(chart_name,chart_name_en) LIKE '%` + v + `%'`
+			}
+		}
 	}
 	if sysUserIds != "" {
 		adminIds := strings.Split(sysUserIds, ",")

+ 3 - 0
controllers/data_manage/chart_theme.go

@@ -247,6 +247,9 @@ func (c *ChartThemeController) GetThemePreviewData() {
 			}
 		}
 	}
+	if chartType == utils.CHART_TYPE_SEASON && dateType == utils.DateTypeNYears {
+		dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
 	// 开始/结束日期
 	startDate, endDate := utils.GetDateByDateTypeV2(dateType, tmpStartDate, tmpEndDate, startYear, dateMax)
 

+ 1653 - 0
controllers/data_manage/clarksons_data.go

@@ -0,0 +1,1653 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/request"
+	"eta/eta_api/models/data_manage/response"
+	"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"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/tealeg/xlsx"
+)
+
+type ClarksonsDataController struct {
+	controllers.BaseAuthController
+}
+
+// @Title 克拉克森数据分类
+// @Description 克拉克森数据分类接口
+// @Success 200 {object} data_manage.SciClassify
+// @router /clarksons/classify [get]
+func (this *ClarksonsDataController) Classify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	classifyList, err := data_manage.GetClarksonsClassifyAll()
+	if err != nil {
+		br.Msg = "查询失败"
+		br.ErrMsg = "查询失败, Err:" + err.Error()
+		return
+	}
+
+	initClassify := &data_manage.BaseFromClarksonsClassifyItem{
+		BaseFromClassifyId: 0,
+		ClassifyName:       "未分类",
+		ClassifyNameEn:     "Unclassified",
+		UniqueCode:         "0",
+		ParentId:           0,
+		Level:              1,
+		Sort:               0,
+		Children:           nil,
+	}
+	finalList := make([]*data_manage.BaseFromClarksonsClassifyItem, 0)
+	classifyTree := getClarksonsClassifyTree(classifyList, 0)
+	finalList = append(finalList, initClassify)
+	finalList = append(finalList, classifyTree...)
+
+	br.Msg = "查询成功"
+	br.Data = finalList
+	br.Success = true
+	br.Ret = 200
+}
+
+// AddSciClassify
+// @Title 新增分类
+// @Description 新增分类接口
+// @Param	request	body data_manage.AddBaseFromSciClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /clarksons/classify/add [post]
+func (this *ClarksonsDataController) AddClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.AddBaseFromClarksonsClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "操作异常"
+		return
+	}
+	ok, msg, err := data.AddClarksonsClassify(req.ClassifyName, req.ParentId)
+	if err != nil {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+	if !ok {
+		if msg != "" {
+			br.Msg = msg
+			return
+		}
+		br.Msg = "添加失败"
+		return
+	}
+	br.Msg = "添加成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// DelClassify
+// @Title 新增分类
+// @Description 新增分类接口
+// @Param	request	body data_manage.AddBaseFromSciClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /clarksons/classify/del [post]
+func (this *ClarksonsDataController) DelClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.DelBaseFromClarksonsClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.BaseFromClassifyId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	err = data.DelClarksonsClassify(req.BaseFromClassifyId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// EditClassify
+// @Title 修改分类
+// @Description 修改分类接口
+// @Param	request	body data_manage.EditBaseFromMysteelChemicalClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /clarksons/classify/edit [post]
+func (this *ClarksonsDataController) EditClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.EditBaseFromClarksonsClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.BaseFromClassifyId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	classify, err := data_manage.GetClarksonsClassifyById(req.BaseFromClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在"
+			return
+		}
+		br.Msg = "编辑失败"
+		br.ErrMsg = "获取分类失败,Err:" + err.Error()
+		return
+	}
+
+	if classify.ClassifyName != req.ClassifyName {
+		count, err := data_manage.GetClarksonsClassifyCountByName(req.ClassifyName)
+		if err != nil {
+			br.Msg = "编辑失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+		classify.ClassifyName = req.ClassifyName
+		classify.ModifyTime = time.Now()
+		err = classify.Update([]string{"classify_name", "modify_time"})
+		if err != nil {
+			br.Msg = "编辑失败"
+			br.ErrMsg = "编辑失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	br.Msg = "编辑成功"
+	br.Success = true
+	br.Ret = 200
+	br.IsAddLog = true
+}
+
+// MoveClassify
+// @Title 分类移动接口
+// @Description 分类移动接口
+// @Success 200 {object} data_manage.MoveBaseFromMysteelChemicalClassifyReq
+// @router /clarksons/classify/move [post]
+func (this *ClarksonsDataController) MoveClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.MoveBaseFromClarksonsClassifyReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.BaseFromClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "分类id小于等于0"
+		br.IsSendEmail = false
+		return
+	}
+
+	err, errMsg := data.MoveClarksonsClassify(req.BaseFromClassifyId, req.ParentClassifyId, req.PrevClassifyId, req.NextClassifyId)
+	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.IsAddLog = true
+	br.Msg = "移动成功"
+}
+
+// IndexList
+// @Title 克拉克森指标列表
+// @Description 克拉克森数据指标列表接口
+// @Param   BaseFromClassifyId   query   int  true       "分类id"
+// @Success 200 {object} data_manage.BaseFromMysteelChemicalIndexResp
+// @router /clarksons/index/list [get]
+func (this *ClarksonsDataController) IndexList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	classifyId, _ := this.GetInt("BaseFromClassifyId", 0)
+	indexList, err := data_manage.GetClarksonsIndexBaseInfoByClassifyId(classifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexList
+}
+
+// GetClarksonsIndexInfo
+// @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.BaseFromRzdIndexPage
+// @router /clarksons/get/index/info [post]
+func (this *ClarksonsDataController) GetClarksonsIndexInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.ClarksonsDataBatchListReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	keyWord := req.KeyWord
+	classifyIds := req.ClassifyIds
+	frequencies := req.Frequencies
+
+	pageSize := req.PageSize
+	currentIndex := req.CurrentIndex
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var classifyIdList []string
+	var frequencyList []string
+	if classifyIds != "" {
+		classifyIdList = strings.Split(classifyIds, ",")
+	}
+	if frequencies != "" {
+		frequencyList = strings.Split(frequencies, ",")
+	}
+	indexInfoPage, err := data.GetClarksonsIndexInfo(keyWord, classifyIdList, frequencyList, currentIndex, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexInfoPage
+}
+
+// SearchList
+// @Title 克拉克森模糊搜索
+// @Description 克拉克森模糊搜索
+// @Param   Keyword   query   string  true       "关键字搜索"
+// @Success 200 {object} models.BaseResponse
+// @router /clarksons/search_list [get]
+func (this *ClarksonsDataController) SearchList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	//关键字
+	keyword := this.GetString("Keyword")
+	var condition string
+	var pars []interface{}
+	if keyword != "" {
+		condition += ` AND (index_code LIKE ? OR index_name LIKE ?)`
+		pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+	} else {
+		br.Msg = "请输入指标ID/指标名称"
+		return
+	}
+
+	list, err := data_manage.GetClarksonsIndexBaseInfoByCondition(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// SingleData
+// @Title 获取克拉克森据
+// @Description 获取克拉克森单条数据接口
+// @Param   BaseFromClarksonsIndexId   query   string  true       "指标唯一编码"
+// @Success 200 {object} models.BaseResponse
+// @router /clarksons/single_data [get]
+func (this *ClarksonsDataController) SingleData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	baseFromClarksonsIndexId, _ := this.GetInt("BaseFromClarksonsIndexId")
+	indexInfo, err := data_manage.GetClarksonsIndexByIndexId(baseFromClarksonsIndexId)
+	if err != nil {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_CLARKSONS, indexInfo.IndexCode)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取指标数据失败"
+		br.ErrMsg = "获取指标库数据失败,Err:" + err.Error()
+		return
+	}
+	var edbInfoId int
+	if edbInfo != nil {
+		edbInfoId = edbInfo.EdbInfoId
+	}
+	dataList, err := data_manage.GetClarksonsDataByIndexId(baseFromClarksonsIndexId)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	var ret response.ClarksonsSingleDataResp
+	ret.ClassifyId = indexInfo.ClassifyId
+	ret.BaseFromClarksonsIndexId = indexInfo.BaseFromClarksonsIndexId
+	ret.IndexCode = indexInfo.IndexCode
+	ret.EdbInfoId = edbInfoId
+	ret.IndexName = indexInfo.IndexName
+	ret.Frequency = indexInfo.Frequency
+	ret.CreateTime = indexInfo.CreateTime.Format(utils.FormatDateTime)
+	ret.ModifyTime = indexInfo.ModifyTime.Format(utils.FormatDateTime)
+	ret.Unit = indexInfo.Unit
+	ret.Data = dataList
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// MoveClarksonsData
+// @Title 克拉克森指标移动接口
+// @Description 克拉克森指标移动接口
+// @Success 200 {object} request.MoveBaseFromClarksonsReq
+// @router /clarksons/move [post]
+func (this *ClarksonsDataController) MoveClarksonsData() {
+	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 request.MoveBaseFromClarksonsReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.BaseFromClarksonsIndexId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "指标id小于等于0"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.BaseFromClassifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		br.IsSendEmail = false
+		return
+	}
+
+	err, errMsg := data.MoveClarksonsData(req.BaseFromClarksonsIndexId, req.BaseFromClassifyId, req.PrevBaseFromClarksonsIndexId, req.NextBaseFromClarksonsIndexId)
+	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.IsAddLog = true
+	br.Msg = "移动成功"
+}
+
+// ResetClarksonsIndex
+// @Title 指标数据清除分类
+// @Description 指标数据清除分类
+// @Param	request	body data_manage.DelBaseFromSciReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /clarksons/reset [post]
+//func (this *ClarksonsDataController) ResetClarksonsIndex() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	var req request.ResetBaseFromClarksonsReq
+//	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+//	if err != nil {
+//		br.Msg = "参数解析异常!"
+//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	if req.BaseFromClarksonsIndexId < 0 {
+//		br.Msg = "参数错误"
+//		br.IsSendEmail = false
+//		return
+//	}
+//
+//	err = data.ResetClarksonsIndex(req.BaseFromClarksonsIndexId)
+//	if err != nil {
+//		br.Msg = "移动失败"
+//		br.ErrMsg = "移动失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	br.Ret = 200
+//	br.Msg = "操作成功"
+//	br.Success = true
+//	br.IsAddLog = true
+//}
+
+// AddEdbInfo
+// @Title 新增指标接口
+// @Description 新增指标接口
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /clarksons/edb_info/add [post]
+//func (this *ClarksonsDataController) AddEdbInfo() {
+//	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
+//	}
+//
+//	_, err = data_manage.GetClarksonsIndexByIndexCode(req.EdbCode)
+//	if err != nil {
+//		if err.Error() == utils.ErrNoRow() {
+//			br.Msg = "指标不存在"
+//			return
+//		}
+//		br.Msg = "获取失败"
+//		br.ErrMsg = "获取失败,Err:" + err.Error()
+//		return
+//	}
+//	source := utils.DATA_SOURCE_CLARKSONS
+//	// 指标入库
+//	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
+//	br.Ret = 200
+//	br.Success = true
+//	br.Msg = "保存成功"
+//	br.Data = resp
+//	br.IsAddLog = true
+//}
+
+// AddCheck
+// @Title 新增校验
+// @Description 新增校验
+// @Param	request	body request.BusinessDataBatchAddCheckReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /clarksons/edb_info/add_check [post]
+func (this *ClarksonsDataController) AddCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req *request.ClarksonsDataBatchAddCheckReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	codeMax := 30
+	codeLen := len(req.IndexCodes)
+	if len(req.IndexCodes) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+	// 获取指标库已有指标
+	existsEdb, e := data_manage.GetEdbCodesBySource(utils.DATA_SOURCE_CLARKSONS)
+	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(codeLen))
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.IndexCodes)
+	list, err := data_manage.GetClarksonsIndexAndEdbInfoByCondition(cond, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取克拉克森原始指标列表失败, Err: " + err.Error()
+		return
+	}
+
+	resp := make([]*data_manage.BaseFromClarksonsIndexView, 0)
+	for _, v := range list {
+		if v.EdbInfoId > 0 {
+			v.EdbExist = 1
+		}
+		resp = append(resp, v)
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// NameCheck
+// @Title 重名校验
+// @Description 批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /clarksons/edb_info/name_check [post]
+func (c *ClarksonsDataController) NameCheck() {
+	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 err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.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_CLARKSONS, 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_CLARKSONS, 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
+}
+
+// BatchAdd
+// @Title 批量新增
+// @Description 批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /clarksons/edb_info/batch_add [post]
+func (this *ClarksonsDataController) BatchAdd() {
+	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_CLARKSONS_" + 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.ClarksonsIndexSource2EdbReq
+		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, errMsg, skip, e := data.ClarksonsIndexSource2Edb(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)
+		}
+
+		// 更新es
+		go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// BatchDelete
+// @Title 批量删除
+// @Description 批量删除
+// @Param	request	body []*request.DelBaseFromClarksonsReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /clarksons/edb_info/batch_delete [post]
+func (this *ClarksonsDataController) BatchDelete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req []*request.DelBaseFromClarksonsReq
+	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
+	}
+	var deleteIds []int
+	for _, v := range req {
+		if v.BaseFromClarksonsIndexId <= 0 {
+			continue
+		}
+		deleteIds = append(deleteIds, v.BaseFromClarksonsIndexId)
+	}
+	existList, err := data.BatchDelClarksonsData(deleteIds)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = existList
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// BatchEdit
+// @Title 批量编辑
+// @Description 批量编辑
+// @Param	request	body []*request.DelBaseFromClarksonsReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /clarksons/edb_info/batch_edit [post]
+func (this *ClarksonsDataController) BatchEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req []*request.EditBaseFromClarksonsReq
+	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
+	}
+
+	indexIds := make([]int, 0)
+	classifyIds := make([]int, 0)
+	indexIdToclassifyId := make(map[int]int)
+	for _, v := range req {
+		if v.BaseFromClarksonsIndexId <= 0 {
+			continue
+		}
+		if v.BaseFromClassifyId <= 0 {
+			continue
+		}
+		indexIds = append(indexIds, v.BaseFromClarksonsIndexId)
+		classifyIds = append(classifyIds, v.BaseFromClassifyId)
+		indexIdToclassifyId[v.BaseFromClarksonsIndexId] = v.BaseFromClassifyId
+	}
+	if len(indexIds) == 0 {
+		br.Msg = "请选择指标或指定正确的分类"
+		return
+	}
+	indexList, err := data_manage.GetClarksonsIndexListByIndexIds(indexIds)
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "获取指标失败,Err:" + err.Error()
+		return
+	}
+	classifySort, err := data_manage.GetClarksonsClassifyMaxSortByClassifyIds(classifyIds)
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "获取分类最大排序失败,Err:" + err.Error()
+		return
+	}
+	classifySortMap := make(map[int]int)
+	for _, v := range classifySort {
+		classifySortMap[v.BaseFromClassifyId] = v.MaxSort
+	}
+	classifyList, err := data_manage.GetClarksonsClassifyListByIds(classifyIds)
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "获取分类失败,Err:" + err.Error()
+		return
+	}
+	existClassifyList := make(map[int]struct{})
+	for _, v := range classifyList {
+		existClassifyList[v.BaseFromClassifyId] = struct{}{}
+	}
+	//编辑指标
+	updateIndexList := make([]*data_manage.BaseFromClarksonsIndex, 0)
+	for _, v := range indexList {
+		tmpClassifyId := indexIdToclassifyId[v.BaseFromClarksonsIndexId]
+		if _, ok := existClassifyList[tmpClassifyId]; ok {
+			v.ClassifyId = tmpClassifyId
+			v.Sort = classifySortMap[tmpClassifyId] + 1
+			classifySortMap[tmpClassifyId] += 1
+			updateIndexList = append(updateIndexList, v)
+		}
+	}
+	err = data_manage.BatchModifyClarksonsIndexClassify(updateIndexList)
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "编辑指标失败,Err:" + err.Error()
+		return
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+
+}
+
+// EditClarksons
+// @Title 编辑克拉克森指标
+// @Description 编辑克拉克森指标接口
+// @Param	request	body data_manage.AddEdbClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /clarksons/edit [post]
+func (this *ClarksonsDataController) EditClarksons() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.EditBaseFromClarksonsReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.BaseFromClarksonsIndexId <= 0 {
+		br.Msg = "请选择指标"
+		br.IsSendEmail = false
+		return
+	}
+	if req.BaseFromClassifyId < 0 {
+		br.Msg = "请选择分类"
+		br.IsSendEmail = false
+		return
+	}
+
+	//编辑指标
+	sciIndexInfo, errMsg, err := data.EditClarksonsIndex(req.BaseFromClarksonsIndexId, req.BaseFromClassifyId)
+	if errMsg != `` {
+		br.Msg = errMsg
+		return
+	}
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "编辑指标失败,Err:" + err.Error()
+		return
+	}
+
+	resp := response.EditClarksonsIndexInfoResp{
+		BaseFromClarksonsIndexId: sciIndexInfo.BaseFromClarksonsIndexId,
+		IndexCode:                sciIndexInfo.IndexCode,
+	}
+	br.Data = resp
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// DeleteClarksonsData
+// @Title 删除指标
+// @Description 删除指标接口
+// @Param	request	body data_manage.DelBaseFromSciReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /clarksons/del [post]
+func (this *ClarksonsDataController) DeleteClarksonsData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req request.DelBaseFromClarksonsReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.BaseFromClarksonsIndexId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	err, errMsg := data.DelClarksonsData(req.BaseFromClarksonsIndexId)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = errMsg + ";Err:" + err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// ExportClarksonsList
+// @Title 导出Sci数据
+// @Description 导出Sci数据
+// @Param   BaseFromClassifyId   query   int  true       "关键字搜索"
+// @Param   IndexCode   query   string  true       "指标编码"
+// @Success 200  导出成功
+// @router /export/clarksonsList [get]
+func (this *ClarksonsDataController) ExportClarksonsList() {
+	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("BaseFromClassifyId")
+	indexCode := this.GetString("IndexCode")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	secNameList := make([]*data_manage.BaseFromClarksonsIndex, 0)
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+	downLoadnFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	var condition string
+	var pars []interface{}
+	if classifyId > 0 {
+		childClassify, err := data_manage.GetChildClarksonsClassifyListById(classifyId)
+		if err != nil {
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			return
+		}
+		if len(childClassify) > 0 {
+			condition += `AND classify_id IN (` + utils.GetOrmInReplace(len(childClassify)) + `)`
+			for _, child := range childClassify {
+				pars = append(pars, child.BaseFromClassifyId)
+			}
+		} else {
+			condition += ` AND classify_id=?`
+			pars = append(pars, classifyId)
+		}
+	} else {
+		condition += ` AND classify_id=?`
+		pars = append(pars, classifyId)
+	}
+	if indexCode != "" {
+		condition += ` AND index_code=? `
+		pars = append(pars, indexCode)
+	}
+	frequencies, err := data_manage.GetClarksonsFrequencyByCondition(condition, pars)
+	if err != nil {
+		fmt.Println("GetSciFrequency err:", err.Error())
+		utils.FileLog.Info("GetSciFrequency err:" + err.Error())
+		return
+	}
+	for _, frequency := range frequencies {
+		//获取指标
+		secNameList, err = data_manage.GetClarksonsIndexByConditionAndFrequency(condition, *frequency, pars)
+		if err != nil {
+			fmt.Println("获取数据失败,Err:" + err.Error())
+			return
+		}
+		if len(secNameList) <= 0 {
+			fmt.Println("secNameList长度为0")
+			return
+		}
+		sheetNew, err := xlsxFile.AddSheet(*frequency)
+		if err != nil {
+			fmt.Println("新增Sheet失败", err.Error())
+			return
+		}
+		secNameRow := sheetNew.AddRow()
+		frequencyRow := sheetNew.AddRow()
+		unitRow := sheetNew.AddRow()
+		lastModifyDateRow := sheetNew.AddRow()
+
+		var indexIdList []int
+		for _, sv := range secNameList {
+			indexIdList = append(indexIdList, sv.BaseFromClarksonsIndexId)
+		}
+		dataTimeList, err := data_manage.GetClarksonsDataDataTimeByIndexId(indexIdList)
+		if err != nil {
+			fmt.Println("获取数据时间失败", err.Error())
+			return
+		}
+
+		// 添加excel左侧指标日期
+		setRowIndex := 4
+		for rk, dv := range dataTimeList {
+			rowIndex := setRowIndex + rk
+			row := sheetNew.Row(rowIndex)
+			displayDate, _ := time.Parse(utils.FormatDate, dv)
+			displayDateCell := row.AddCell()
+			style := new(xlsx.Style)
+			style.ApplyAlignment = true
+			style.Alignment.WrapText = true
+			displayDateCell.SetStyle(style)
+			displayDateCell.SetDate(displayDate)
+
+		}
+		for k, sv := range secNameList {
+			//获取数据
+			dataList, err := data_manage.GetClarksonsIndexDataByCode(sv.IndexCode)
+			if err != nil {
+				br.Msg = "获取数据失败"
+				br.ErrMsg = "获取数据失败,Err:" + err.Error()
+				return
+			}
+			if k == 0 {
+				secNameRow.AddCell().SetValue("指标名称")
+				frequencyRow.AddCell().SetValue("频率")
+				unitRow.AddCell().SetValue("单位")
+				lastModifyDateRow.AddCell().SetValue("更新时间")
+				min := k * 3
+				sheetNew.SetColWidth(min, min, 15)
+			}
+			if len(dataList) == 0 {
+				continue
+			}
+			secNameRow.AddCell().SetValue(sv.IndexName)
+			frequencyRow.AddCell().SetValue(sv.Frequency)
+			unitRow.AddCell().SetValue(sv.Unit)
+
+			lastModifyDateRow.AddCell().SetValue(sv.ModifyTime.Format(utils.FormatDate))
+			dataInfoMap := make(map[string]*data_manage.BaseFromClarksonsData)
+			for _, v := range dataList {
+				dataInfoMap[v.DataTime] = v
+			}
+
+			for rk, dtv := range dataTimeList {
+				rowIndex := setRowIndex + rk
+				row := sheetNew.Row(rowIndex)
+				displayDateCell := row.AddCell()
+				tmpData, ok := dataInfoMap[dtv]
+				if ok {
+					displayDateCell.SetValue(tmpData.Value)
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadnFilePath)
+	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(downLoadnFilePath)
+		if err != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+	fileName := `克拉克森`
+	if indexCode != "" && len(secNameList) == 1 {
+		fileName = secNameList[0].IndexName
+	}
+	fileName += time.Now().Format("2006.01.02") + `.xlsx` //文件名称
+	this.Ctx.Output.Download(downLoadnFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadnFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}
+
+// getClarksonsClassifyTree 返回卓创红旗的树形结构
+func getClarksonsClassifyTree(items []*data_manage.BaseFromClarksonsClassifyItem, parentId int) []*data_manage.BaseFromClarksonsClassifyItem {
+	res := make([]*data_manage.BaseFromClarksonsClassifyItem, 0)
+	for _, item := range items {
+		if item.ParentId == parentId {
+			t := new(data_manage.BaseFromClarksonsClassifyItem)
+			t.BaseFromClassifyId = item.BaseFromClassifyId
+			t.ClassifyName = item.ClassifyName
+			t.ParentId = item.ParentId
+			t.Level = item.Level
+			t.Sort = item.Sort
+			t.ModifyTime = item.ModifyTime
+			t.CreateTime = item.CreateTime
+			t.ClassifyNameEn = item.ClassifyNameEn
+			if item.UniqueCode == "" {
+				t.UniqueCode = strconv.Itoa(item.BaseFromClassifyId)
+			}
+			t.Children = getClarksonsClassifyTree(items, item.BaseFromClassifyId)
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+// @Title 获取克拉克森数据
+// @Description 获取克拉克森数据接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   BaseFromSci99ClassifyId   query   int  true       "分类id"
+// @Param   KeyWord   query   string  true       "关键词"
+// @Success 200 {object} data_source.BaseFromSci99IndexView
+// @router /clarksons/index/data [get]
+func (this *ClarksonsDataController) ClarksonsData() {
+	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 = utils.StartIndex(currentIndex, pageSize)
+
+	baseFromClarksonsClassifyId, _ := this.GetInt("BaseFromClarksonsClassifyId")
+	if baseFromClarksonsClassifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+
+	keyword := this.GetString("KeyWord")
+
+	//获取指标
+	var condition string
+	var pars []interface{}
+
+	if baseFromClarksonsClassifyId > 0 {
+		condition += ` AND classify_id=? `
+		pars = append(pars, baseFromClarksonsClassifyId)
+	}
+
+	if keyword != "" {
+		condition += ` AND (index_code =? OR index_name LIKE ?)  `
+		pars = append(pars, keyword)
+		pars = append(pars, "%"+keyword+"%")
+	}
+
+	sci99List, err := data_manage.GetClarksonsIndex(condition, pars)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	resultList := make([]*data_manage.BaseFromClarksonsIndexList, 0)
+	for _, v := range sci99List {
+		product := new(data_manage.BaseFromClarksonsIndexList)
+		product.BaseFromClarksonsIndexId = v.BaseFromClarksonsIndexId
+		product.ClassifyId = v.ClassifyId
+		product.IndexCode = v.IndexCode
+		product.IndexName = v.IndexName
+		product.Frequency = v.Frequency
+		product.Unit = v.Unit
+		product.ModifyTime = v.ModifyTime
+
+		modifyTime, err := data_manage.GetClarksonsIndexLatestDate(v.IndexCode)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取更新时间失败"
+			br.ErrMsg = "获取更新时间失败,Err:" + err.Error()
+			return
+		}
+		product.ModifyTime = modifyTime
+
+		total, err := data_manage.GetClarksonsIndexDataCount(v.IndexCode)
+		page := paging.GetPaging(currentIndex, pageSize, total)
+		dataList, err := data_manage.GetClarksonsIndexData(v.IndexCode, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+		if dataList == nil {
+			dataList = make([]*data_manage.BaseFromClarksonsData, 0)
+		}
+		product.DataList = dataList
+		product.Paging = page
+		resultList = append(resultList, product)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resultList
+}
+
+// IndexPageList
+// @Title 克拉克森指标列表
+// @Description 克拉克森数据指标列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   int  true       "分类id"
+// @Param   IsListAll   query   int  true       "是否展示全部"
+// @Success 200 {object} data_manage.BaseFromMysteelChemicalIndexResp
+// @router /clarksons/index/page/list [get]
+func (this *ClarksonsDataController) IndexPageList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	classifyId, _ := this.GetInt("ClassifyId", 0)
+	pageSize, _ := this.GetInt("PageSize")
+	currrentIndex, _ := this.GetInt("CurrentIndex")
+	isListAll, _ := this.GetBool("IsListAll")
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currrentIndex <= 0 {
+		currrentIndex = 1
+	}
+	startSize = utils.StartIndex(currrentIndex, pageSize)
+	var total int
+	var indexList []*data_manage.BaseFromClarksonsIndexView
+	if isListAll {
+		tmpTotal, err := data_manage.GetClarksonsIndexCount()
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		total = tmpTotal
+		tmpIndexList, err := data_manage.GetClarksonsIndexByPage(startSize, pageSize)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		indexList = tmpIndexList
+	} else {
+		var classifyIds []int
+		if classifyId > 0 {
+			tmpClassifyIds, err := data_manage.GetClarksonsChildClassifyIdsById(classifyId)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+				return
+			}
+			if len(tmpClassifyIds) > 0 {
+				classifyIds = append(classifyIds, tmpClassifyIds...)
+			}
+		}
+		classifyIds = append(classifyIds, classifyId)
+		tmpTotal, err := data_manage.GetClarksonsIndexCountByClassifyId(classifyIds)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		total = tmpTotal
+		tmpIndexList, err := data_manage.GetClarksonsIndexByClassifyId(classifyIds, startSize, pageSize)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+			return
+		}
+		indexList = tmpIndexList
+	}
+
+	page := paging.GetPaging(currrentIndex, pageSize, total)
+	resp := new(response.ClarksonsIndexPageListResp)
+	resp.List = indexList
+	resp.Paging = page
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 18 - 1
controllers/data_manage/correlation/correlation_chart_info.go

@@ -517,8 +517,25 @@ func (this *CorrelationChartInfoController) List() {
 		}
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
+
+	var keyWordArr []string
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		newKeyWord := strings.Split(keyword, " ")
+		keywordStr := strings.Replace(keyword, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` chart_name LIKE '%` + keywordStr + `%' OR chart_name_en LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				if v != "" {
+					condition += ` chart_name LIKE '%` + v + `%' OR chart_name_en LIKE '%` + v + `%' OR`
+				}
+			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
 
 	//只看我的

+ 17 - 1
controllers/data_manage/cross_variety/chart_info.go

@@ -91,8 +91,24 @@ func (c *ChartInfoController) List() {
 		}
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
+
+	var keyWordArr []string
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		newKeyWord := strings.Split(keyword, " ")
+		keywordStr := strings.Replace(keyword, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` chart_name LIKE '%` + keywordStr + `%' OR chart_name_en LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				if v != "" {
+					condition += ` chart_name LIKE '%` + v + `%' OR chart_name_en LIKE '%` + v + `%' OR`
+				}			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
 
 	//只看我的

+ 161 - 3
controllers/data_manage/edb_classify.go

@@ -9,7 +9,10 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
 	"sort"
+	"strconv"
+	"strings"
 )
 
 // EdbClassifyController 数据管理-分类模块
@@ -288,7 +291,7 @@ func (this *EdbClassifyController) EditEdbClassify() {
 		return
 	}
 
-	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ClassifyName, this.Lang, this.SysUser)
+	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ParentId, req.ClassifyName, this.Lang, this.SysUser)
 	if errMsg != `` {
 		br.Msg = errMsg
 		br.ErrMsg = errMsg
@@ -1109,7 +1112,7 @@ func (this *EdbClassifyController) ClassifyTree() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
+	level, _ := this.GetInt(`Level`)
 	allList, err := data_manage.GetNormalEdbClassifyAll()
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取失败"
@@ -1135,7 +1138,7 @@ func (this *EdbClassifyController) ClassifyTree() {
 			button := data.GetEdbClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
 			allList[k].Button = button
 		}
-		nodeAll = data.GetClassifyTreeRecursive(allList, 0)
+		nodeAll = data.GetClassifyTreeRecursive(allList, 0, level)
 		//根据sort值排序
 		sortList = nodeAll
 		sort.Sort(sortList)
@@ -1221,3 +1224,158 @@ func (this *EdbClassifyController) ClassifyTree() {
 //	br.Success = true
 //	br.Msg = "移动成功"
 //}
+
+// EdbInfoList
+// @Title 批量编辑指标列表接口
+// @Description 批量编辑指标列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   EdbInfoId   query   int  true       "指标id"
+// @Param   KeyWord   query   string  false       "搜索关键词:指标ID/指标名称"
+// @Success 200 {object} data_manage.EdbInfoListResp
+// @router /classify/edb/list [get]
+func (this *EdbInfoController) ClassifyEdbInfoList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	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)
+
+	subClassify, _ := this.GetBool("SubClassify")
+	sources := this.GetString("Sources")
+	keyWord := this.GetString("KeyWord")
+	sysUserIds := this.GetString("SysUserIds")
+	classifyIdsStr := this.GetString("ClassifyIds")
+	classifyIds := strings.Split(classifyIdsStr, ",")
+
+	var condition string
+	var pars []interface{}
+
+	// 已授权分类id
+	//permissionClassifyIdList, err := data_manage_permission.GetUserEdbClassifyPermissionList(this.SysUser.AdminId, 0)
+	//if err != nil {
+	//	br.Msg = "获取失败"
+	//	br.ErrMsg = "获取已授权分类id数据失败,Err:" + err.Error()
+	//	return
+	//}
+	classifyIdsArr := make([]int, 0)
+	for _, v := range classifyIds {
+		if v != `` {
+			id, _ := strconv.Atoi(v)
+			classifyIdsArr = append(classifyIdsArr, id)
+		}
+	}
+
+	//if len(permissionClassifyIdList) > 0 {
+	//	classifyIdsArr = utils.IntersectInt(permissionClassifyIdList, classifyIdsArr)
+	//}
+
+	condition += " AND edb_info_type = 0 "
+	if len(classifyIdsArr) > 0 {
+		if !subClassify {
+			condition += " AND classify_id IN(" + utils.GetOrmInReplace(len(classifyIdsArr)) + ") "
+			pars = append(pars, classifyIdsArr)
+		} else {
+			classifyAll, err := data_manage.GetNormalEdbClassifyAll()
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取数据失败,Err:" + err.Error()
+				return
+			}
+			finalClassifyIds := make([]int, 0)
+			parents := data.GetEdbClassifyChildrenRecursiveByParentIds(classifyAll, classifyIds)
+			sort.Slice(parents, func(i, j int) bool {
+				return parents[i].Level < parents[i].Level
+			})
+			for _, v := range parents {
+				finalClassifyIds = append(finalClassifyIds, v.ClassifyId)
+			}
+
+			condition += " AND classify_id IN(" + utils.GetOrmInReplace(len(finalClassifyIds)) + ") "
+			pars = append(pars, finalClassifyIds)
+		}
+	}
+
+	if keyWord != "" {
+		keyWordArr := strings.Split(keyWord, " ")
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				condition += ` AND edb_name LIKE '%` + v + `%' `
+			}
+		}
+	}
+	if sources != "" {
+		condition += " AND source IN(" + utils.GetOrmInReplace(len(strings.Split(sources, ","))) + ") "
+		pars = append(pars, strings.Split(sources, ","))
+	}
+	if sysUserIds != "" {
+		adminIds := strings.Split(sysUserIds, ",")
+		if len(adminIds) == 0 {
+			br.Msg = "请选择正确的创建人"
+			return
+		}
+		adminIdsSlice := make([]int, 0)
+		for _, adminId := range adminIds {
+			adminIdInt, e := strconv.Atoi(adminId)
+			if e != nil {
+				br.Msg = "请选择正确的创建人"
+				return
+			}
+			adminIdsSlice = append(adminIdsSlice, adminIdInt)
+		}
+		condition += "  AND sys_user_id in (" + utils.GetOrmInReplace(len(adminIds)) + ") "
+		pars = append(pars, adminIdsSlice)
+	}
+
+	// 获取当前账号的不可见指标
+	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	confList, err := obj.GetAllListByAdminId(this.SysUser.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+		return
+	}
+	noPermissionEdbInfoIds := make([]int, 0)
+	for _, v := range confList {
+		noPermissionEdbInfoIds = append(noPermissionEdbInfoIds, v.EdbInfoId)
+	}
+	if len(noPermissionEdbInfoIds) > 0 {
+		condition += " AND edb_info_id NOT IN(" + utils.GetOrmInReplace(len(noPermissionEdbInfoIds)) + ") "
+		pars = append(pars, noPermissionEdbInfoIds)
+	}
+
+	count, err := data_manage.GetEdbInfoByConditionCount(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, count)
+
+	list, err := data_manage.GetEdbInfoListByCondition(condition, pars, startSize, pageSize, "")
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	resp := new(data_manage.EdbInfoFilterDataResp)
+	resp.List = list
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 189 - 0
controllers/data_manage/edb_info.go

@@ -21,6 +21,7 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -4673,7 +4674,9 @@ func (this *ChartInfoController) EdbInfoDataSeasonal() {
 	}
 	var latestDateT time.Time
 	if edbInfo.LatestDate != "" {
+		// 季节性图需要特殊处理最近N年数据
 		latestDateT, _ = time.Parse(utils.FormatDate, edbInfo.LatestDate)
+		latestDateT = time.Date(latestDateT.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
 	}
 
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, latestDateT)
@@ -6717,3 +6720,189 @@ func (this *EdbInfoController) ChartImageSetBySvg() {
 	br.Success = true
 	br.Msg = "保存成功"
 }
+
+// ModifyChartList
+// @Title 批量修改图表
+// @Description 批量修改图表
+// @Param   SysUserIds   query   string  true       "根据创建人查询"
+// @Param   ChartClassifyIds   query   string  true       "图片分类id"
+// @Success 200 {object} models.ChartClassifyListResp
+// @router /modify/edbList [post]
+func (this *EdbInfoController) ModifyEdbList() {
+	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 request.ModifyEdbListReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	classifyIds := strings.Split(req.EdbClassifyIds, ",")
+
+
+	if !req.SelectAll && req.EdbInfoIds == "" {
+		br.Msg = "请选择图表"
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择新分类"
+		return
+	}
+
+	var condition string
+	var pars []interface{}
+	edbInfoIds := make([]int, 0)
+
+	if req.SelectAll {
+		// 已授权分类id
+		permissionClassifyIdList, err := data_manage_permission.GetUserEdbClassifyPermissionList(this.SysUser.AdminId, 0)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取已授权分类id数据失败,Err:" + err.Error()
+			return
+		}
+		classifyIdsArr := make([]int, 0)
+		for _, v := range classifyIds {
+			if v != `` {
+				id, _ := strconv.Atoi(v)
+				classifyIdsArr = append(classifyIdsArr, id)
+			}
+		}
+
+		if len(permissionClassifyIdList) > 0 {
+			classifyIdsArr = utils.IntersectInt(permissionClassifyIdList, classifyIdsArr)
+		}
+
+		condition += " AND edb_info_type = 0 "
+		if len(classifyIdsArr) > 0 {
+			if !req.SubClassify {
+				condition += " AND classify_id IN(" + utils.GetOrmInReplace(len(classifyIdsArr)) + ") "
+				pars = append(pars, classifyIdsArr)
+			} else {
+				classifyAll, err := data_manage.GetNormalEdbClassifyAll()
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取数据失败,Err:" + err.Error()
+					return
+				}
+				finalClassifyIds := make([]int, 0)
+				parents := data.GetEdbClassifyChildrenRecursiveByParentIds(classifyAll, classifyIds)
+				sort.Slice(parents, func(i, j int) bool {
+					return parents[i].Level < parents[i].Level
+				})
+				for _, v := range parents {
+					finalClassifyIds = append(finalClassifyIds, v.ClassifyId)
+				}
+
+				condition += " AND classify_id IN(" + utils.GetOrmInReplace(len(finalClassifyIds)) + ") "
+				pars = append(pars, finalClassifyIds)
+			}
+		}
+		if req.KeyWord != "" {
+			condition += ` AND edb_name LIKE '%` + req.KeyWord + `%' `
+		}
+		if req.Sources != "" {
+			condition += " AND source IN(" + utils.GetOrmInReplace(len(strings.Split(req.Sources, ","))) + ") "
+			pars = append(pars, strings.Split(req.Sources, ","))
+		}
+		if req.SysUserIds != "" {
+			adminIds := strings.Split(req.SysUserIds, ",")
+			if len(adminIds) == 0 {
+				br.Msg = "请选择正确的创建人"
+				return
+			}
+			adminIdsSlice := make([]int, 0)
+			for _, adminId := range adminIds {
+				adminIdInt, e := strconv.Atoi(adminId)
+				if e != nil {
+					br.Msg = "请选择正确的创建人"
+					return
+				}
+				adminIdsSlice = append(adminIdsSlice, adminIdInt)
+			}
+			condition += "  AND sys_user_id in (" + utils.GetOrmInReplace(len(adminIds)) + ") "
+			pars = append(pars, adminIdsSlice)
+		}
+		// 获取当前账号的不可见指标
+		obj := data_manage.EdbInfoNoPermissionAdmin{}
+		confList, err := obj.GetAllListByAdminId(this.SysUser.AdminId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+			return
+		}
+		noPermissionEdbInfoIds := make([]int, 0)
+		for _, v := range confList {
+			noPermissionEdbInfoIds = append(noPermissionEdbInfoIds, v.EdbInfoId)
+		}
+		if len(noPermissionEdbInfoIds) > 0 {
+			condition += " AND edb_info_id NOT IN(" + utils.GetOrmInReplace(len(noPermissionEdbInfoIds)) + ") "
+			pars = append(pars, noPermissionEdbInfoIds)
+		}
+
+		count, err := data_manage.GetEdbInfoByConditionCount(condition, pars)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if count > 100 {
+			br.Msg = "最多选择100个"
+			return
+		}
+
+		list, err := data_manage.GetEdbInfoListByCondition(condition, pars, 0, 0, "")
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+
+
+		for _, v := range list {
+			edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+		}
+	} else {
+		edbIds := strings.Split(req.EdbInfoIds, ",")
+		if len(edbIds) == 0 {
+			br.Msg = "请选择图表"
+			return
+		}
+		for _, id := range edbIds {
+			tmp, e := strconv.Atoi(id)
+			if e != nil {
+				br.Msg = "请选择正确的分类"
+				return
+			}
+			edbInfoIds = append(edbInfoIds, tmp)
+		}
+		if len(edbInfoIds) > 100 {
+			br.Msg = "最多只能选择100个图表"
+			return
+		}
+	}
+
+	err = data_manage.UpdateEdbClassifyIdByChartInfoId(edbInfoIds, req.ClassifyId)
+	if err != nil {
+		br.Msg = "更新失败"
+		br.ErrMsg = "更新图表分类失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 35 - 7
controllers/data_manage/edb_info_relation.go

@@ -144,6 +144,9 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 	// 查询事件日历
 	eventInfoIds := make([]int, 0)
 
+	// 预测指标
+	predictEdbIds := make([]int, 0)
+
 	for _, v := range relationList {
 		switch v.ReferObjectType {
 		case utils.EDB_RELATION_SANDBOX:
@@ -154,9 +157,11 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 			chartInfoIds = append(chartInfoIds, v.ReferObjectId)
 		case utils.EDB_RELATION_TABLE:
 			tableInfoIds = append(tableInfoIds, v.ReferObjectId)
+		case utils.EDB_RELATION_PREDICT_EDB:
+			predictEdbIds = append(predictEdbIds, v.ReferObjectId)
 		}
 	}
-	objectNameMap := make(map[int]string)
+	objectNameMap := make(map[string]string)
 	if len(sandboxIds) > 0 {
 		sandboxList, err := sandbox.GetSandboxNameByIds(sandboxIds)
 		if err != nil && err.Error() != utils.ErrNoRow() {
@@ -165,7 +170,8 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 			return
 		}
 		for _, v := range sandboxList {
-			objectNameMap[v.SandboxId] = v.Name
+			name := fmt.Sprintf("%d_%d", utils.EDB_RELATION_SANDBOX, v.SandboxId)
+			objectNameMap[name] = v.Name
 		}
 	}
 
@@ -182,7 +188,8 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 			return
 		}
 		for _, v := range eventList {
-			objectNameMap[v.FeCalendarMatterId] = fmt.Sprintf("%s, %s", v.MatterDate, v.ChartPermissionName)
+			name := fmt.Sprintf("%d_%d", utils.EDB_RELATION_CALENDAR, v.FeCalendarMatterId)
+			objectNameMap[name] = fmt.Sprintf("%s, %s", v.MatterDate, v.ChartPermissionName)
 		}
 	}
 
@@ -195,7 +202,8 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 			return
 		}
 		for _, v := range chartList {
-			objectNameMap[v.ChartInfoId] = v.ChartName
+			name := fmt.Sprintf("%d_%d", utils.EDB_RELATION_CHART, v.ChartInfoId)
+			objectNameMap[name] = v.ChartName
 		}
 	}
 
@@ -233,16 +241,32 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 			}
 		}
 		for _, v := range tableList {
+			name := fmt.Sprintf("%d_%d", utils.EDB_RELATION_TABLE, v.ExcelInfoId)
 			if v.ParentId > 0 {
 				parentName := excelParentName[v.ParentId]
-				objectNameMap[v.ExcelInfoId] = fmt.Sprintf("%s_%s", parentName, v.ExcelName)
+				objectNameMap[name] = fmt.Sprintf("%s_%s", parentName, v.ExcelName)
 			} else {
-				objectNameMap[v.ExcelInfoId] = v.ExcelName
+				objectNameMap[name] = v.ExcelName
 			}
 		}
 	}
+
+	// 查询预测指标名称
+	if len(predictEdbIds) > 0 {
+		predictList, err := data_manage.GetEdbInfoByIdList(predictEdbIds)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取预测指标信息失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range predictList {
+			name := fmt.Sprintf("%d_%d", utils.EDB_RELATION_PREDICT_EDB, v.EdbInfoId)
+			objectNameMap[name] = v.EdbName
+		}
+	}
 	for _, v := range relationList {
-		referObjectName, _ := objectNameMap[v.ReferObjectId]
+		name := fmt.Sprintf("%d_%d", v.ReferObjectType, v.ReferObjectId)
+		referObjectName, _ := objectNameMap[name]
 		tmp := &data_manage.EdbInfoRelationDetail{
 			EdbInfoRelationId:  v.EdbInfoRelationId,
 			EdbInfoId:          v.EdbInfoId,
@@ -271,6 +295,8 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 				tmp.ReferObjectTypeName = "跨品种分析"
 			case utils.CHART_SOURCE_FUTURE_GOOD, utils.CHART_SOURCE_FUTURE_GOOD_PROFIT:
 				tmp.ReferObjectTypeName = "商品价格曲线"
+			case utils.CHART_SOURCE_RANGE_ANALYSIS:
+				tmp.ReferObjectTypeName = "区间分析"
 			}
 		case utils.EDB_RELATION_TABLE:
 			switch v.ReferObjectSubType {
@@ -281,6 +307,8 @@ func (c *EdbInfoRelationController) RelationEdbListDetail() {
 			case utils.BALANCE_TABLE:
 				tmp.ReferObjectTypeName = "平衡表"
 			}
+		case utils.EDB_RELATION_PREDICT_EDB:
+			tmp.ReferObjectTypeName = "预测指标"
 		}
 		list = append(list, tmp)
 	}

+ 3 - 2
controllers/data_manage/excel/balance_table.go

@@ -17,13 +17,14 @@ import (
 	excel2 "eta/eta_api/services/excel"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/tealeg/xlsx"
 	"io/ioutil"
 	"os"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/tealeg/xlsx"
 )
 
 // GetChildTable
@@ -1413,7 +1414,7 @@ func downloadBalanceTable(excelInfo *excel.ExcelInfo, lang string) (savePath, zi
 				errMsg = msg
 				return
 			}
-			tableData, er := excel2.GetTableDataByMixedTableData(newResult, false)
+			tableData, er := excel2.GetTableDataByMixedTableData(newResult, false, childExcelInfo.ExcelInfoId)
 			if er != nil {
 				errMsg = "获取失败"
 				err = fmt.Errorf("转换成table失败,Err:" + err.Error())

+ 31 - 12
controllers/data_manage/excel/excel_info.go

@@ -373,10 +373,14 @@ func (c *ExcelInfoController) List() {
 
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
-	keyword := c.GetString("Keyword")
+	keyword := c.GetString("KeyWord")
 	adminId, _ := c.GetInt("AdminId")
 	source, _ := c.GetInt("Source")
 
+	if keyword == `` { // 兼容前端
+		keyword = c.GetString("Keyword")
+	}
+
 	var total int
 	page := paging.GetPaging(currentIndex, pageSize, total)
 
@@ -431,10 +435,27 @@ func (c *ExcelInfoController) List() {
 			pars = append(pars, classifyIds)
 		}
 	}
+
+	var keyWordArr []string
 	if keyword != "" {
-		condition += ` AND  ( excel_name LIKE ? )`
-		pars = utils.GetLikeKeywordPars(pars, keyword, 1)
+		newKeyWord := strings.Split(keyword, " ")
+		keywordStr := strings.Replace(keyword, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` excel_name LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				if v != "" {
+					condition += ` excel_name LIKE '%` + v + `%' OR`
+				}
+			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
+
 	if adminId > 0 {
 		condition += " AND sys_user_id = ? "
 		pars = append(pars, adminId)
@@ -1617,7 +1638,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult, true)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, true, excelInfo.ExcelInfoId)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
@@ -1646,7 +1667,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 	}
 
 	tableData = excel.HandleTableCell(tableData)
-	tableData, err = excel.HandleRuleToTableCell(excelInfo.ExcelInfoId, tableData)
+	// tableData, err = excel.HandleRuleToTableCell(excelInfo.ExcelInfoId, tableData)
 	if err != nil {
 		utils.FileLog.Info("表格管理规则处理失败,HandleRuleToTableCell err:", err.Error())
 	}
@@ -2626,7 +2647,7 @@ func (c *ExcelInfoController) Download() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult, false)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, false, excelInfo.ExcelInfoId)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
@@ -3208,12 +3229,10 @@ func (c *ExcelInfoController) EditExcelRule() {
 		br.Msg = "应用选区不能为空"
 		return
 	}
-	if req.FontColor == "" {
-		br.Msg = "字体颜色不能为空"
-		return
-	}
-	if req.BackgroundColor == "" {
-		br.Msg = "背景颜色不能为空"
+	req.BackgroundColor = strings.TrimSpace(req.BackgroundColor)
+	req.FontColor = strings.TrimSpace(req.FontColor)
+	if req.FontColor == "" && req.BackgroundColor == "" {
+		br.Msg = "字体颜色或背景颜色不能同时为空"
 		return
 	}
 	if req.RuleType == 3 && req.RightValue == "" {

+ 17 - 1
controllers/data_manage/future_good/future_good_chart_info.go

@@ -90,8 +90,24 @@ func (this *FutureGoodChartInfoController) ChartList() {
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 		//pars = append(pars, chartClassifyId)
 	}
+
+	var keyWordArr []string
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		newKeyWord := strings.Split(keyword, " ")
+		keywordStr := strings.Replace(keyword, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` chart_name LIKE '%` + keywordStr + `%' OR chart_name_en LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				if v != "" {
+					condition += ` chart_name LIKE '%` + v + `%' OR chart_name_en LIKE '%` + v + `%' OR`
+				}			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
 
 	//只看我的

+ 18 - 1
controllers/data_manage/line_equation/line_chart_info.go

@@ -595,8 +595,25 @@ func (this *LineEquationChartInfoController) List() {
 		}
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
+
+	var keyWordArr []string
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		newKeyWord := strings.Split(keyword, " ")
+		keywordStr := strings.Replace(keyword, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` chart_name LIKE '%` + keywordStr + `%' OR chart_name_en LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				if v != "" {
+					condition += ` chart_name LIKE '%` + v + `%' OR chart_name_en LIKE '%` + v + `%' OR`
+				}
+			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
 
 	//只看我的

+ 28 - 1
controllers/data_manage/line_feature/chart_info.go

@@ -1640,8 +1640,23 @@ func (this *LineFeaturesChartInfoController) List() {
 		}
 		condition += " AND chart_classify_id IN(" + chartClassifyId + ") "
 	}
+
+	var keyWordArr []string
 	if keyword != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyword + `%' )`
+		newKeyWord := strings.Split(keyword, " ")
+		keywordStr := strings.Replace(keyword, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` chart_name LIKE '%` + keywordStr + `%' OR chart_name_en LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				condition += ` chart_name LIKE '%` + v + `%' OR chart_name_en LIKE '%` + v + `%' OR`
+			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
 
 	//只看我的
@@ -1836,6 +1851,9 @@ func (this *LineFeaturesChartInfoController) Detail() {
 			latestDateT, _ := time.Parse(utils.FormatDate, edbMapping.LatestDate)
 			maxDate = latestDateT
 		}
+		if chartInfo.ChartType == utils.CHART_TYPE_SEASON && chartInfo.DateType == utils.DateTypeNYears {
+			maxDate = time.Date(maxDate.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+		}
 		startDate, endDate := utils.GetDateByDateTypeV2(chartInfo.DateType, chartInfo.StartDate, chartInfo.EndDate, chartInfo.StartYear, maxDate)
 		edbList, resultResp, err, errMsg = lineFeatureServ.GetStandardDeviationData(0, startDate, endDate, edbMapping, calculateValue)
 	case utils.CHART_SOURCE_LINE_FEATURE_PERCENTILE:
@@ -1851,6 +1869,9 @@ func (this *LineFeaturesChartInfoController) Detail() {
 			latestDateT, _ := time.Parse(utils.FormatDate, edbMapping.LatestDate)
 			maxDate = latestDateT
 		}
+		if chartInfo.ChartType == utils.CHART_TYPE_SEASON && chartInfo.DateType == utils.DateTypeNYears {
+			maxDate = time.Date(maxDate.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+		}
 		startDate, endDate := utils.GetDateByDateTypeV2(chartInfo.DateType, chartInfo.StartDate, chartInfo.EndDate, chartInfo.StartYear, maxDate)
 		edbList, resultResp, err, errMsg = lineFeatureServ.GetPercentileData(0, startDate, endDate, edbMapping, percentileConfig.CalculateValue, percentileConfig.CalculateUnit, percentileConfig.PercentType)
 	case utils.CHART_SOURCE_LINE_FEATURE_FREQUENCY:
@@ -2465,6 +2486,9 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 			latestDateT, _ := time.Parse(utils.FormatDate, edbMapping.LatestDate)
 			maxDate = latestDateT
 		}
+		if chartInfo.ChartType == utils.CHART_TYPE_SEASON && chartInfo.DateType == utils.DateTypeNYears {
+			maxDate = time.Date(maxDate.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+		}
 		startDate, endDate := utils.GetDateByDateTypeV2(chartInfo.DateType, chartInfo.StartDate, chartInfo.EndDate, chartInfo.StartYear, maxDate)
 		edbList, resultResp, err, msg = lineFeatureServ.GetStandardDeviationData(0, startDate, endDate, edbMapping, calculateValue)
 	case utils.CHART_SOURCE_LINE_FEATURE_PERCENTILE:
@@ -2480,6 +2504,9 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 			latestDateT, _ := time.Parse(utils.FormatDate, edbMapping.LatestDate)
 			maxDate = latestDateT
 		}
+		if chartInfo.ChartType == utils.CHART_TYPE_SEASON && chartInfo.DateType == utils.DateTypeNYears {
+			maxDate = time.Date(maxDate.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+		}
 		startDate, endDate := utils.GetDateByDateTypeV2(chartInfo.DateType, chartInfo.StartDate, chartInfo.EndDate, chartInfo.StartYear, maxDate)
 		edbList, resultResp, err, msg = lineFeatureServ.GetPercentileData(0, startDate, endDate, edbMapping, percentileConfig.CalculateValue, percentileConfig.CalculateUnit, percentileConfig.PercentType)
 	case utils.CHART_SOURCE_LINE_FEATURE_FREQUENCY:

+ 18 - 1
controllers/data_manage/my_chart.go

@@ -96,8 +96,25 @@ func (this *MyChartController) ChartList() {
 		condition += " AND chart_classify_id IN(" + utils.GetOrmInReplace(len(chartClassifyIds)) + ") "
 		pars = append(pars, chartClassifyIds)
 	}
+
+	var keyWordArr []string
 	if keyWord != "" {
-		condition += ` AND  ( chart_name LIKE '%` + keyWord + `%' OR chart_name_en LIKE '%` + keyWord + `%' )`
+		newKeyWord := strings.Split(keyWord, " ")
+		keywordStr := strings.Replace(keyWord, " ", "", -1)
+
+		condition += " AND ( "
+		condition += ` chart_name LIKE '%` + keywordStr + `%' OR chart_name_en LIKE '%` + keywordStr + `%' OR`
+
+		keyWordArr = append(keyWordArr, newKeyWord...)
+		if len(keyWordArr) > 0 {
+			for _, v := range keyWordArr {
+				if v != "" {
+					condition += ` chart_name LIKE '%` + v + `%' OR chart_name_en LIKE '%` + v + `%' OR`
+				}
+			}
+		}
+		condition = strings.TrimRight(condition, "OR")
+		condition += " ) "
 	}
 
 	//只看我的

+ 2 - 2
controllers/data_manage/predict_edb_classify.go

@@ -259,7 +259,7 @@ func (this *PredictEdbClassifyController) Edit() {
 		return
 	}
 
-	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ClassifyName, this.Lang, this.SysUser)
+	err, errMsg := data.EditEdbClassify(req.ClassifyId, req.ParentId, req.ClassifyName, this.Lang, this.SysUser)
 	if errMsg != `` {
 		br.Msg = errMsg
 		br.ErrMsg = errMsg
@@ -869,7 +869,7 @@ func (this *PredictEdbClassifyController) ClassifyTree() {
 			button := data.GetPredictEdbClassifyOpButton(this.SysUser, v.SysUserId, v.HaveOperaAuth)
 			allList[k].Button = button
 		}
-		nodeAll = data.GetClassifyTreeRecursive(allList, 0)
+		nodeAll = data.GetClassifyTreeRecursive(allList, 0, 0)
 		//根据sort值排序
 		sortList = nodeAll
 		sort.Sort(sortList)

+ 181 - 70
controllers/data_manage/predict_edb_info.go

@@ -427,31 +427,56 @@ func (this *PredictEdbInfoController) Add() {
 	ruleList := make([]request.RuleConfig, 0)
 	{
 		ruleMap := make(map[string]request.RuleConfig)
+		ruleEndNumSortMap := make(map[int]request.RuleConfig)
 		dateIntList := make([]int64, 0)
+		endNumList := make([]int, 0)
 		for _, v := range req.RuleList {
-			confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
-			if err != nil {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
-				br.IsSendEmail = false
-				return
+			if req.EndDateType == 0 {
+				confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
+				if err != nil {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
+					br.IsSendEmail = false
+					return
+				}
+				dateIntList = append(dateIntList, confEndDate.Unix())
+				ruleMap[v.EndDate] = v
+			} else {
+				if _, ok := ruleEndNumSortMap[v.EndNum]; ok {
+					br.Msg = "所选期数不能和其他时间段相同"
+					return
+				}
+				endNumList = append(endNumList, v.EndNum)
+				ruleEndNumSortMap[v.EndNum] = v
 			}
-			dateIntList = append(dateIntList, confEndDate.Unix())
-			ruleMap[v.EndDate] = v
-		}
-		sort.Slice(dateIntList, func(i, j int) bool {
-			return dateIntList[i] < dateIntList[j]
-		})
-		for _, dateInt := range dateIntList {
-			currDateTime := time.Unix(dateInt, 0)
-			item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
-			if !ok {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择"
-				br.IsSendEmail = false
-				return
+		}
+		if req.EndDateType == 0 {
+			sort.Slice(dateIntList, func(i, j int) bool {
+				return dateIntList[i] < dateIntList[j]
+			})
+			for _, dateInt := range dateIntList {
+				currDateTime := time.Unix(dateInt, 0)
+				item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
+			}
+		} else {
+			sort.Ints(endNumList)
+			for _, endNum := range endNumList {
+				item, ok := ruleEndNumSortMap[endNum]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
 			}
-			ruleList = append(ruleList, item)
 		}
 	}
 	req.RuleList = ruleList
@@ -479,6 +504,19 @@ func (this *PredictEdbInfoController) Add() {
 	}
 	resp := respItem.Data
 
+	edbInfoIdArr := make([]int, 0)
+	//查询相关的指标
+	edbMappingList, err := data_manage.GetAllCalculateByEdbInfoIdV2(resp.EdbInfoId)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取引用的指标信息失败,Err:" + err.Error()
+		return
+	}
+	for _, v := range edbMappingList {
+		edbInfoIdArr = append(edbInfoIdArr, v.FromEdbInfoId)
+	}
+	// 添加指标引用记录
+	_ = data.SavePredictEdbInfoRelation(edbInfoIdArr, resp.EdbInfoId)
 	//添加es
 	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
 
@@ -603,31 +641,56 @@ func (this *PredictEdbInfoController) Edit() {
 	ruleList := make([]request.RuleConfig, 0)
 	{
 		ruleMap := make(map[string]request.RuleConfig)
+		ruleEndNumSortMap := make(map[int]request.RuleConfig)
 		dateIntList := make([]int64, 0)
+		endNumList := make([]int, 0)
 		for _, v := range req.RuleList {
-			confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
-			if err != nil {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
-				br.IsSendEmail = false
-				return
+			if req.EndDateType == 0 {
+				confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
+				if err != nil {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
+					br.IsSendEmail = false
+					return
+				}
+				dateIntList = append(dateIntList, confEndDate.Unix())
+				ruleMap[v.EndDate] = v
+			} else {
+				if _, ok := ruleEndNumSortMap[v.EndNum]; ok {
+					br.Msg = "所选期数不能和其他时间段相同"
+					return
+				}
+				endNumList = append(endNumList, v.EndNum)
+				ruleEndNumSortMap[v.EndNum] = v
 			}
-			dateIntList = append(dateIntList, confEndDate.Unix())
-			ruleMap[v.EndDate] = v
-		}
-		sort.Slice(dateIntList, func(i, j int) bool {
-			return dateIntList[i] < dateIntList[j]
-		})
-		for _, dateInt := range dateIntList {
-			currDateTime := time.Unix(dateInt, 0)
-			item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
-			if !ok {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择"
-				br.IsSendEmail = false
-				return
+		}
+		if req.EndDateType == 0 {
+			sort.Slice(dateIntList, func(i, j int) bool {
+				return dateIntList[i] < dateIntList[j]
+			})
+			for _, dateInt := range dateIntList {
+				currDateTime := time.Unix(dateInt, 0)
+				item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
+			}
+		} else {
+			sort.Ints(endNumList)
+			for _, endNum := range endNumList {
+				item, ok := ruleEndNumSortMap[endNum]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
 			}
-			ruleList = append(ruleList, item)
 		}
 	}
 	req.RuleList = ruleList
@@ -681,6 +744,19 @@ func (this *PredictEdbInfoController) Edit() {
 	}
 	resp := respItem.Data
 
+	edbInfoIdArr := make([]int, 0)
+	//查询相关的指标
+	edbMappingList, err := data_manage.GetAllCalculateByEdbInfoIdV2(resp.EdbInfoId)
+	if err != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取引用的指标信息失败,Err:" + err.Error()
+		return
+	}
+	for _, v := range edbMappingList {
+		edbInfoIdArr = append(edbInfoIdArr, v.FromEdbInfoId)
+	}
+	// 添加指标引用记录
+	_ = data.SavePredictEdbInfoRelation(edbInfoIdArr, resp.EdbInfoId)
 	//修改es
 	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
 
@@ -901,6 +977,7 @@ func (this *PredictEdbInfoController) Detail() {
 				ModifyTime:       v.ModifyTime,
 				CreateTime:       v.CreateTime,
 				CalculateList:    tmpPredictEdbConfCalculateMappingDetail,
+				EndNum:           v.EndNum,
 			}
 			predictEdbConfList = append(predictEdbConfList, tmp)
 		}
@@ -1412,6 +1489,10 @@ func (this *PredictEdbInfoController) DataList() {
 		latestDateT, _ := time.Parse(utils.FormatDate, edbInfo.LatestDate)
 		maxDate = latestDateT
 	}
+	if chartType == utils.CHART_TYPE_SEASON && dateType == utils.DateTypeNYears {
+		// 季节性图需要特殊处理最近N年数据
+		maxDate = time.Date(maxDate.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
 
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, maxDate)
 	if endDate == "" {
@@ -1519,31 +1600,56 @@ func (this *PredictEdbInfoController) ChartDataList() {
 	ruleList := make([]request.RuleConfig, 0)
 	{
 		ruleMap := make(map[string]request.RuleConfig)
+		ruleEndNumSortMap := make(map[int]request.RuleConfig)
 		dateIntList := make([]int64, 0)
+		endNumList := make([]int, 0)
 		for _, v := range req.RuleList {
-			confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
-			if err != nil {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
-				br.IsSendEmail = false
-				return
+			if req.EndDateType == 0 {
+				confEndDate, err := time.Parse(utils.FormatDate, v.EndDate)
+				if err != nil {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
+					br.IsSendEmail = false
+					return
+				}
+				dateIntList = append(dateIntList, confEndDate.Unix())
+				ruleMap[v.EndDate] = v
+			} else {
+				if _, ok := ruleEndNumSortMap[v.EndNum]; ok {
+					br.Msg = "所选期数不能和其他时间段相同"
+					return
+				}
+				endNumList = append(endNumList, v.EndNum)
+				ruleEndNumSortMap[v.EndNum] = v
 			}
-			dateIntList = append(dateIntList, confEndDate.Unix())
-			ruleMap[v.EndDate] = v
-		}
-		sort.Slice(dateIntList, func(i, j int) bool {
-			return dateIntList[i] < dateIntList[j]
-		})
-		for _, dateInt := range dateIntList {
-			currDateTime := time.Unix(dateInt, 0)
-			item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
-			if !ok {
-				br.Msg = "配置项中时间异常,请重新选择"
-				br.ErrMsg = "配置项中时间异常,请重新选择"
-				br.IsSendEmail = false
-				return
+		}
+		if req.EndDateType == 0 {
+			sort.Slice(dateIntList, func(i, j int) bool {
+				return dateIntList[i] < dateIntList[j]
+			})
+			for _, dateInt := range dateIntList {
+				currDateTime := time.Unix(dateInt, 0)
+				item, ok := ruleMap[currDateTime.Format(utils.FormatDate)]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
+			}
+		} else {
+			sort.Ints(endNumList)
+			for _, endNum := range endNumList {
+				item, ok := ruleEndNumSortMap[endNum]
+				if !ok {
+					br.Msg = "配置项中时间异常,请重新选择"
+					br.ErrMsg = "配置项中时间异常,请重新选择"
+					br.IsSendEmail = false
+					return
+				}
+				ruleList = append(ruleList, item)
 			}
-			ruleList = append(ruleList, item)
 		}
 	}
 
@@ -1552,13 +1658,17 @@ func (this *PredictEdbInfoController) ChartDataList() {
 
 	endDateStr := `` //数据的结束日期
 	for _, v := range ruleList {
-		endDateStr = v.EndDate //预测指标的结束日期
-		confEndDate, err := time.ParseInLocation(utils.FormatDate, v.EndDate, time.Local)
-		if err != nil {
-			br.Msg = "配置项中时间异常,请重新选择"
-			br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
-			return
+		var confEndDate time.Time
+		if v.EndDate != "" {
+			endDateStr = v.EndDate //预测指标的结束日期
+			confEndDate, err = time.ParseInLocation(utils.FormatDate, v.EndDate, time.Local)
+			if err != nil {
+				br.Msg = "配置项中时间异常,请重新选择"
+				br.ErrMsg = "配置项中时间异常,请重新选择,err:" + err.Error()
+				return
+			}
 		}
+
 		// 没有数据,自己瞎测试
 		//switch v.RuleType {
 		//case 3: //3:同比
@@ -1619,6 +1729,7 @@ func (this *PredictEdbInfoController) ChartDataList() {
 			FixedValue:       0,
 			Value:            v.Value,
 			EndDate:          confEndDate,
+			EndNum:           v.EndNum,
 			ModifyTime:       time.Now(),
 			CreateTime:       time.Now(),
 			DataList:         tmpDataList,
@@ -1692,7 +1803,7 @@ func (this *PredictEdbInfoController) ChartDataList() {
 		// 获取预测数据
 		var predictMinValue, predictMaxValue float64
 
-		predictDataList, predictMinValue, predictMaxValue, err, errMsg := data.GetChartPredictEdbInfoDataListByConfList(predictEdbConfAndDataList, startDate, sourceEdbInfoItem.LatestDate, endDateStr, sourceEdbInfoItem.Frequency, req.DataDateType, allDataList)
+		predictDataList, predictMinValue, predictMaxValue, err, errMsg := data.GetChartPredictEdbInfoDataListByConfList(predictEdbConfAndDataList, startDate, sourceEdbInfoItem.LatestDate, endDateStr, req.EndDateType, sourceEdbInfoItem.Frequency, req.DataDateType, allDataList)
 		if err != nil {
 			br.Msg = "获取预测指标数据失败"
 			if errMsg != `` {

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

@@ -1804,6 +1804,9 @@ func (this *RangeChartChartInfoController) SearchByEs() {
 	startSize = paging.StartIndex(currentIndex, pageSize)
 
 	keyword := this.GetString("Keyword")
+	if keyword == `` { // 兼容jp前端
+		keyword = this.GetString("KeyWord")
+	}
 
 	//只看我的
 	isShowMe, _ := this.GetBool("IsShowMe")

+ 4 - 1
controllers/data_manage/supply_analysis/variety_edb.go

@@ -750,7 +750,10 @@ func (this *VarietyController) EdbInfoDataSeasonal() {
 		Calendar:         "",
 	}
 
-	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, varietyEdbInfo.EndDate)
+	// 特殊处理季节性图的日期
+	maxDate := time.Date(varietyEdbInfo.EndDate.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+
+	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, maxDate)
 
 	dataList, minVal, maxVal, err := supply_analysisServ.GetChartEdbSeasonalData(varietyEdbId, calendar, startDate, endDate, edbInfo.LatestDate)
 	if err != nil {

+ 1394 - 0
controllers/data_manage/usda_fas_data.go

@@ -0,0 +1,1394 @@
+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"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+)
+
+type BaseFromUsdaFasController struct {
+	controllers.BaseAuthController
+}
+
+// UsdaFasClassify
+// @Title 美国农业部数据分类
+// @Description 美国农业部数据分类接口
+// @Success 200 {object} data_manage.BaseFromUsdaFasClassify
+// @router /usda_fas/classify [get]
+func (this *BaseFromUsdaFasController) UsdaFasClassify() {
+	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
+	}
+
+	classifyAll, err := data_manage.GetAllBaseFromUsdaFasClassify()
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	//组装一级分类
+	rootMap := make(map[int][]*data_manage.BaseFromUsdaFasClassifyItems)
+	list := make([]*data_manage.BaseFromUsdaFasClassifyItems, 0)
+	for _, classify := range classifyAll {
+		classify.UniqueCode = strconv.Itoa(classify.ClassifyId)
+		if classify.ParentId == 0 {
+			if _, ok := rootMap[classify.ClassifyId]; !ok {
+				rootMap[classify.ClassifyId] = make([]*data_manage.BaseFromUsdaFasClassifyItems, 0)
+				list = append(list, classify)
+
+			}
+		} else {
+			child, ok := rootMap[classify.ParentId]
+			if ok {
+				child = append(child, classify)
+				rootMap[classify.ParentId] = child
+			}
+		}
+	}
+
+	for k, v := range list {
+		child, ok := rootMap[v.ClassifyId]
+		if ok {
+			list[k].Children = child
+		}
+	}
+	//组装二级分类
+	var ret data_manage.BaseFromUsdaFasClassifyResp
+	ret.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// UsdaFasIndexData
+// @Title 获取美国农业部数据
+// @Description 获取美国农业部数据接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   string  true       "分类id"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /usda_fas/index/data [get]
+func (this *BaseFromUsdaFasController) UsdaFasIndexData() {
+	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 = utils.StartIndex(currentIndex, pageSize)
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+	// 增加频度请求入参
+	frequency := this.GetString("Frequency")
+
+	//获取指标
+	var condition string
+	var pars []interface{}
+
+	if classifyId >= 0 {
+		classifyInfo, err := data_manage.GetBaseFromUsdaFasClassifyById(classifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "获取分类信息失败"
+			br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+			return
+		}
+		if classifyInfo.Level == 2 || classifyInfo.ParentId > 0 {
+			condition += ` AND classify_id=? `
+			pars = append(pars, classifyId)
+		} else if classifyInfo.Level == 1 {
+			childClassify, err := data_manage.GetBaseFromUsdaFasClassifyByParentId(classifyId)
+			if err != nil {
+				br.Msg = "获取分类信息失败"
+				br.ErrMsg = "获取子分类信息失败,Err:" + err.Error()
+				return
+			}
+			var classifyList []int
+			for _, v := range childClassify {
+				classifyList = append(classifyList, v.ClassifyId)
+			}
+			condition += ` AND classify_id IN (` + utils.GetOrmInReplace(len(classifyList)) + `) `
+			pars = append(pars, classifyList)
+		}
+	}
+	if frequency != "" {
+		condition += ` AND frequency=? `
+		pars = append(pars, frequency)
+	}
+
+	UsdaFasList, err := data_manage.GetUsdaFasIndex(condition, pars)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	edbCodeList := make([]string, 0)
+	for _, v := range UsdaFasList {
+		edbCodeList = append(edbCodeList, v.IndexCode)
+	}
+	edbInfoMap := make(map[string]*data_manage.EdbInfo)
+	dataMap := make(map[string][]*data_manage.BaseFromUsdaFasData)
+	total := 0
+	if len(edbCodeList) > 0 {
+		edbInfoList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_USDA_FAS, edbCodeList)
+		if err != nil {
+			br.Msg = "获取数据源失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range edbInfoList {
+			edbInfoMap[v.EdbCode] = v
+		}
+		// 首先对分类下的指标按照日期进行分页,再针对日期,进行排序
+		dataTimes, err := data_manage.GetUsdaFasIndexDataTimePageByCodes(edbCodeList, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据日期信息失败,Err:" + err.Error()
+			return
+		}
+		if len(dataTimes) > 0 {
+			startDate := dataTimes[len(dataTimes)-1]
+			endDate := dataTimes[0]
+			// 把截止日往后加1天
+			endDateT, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+			endDate = endDateT.AddDate(0, 0, 1).Format(utils.FormatDate)
+			dataList, e := data_manage.GetUsdaFasIndexDataByDataTime(edbCodeList, startDate, endDate)
+			if e != nil {
+				br.Msg = "获取数据失败"
+				br.ErrMsg = "获取指标数据失败,Err:" + e.Error()
+				return
+			}
+			//将数据按照指标进行分类
+			for _, v := range dataList {
+				dataMap[v.IndexCode] = append(dataMap[v.IndexCode], v)
+			}
+		}
+
+		total, err = data_manage.GetUsdaFasIndexDataTimePageCount(edbCodeList)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resultList := make([]*data_manage.BaseFromUsdaFasIndexList, 0)
+
+	for _, v := range UsdaFasList {
+		product := new(data_manage.BaseFromUsdaFasIndexList)
+		product.BaseFromUsdaFasIndexId = v.BaseFromUsdaFasIndexId
+		product.Unit = v.Unit
+		product.IndexCode = v.IndexCode
+		product.IndexName = v.IndexName
+		product.Frequency = v.Frequency
+		product.ModifyTime = v.ModifyTime
+		if edb, ok := edbInfoMap[v.IndexCode]; ok {
+			product.EdbInfoId = edb.EdbInfoId
+			product.EdbExist = 1
+		}
+
+		/*total, err := data_manage.GetUsdaFasIndexDataCount(v.IndexCode)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+		page := paging.GetPaging(currentIndex, pageSize, total)
+		dataList, err := data_manage.GetUsdaFasIndexData(v.IndexCode, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}*/
+		dataListTmp, ok := dataMap[v.IndexCode]
+		if !ok {
+			dataListTmp = make([]*data_manage.BaseFromUsdaFasData, 0)
+		}
+		product.DataList = dataListTmp
+		product.Paging = page
+		resultList = append(resultList, product)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resultList
+}
+
+// UsdaFasSearchList
+// @Title UsdaFas模糊搜索
+// @Description UsdaFas模糊搜索
+// @Param   Keyword   query   string  ture       "关键字搜索"
+// @Success 200 {object} models.BaseResponse
+// @router /usda_fas/search_list [get]
+func (this *BaseFromUsdaFasController) UsdaFasSearchList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	list := make([]*data_manage.BaseFromUsdaFasIndexSearchItem, 0)
+	var err error
+	//关键字
+	keyword := this.GetString("Keyword")
+	if keyword != "" {
+		keyWordArr := strings.Split(keyword, " ")
+
+		if len(keyWordArr) > 0 {
+			condition := ""
+			for _, v := range keyWordArr {
+				condition += ` AND CONCAT(index_name,index_code) LIKE '%` + v + `%'`
+			}
+			list, err = data_manage.GetUsdaFasItemList(condition)
+			if err != nil {
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				br.Msg = "获取失败"
+				return
+			}
+		}
+
+	} else {
+		// todo es 模糊搜索
+		list, err = data_manage.GetUsdaFasItemList("")
+		if err != nil {
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			br.Msg = "获取失败"
+			return
+		}
+	}
+	classifyIds := make([]int, 0)
+	for _, v := range list {
+		classifyIds = append(classifyIds, v.ClassifyId)
+	}
+	classifyList, err := data_manage.GetBaseFromUsdaFasClassifyByIds(classifyIds)
+	if err != nil {
+		br.Msg = "搜索失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+	classifyMap := make(map[int]int)
+	for _, v := range classifyList {
+		classifyMap[v.ClassifyId] = v.ParentId
+	}
+	for _, v := range list {
+		v.ParentClassifyId = classifyMap[v.ClassifyId]
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// UsdaFasSingleData
+// @Title 获取UsdaFas数据
+// @Description 获取UsdaFas单条数据接口
+// @Param   IndexCode   query   string  true       "指标唯一编码"
+// @Success 200 {object} models.BaseResponse
+// @router /usda_fas/single_data [get]
+func (this *BaseFromUsdaFasController) UsdaFasSingleData() {
+	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
+	}
+	indexCode := this.GetString("IndexCode")
+	indexInfo, err := data_manage.GetBaseFromUsdaFasIndexByIndexCode(indexCode)
+	if err != nil {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+	dataTmpList, err := data_manage.GetUsdaFasIndexDataByCode(indexCode)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_USDA_FAS, indexCode)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取数据源失败"
+		br.ErrMsg = "获取数据源失败,Err:" + err.Error()
+		return
+	}
+
+	var ret data_manage.UsdaFasSingleDataResp
+	var dataList []*data_manage.UsdaFasSingleData
+
+	if edbInfo != nil {
+		ret.EdbInfoId = edbInfo.EdbInfoId
+		ret.EdbExist = 1
+	}
+	ret.ClassifyId = indexInfo.ClassifyId
+	ret.BaseFromUsdaFasIndexId = indexInfo.BaseFromUsdaFasIndexId
+	ret.IndexCode = indexInfo.IndexCode
+	ret.IndexName = indexInfo.IndexName
+	ret.Frequency = indexInfo.Frequency
+	ret.CreateTime = indexInfo.CreateTime.Format(utils.FormatDateTime)
+	ret.ModifyTime = indexInfo.ModifyTime.Format(utils.FormatDateTime)
+	ret.Unit = indexInfo.Unit
+	for _, v := range dataTmpList {
+		tmp := &data_manage.UsdaFasSingleData{
+			Value:    v.Value,
+			DataTime: v.DataTime,
+		}
+		dataList = append(dataList, tmp)
+	}
+	ret.Data = dataList
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// UsdaFasIndexList
+// @Title 美国农业部指标列表
+// @Description 美国农业部指标列表
+// @Param   ClassifyId   query   int  true       "分类id"
+// @Success 200 {object} data_manage.BaseFromUsdaFasClassifyResp
+// @router /usda_fas/classify/index/list [get]
+func (this *BaseFromUsdaFasController) UsdaFasIndexList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	classifyId, _ := this.GetInt("ClassifyId", 0)
+	indexList, err := data_manage.GetUsdaFasIndexByClassifyId(classifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	var ret data_manage.BaseFromUsdaFasClassifyResp
+	list := make([]*data_manage.BaseFromUsdaFasClassifyItems, 0)
+	for _, v := range indexList {
+		classify := new(data_manage.BaseFromUsdaFasClassifyItems)
+		classify.ClassifyId = classifyId
+		classify.BaseFromUsdaFasIndexId = v.BaseFromUsdaFasIndexId
+		classify.IndexCode = v.IndexCode
+		classify.ClassifyName = v.IndexName
+		classify.UniqueCode = fmt.Sprintf("%d_%d", classifyId, v.BaseFromUsdaFasIndexId)
+		list = append(list, classify)
+	}
+	ret.List = list
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// UsdaFasBatchSearch
+// @Title 美国农业部指标查询
+// @Description 美国农业部指标查询
+// @Param   ClassifyIds   query   string  true       "分类id, 多个分类用英文"
+// @Param   Keyword   query   string  true       "关键词, 指标ID/指标名称"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /usda_fas/batch_search [get]
+func (this *BaseFromUsdaFasController) UsdaFasBatchSearch() {
+	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("ClassifyIds")
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+	resp := data_manage.BaseFromUsdaFasIndexSearchList{}
+	total := 0
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	var list = make([]*data_manage.BaseFromUsdaFasIndexList, 0)
+	var condition string
+	var pars []interface{}
+	classifyIds := strings.Split(classifyIdStr, ",")
+	if len(classifyIds) > 0 && classifyIds[0] != `` {
+		condition += " AND 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)
+	}
+	frequencies := this.GetString("Frequencies")
+	if frequencies != "" {
+		frequencyList := strings.Split(frequencies, ",")
+		condition += " AND frequency IN (" + utils.GetOrmInReplace(len(frequencyList)) + " ) "
+		pars = append(pars, frequencyList)
+	}
+	/*if classifyIdStr == `` && keyword == `` && frequencies == `` {
+		resp.Paging = page
+		resp.List = list
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}*/
+	condition += ` AND index_code not in (SELECT edb_code FROM edb_info WHERE source=?) `
+	pars = append(pars, utils.DATA_SOURCE_USDA_FAS)
+
+	list, err := data_manage.GetUsdaFasIndexPage(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total, err = data_manage.GetUsdaFasIndexPageCount(condition, pars)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, total)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// UsdaFasBatchAdd
+// @Title 美国农业部批量新增
+// @Description 美国农业部批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /usda_fas/batch_add [post]
+func (this *BaseFromUsdaFasController) UsdaFasBatchAdd() {
+	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_UsdaFas_" + 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_manage.UsdaFasIndexSource2EdbReq
+		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.UsdaFasIndexSource2Edb(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
+}
+
+// UsdaFasNameCheck
+// @Title 加入指标库的重名检测
+// @Description 加入指标库的重名检测
+// @Param   ClassifyIds   query   string  true       "分类id, 多个分类用英文"
+// @Param   Keyword   query   string  true       "关键词, 指标ID/指标名称"
+// @Success 200 {object} NameCheckResult
+// @router /usda_fas/edb_info/name_check [post]
+func (this *BaseFromUsdaFasController) UsdaFasNameCheck() {
+	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.NameCheckEdbInfoReq
+	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
+	}
+	codeMaxT := 30
+	codeLen := len(req)
+	if codeLen > codeMaxT {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+
+	indexNames := make([]string, 0)
+	resp := make([]*data_manage.EdbNameCheckResult, 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, &data_manage.EdbNameCheckResult{
+			EdbCode: v.EdbCode,
+			EdbName: v.EdbName,
+		})
+		dataItems, err := data_manage.GetEdbDataAllByEdbCode(v.EdbCode, utils.DATA_SOURCE_USDA_FAS, 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_USDA_FAS, 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
+}
+
+// UsdaFasAddCheck
+// @Title 加入指标库指标Id检测
+// @Description 加入指标库指标Id检测
+// @Param	request	body request.BatchAddCheckReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /usda_fas/edb_info/add_check [post]
+func (c *BaseFromUsdaFasController) UsdaFasAddCheck() {
+	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.BatchAddCheckReq
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	codeMaxT := 30
+	codeLen := len(req.IndexCodes)
+
+	// 获取指标库已有指标
+	existsEdb, e := data_manage.GetEdbCodesBySource(utils.DATA_SOURCE_USDA_FAS)
+	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
+	}
+
+	if codeLen == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if codeLen > codeMaxT {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMaxT)
+		return
+	}
+
+	// 查询选中的指标
+	cond := fmt.Sprintf(` AND index_code IN (%s)`, utils.GetOrmInReplace(codeLen))
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.IndexCodes)
+	list, err := data_manage.GetUsdaFasIndex(cond, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
+		return
+	}
+
+	if len(list) > codeMaxT {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMaxT)
+		return
+	}
+
+	resp := make([]*data_manage.BaseFromUsdaFasIndexList, 0)
+	for _, v := range list {
+		if edb, ok := existMap[v.IndexCode]; ok {
+			v.EdbInfoId = edb.EdbInfoId
+			v.EdbClassifyId = edb.ClassifyId
+			v.EdbUniqueCode = edb.UniqueCode
+			v.EdbExist = 1
+		}
+		resp = append(resp, v)
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// UsdaFasEdbInfoAdd
+// @Title 新增指标接口
+// @Description 新增指标接口
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /usda_fas/edb_info/add [post]
+func (this *BaseFromUsdaFasController) UsdaFasEdbInfoAdd() {
+	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
+	}
+
+	count, err := data_manage.GetUsdaFasIndexDataCount(req.EdbCode)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	if count == 0 {
+		br.Msg = "指标不存在"
+	}
+
+	// 指标入库
+	edbInfo, err, errMsg, isSendEmail := data.EdbInfoAdd(utils.DATA_SOURCE_USDA_FAS, 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)
+		}()
+	}
+
+	//新增操作日志
+	{
+		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
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// ExportUsdaFasList
+// @Title 导出美国农业部数据
+// @Description 导出美国农业部数据
+// @Param   ClassifyId   query   int  true       "关键字搜索"
+// @Param   IndexCode   query   string  true       "指标编码"
+// @Success 200  导出成功
+// @router /usda_fas/export [get]
+func (this *BaseFromUsdaFasController) ExportUsdaFasList() {
+	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 && indexCode == "" {
+		br.Msg = "请选择分类或者指标"
+		return
+	}
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+	downLoadnFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	var condition string
+	var pars []interface{}
+	var classifyName string
+	if classifyId > 0 {
+		classifyInfo, err := data_manage.GetBaseFromUsdaFasClassifyById(classifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			return
+		}
+		classifyName = classifyInfo.ClassifyName
+		childClassify, err := data_manage.GetBaseFromUsdaFasClassifyByParentId(classifyId)
+		if err != nil {
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			return
+		}
+
+		if len(childClassify) > 0 {
+			condition += `AND classify_id IN (` + utils.GetOrmInReplace(len(childClassify)) + `)`
+			for _, child := range childClassify {
+				pars = append(pars, child.ClassifyId)
+			}
+		} else {
+			condition += ` AND classify_id=?`
+			pars = append(pars, classifyId)
+		}
+	}
+	if indexCode != "" {
+		condition += ` AND index_code=? `
+		pars = append(pars, indexCode)
+	}
+
+	indexList, err := data_manage.GetUsdaFasIndex(condition, pars)
+	if err != nil {
+		br.Msg = "下载失败"
+		br.ErrMsg = "获取指标失败,Err:" + err.Error()
+		fmt.Println("获取数据失败,Err:" + err.Error())
+		return
+	}
+	if len(indexList) <= 0 {
+		fmt.Println("indexList 为空")
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "success"
+		return
+	}
+
+	codeList := make([]string, 0)
+	frequenciesMap := make(map[string][]*data_manage.BaseFromUsdaFasIndexList)
+	for _, v := range indexList {
+		codeList = append(codeList, v.IndexCode)
+		frequenciesMap[v.Frequency] = append(frequenciesMap[v.Frequency], v)
+	}
+	dataListMap := make(map[string][]*data_manage.BaseFromUsdaFasData)
+	if len(indexList) > 0 {
+		allDataList, e := data_manage.GetUsdaFasIndexDataByCodes(codeList)
+		if e != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + e.Error()
+			return
+		}
+		for _, v := range allDataList {
+			dataListMap[v.IndexCode] = append(dataListMap[v.IndexCode], v)
+		}
+	}
+	// 按照频率分组排序
+	frequencies := []string{
+		"日度", "周度", "旬度", "月度", "季度", "半年度", "年度",
+	}
+	for _, frequency := range frequencies {
+		//获取指标
+		indexCodeList, ok := frequenciesMap[frequency]
+		if !ok {
+			continue
+		}
+		if len(indexCodeList) <= 0 {
+			fmt.Printf("sheet:%s, 不存在指标", frequency)
+			return
+		}
+		var sheetName string
+		switch frequency {
+		case "日度":
+			sheetName = "日度(Daily)"
+		case "周度":
+			sheetName = "周度(Weekly)"
+		case "旬度":
+			sheetName = "旬度(ten-day)"
+		case "月度":
+			sheetName = "月度(Monthly)"
+		case "季度":
+			sheetName = "季度(Quarterly)"
+		case "半年度":
+			sheetName = "半年度(Semi-annual)"
+		case "年度":
+			sheetName = "年度(Annual)"
+		default:
+			sheetName = "其他数据"
+		}
+		sheetNew, err := xlsxFile.AddSheet(sheetName)
+		if err != nil {
+			fmt.Println("新增Sheet失败", err.Error())
+			return
+		}
+		secNameRow := sheetNew.AddRow()
+		frequencyRow := sheetNew.AddRow()
+		unitRow := sheetNew.AddRow()
+		lastModifyDateRow := sheetNew.AddRow()
+
+		var indexIdList []int
+		for _, idx := range frequenciesMap[frequency] {
+			indexIdList = append(indexIdList, idx.BaseFromUsdaFasIndexId)
+		}
+		dataTimeList, err := data_manage.GetUsdaFasDataDataTimeByIndexId(indexIdList)
+		if err != nil {
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取数据时间失败,Err:" + err.Error()
+			fmt.Println("获取数据时间失败", err.Error())
+			return
+		}
+
+		// 添加excel左侧指标日期
+		setRowIndex := 4
+		for rk, dv := range dataTimeList {
+			rowIndex := setRowIndex + rk
+			row := sheetNew.Row(rowIndex)
+			displayDate, _ := time.Parse(utils.FormatDate, dv)
+			displayDateCell := row.AddCell()
+			style := new(xlsx.Style)
+			style.ApplyAlignment = true
+			style.Alignment.WrapText = true
+			displayDateCell.SetStyle(style)
+			displayDateCell.SetDate(displayDate)
+
+		}
+		for k, icl := range indexCodeList {
+			// 获取数据
+			dataList, ok := dataListMap[icl.IndexCode]
+			if !ok {
+				continue
+			}
+			if k == 0 {
+				secNameRow.AddCell().SetValue("指标名称/Metric Name")
+				frequencyRow.AddCell().SetValue("频度/Frequency")
+				unitRow.AddCell().SetValue("单位/Unit")
+				lastModifyDateRow.AddCell().SetValue("更新时间/Update Time")
+				min := k * 3
+				sheetNew.SetColWidth(min, min, 15)
+			}
+			if len(dataList) == 0 {
+				continue
+			}
+			secNameRow.AddCell().SetValue(icl.IndexName)
+			frequencyRow.AddCell().SetValue(icl.Frequency)
+			unitRow.AddCell().SetValue(icl.Unit)
+
+			timeDate, err := time.Parse(utils.FormatDateTime, dataList[0].ModifyTime)
+			if err != nil {
+				continue
+			}
+			lastModifyDateRow.AddCell().SetValue(timeDate.Format(utils.FormatDate))
+			dataInfoMap := make(map[string]*data_manage.BaseFromUsdaFasData)
+			for _, v := range dataList {
+				dataInfoMap[v.DataTime] = v
+			}
+
+			for rk, dtv := range dataTimeList {
+				rowIndex := setRowIndex + rk
+				row := sheetNew.Row(rowIndex)
+				displayDateCell := row.AddCell()
+				tmpData, ok := dataInfoMap[dtv]
+				if ok {
+					displayDateCell.SetValue(tmpData.Value)
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadnFilePath)
+	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("")
+		e := xlsxFile.Save(downLoadnFilePath)
+		if e != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+
+	fileName := classifyName
+	if indexCode != "" && len(indexList) == 1 {
+		fileName = indexList[0].IndexName
+	}
+	fileName = strings.Replace(fileName, ": ", "_", -1)
+	fileName = strings.Replace(fileName, ", ", "_", -1)
+	fileName = strings.Replace(fileName, " ", "_", -1)
+	fileName += time.Now().Format("06.01.02") + `.xlsx` //文件名称
+	fmt.Println(fileName)
+	this.Ctx.Output.Download(downLoadnFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadnFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}
+
+// GetFrequency
+// @Title 美国农业部数据频度
+// @Description 美国农业部数据频度接口
+// @Param   ClassifyId   query   string  true       "分类Id"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /usda_fas/frequency [get]
+func (this *BaseFromUsdaFasController) GetFrequency() {
+	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
+	}
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+
+	frequencyList, err := data_manage.GetUsdaFasFrequencyByClassifyId(classifyId)
+	if err != nil {
+		br.Msg = "获取频度失败"
+		br.ErrMsg = "获取频度失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = frequencyList
+}
+
+// BatchAddEdbCheck
+// @Title 新增校验
+// @Description 新增校验
+// @Param	request	body data_manage.BatchManualEdbReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /usda_fas/batch/add/check [post]
+func (c *BaseFromUsdaFasController) BatchAddEdbCheck() {
+	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
+	}
+
+	// 最大批量添加的数量
+	codeMaxT := 31
+
+	var req data_manage.BatchCheckUsdaFasEdbReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,Err:" + err.Error()
+		return
+	}
+	req.Keyword = strings.TrimSpace(req.Keyword)
+	classifyIdStr := req.ClassifyIds
+
+	var list = make([]*data_manage.BaseFromUsdaFasIndexList, 0)
+	var condition string
+	var pars []interface{}
+
+	if req.ListAll {
+		classifyIds := strings.Split(classifyIdStr, ",")
+		if len(classifyIds) > 0 && classifyIds[0] != `` {
+			condition += " AND classify_id IN (" + utils.GetOrmInReplace(len(classifyIds)) + " ) "
+			pars = append(pars, classifyIds)
+		}
+		keyword := req.Keyword
+		if keyword != `` {
+			condition += " AND (index_name like ? OR index_code like ?) "
+			pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+		}
+		frequencies := req.Frequencies
+		if frequencies != "" {
+			frequencyList := strings.Split(frequencies, ",")
+			condition += " AND frequency IN (" + utils.GetOrmInReplace(len(frequencyList)) + " ) "
+			pars = append(pars, frequencyList)
+		}
+		codes := req.TradeCodeList
+		codeList := make([]string, 0)
+		if codes != "" {
+			codeList = strings.Split(codes, ",")
+		}
+		if len(codeList) > 0 {
+			condition += ` AND index_code not in (` + utils.GetOrmInReplace(len(codeList)) + `) `
+			pars = append(pars, codeList)
+		}
+	} else {
+		codes := req.TradeCodeList
+		codeList := make([]string, 0)
+		if codes != "" {
+			codeList = strings.Split(codes, ",")
+		}
+		if len(codeList) <= 0 {
+			br.Msg = "请选择指标"
+			br.ErrMsg = "请选择指标"
+			return
+		}
+		// 指标
+		condition += ` AND index_code in (` + utils.GetOrmInReplace(len(codeList)) + `) `
+		pars = append(pars, codeList)
+
+	}
+	condition += ` AND index_code not in (SELECT edb_code FROM edb_info WHERE source=?) `
+	pars = append(pars, utils.DATA_SOURCE_USDA_FAS)
+
+	list, err = data_manage.GetUsdaFasIndexPage(condition, pars, 0, codeMaxT)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	if len(list) >= codeMaxT {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+
+	br.Data = list
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 1 - 0
controllers/report_chapter_type.go

@@ -161,6 +161,7 @@ func (this *ReportChapterTypeController) Add() {
 	item.IsSet = 0
 	item.ReportChapterTypeKey = req.ReportChapterTypeName
 	item.TickerTitle = req.ReportChapterTypeName
+	item.IsShow = 1
 
 	if e = item.Create(); e != nil {
 		br.Msg = "操作失败"

+ 15 - 1
controllers/report_v2.go

@@ -481,7 +481,12 @@ func (this *ReportController) Add() {
 	item.IsPublicPublish = req.IsPublicPublish
 	item.ReportCreateTime = time.Now()
 
-	err, errMsg := services.AddReportAndChapter(item, req.InheritReportId, req.GrantAdminIdList)
+	reportDate := time.Now()
+	t, _ := time.ParseInLocation(utils.FormatDate, req.CreateTime, time.Local)
+	if !t.IsZero() {
+		reportDate = t
+	}
+	err, errMsg := services.AddReportAndChapter(item, req.InheritReportId, req.GrantAdminIdList, reportDate)
 	if err != nil {
 		br.Msg = "保存失败"
 		if errMsg != "" {
@@ -1316,7 +1321,11 @@ func (this *ReportController) PublishCancelReport() {
 	}
 	go func() {
 		_, _ = models.AddReportStateRecord(recordItem)
+
+		// 重置小程序详情页海报
+		_ = services.ResetMiniProgramReportDetailCover(reportInfo.Id)
 	}()
+
 	br.Ret = 200
 	br.Success = true
 }
@@ -1645,6 +1654,11 @@ func (this *ReportController) CancelApprove() {
 	//	return
 	//}
 
+	// 重置小程序详情页海报
+	go func() {
+		_ = services.ResetMiniProgramReportDetailCover(reportItem.Id)
+	}()
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"

+ 284 - 0
controllers/residual_analysis/residual_analysis_controller.go

@@ -0,0 +1,284 @@
+package residual_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/residual_analysis_model"
+	"eta/eta_api/services/residual_analysis_service"
+)
+
+// ResidualAnalysisController 残差分析
+type ResidualAnalysisController struct {
+	controllers.BaseAuthController
+}
+
+// ResidualAnalysisPreview
+// @Title 残差分析预览
+// @Description 残差分析预览
+// @Success 200 {object} residual_analysis_model.ResidualAnalysisResp
+// @router /residual/analysis/preview [post]
+func (this *ResidualAnalysisController) ResidualAnalysisPreview() {
+	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 residual_analysis_model.ResidualAnalysisReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.EdbInfoIdA == req.EdbInfoIdB {
+		br.Msg = "自变量指标和因变量指标不能相同"
+		br.ErrMsg = "自变量指标和因变量指标不能相同"
+		return
+	}
+
+	resp, err := residual_analysis_service.ResidualAnalysisPreview(req)
+	if err != nil {
+		br.Ret = 403
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ContrastPreview
+// @Title 对比指标预览
+// @Description 对比指标预览
+// @Success 200 {object} residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+// @router /contrast/preview [get]
+func (this *ResidualAnalysisController) ContrastPreview() {
+	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
+	}
+
+	IndexCode := this.GetString("IndexCode")
+	if IndexCode == "" {
+		br.Ret = 403
+		br.Msg = "IndexCode不能为空"
+		br.ErrMsg = "IndexCode不能为空"
+		return
+	}
+
+	resp, err := residual_analysis_service.ContrastPreview(IndexCode)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// SaveResidualAnalysis
+// @Title 保存残差分析指标
+// @Description 保存残差分析指标
+// @Success 200
+// @router /save/residual/analysis [post]
+func (this *ResidualAnalysisController) SaveResidualAnalysis() {
+	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 residual_analysis_model.ResidualAnalysisIndexSaveReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ClassifyId == 0 {
+		br.Msg = "分类id不能为空"
+		br.ErrMsg = "分类id不能为空"
+		return
+	}
+
+	if req.Source == 0 {
+		br.Msg = "来源不能为空"
+		br.ErrMsg = "来源不能为空"
+		return
+	}
+
+	err = residual_analysis_service.SaveResidualAnalysis(req, sysUser)
+	if err != nil {
+		br.Ret = 403
+		br.Msg = err.Error()
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "添加成功"
+}
+
+// SaveResidualAnalysisConfig
+// @Title 保存残差指标配置
+// @Description 保存残差指标配置
+// @Success 200
+// @router /save/residual/analysis/config [post]
+func (this *ResidualAnalysisController) SaveResidualAnalysisConfig() {
+	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 residual_analysis_model.ResidualAnalysisReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	configId, err := residual_analysis_service.SaveResidualAnalysisConfig(req, sysUser)
+	if err != nil {
+		br.Ret = 403
+		br.Msg = "保存失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "添加成功"
+	br.Data = configId
+}
+
+// ResidualAnalysisDetail
+// @Title 残差分析指标详情
+// @Description 残差分析指标详情
+// @Success 200 {object} []*data_manage.EdbInfoList
+// @router /residual/analysis/detail [get]
+func (this *ResidualAnalysisController) ResidualAnalysisDetail() {
+	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
+	}
+
+	EdbInfoId, err := this.GetInt("EdbInfoId")
+	if err != nil {
+		br.Msg = "EdbInfoId参数异常!"
+		br.ErrMsg = "EdbInfoId参数解析失败,Err:" + err.Error()
+	}
+	if EdbInfoId <= 0 {
+		br.Msg = "EdbInfoId参数异常!"
+		br.ErrMsg = "EdbInfoId参数异常!"
+	}
+
+	resp, err := residual_analysis_service.ResidualAnalysisDetail(EdbInfoId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		br.Ret = 403
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// CheckResidualAnalysisExist
+// @Title 校验残差指标是否存在
+// @Description 校验残差指标是否存在
+// @Success 200 {object}
+// @router /check/residual/analysis/exist [get]
+func (this *ResidualAnalysisController) CheckResidualAnalysisExist() {
+	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
+	}
+
+	configId, err := this.GetInt("ConfigId")
+	if err != nil {
+		br.Msg = "EdbInfoId参数异常!"
+		br.ErrMsg = "EdbInfoId参数解析失败,Err:" + err.Error()
+	}
+	if configId <= 0 {
+		br.Msg = "ConfigId参数异常!"
+		br.ErrMsg = "ConfigId参数异常!"
+	}
+
+	resp, err := residual_analysis_service.CheckResidualAnalysisExist(configId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		br.Ret = 403
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 35 - 20
controllers/sandbox/sandbox.go

@@ -2394,16 +2394,21 @@ func (this *SandboxController) LinkEdbInfoCheck() {
 		return
 	}
 	edbList := make([]*sandbox.SandboxLinkCheckItem, 0)
-	for _, v := range edbInfoList {
-		tmp := &sandbox.SandboxLinkCheckItem{
-			Id:         v.EdbInfoId,
-			Name:       v.EdbName,
-			UniqueCode: v.UniqueCode,
-			ClassifyId: v.ClassifyId,
+	for _, id := range req.EdbInfoIdList {
+		for _, v := range edbInfoList {
+			if v.EdbInfoId == id {
+				tmp := &sandbox.SandboxLinkCheckItem{
+					Id:         v.EdbInfoId,
+					Name:       v.EdbName,
+					UniqueCode: v.UniqueCode,
+					ClassifyId: v.ClassifyId,
+				}
+				edbList = append(edbList, tmp)
+			}
 		}
-		edbList = append(edbList, tmp)
 	}
 
+
 	chartList, err := data_manage.GetChartInfoByIdList(req.ChartInfoIdList)
 	if err != nil {
 		br.Msg = `获取失败`
@@ -2411,16 +2416,21 @@ func (this *SandboxController) LinkEdbInfoCheck() {
 		return
 	}
 	chartListTmp := make([]*sandbox.SandboxLinkCheckItem, 0)
-	for _, v := range chartList {
-		tmp := &sandbox.SandboxLinkCheckItem{
-			Id:         v.ChartInfoId,
-			Name:       v.ChartName,
-			UniqueCode: v.UniqueCode,
-			ClassifyId: v.ChartClassifyId,
+	for _, id := range req.ChartInfoIdList {
+		for _, v := range chartList {
+			if v.ChartInfoId == id {
+				tmp := &sandbox.SandboxLinkCheckItem{
+					Id:         v.ChartInfoId,
+					Name:       v.ChartName,
+					UniqueCode: v.UniqueCode,
+					ClassifyId: v.ChartClassifyId,
+				}
+				chartListTmp = append(chartListTmp, tmp)
+			}
 		}
-		chartListTmp = append(chartListTmp, tmp)
 	}
 
+
 	reportList, err := models.GetSimpleReportByIds(req.ReportIdList)
 	if err != nil {
 		br.Msg = `获取失败`
@@ -2428,14 +2438,19 @@ func (this *SandboxController) LinkEdbInfoCheck() {
 		return
 	}
 	reportListTmp := make([]*sandbox.SandboxLinkCheckItem, 0)
-	for _, v := range reportList {
-		tmp := &sandbox.SandboxLinkCheckItem{
-			Id:         v.Id,
-			Name:       v.Title,
-			UniqueCode: v.ReportCode,
+	for _, id := range req.ReportIdList {
+		for _, v := range reportList {
+			if v.Id == id {
+				tmp := &sandbox.SandboxLinkCheckItem{
+					Id:         v.Id,
+					Name:       v.Title,
+					UniqueCode: v.ReportCode,
+				}
+				reportListTmp = append(reportListTmp, tmp)
+			}
 		}
-		reportListTmp = append(reportListTmp, tmp)
 	}
+
 	resp.EdbInfoIdList = edbList
 	resp.ChartInfoIdList = chartListTmp
 	resp.ReportIdList = reportListTmp

+ 356 - 0
models/ai_predict_model/ai_predict_model_classify.go

@@ -0,0 +1,356 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelClassify 同花顺高频数据-分类
+type AiPredictModelClassify struct {
+	AiPredictModelClassifyId int       `orm:"column(ai_predict_model_classify_id);pk"`
+	UniqueCode               string    `description:"唯一编码"`
+	ClassifyName             string    `description:"分类名称"`
+	ClassifyNameEn           string    `description:"英文分类名称"`
+	ParentId                 int       `description:"父级ID"`
+	Level                    int       `description:"层级"`
+	LevelPath                string    `description:"层级路径"`
+	Sort                     int       `description:"排序"`
+	RootId                   int       `description:"顶级分类ID"`
+	SysUserId                int       `description:"创建人ID"`
+	SysUserRealName          string    `description:"创建人姓名"`
+	CreateTime               time.Time `description:"创建时间"`
+	ModifyTime               time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelClassify) TableName() string {
+	return "ai_predict_model_classify"
+}
+
+type AiPredictModelClassifyCols struct {
+	PrimaryId       string
+	UniqueCode      string
+	ClassifyName    string
+	ClassifyNameEn  string
+	ParentId        string
+	Level           string
+	LevelPath       string
+	Sort            string
+	RootId          string
+	SysUserId       string
+	SysUserRealName string
+	CreateTime      string
+	ModifyTime      string
+}
+
+func (m *AiPredictModelClassify) Cols() AiPredictModelClassifyCols {
+	return AiPredictModelClassifyCols{
+		PrimaryId:       "ai_predict_model_classify_id",
+		UniqueCode:      "unique_code",
+		ClassifyName:    "classify_name",
+		ClassifyNameEn:  "classify_name_en",
+		ParentId:        "parent_id",
+		Level:           "level",
+		LevelPath:       "level_path",
+		Sort:            "sort",
+		RootId:          "root_id",
+		SysUserId:       "sys_user_id",
+		SysUserRealName: "sys_user_real_name",
+		CreateTime:      "create_time",
+		ModifyTime:      "modify_time",
+	}
+}
+
+func (m *AiPredictModelClassify) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelClassifyId = int(id)
+	return
+}
+
+func (m *AiPredictModelClassify) CreateMulti(items []*AiPredictModelClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelClassify) 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.AiPredictModelClassifyId).Exec()
+	return
+}
+
+func (m *AiPredictModelClassify) 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 *AiPredictModelClassify) 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 *AiPredictModelClassify) GetItemById(id int) (item *AiPredictModelClassify, 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 *AiPredictModelClassify) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelClassify, 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 *AiPredictModelClassify) 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 *AiPredictModelClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelClassify, 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 *AiPredictModelClassify) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelClassify, 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
+}
+
+// AiPredictModelClassifyItem 同花顺高频数据信息
+type AiPredictModelClassifyItem 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       []*AiPredictModelClassifyItem `description:"子分类"`
+}
+
+func (m *AiPredictModelClassify) Format2Item() (item *AiPredictModelClassifyItem) {
+	item = new(AiPredictModelClassifyItem)
+	item.ClassifyId = m.AiPredictModelClassifyId
+	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([]*AiPredictModelClassifyItem, 0)
+	return
+}
+
+type AiPredictModelClassifyAddReq struct {
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级ID, 第一级传0"`
+	Level        int    `description:"层级, 第一级传0, 其余传上一级的层级"`
+}
+
+type AiPredictModelClassifyEditReq struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+}
+
+func (m *AiPredictModelClassify) GetSortMax(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT MAX(%s) FROM %s WHERE %s = ?`, m.Cols().Sort, m.TableName(), m.Cols().ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+type AiPredictModelClassifyRemoveReq struct {
+	ClassifyId int `description:"分类ID"`
+	IndexId    int `description:"标的ID"`
+}
+
+type AiPredictModelClassifyListResp struct {
+	AllNodes []*AiPredictModelClassifyListItem `description:"分类节点"`
+}
+
+type AiPredictModelClassifyListItem struct {
+	NodeType     int                               `description:"节点类型: 0-分类; 1-指标"`
+	NodeName     string                            `description:"节点名称"`
+	ClassifyId   int                               `description:"分类ID"`
+	ClassifyName string                            `description:"分类名称"`
+	IndexId      int                               `description:"指标ID"`
+	IndexCode    string                            `description:"指标编码"`
+	IndexName    string                            `description:"指标名称"`
+	ParentId     int                               `description:"父级ID"`
+	Level        int                               `description:"层级"`
+	Sort         int                               `description:"排序"`
+	UniqueCode   string                            `description:"唯一编码, 指标为IndexCode"`
+	Children     []*AiPredictModelClassifyListItem `description:"子分类"`
+}
+
+type AiPredictModelClassifyMoveReq struct {
+	ClassifyId       int `description:"分类ID"`
+	ParentClassifyId int `description:"父级分类ID"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类ID"`
+	NextClassifyId   int `description:"下一个兄弟节点分类ID"`
+	ItemId           int `description:"指标ID, 如果指标ID有值,则移动对象为指标,否则认为移动对象为分类"`
+	PrevItemId       int `description:"上一个指标ID"`
+	NextItemId       int `description:"下一个指标ID"`
+}
+
+func GetAiPredictModelClassifyById(classifyId int) (item *AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = ?`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func GetAiPredictModelClassifyByRootIdLevel(rootId int, orderStr string) (items []*AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM ai_predict_model_classify WHERE root_id = ? `
+	if orderStr != "" {
+		sql += orderStr
+	} else {
+		sql += ` order by level desc, sort asc, ai_predict_model_classify_id asc`
+	}
+	_, err = o.Raw(sql, rootId).QueryRows(&items)
+	return
+}
+
+// UpdateAiPredictModelClassifySortByParentId 根据父类id更新排序
+func UpdateAiPredictModelClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update ai_predict_model_classify set sort = ` + updateSort + ` WHERE parent_id = ? AND sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( ai_predict_model_classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstAiPredictModelClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func GetFirstAiPredictModelClassifyByParentId(parentId int) (item *AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM ai_predict_model_classify WHERE parent_id = ? order by sort asc,ai_predict_model_classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+func UpdateAiPredictModelClassifyChildByParentClassifyId(classifyIds []int, rootId int, levelStep 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 ai_predict_model_classify SET root_id = ?, level = level+? where ai_predict_model_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	_, err = o.Raw(sql, pars).Exec()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// GetAiPredictModelIndexCountByClassifyId 获取目录下(包含子目录)的指标数量
+func GetAiPredictModelIndexCountByClassifyId(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM ai_predict_model_index AS a
+				WHERE a.classify_id IN(
+				SELECT t.ai_predict_model_classify_id FROM 
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM ai_predict_model_classify WHERE parent_id IS NOT NULL) rd,
+					 (SELECT @pid := ?) pd 
+				WHERE FIND_IN_SET(parent_id, @pid) > 0 
+				  AND @pid := CONCAT(@pid, ',', ai_predict_model_classify_id) 
+				UNION SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = @pid
+				)AS t
+			)`
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// GetAiPredictModelClassifyCountByClassifyId 获取目录下子目录数量
+func GetAiPredictModelClassifyCountByClassifyId(chartClassifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM (
+			SELECT rd.*
+			FROM (SELECT * FROM ai_predict_model_classify WHERE parent_id IS NOT NULL) rd,
+				 (SELECT @pid := ?) pd 
+			WHERE FIND_IN_SET(parent_id, @pid) > 0 
+			  AND @pid := CONCAT(@pid, ',', ai_predict_model_classify_id) 
+			UNION SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = @pid
+			)AS t
+			WHERE t.ai_predict_model_classify_id <> ?`
+	err = o.Raw(sql, chartClassifyId, chartClassifyId).QueryRow(&count)
+	return
+}
+
+// RemoveAiPredictModelClassify 删除分类及子分类
+func RemoveAiPredictModelClassify(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM ai_predict_model_classify
+				WHERE ai_predict_model_classify_id IN(
+				SELECT t.ai_predict_model_classify_id FROM
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM ai_predict_model_classify WHERE parent_id IS NOT NULL) rd,
+				(SELECT @pid := ?) pd
+				WHERE FIND_IN_SET(parent_id, @pid) > 0
+				AND @pid := CONCAT(@pid, ',', ai_predict_model_classify_id)
+				UNION SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = @pid
+				)AS t
+			)`
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}

+ 261 - 0
models/ai_predict_model/ai_predict_model_dashboard.go

@@ -0,0 +1,261 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelDashboard AI预测模型-BI看板
+type AiPredictModelDashboard struct {
+	AiPredictModelDashboardId int       `orm:"column(ai_predict_model_dashboard_id);pk"`
+	AiPredictModelIndexId     int       `description:"标的ID"`
+	DashboardName             string    `description:"看板名称"`
+	Sort                      int       `description:"排序"`
+	SysUserId                 int       `description:"创建人ID"`
+	SysUserRealName           string    `description:"创建人姓名"`
+	CreateTime                time.Time `description:"创建时间"`
+	ModifyTime                time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboard) TableName() string {
+	return "ai_predict_model_dashboard"
+}
+
+type AiPredictModelDashboardCols struct {
+	PrimaryId             string
+	AiPredictModelIndexId string
+	DashboardName         string
+	Sort                  string
+	SysUserId             string
+	SysUserRealName       string
+	CreateTime            string
+	ModifyTime            string
+}
+
+func (m *AiPredictModelDashboard) Cols() AiPredictModelDashboardCols {
+	return AiPredictModelDashboardCols{
+		PrimaryId:             "ai_predict_model_dashboard_id",
+		AiPredictModelIndexId: "ai_predict_model_index_id",
+		DashboardName:         "dashboard_name",
+		Sort:                  "sort",
+		SysUserId:             "sys_user_id",
+		SysUserRealName:       "sys_user_real_name",
+		CreateTime:            "create_time",
+		ModifyTime:            "modify_time",
+	}
+}
+
+func (m *AiPredictModelDashboard) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelDashboardId = int(id)
+	return
+}
+
+func (m *AiPredictModelDashboard) CreateMulti(items []*AiPredictModelDashboard) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelDashboard) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelDashboard) 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.AiPredictModelDashboardId).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboard) 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 *AiPredictModelDashboard) 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 *AiPredictModelDashboard) GetItemById(id int) (item *AiPredictModelDashboard, 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 *AiPredictModelDashboard) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelDashboard, 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 *AiPredictModelDashboard) 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 *AiPredictModelDashboard) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelDashboard, 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 *AiPredictModelDashboard) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelDashboard, 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
+}
+
+// AiPredictModelDashboardItem AI预测模型-BI看板
+type AiPredictModelDashboardItem struct {
+	DashboardId     int    `description:"看板ID"`
+	IndexId         int    `description:"标的ID"`
+	DashboardName   string `description:"看板名称"`
+	Sort            int    `description:"排序"`
+	SysUserId       int    `description:"创建人ID"`
+	SysUserRealName string `description:"创建人姓名"`
+	CreateTime      string `description:"创建时间"`
+	ModifyTime      string `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboard) Format2Item() (item *AiPredictModelDashboardItem) {
+	item = new(AiPredictModelDashboardItem)
+	item.DashboardId = m.AiPredictModelDashboardId
+	item.IndexId = m.AiPredictModelIndexId
+	item.DashboardName = m.DashboardName
+	item.Sort = m.Sort
+	item.SysUserId = m.SysUserId
+	item.SysUserRealName = m.SysUserRealName
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+// AiPredictModelDashboardDetailResp BI看板详情响应
+type AiPredictModelDashboardDetailResp struct {
+	*AiPredictModelDashboardItem
+	CreateUserId       int    `description:"标的创建人ID"`
+	CreateUserRealName string `description:"标的创建人姓名"`
+	List               []*AiPredictModelDashboardDetailItem
+}
+
+// AiPredictModelDashboardSaveReq BI看板保存请求
+type AiPredictModelDashboardSaveReq struct {
+	IndexId       int    `description:"标的ID"`
+	DashboardName string `description:"看板名称"`
+	List          []*AiPredictModelDashboardDetailReq
+}
+
+type AiPredictModelDashboardDetailReq struct {
+	Type       int
+	UniqueCode string
+	Sort       int
+}
+
+// SaveIndexDashboard 保存标的看板
+func (m *AiPredictModelDashboard) SaveIndexDashboard(dashboardItem *AiPredictModelDashboard, dashboardDetails []*AiPredictModelDashboardDetail, isUpdate bool, updateCols []string) (err error) {
+	if dashboardItem == nil {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("trans begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 新增/更新看板
+	var dashboardId int
+	if !isUpdate {
+		newId, e := tx.Insert(dashboardItem)
+		if e != nil {
+			err = fmt.Errorf("insert dashboard err: %v", e)
+			return
+		}
+		dashboardId = int(newId)
+	} else {
+		_, e = tx.Update(dashboardItem, updateCols...)
+		if e != nil {
+			err = fmt.Errorf("update dashboard err: %v", e)
+			return
+		}
+		dashboardId = dashboardItem.AiPredictModelDashboardId
+	}
+
+	// 清空详情并新增
+	if isUpdate {
+		sql := `DELETE FROM ai_predict_model_dashboard_detail WHERE ai_predict_model_dashboard_id = ?`
+		_, e = tx.Raw(sql, dashboardItem.AiPredictModelDashboardId).Exec()
+		if e != nil {
+			err = fmt.Errorf("clear dashboard detail err: %v", e)
+			return
+		}
+	}
+	if len(dashboardDetails) > 0 {
+		for _, v := range dashboardDetails {
+			v.AiPredictModelDashboardId = dashboardId
+		}
+		_, e = tx.InsertMulti(utils.MultiAddNum, dashboardDetails)
+		if e != nil {
+			err = fmt.Errorf("insert dashboard detail err: %v", e)
+			return
+		}
+	}
+	return
+}

+ 176 - 0
models/ai_predict_model/ai_predict_model_dashboard_detail.go

@@ -0,0 +1,176 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelDashboardDetail AI预测模型-BI看板
+type AiPredictModelDashboardDetail struct {
+	AiPredictModelDashboardDetailId int       `orm:"column(ai_predict_model_dashboard_detail_id);pk"`
+	AiPredictModelDashboardId       int       `description:"看板ID"`
+	Type                            int       `description:"类型:1-图表;2-表格"`
+	UniqueCode                      string    `description:"图表/表格唯一编码"`
+	Sort                            int       `description:"排序"`
+	CreateTime                      time.Time `description:"创建时间"`
+	ModifyTime                      time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboardDetail) TableName() string {
+	return "ai_predict_model_dashboard_detail"
+}
+
+type AiPredictModelDashboardDetailCols struct {
+	PrimaryId                 string
+	AiPredictModelDashboardId string
+	Type                      string
+	UniqueCode                string
+	Sort                      string
+	CreateTime                string
+	ModifyTime                string
+}
+
+func (m *AiPredictModelDashboardDetail) Cols() AiPredictModelDashboardDetailCols {
+	return AiPredictModelDashboardDetailCols{
+		PrimaryId:                 "ai_predict_model_dashboard_detail_id",
+		AiPredictModelDashboardId: "ai_predict_model_dashboard_id",
+		Type:                      "type",
+		UniqueCode:                "unique_code",
+		Sort:                      "sort",
+		CreateTime:                "create_time",
+		ModifyTime:                "modify_time",
+	}
+}
+
+func (m *AiPredictModelDashboardDetail) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelDashboardDetailId = int(id)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) CreateMulti(items []*AiPredictModelDashboardDetail) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) 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.AiPredictModelDashboardDetailId).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) 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 *AiPredictModelDashboardDetail) 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 *AiPredictModelDashboardDetail) GetItemById(id int) (item *AiPredictModelDashboardDetail, 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 *AiPredictModelDashboardDetail) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelDashboardDetail, 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 *AiPredictModelDashboardDetail) 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 *AiPredictModelDashboardDetail) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelDashboardDetail, 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 *AiPredictModelDashboardDetail) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelDashboardDetail, 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
+}
+
+// AiPredictModelDashboardDetailItem AI预测模型-BI看板详情
+type AiPredictModelDashboardDetailItem struct {
+	DashboardDetailId int    `description:"详情ID"`
+	DashboardId       int    `description:"看板ID"`
+	Type              int    `description:"类型:1-图表;2-表格"`
+	UniqueCode        string `description:"图表/表格唯一编码"`
+	Sort              int    `description:"排序"`
+	CreateTime        string `description:"创建时间"`
+	ModifyTime        string `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboardDetail) Format2Item() (item *AiPredictModelDashboardDetailItem) {
+	item = new(AiPredictModelDashboardDetailItem)
+	item.DashboardDetailId = m.AiPredictModelDashboardDetailId
+	item.DashboardId = m.AiPredictModelDashboardId
+	item.Type = m.Type
+	item.UniqueCode = m.UniqueCode
+	item.Sort = m.Sort
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}

+ 205 - 0
models/ai_predict_model/ai_predict_model_data.go

@@ -0,0 +1,205 @@
+package data_manage
+
+import (
+	"database/sql"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	ModelDataSourceMonthly = 1 // 月度预测数据
+	ModelDataSourceDaily   = 2 // 日度预测数据
+)
+
+// AiPredictModelData AI预测模型标的数据
+type AiPredictModelData struct {
+	AiPredictModelDataId  int             `orm:"column(ai_predict_model_data_id);pk"`
+	AiPredictModelIndexId int             `description:"标的ID"`
+	IndexCode             string          `description:"标的编码"`
+	DataTime              time.Time       `description:"数据日期"`
+	Value                 sql.NullFloat64 `description:"实际值"`
+	PredictValue          sql.NullFloat64 `description:"预测值"`
+	Direction             string          `description:"方向"`
+	DeviationRate         string          `description:"偏差率"`
+	CreateTime            time.Time       `description:"创建时间"`
+	ModifyTime            time.Time       `description:"修改时间"`
+	DataTimestamp         int64           `description:"数据日期时间戳"`
+	Source                int             `description:"来源:1-月度预测(默认);2-日度预测"`
+}
+
+func (m *AiPredictModelData) TableName() string {
+	return "ai_predict_model_data"
+}
+
+type AiPredictModelDataCols struct {
+	PrimaryId             string
+	AiPredictModelIndexId string
+	IndexCode             string
+	DataTime              string
+	Value                 string
+	PredictValue          string
+	Direction             string
+	DeviationRate         string
+	CreateTime            string
+	ModifyTime            string
+	DataTimestamp         string
+	Source                string
+}
+
+func (m *AiPredictModelData) Cols() AiPredictModelDataCols {
+	return AiPredictModelDataCols{
+		PrimaryId:             "ai_predict_model_data_id",
+		AiPredictModelIndexId: "ai_predict_model_index_id",
+		IndexCode:             "index_code",
+		DataTime:              "data_time",
+		Value:                 "value",
+		PredictValue:          "predict_value",
+		Direction:             "direction",
+		DeviationRate:         "deviation_rate",
+		CreateTime:            "create_time",
+		ModifyTime:            "modify_time",
+		DataTimestamp:         "data_timestamp",
+		Source:                "source",
+	}
+}
+
+func (m *AiPredictModelData) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelDataId = int(id)
+	return
+}
+
+func (m *AiPredictModelData) CreateMulti(items []*AiPredictModelData) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelData) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelData) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sqlRun, m.AiPredictModelDataId).Exec()
+	return
+}
+
+func (m *AiPredictModelData) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sqlRun, ids).Exec()
+	return
+}
+
+func (m *AiPredictModelData) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sqlRun, pars).Exec()
+	return
+}
+
+func (m *AiPredictModelData) GetItemById(id int) (item *AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sqlRun, id).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelData) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sqlRun := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sqlRun, pars).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelData) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sqlRun, pars).QueryRow(&count)
+	return
+}
+
+func (m *AiPredictModelData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelData, 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
+	}
+	sqlRun := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sqlRun, pars).QueryRows(&items)
+	return
+}
+
+func (m *AiPredictModelData) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelData, 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
+	}
+	sqlRun := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sqlRun, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// AiPredictModelDataItem AI预测模型标的数据
+type AiPredictModelDataItem struct {
+	DataId        int      `description:"ID"`
+	IndexId       int      `description:"标的ID"`
+	IndexCode     string   `description:"标的编码"`
+	DataTime      string   `description:"数据日期"`
+	Value         *float64 `description:"实际值"`
+	PredictValue  *float64 `description:"预测值"`
+	Direction     string   `description:"方向"`
+	DeviationRate string   `description:"偏差率"`
+	DataTimestamp int64    `description:"数据日期时间戳"`
+}
+
+func (m *AiPredictModelData) Format2Item() (item *AiPredictModelDataItem) {
+	item = new(AiPredictModelDataItem)
+	item.DataId = m.AiPredictModelDataId
+	item.IndexId = m.AiPredictModelIndexId
+	item.IndexCode = m.IndexCode
+	item.DataTime = utils.TimeTransferString(utils.FormatDate, m.DataTime)
+	if m.Value.Valid {
+		item.Value = &m.Value.Float64
+	}
+	if m.PredictValue.Valid {
+		item.PredictValue = &m.PredictValue.Float64
+	}
+	item.Direction = m.Direction
+	item.DeviationRate = m.DeviationRate
+	item.DataTimestamp = m.DataTimestamp
+	return
+}

+ 390 - 0
models/ai_predict_model/ai_predict_model_index.go

@@ -0,0 +1,390 @@
+package data_manage
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndex AI预测模型标的
+type AiPredictModelIndex struct {
+	AiPredictModelIndexId int       `orm:"column(ai_predict_model_index_id);pk"`
+	IndexName             string    `description:"标的名称"`
+	IndexCode             string    `description:"自生成的指标编码"`
+	ClassifyId            int       `description:"分类ID"`
+	ModelFramework        string    `description:"模型框架"`
+	PredictDate           time.Time `description:"预测日期"`
+	PredictValue          float64   `description:"预测值"`
+	PredictFrequency      string    `description:"预测频度"`
+	DirectionAccuracy     string    `description:"方向准确度"`
+	AbsoluteDeviation     string    `description:"绝对偏差"`
+	ExtraConfig           string    `description:"模型参数"`
+	Sort                  int       `description:"排序"`
+	SysUserId             int       `description:"创建人ID"`
+	SysUserRealName       string    `description:"创建人姓名"`
+	LeftMin               string    `description:"图表左侧最小值"`
+	LeftMax               string    `description:"图表左侧最大值"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelIndex) TableName() string {
+	return "ai_predict_model_index"
+}
+
+type AiPredictModelIndexCols struct {
+	PrimaryId         string
+	IndexName         string
+	IndexCode         string
+	ClassifyId        string
+	ModelFramework    string
+	PredictDate       string
+	PredictValue      string
+	DirectionAccuracy string
+	AbsoluteDeviation string
+	ExtraConfig       string
+	Sort              string
+	SysUserId         string
+	SysUserRealName   string
+	LeftMin           string
+	LeftMax           string
+	CreateTime        string
+	ModifyTime        string
+}
+
+func (m *AiPredictModelIndex) Cols() AiPredictModelIndexCols {
+	return AiPredictModelIndexCols{
+		PrimaryId:         "ai_predict_model_index_id",
+		IndexName:         "index_name",
+		IndexCode:         "index_code",
+		ClassifyId:        "classify_id",
+		ModelFramework:    "model_framework",
+		PredictDate:       "predict_date",
+		PredictValue:      "predict_value",
+		DirectionAccuracy: "direction_accuracy",
+		AbsoluteDeviation: "absolute_deviation",
+		ExtraConfig:       "extra_config",
+		Sort:              "sort",
+		SysUserId:         "sys_user_id",
+		SysUserRealName:   "sys_user_real_name",
+		LeftMin:           "left_min",
+		LeftMax:           "left_max",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+	}
+}
+
+func (m *AiPredictModelIndex) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelIndexId = int(id)
+	return
+}
+
+func (m *AiPredictModelIndex) CreateMulti(items []*AiPredictModelIndex) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelIndex) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelIndex) 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.AiPredictModelIndexId).Exec()
+	return
+}
+
+func (m *AiPredictModelIndex) 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 *AiPredictModelIndex) 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 *AiPredictModelIndex) GetItemById(id int) (item *AiPredictModelIndex, 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 *AiPredictModelIndex) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelIndex, 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 *AiPredictModelIndex) 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 *AiPredictModelIndex) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelIndex, 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 *AiPredictModelIndex) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelIndex, 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
+}
+
+// AiPredictModelIndexItem AI预测模型标的信息
+type AiPredictModelIndexItem struct {
+	IndexId           int     `description:"标的ID"`
+	IndexName         string  `description:"标的名称"`
+	IndexCode         string  `description:"自生成的指标编码"`
+	ClassifyId        int     `description:"分类ID"`
+	ClassifyName      string  `description:"分类名称"`
+	ModelFramework    string  `description:"模型框架"`
+	PredictDate       string  `description:"预测日期"`
+	PredictValue      float64 `description:"预测值"`
+	PredictFrequency  string  `description:"预测频度"`
+	DirectionAccuracy string  `description:"方向准确度"`
+	AbsoluteDeviation string  `description:"绝对偏差"`
+	ExtraConfig       string  `description:"模型参数"`
+	SysUserId         int     `description:"创建人ID"`
+	SysUserRealName   string  `description:"创建人姓名"`
+	CreateTime        string  `description:"创建时间"`
+	ModifyTime        string  `description:"修改时间"`
+}
+
+func (m *AiPredictModelIndex) Format2Item() (item *AiPredictModelIndexItem) {
+	item = new(AiPredictModelIndexItem)
+	item.IndexId = m.AiPredictModelIndexId
+	item.IndexName = m.IndexName
+	item.IndexCode = m.IndexCode
+	item.ClassifyId = m.ClassifyId
+	item.ModelFramework = m.ModelFramework
+	item.PredictDate = utils.TimeTransferString(utils.FormatDate, m.PredictDate)
+	item.PredictValue = m.PredictValue
+	item.PredictFrequency = m.PredictFrequency
+	item.DirectionAccuracy = m.DirectionAccuracy
+	item.AbsoluteDeviation = m.AbsoluteDeviation
+	item.ExtraConfig = m.ExtraConfig
+	item.SysUserId = m.SysUserId
+	item.SysUserRealName = m.SysUserRealName
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+type AiPredictModelIndexPageListResp struct {
+	Paging *paging.PagingItem
+	List   []*AiPredictModelIndexItem `description:"列表"`
+}
+
+// RemoveIndexAndData 删除标的及数据
+func (m *AiPredictModelIndex) RemoveIndexAndData(indexId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("trans begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	sql := `DELETE FROM ai_predict_model_index WHERE ai_predict_model_index_id = ? LIMIT 1`
+	_, e = tx.Raw(sql, indexId).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove index err: %v", e)
+		return
+	}
+	sql = ` DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ?`
+	_, e = tx.Raw(sql, indexId).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove index data err: %v", e)
+		return
+	}
+	return
+}
+
+// UpdateAiPredictModelIndexSortByClassifyId 根据分类id更新排序
+func UpdateAiPredictModelIndexSortByClassifyId(classifyId, nowSort int, prevEdbInfoId int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` UPDATE ai_predict_model_index SET sort = ` + updateSort + ` WHERE classify_id = ?`
+	if prevEdbInfoId > 0 {
+		sql += ` AND ( sort > ? or ( ai_predict_model_index_id > ` + fmt.Sprint(prevEdbInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	} else {
+		sql += ` AND ( sort > ? )`
+	}
+	_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// GetFirstAiPredictModelIndexByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstAiPredictModelIndexByClassifyId(classifyId int) (item *AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM ai_predict_model_index WHERE classify_id = ? order by sort asc,ai_predict_model_index_id asc limit 1`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+type AiPredictModelImportData struct {
+	Index *AiPredictModelIndex
+	Data  []*AiPredictModelData
+}
+
+// ImportIndexAndData 导入数据
+func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []*AiPredictModelImportData, updateCols []string) (err error) {
+	if len(createIndexes) == 0 && len(updateIndexes) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("trans begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if len(updateIndexes) > 0 {
+		for _, v := range updateIndexes {
+			// 更新指标
+			_, e = tx.Update(v.Index, updateCols...)
+			if e != nil {
+				err = fmt.Errorf("update index err: %v", e)
+				return
+			}
+			for _, d := range v.Data {
+				d.AiPredictModelIndexId = v.Index.AiPredictModelIndexId
+				d.IndexCode = v.Index.IndexCode
+				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+			}
+
+			// 清空指标并新增
+			sql := `DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ?`
+			_, e = tx.Raw(sql, v.Index.AiPredictModelIndexId).Exec()
+			if e != nil {
+				err = fmt.Errorf("clear index data err: %v", e)
+				return
+			}
+			_, e = tx.InsertMulti(utils.MultiAddNum, v.Data)
+			if e != nil {
+				err = fmt.Errorf("insert index data err: %v", e)
+				return
+			}
+		}
+	}
+
+	if len(createIndexes) > 0 {
+		for _, v := range createIndexes {
+			indexId, e := tx.Insert(v.Index)
+			if e != nil {
+				err = fmt.Errorf("insert index err: %v", e)
+				return
+			}
+			for _, d := range v.Data {
+				d.AiPredictModelIndexId = int(indexId)
+				d.IndexCode = v.Index.IndexCode
+				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+			}
+			_, e = tx.InsertMulti(utils.MultiAddNum, v.Data)
+			if e != nil {
+				err = fmt.Errorf("insert index data err: %v", e)
+				return
+			}
+		}
+	}
+	return
+}
+
+type AiPredictModelDetailResp struct {
+	TableData      []*AiPredictModelDataItem        `description:"表格数据"`
+	ChartView      *data_manage.ChartInfoDetailResp `description:"月度预测数据图表"`
+	DailyChartView *data_manage.ChartInfoDetailResp `description:"日度预测数据图表"`
+}
+
+type AiPredictModelIndexSaveReq struct {
+	IndexId      int                           `description:"指标ID"`
+	MonthlyChart *AiPredictModelIndexSaveChart `description:"月度图表信息"`
+	DailyChart   *AiPredictModelIndexSaveChart `description:"日度图表信息"`
+}
+
+type AiPredictModelIndexSaveChart struct {
+	LeftMin string `description:"图表左侧最小值"`
+	LeftMax string `description:"图表左侧最大值"`
+	Unit    string `description:"单位"`
+}
+
+type AiPredictModelIndexExtraConfig struct {
+	MonthlyChart struct {
+		LeftMin string `description:"图表左侧最小值"`
+		LeftMax string `description:"图表左侧最大值"`
+		Unit    string `description:"单位"`
+	}
+	DailyChart struct {
+		LeftMin           string `description:"图表左侧最小值"`
+		LeftMax           string `description:"图表左侧最大值"`
+		Unit              string `description:"单位"`
+		PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
+	}
+}

+ 233 - 0
models/data_manage/base_from_clarksons_classify.go

@@ -0,0 +1,233 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// BaseFromClarksonsClassify 卓创红期原始数据分类表
+type BaseFromClarksonsClassify struct {
+	BaseFromClassifyId int       `orm:"column(base_from_clarksons_classify_id);pk"`
+	ClassifyName       string    `description:"分类名称"`
+	ParentId           int       `description:"父级id"`
+	Level              int       `description:"层级"`
+	Sort               int       `description:"排序字段"`
+	ModifyTime         time.Time `description:"修改时间"`
+	CreateTime         time.Time `description:"创建时间"`
+}
+
+type BaseFromClarksonsClassifyItem struct {
+	BaseFromClassifyId int                              `orm:"column(base_from_clarksons_classify_id);pk"`
+	ClassifyName       string                           `description:"分类名称"`
+	ParentId           int                              `description:"父级id"`
+	Level              int                              `description:"层级"`
+	Sort               int                              `description:"排序字段"`
+	UniqueCode         string                           `description:"唯一code"`
+	ModifyTime         time.Time                        `description:"修改时间"`
+	CreateTime         time.Time                        `description:"创建时间"`
+	ClassifyNameEn     string                           `description:"英文分类名称"`
+	Children           []*BaseFromClarksonsClassifyItem `description:"子分类"`
+}
+
+type BaseFromClarksonsClassifyMaxSort struct {
+	BaseFromClassifyId int `description:"分类id"`
+	MaxSort            int `description:"最大排序"`
+}
+
+func (t *BaseFromClarksonsClassify) Add() (insertId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	insertId, err = o.Insert(t)
+	return
+}
+
+func (t *BaseFromClarksonsClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(t, cols...)
+	return
+}
+
+func BatchAddClarksonsClassify(items []*BaseFromClarksonsClassify) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// 获取所有分类
+func GetClarksonsClassifyAll() (items []*BaseFromClarksonsClassifyItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_classify ORDER BY sort ASC, base_from_clarksons_classify_id ASC`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetChildClarksonsClassifyListById 获取子分类列表
+func GetChildClarksonsClassifyListById(classifyId int) (items []*BaseFromClarksonsClassifyItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_classify WHERE parent_id=? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+// GetChildClarksonsClassifyIdsById 获取子分类的id集合
+func GetChildClarksonsClassifyIdsById(classifyId int) (items []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT base_from_clarksons_classify_id FROM base_from_clarksons_classify WHERE parent_id=? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+// GetChildClarksonsClassifyMaxSortById 获取子分类最大排序
+func GetChildClarksonsClassifyMaxSortById(classifyId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT MAX(sort) AS sort FROM base_from_clarksons_classify WHERE parent_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+// GetClarksonsClassifyCountById 获取分类数量
+func GetClarksonsClassifyCountById(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(*) AS count FROM base_from_clarksons_classify WHERE base_from_clarksons_classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// GetClarksonsClassifyById 通过分类id获取分类
+func GetClarksonsClassifyById(classifyId int) (item *BaseFromClarksonsClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_classify WHERE base_from_clarksons_classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// GetClarksonsChildClassifyById 通过分类id获取子分类
+func GetClarksonsChildClassifyIdsById(classifyId int) (items []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT base_from_clarksons_classify_id FROM base_from_clarksons_classify WHERE parent_id=? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+// GetClarksonsClassifyListByIds 通过分类id获取分类列表
+func GetClarksonsClassifyListByIds(classifyIds []int) (items []*BaseFromClarksonsClassify, err error) {
+	if len(classifyIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_classify WHERE base_from_clarksons_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	_, err = o.Raw(sql, classifyIds).QueryRows(&items)
+	return
+}
+
+// GetClarksonsClassifyCountByName 通过分类名称获取分类
+func GetClarksonsClassifyCountByName(classifyName string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(*) AS count FROM base_from_clarksons_classify WHERE 1=1`
+
+	sql += ` AND classify_name=? `
+	err = o.Raw(sql, classifyName).QueryRow(&count)
+	return
+}
+
+func GetBaseFromClarksonsClassifyEnCount(classifyNameEn string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM base_from_clarksons_classify WHERE classify_name_en=? AND parent_id=? `
+	err = o.Raw(sql, classifyNameEn, parentId).QueryRow(&count)
+	return
+}
+
+func GetBaseFromClarksonsClassifyCount(classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM base_from_clarksons_classify WHERE classify_name=? AND parent_id=? `
+	err = o.Raw(sql, classifyName, parentId).QueryRow(&count)
+	return
+}
+
+func DeleteClarksonsClassifyById(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` DELETE FROM base_from_clarksons_classify WHERE base_from_clarksons_classify_id=? `
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}
+
+// BatchDeleteClarksonsClassifyById 批量删除分类
+func BatchDeleteClarksonsClassifyById(classifyId []int) (err error) {
+	if len(classifyId) == 0 {
+		return
+	}
+	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 base_from_clarksons_classify WHERE base_from_clarksons_classify_id IN (` + utils.GetOrmInReplace(len(classifyId)) + `) `
+	_, err = o.Raw(sql, classifyId).Exec()
+
+	return
+}
+
+// DeleteClarksonsClassifyByClassifyId 根据分类id删除对应的指标分类
+func DeleteClarksonsClassifyByClassifyId(classifyIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	num := len(classifyIdList)
+	if num <= 0 {
+		return
+	}
+	//删除分类
+	sql := `DELETE FROM base_from_clarksons_classify WHERE base_from_clarksons_classify_id IN (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, classifyIdList).Exec()
+	return
+}
+
+// GetClarksonsIndexClassifyMinSort 获取最小不等于0的排序
+func GetClarksonsIndexClassifyMinSort(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT MIN(sort) FROM base_from_clarksons_classify WHERE parent_id=? AND sort <> 0 `
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+// MoveUpClarksonsIndexClassifyBySort 往上移动
+func MoveUpClarksonsIndexClassifyBySort(parentId, nextSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_clarksons_classify set sort = sort + 1 where parent_id=? and sort >= ? and sort< ?`
+	_, err = o.Raw(sql, parentId, nextSort, currentSort).Exec()
+	return
+}
+
+// MoveDownClarksonsIndexClassifyBySort 往下移动
+func MoveDownClarksonsIndexClassifyBySort(parentId, prevSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_clarksons_classify set sort = sort - 1 where parent_id=? and sort <= ? and sort> ? `
+	_, err = o.Raw(sql, parentId, prevSort, currentSort).Exec()
+	return
+}
+
+// UpdateClarksonsClassifySortByParentId 根据父类id更新排序
+func UpdateClarksonsClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update base_from_clarksons_classify set sort = ` + updateSort + ` WHERE parent_id=? and sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( base_from_clarksons_classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstClarksonsClassifyByParentId 获取当前父级分类下的排序第一条的数据
+func GetFirstClarksonsClassifyByParentId(parentId int) (item *ChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_classify WHERE parent_id=? order by sort asc,base_from_clarksons_classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}

+ 81 - 0
models/data_manage/base_from_clarksons_data.go

@@ -0,0 +1,81 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromClarksonsData struct {
+	BaseFromClarksonsDataId          int       `orm:"column(base_from_clarksons_data_id);pk"`
+	BaseFromClarksonsIndexId int       `description:"指标id"`
+	IndexCode            string    `description:"指标编码"`
+	DataTime             string    `description:"数据日期"`
+	Value                float64   `description:"数据值"`
+	CreateTime           time.Time `description:"创建时间"`
+	ModifyTime           time.Time `description:"修改时间"`
+	DataTimestamp        int64     `description:"数据时间戳"`
+}
+
+// GetClarksonsDataByIndexId 根据指标id获取指标数据
+func GetClarksonsDataByIndexId(indexId int) (items []*BaseFromClarksonsData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_data WHERE base_from_clarksons_index_id=? ORDER BY data_time DESC`
+	_, err = o.Raw(sql, indexId).QueryRows(&items)
+	return
+}
+
+// GetClarksonsDataDataTimeByIndexId 根据指标id获取指标数据的日期列表
+func GetClarksonsDataDataTimeByIndexId(indexIdList []int) (items []string, err error) {
+	if len(indexIdList) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT DISTINCT data_time FROM base_from_clarksons_data WHERE base_from_clarksons_index_id IN (` + utils.GetOrmInReplace(len(indexIdList)) + `) ORDER BY data_time DESC`
+	_, err = o.Raw(sql, indexIdList).QueryRows(&items)
+	return
+}
+
+func GetClarksonsIndexDataByCode(indexCode string) (items []*BaseFromClarksonsData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_clarksons_data WHERE index_code=? ORDER BY data_time DESC  `
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+func GetClarksonsIndexDataCount(indexCode string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count  FROM base_from_clarksons_data WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&count)
+	return
+}
+
+// GetClarksonsLastUpdateTimeLastByIndexCode 根据指标编码查询 返回ModifyTime最后一条数据
+func GetClarksonsLastUpdateTimeLastByIndexCode(indexCodes []string) (items []*BaseFromClarksonsData, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 构造 SQL 查询
+	sql := `SELECT t1.index_code, t1.data_time, t2.value
+			FROM (
+    			SELECT index_code, MAX(data_time) AS data_time
+   				 FROM base_from_clarksons_data
+    			WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodes)) + `)
+    			GROUP BY index_code
+			) AS t1
+			JOIN base_from_clarksons_data AS 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
+}
+
+func GetClarksonsIndexData(indexCode string, startSize, pageSize int) (items []*BaseFromClarksonsData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_clarksons_data WHERE index_code=? ORDER BY data_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, indexCode, startSize, pageSize).QueryRows(&items)
+	return
+}

+ 424 - 0
models/data_manage/base_from_clarksons_index.go

@@ -0,0 +1,424 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromClarksonsIndex struct {
+	BaseFromClarksonsIndexId int       `orm:"pk"`
+	ClassifyId           int       `description:"指标分类id"`
+	IndexCode            string    `description:"指标编码"`
+	IndexName            string    `description:"指标名称"`
+	Unit                 string    `description:"单位"`
+	Frequency            string    `description:"频度"`
+	StartDate            string    `description:"开始日期"`
+	EndDate              string    `description:"结束日期"`
+	Sort                 int       `description:"排序"`
+	CreateTime           time.Time
+	ModifyTime           time.Time
+}
+
+type BaseFromClarksonsIndexView struct {
+	BaseFromClarksonsIndexId int     `orm:"pk"`
+	EdbInfoId            int     `description:"指标库id"`
+	ClassifyId           int     `description:"指标分类id"`
+	IndexCode            string  `description:"指标编码"`
+	IndexName            string  `description:"指标名称"`
+	UniqueCode           string  `description:"唯一code"`
+	Frequency            string  `description:"频度"`
+	Unit                 string  `description:"单位"`
+	StartDate            string  `description:"开始日期"`
+	EndDate              string  `description:"结束日期"`
+	Sort                 int     `description:"排序"`
+	LatestDate           string  `description:"最后更新时间"`
+	EdbExist             int     `description:"edb是否存在"`
+	ModifyTime           string
+}
+
+type BaseFromClarksonsIndexList struct {
+	BaseFromClarksonsIndexId int     `orm:"pk"`
+	IndexCode          string    // 指标编码
+	IndexName          string    // 指标名称
+	ClassifyId         int       // 分类Id
+	Unit               string    // 单位
+	Frequency          string    // 频度
+	Describe           string    // 指标描述
+	CreateTime         string // 创建时间
+	ModifyTime         string // 修改时间
+	DataList           []*BaseFromClarksonsData
+	Paging             *paging.PagingItem `description:"分页数据"`
+}
+func (b *BaseFromClarksonsIndex) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(b, cols...)
+	return
+}
+
+// GetClarksonsIndexByCondition 根据条件获取克拉克森指标列表
+func GetClarksonsIndexByCondition(condition string, pars []interface{}) (items []*BaseFromClarksonsIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_clarksons_index_id ASC`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetClarksonsIndexByCondition 根据条件获取克拉克森指标列表
+func GetClarksonsIndexByConditionAndFrequency(condition, frequency string, pars []interface{}) (items []*BaseFromClarksonsIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` AND frequency=?`
+	sql += ` ORDER BY sort ASC, base_from_clarksons_index_id ASC`
+	_, err = o.Raw(sql, pars, frequency).QueryRows(&items)
+	return
+}
+
+func GetClarksonsIndexCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(*) AS count FROM base_from_clarksons_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_clarksons_index_id ASC`
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetClarksonsIndexAndEdbInfoByCondition 根据条件获取克拉克森index和指标库的信息
+func GetClarksonsIndexAndEdbInfoByCondition(condition string, pars []interface{}) (items []*BaseFromClarksonsIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT b.*, e.edb_info_id FROM base_from_clarksons_index AS b LEFT JOIN edb_info AS e ON b.index_code=e.edb_code AND e.source=? WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC `
+	_, err = o.Raw(sql, utils.DATA_SOURCE_SCI_HQ, pars).QueryRows(&items)
+	return
+}
+
+// GetClarksonsIndexByIndexCode 根据指标编码获取指标信息
+func GetClarksonsIndexByIndexCode(indexCode string) (item *BaseFromClarksonsIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&item)
+	return
+}
+
+// GetClarksonsIndexByIndexId 根据指标id获取指标信息
+func GetClarksonsIndexByIndexId(indexId int) (item *BaseFromClarksonsIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE base_from_clarksons_index_id=? `
+	err = o.Raw(sql, indexId).QueryRow(&item)
+	return
+}
+
+// GetClarksonsIndexListByIndexIds 根据指标id获取指标信息
+func GetClarksonsIndexListByIndexIds(indexIds []int) (items []*BaseFromClarksonsIndex, err error) {
+	if len(indexIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE base_from_clarksons_index_id IN (` + utils.GetOrmInReplace(len(indexIds)) + `) `
+	_, err = o.Raw(sql, indexIds).QueryRows(&items)
+	return
+}
+
+// GetClarksonsIndexCountByClassifyIds 获取分类下指标的个数
+func GetClarksonsIndexCountByClassifyIds(classifyIds []int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	num := len(classifyIds)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT COUNT(1) AS count FROM base_from_clarksons_index WHERE classify_id IN (` + utils.GetOrmInReplace(num) + `) `
+	err = o.Raw(sql, classifyIds).QueryRow(&count)
+	return
+}
+
+// GetClarksonsIndexByClassifyId 根据分类id获取克拉克森指标列表
+func GetClarksonsIndexByClassifyId(classifyIds []int, startSize, pageSize int) (items []*BaseFromClarksonsIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT b.*, e.edb_info_id,
+	CASE WHEN e.edb_info_id IS NULL THEN 0 ELSE 1 END AS edb_exist
+	FROM base_from_clarksons_index AS b
+	LEFT JOIN edb_info AS e ON b.index_code=e.edb_code AND e.source=101
+	WHERE b.classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) ORDER BY b.sort ASC LIMIT ?,? `
+	_, err = o.Raw(sql, classifyIds, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetClarksonsIndexCountByClassifyId 根据分类id获取克拉克森指标数量
+func GetClarksonsIndexCountByClassifyId(classifyIds []int) (count int, err error) {
+	if len(classifyIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(*) AS count FROM base_from_clarksons_index WHERE classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) `
+	err = o.Raw(sql, classifyIds).QueryRow(&count)
+	return
+}
+
+// GetClarksonsIndexCount 获取克拉克森指标数量
+func GetClarksonsIndexCount() (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(*) AS count FROM base_from_clarksons_index `
+	err = o.Raw(sql).QueryRow(&count)
+	return
+}
+
+func GetClarksonsIndexByPage(startSize, pageSize int) (items []*BaseFromClarksonsIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT b.*, e.edb_info_id,
+	CASE WHEN e.edb_info_id IS NULL THEN 0 ELSE 1 END AS edb_exist
+	FROM base_from_clarksons_index AS b
+	LEFT JOIN edb_info AS e ON b.index_code=e.edb_code AND e.source=101
+	ORDER BY b.modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetClarksonsIndexBaseInfoByClassifyId 根据分类id获取克拉克森指标列表
+func GetClarksonsIndexBaseInfoByClassifyId(classifyId int) (items []*BaseFromClarksonsIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT base_from_clarksons_index_id, classify_id, index_code, index_name, CONCAT(classify_id, '_', base_from_clarksons_index_id) AS unique_code  FROM base_from_clarksons_index WHERE classify_id = ? ORDER BY sort ASC `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+// GetClarksonsIndexBaseInfoByClassifyId 根据分类id获取克拉克森指标列表
+func GetClarksonsIndexBaseInfoByCondition(condition string, pars []interface{}) (items []*BaseFromClarksonsIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT base_from_clarksons_index_id, index_code, index_name  FROM base_from_clarksons_index WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC `
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+func GetClarksonsDataMaxCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT MAX(t.num) AS count FROM ( SELECT COUNT(1) AS num  FROM base_from_clarksons_index AS a INNER JOIN base_from_clarksons_data AS b ON a.index_code=b.index_code WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` GROUP BY a.base_from_clarksons_index_id) AS t `
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetClarksonsIndexMaxSortByClassifyId 根据分类id获取指标最大排序
+func GetClarksonsIndexMaxSortByClassifyId(classifyId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT MAX(sort) FROM base_from_clarksons_index WHERE classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+func GetClarksonsFrequency(classifyId int) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency FROM base_from_clarksons_index WHERE classify_id=? ORDER BY FIELD(frequency,'日度','周度','月度','季度','半年','年度') `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+func GetClarksonsFrequencyByCondition(condition string, pars []interface{}) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency FROM base_from_clarksons_index WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY FIELD(frequency,'日度','周度','月度','季度','半年','年度') `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+func GetClarksonsFrequencyByCode(code string) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency FROM base_from_clarksons_index WHERE index_code=? ORDER BY FIELD(frequency,'日度','周度','月度','季度','半年','年度') `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, code).QueryRows(&items)
+	return
+}
+
+// GetClarksonsClassifyMaxSortByClassifyIds 通过分类id获取对应分类的最大sort
+func GetClarksonsClassifyMaxSortByClassifyIds(classifyIds []int) (items []*BaseFromClarksonsClassifyMaxSort, err error) {
+	if len(classifyIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT bc.base_from_clarksons_classify_id, COALESCE(MAX(bi.sort), 0) AS max_sort FROM base_from_clarksons_classify  AS bc
+	LEFT JOIN base_from_clarksons_index AS bi
+	ON bc.base_from_clarksons_classify_id=bi.classify_id  
+	WHERE bc.base_from_clarksons_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)
+	GROUP BY bc.base_from_clarksons_classify_id
+	`
+	// sql = ` SELECT classify_id, MAX(sort) AS max_sort FROM base_from_clarksons_index WHERE classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) GROUP BY classify_id `
+	_, err = o.Raw(sql, classifyIds).QueryRows(&items)
+	return
+}
+
+func BatchModifyClarksonsIndexClassify(items []*BaseFromClarksonsIndex) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_clarksons_index SET classify_id=?, sort=? WHERE base_from_clarksons_index_id=? `
+	p, err := o.Raw(sql).Prepare()
+	if err != nil {
+		return
+	}
+	defer func() {
+		p.Close()
+	}()
+	for _, v := range items {
+		_, err = p.Exec(v.ClassifyId, v.Sort, v.BaseFromClarksonsIndexId)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+// MoveDownClarksonsIndexBySort 往下移动
+func MoveDownClarksonsIndexBySort(classifyId, prevSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_clarksons_index set sort = sort - 1 where classify_id=? and sort <= ? and sort> ? `
+	_, err = o.Raw(sql, classifyId, prevSort, currentSort).Exec()
+	return
+}
+
+// MoveUpClarksonsIndexBySort 往上移动
+func MoveUpClarksonsIndexBySort(classifyId, nextSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_clarksons_index set sort = sort + 1 where classify_id=? and sort >= ? and sort< ?`
+	_, err = o.Raw(sql, classifyId, nextSort, currentSort).Exec()
+	return
+}
+
+func DeleteClarksonsIndexById(indexId 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 base_from_clarksons_index WHERE base_from_clarksons_index_id=? `
+	_, err = to.Raw(sql, indexId).Exec()
+	if err != nil {
+		return
+	}
+	sql = `DELETE FROM base_from_clarksons_data WHERE base_from_clarksons_index_id=? `
+	_, err = to.Raw(sql, indexId).Exec()
+	if err != nil {
+		return
+	}
+	return
+}
+
+func DeleteClarksonsIndexByIds(indexIds []int) (err error) {
+	if len(indexIds) == 0 {
+		return
+	}
+	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 base_from_clarksons_index WHERE base_from_clarksons_index_id IN (` + utils.GetOrmInReplace(len(indexIds)) + `) `
+	_, err = o.Raw(sql, indexIds).Exec()
+	if err != nil {
+		return
+	}
+	sql = `DELETE FROM base_from_clarksons_data WHERE base_from_clarksons_index_id IN (` + utils.GetOrmInReplace(len(indexIds)) + `) `
+	_, err = o.Raw(sql, indexIds).Exec()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// MoveClarksonsIndex 移动指标分类
+func MoveClarksonsIndex(indexId, classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` UPDATE base_from_clarksons_index
+			SET
+			  classify_id = ?, modify_time=NOW() 
+			WHERE base_from_clarksons_index_id = ?`
+	_, err = o.Raw(sql, classifyId, indexId).Exec()
+	return
+}
+
+// GetClarksonsIndexMinSortByClassifyId 获取最小不等于0的排序
+func GetClarksonsIndexMinSortByClassifyId(classifyId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT min(sort) FROM base_from_clarksons_index WHERE classify_id=? and sort <> 0 `
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+// GetClarksonsIndexInfoCount 分页查询指标信息行数
+func GetClarksonsIndexInfoCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT count(1) FROM base_from_clarksons_index WHERE index_code not in (select edb_code from edb_info) `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetClarksonsIndexInfoPage 分页查询指标信息
+func GetClarksonsIndexInfoPage(condition string, pars []interface{}) (items []*BaseFromRzdIndexAndData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE index_code not in (select edb_code from edb_info) `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+
+}
+
+func GetClarksonsIndex(condition string, pars interface{}) (items []*BaseFromClarksonsIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_clarksons_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += `ORDER BY base_from_clarksons_index_id ASC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func GetClarksonsIndexLatestDate(indexCode string) (ModifyTime string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT modify_time FROM base_from_clarksons_data WHERE index_code=? ORDER BY modify_time DESC limit 1 `
+	err = o.Raw(sql, indexCode).QueryRow(&ModifyTime)
+	return
+}

+ 60 - 0
models/data_manage/base_from_rzd_classify.go

@@ -0,0 +1,60 @@
+// Package data_manage @Author gmy 2024/10/21 9:26:00
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromRzdClassify struct {
+	BaseFromRzdClassifyId int    `orm:"column(base_from_rzd_classify_id);pk"`
+	CreateTime            string `orm:"column(create_time)"`
+	ModifyTime            string `orm:"column(modify_time)"`
+	ClassifyName          string `orm:"column(classify_name)"`
+	ParentId              int    `orm:"column(parent_id)"`
+	Sort                  int    `orm:"column(sort)"`
+	ClassifyNameEn        string `orm:"column(classify_name_en)"`
+}
+
+type BaseFromRzdClassifyResponse struct {
+	BaseFromRzdClassifyId int    `orm:"column(base_from_rzd_classify_id);pk"`
+	CreateTime            string `orm:"column(create_time)"`
+	ModifyTime            string `orm:"column(modify_time)"`
+	ClassifyName          string `orm:"column(classify_name)"`
+	ParentId              int    `orm:"column(parent_id)"`
+	Sort                  int    `orm:"column(sort)"`
+	ClassifyNameEn        string `orm:"column(classify_name_en)"`
+	IndexInfo             []*BaseFromRzdIndex
+	Child                 []*BaseFromRzdClassifyResponse
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromRzdClassify))
+}
+
+// GetAllRzdClassify 查询所有分类
+func GetAllRzdClassify() (items []*BaseFromRzdClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_rzd_classify ORDER BY sort asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetRzdClassifyItemByClassifyId 根据分类id查询分类信息
+func GetRzdClassifyItemByClassifyId(classifyId int) (item *BaseFromRzdClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_rzd_classify WHERE base_from_rzd_classify_id = ?`
+
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	if err != nil {
+		return
+	}
+	return item, nil
+}
+
+func GetRzdClassifyItemByParentId(parentId int) (items []*BaseFromRzdClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_rzd_classify WHERE parent_id = ?`
+
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}

+ 115 - 0
models/data_manage/base_from_rzd_data.go

@@ -0,0 +1,115 @@
+// Package data_manage @Author gmy 2024/8/7 9:50:00
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type BaseFromRzdData struct {
+	BaseFromRzdDataId  int     `orm:"column(base_from_rzd_data_id);pk"`
+	BaseFromRzdIndexId int     `orm:"column(base_from_rzd_index_id)"`
+	CreateTime         string  `orm:"column(create_time)"`
+	DataTime           string  `orm:"column(data_time)"`
+	IndexCode          string  `orm:"column(index_code)"`
+	ModifyTime         string  `orm:"column(modify_time)"`
+	Value              float64 `orm:"column(value)"`
+}
+
+// RzdIndexAddReq 指标添加vo
+type RzdIndexAddReq 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 RzdIndexDataCountGroup struct {
+	IndexCode string
+	Count     int
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromRzdData))
+}
+
+func GetRzdIndexDataCountGroup(indexCodes []string) (items []*RzdIndexDataCountGroup, err error) {
+	if len(indexCodes) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count, index_code FROM base_from_rzd_data WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodes)) + `) GROUP BY index_code`
+	_, err = o.Raw(sql, indexCodes).QueryRows(&items)
+	return
+}
+
+func GetRzdIndexData(indexCode string, startSize, pageSize int) (items []*BaseFromRzdData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_rzd_data WHERE index_code=? ORDER BY data_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, indexCode, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetBaseFormRzdDataByIndexCode 根据指标编码查询
+func GetBaseFormRzdDataByIndexCode(indexCode string) (items []*BaseFromRzdData, err error) {
+	sql := `SELECT * FROM base_from_rzd_data WHERE index_code=? ORDER BY data_time desc`
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+func GetBaseFormRzdDataByConditionCount(condition string, pars interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT count(1) FROM base_from_rzd_data WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	if err != nil {
+		return 0, err
+	}
+	return
+}
+
+func GetBaseFormRzdDataByCondition(condition string, pars interface{}) (items []*BaseFromRzdData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_rzd_data WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetRzdDataListByIndexCodes 根据指标编码查询
+func GetRzdDataListByIndexCodes(IndexCodes string) (items []string, err error) {
+	sql := ` SELECT data_time FROM base_from_rzd_data WHERE index_code IN(` + IndexCodes + `)  GROUP BY data_time DESC `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetRzdLastUpdateTimeLastByIndexCode 根据指标编码查询 返回ModifyTime最后一条数据
+func GetRzdLastUpdateTimeLastByIndexCode(indexCodes []string) (items []*BaseFromRzdData, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	// 构造 SQL 查询
+	sql := `SELECT t1.index_code, t1.data_time, t2.value
+			FROM (
+    			SELECT index_code, MAX(data_time) AS data_time
+   				 FROM base_from_rzd_data
+    			WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodes)) + `)
+    			GROUP BY index_code
+			) AS t1
+			JOIN base_from_rzd_data AS 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
+}

+ 213 - 0
models/data_manage/base_from_rzd_index.go

@@ -0,0 +1,213 @@
+// Package data_manage
+// @Author gmy 2024/8/7 9:38:00
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type BaseFromRzdIndex struct {
+	BaseFromRzdIndexId    int    `orm:"column(base_from_rzd_index_id);pk"`
+	CreateTime            string `orm:"column(create_time)"`
+	ModifyTime            string `orm:"column(modify_time)"`
+	BaseFromRzdClassifyId int    `orm:"column(base_from_rzd_classify_id)"`
+	IndexCode             string `orm:"column(index_code)"`
+	IndexName             string `orm:"column(index_name)"`
+	Frequency             string `orm:"column(frequency)"`
+	Unit                  string `orm:"column(unit)"`
+}
+
+type BaseFromRzdIndexAndData struct {
+	BaseFromRzdIndexId    int     `orm:"column(base_from_rzd_index_id);pk"`
+	CreateTime            string  `orm:"column(create_time)"`
+	ModifyTime            string  `orm:"column(modify_time)"`
+	BaseFromRzdClassifyId int     `orm:"column(base_from_rzd_classify_id)"`
+	IndexCode             string  `orm:"column(index_code)"`
+	IndexName             string  `orm:"column(index_name)"`
+	Frequency             string  `orm:"column(frequency)"`
+	Unit                  string  `orm:"column(unit)"`
+	ModifyTimeMax         string  `json:"modify_time_max" description:"最后修改时间"`
+	Value                 float64 `orm:"column(value)" description:"数据值"`
+}
+
+type BaseFromRzdIndexPage struct {
+	List   []*BaseFromRzdIndexAndData `description:"指标列表"`
+	Paging *paging.PagingItem         `description:"分页数据"`
+}
+
+type BaseFromRzdIndexList struct {
+	BaseFromRzdIndexId    int    `orm:"column(base_from_rzd_index_id);pk"`
+	CreateTime            string `orm:"column(create_time)"`
+	ModifyTime            string `orm:"column(modify_time)"`
+	BaseFromRzdClassifyId int    `orm:"column(base_from_rzd_classify_id)"`
+	IndexCode             string `orm:"column(index_code)"`
+	IndexName             string `orm:"column(index_name)"`
+	Frequency             string `orm:"column(frequency)"`
+	Unit                  string `orm:"column(unit)"`
+	DataList              []*BaseFromRzdData
+	Paging                *paging.PagingItem `description:"分页数据"`
+	EdbInfoId             int                `description:"指标库主键id"`
+}
+
+// RzdNameCheckResult 校验名称是否存在
+type RzdNameCheckResult struct {
+	IndexCode string `from:"EdbCode" description:"edb编码"`
+	IndexName string `from:"EdbName" description:"edb名称"`
+	Exist     bool
+}
+
+// RzdIndexCheckData 校验编码是否存在
+type RzdIndexCheckData 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"`
+}
+
+// BaseFromRzdIndexBatchAddCheckReq 校验编码是否存在请求参数
+type BaseFromRzdIndexBatchAddCheckReq struct {
+	IndexCodes     []string `form:"IndexCodes" description:"指标编码列表"`
+	ClassifyIdList []int    `description:"分类id"`
+	FrequencyList  []string `description:"频度"`
+	SearchParams   string   `description:"搜索参数 指标编码/指标名称"`
+	IsCheckAll     bool     `description:"是否全选"`
+}
+
+func init() {
+	orm.RegisterModel(new(BaseFromRzdIndex))
+}
+
+// GetRzdIndexByClassifyIds 根据分类id获取指标信息
+func GetRzdIndexByClassifyIds(classifyIds []int) (items []*BaseFromRzdIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := fmt.Sprintf(`SELECT * FROM base_from_rzd_index WHERE base_from_rzd_classify_id IN (`+utils.GetOrmInReplace(len(classifyIds))) + `)`
+	_, err = o.Raw(sql, classifyIds).QueryRows(&items)
+	if err != nil {
+		return nil, err
+	}
+
+	return items, nil
+}
+
+func GetRzdIndex(condition string, pars interface{}) (items []*BaseFromRzdIndexList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_rzd_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY base_from_rzd_index_id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func GetRzdIndexNotExistEdbInfoCount(condition string, pars interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT count(1) FROM base_from_rzd_index WHERE index_code not in (select edb_code from edb_info) `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetRzdIndexNotExistEdbInfoPage(condition string, pars interface{}) (items []*BaseFromRzdIndexAndData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_rzd_index WHERE index_code not in (select edb_code from edb_info) `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetRzdIndexFrequency 获取指标频度
+func GetRzdIndexFrequency(classifyIdList []int) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency 
+        FROM base_from_rzd_index 
+        WHERE frequency is not null`
+
+	// 如果 classifyId > 0,则添加该条件
+	if len(classifyIdList) > 0 {
+		sql += ` AND base_from_rzd_classify_id in (` + utils.GetOrmInReplace(len(classifyIdList)) + `)`
+	}
+
+	sql += ` ORDER BY FIELD(frequency, '日度', '周度', '月度', '季度', '半年度', '年度')`
+
+	o := orm.NewOrmUsingDB("data")
+	if len(classifyIdList) > 0 {
+		_, err = o.Raw(sql, classifyIdList).QueryRows(&items)
+	} else {
+		_, err = o.Raw(sql).QueryRows(&items)
+	}
+
+	return items, err
+}
+
+// GetRzdIndexByCodeAndClassify 根据指标编码和分类查询 indexCode非必传
+func GetRzdIndexByCodeAndClassify(indexCode string, classifyIdList []int, frequency *string) (items []*BaseFromRzdIndex, 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_rzd_index AS a
+			INNER JOIN base_from_rzd_data AS b ON a.base_from_rzd_index_id = b.base_from_rzd_index_id
+			WHERE 1=1`
+
+	var params []interface{}
+
+	if len(classifyIdList) > 0 {
+		sql += ` AND a.base_from_rzd_classify_id in (` + utils.GetOrmInReplace(len(classifyIdList)) + `)`
+		for _, id := range classifyIdList {
+			params = append(params, id)
+		}
+	}
+
+	// 如果 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
+}
+
+// GetRzdIndexInfoCount 分页查询指标信息行数
+func GetRzdIndexInfoCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT count(1) FROM base_from_rzd_index WHERE index_code not in (select edb_code from edb_info) `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetRzdIndexInfoPage 分页查询指标信息
+func GetRzdIndexInfoPage(condition string, pars []interface{}) (items []*BaseFromRzdIndexAndData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_rzd_index WHERE index_code not in (select edb_code from edb_info) `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+
+}

+ 272 - 0
models/data_manage/base_from_usda_fas.go

@@ -0,0 +1,272 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type BaseFromUsdaFasIndex struct {
+	BaseFromUsdaFasIndexId int `orm:"column(base_from_usda_fas_index_id);pk"`
+	ClassifyId             int
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	Sort                   int
+	StartDate              string `description:"开始日期"`
+	EndDate                string `description:"结束日期"`
+	EndValue               float64
+	CreateTime             time.Time
+	ModifyTime             time.Time
+}
+
+type BaseFromUsdaFasIndexList struct {
+	BaseFromUsdaFasIndexId int `orm:"column(base_from_usda_fas_index_id);pk"`
+	ClassifyId             int
+	Interface              string
+	EdbInfoId              int
+	EdbUniqueCode          string `description:"指标库唯一编码"`
+	EdbClassifyId          int    `description:"指标库分类ID"`
+	StartDate              string
+	EndDate                string
+	EndValue               float64
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	Sort                   int
+	CreateTime             string
+	ModifyTime             string
+	EdbExist               int `description:"指标库是否已添加:0-否;1-是"`
+	DataList               []*BaseFromUsdaFasData
+	Paging                 *paging.PagingItem `description:"分页数据"`
+}
+type BaseFromUsdaFasIndexSearchList struct {
+	List   []*BaseFromUsdaFasIndexList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+type UsdaFasSingleDataResp struct {
+	BaseFromUsdaFasIndexId int
+	ClassifyId             int
+	EdbInfoId              int
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	StartTime              string
+	CreateTime             string
+	ModifyTime             string
+	EdbExist               int `description:"指标库是否已添加:0-否;1-是"`
+	Data                   []*UsdaFasSingleData
+}
+
+type UsdaFasSingleData struct {
+	Value    string `orm:"column(value)" description:"日期"`
+	DataTime string `orm:"column(data_time)" description:"值"`
+}
+
+func GetUsdaFasIndexByClassifyId(classifyId int) (items []*BaseFromUsdaFasIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT base_from_usda_fas_index_id, classify_id, index_code, index_name FROM base_from_usda_fas_index WHERE classify_id=? ORDER BY sort ASC, base_from_usda_fas_index_id ASC `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}
+
+func GetUsdaFasIndex(condition string, pars interface{}) (items []*BaseFromUsdaFasIndexList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_usda_fas_index_id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func GetUsdaFasIndexPage(condition string, pars interface{}, startSize, pageSize int) (items []*BaseFromUsdaFasIndexList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_usda_fas_index_id asc LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetUsdaFasIndexPageCount(condition string, pars interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count  FROM base_from_usda_fas_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetUsdaFasIndexDataCount(indexCode string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count  FROM base_from_usda_fas_data WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&count)
+	return
+}
+
+func GetUsdaFasIndexDataByDataTime(indexCodes []string, startDate, endDate string) (items []*BaseFromUsdaFasData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_usda_fas_data WHERE  index_code in (` + utils.GetOrmInReplace(len(indexCodes)) + `) and data_time >=? and data_time <? ORDER BY data_time DESC `
+	_, err = o.Raw(sql, indexCodes, startDate, endDate).QueryRows(&items)
+	return
+}
+
+func GetUsdaFasIndexDataTimePageByCodes(indexCodes []string, startSize, pageSize int) (dataTimes []string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT data_time FROM base_from_usda_fas_data WHERE index_code in (` + utils.GetOrmInReplace(len(indexCodes)) + `) GROUP BY data_time ORDER BY data_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, indexCodes, startSize, pageSize).QueryRows(&dataTimes)
+	return
+}
+
+func GetUsdaFasIndexDataTimePageCount(indexCodes []string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(DISTINCT data_time) AS count  FROM base_from_usda_fas_data WHERE index_code in (` + utils.GetOrmInReplace(len(indexCodes)) + `) `
+	err = o.Raw(sql, indexCodes).QueryRow(&count)
+	return
+}
+
+func GetUsdaFasIndexDataByCodes(indexCode []string) (items []*BaseFromUsdaFasData, err error) {
+	if len(indexCode) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_usda_fas_data WHERE index_code in (` + utils.GetOrmInReplace(len(indexCode)) + `) ORDER BY data_time DESC  `
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+// GetUsdaFasByConditionAndFrequency 根据条件获取涌益咨询指标列表
+func GetUsdaFasByConditionAndFrequency(condition, frequency string, pars []interface{}) (items []*BaseFromUsdaFasIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` AND frequency=?`
+	sql += ` ORDER BY sort ASC, base_from_usda_fas_index_id ASC`
+	_, err = o.Raw(sql, pars, frequency).QueryRows(&items)
+	return
+}
+
+func GetUsdaFasFrequencyByCondition(condition string, pars []interface{}) (items []string, err error) {
+	sql := `SELECT DISTINCT frequency FROM base_from_usda_fas_index WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY FIELD(frequency,'日度','周度','旬度','月度','季度','半年度','年度') `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+// GetUsdaFasDataDataTimeByIndexId 根据指标id获取指标数据的日期列表
+func GetUsdaFasDataDataTimeByIndexId(indexIdList []int) (items []string, err error) {
+	if len(indexIdList) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT DISTINCT data_time FROM base_from_usda_fas_data WHERE base_from_usda_fas_index_id IN (` + utils.GetOrmInReplace(len(indexIdList)) + `) ORDER BY data_time DESC`
+	_, err = o.Raw(sql, indexIdList).QueryRows(&items)
+	return
+}
+
+type BaseFromUsdaFasData struct {
+	BaseFromUsdaFasDataId  int `orm:"column(base_from_usda_fas_data_id);pk"`
+	BaseFromUsdaFasIndexId int
+	IndexCode              string
+	DataTime               string
+	Value                  string
+	CreateTime             string
+	ModifyTime             string
+	DataTimestamp          int64
+}
+
+type BaseFromUsdaFasIndexSearchItem struct {
+	BaseFromUsdaFasIndexId int `orm:"column(base_from_usda_fas_index_id);pk"`
+	ClassifyId             int
+	ParentClassifyId       int
+	IndexCode              string
+	IndexName              string
+}
+
+// BatchCheckUsdaFasEdbReq 指标数据结构体
+type BatchCheckUsdaFasEdbReq struct {
+	//IsJoinEdb      int      `form:"IsJoinEdb" description:"是否加到指标库,0:未加到指标库"`
+	Frequencies   string `description:"频度;枚举值:日度、周度、月度、季度、半年度、年度"`
+	Keyword       string `description:"关键字"`
+	ClassifyIds   string `description:"所选品种id列表"`
+	ListAll       bool   `form:"ListAll" json:"ListAll" description:"列表全选"`
+	TradeCodeList string `form:"TradeCodeList" json:"TradeCodeList" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
+}
+
+// GetUsdaFasItemList 模糊查询UsdaFas数据库指标列表
+func GetUsdaFasItemList(condition string) (items []*BaseFromUsdaFasIndexSearchItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM base_from_usda_fas_index  WHERE 1=1"
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetUsdaFasIndexDataByCode(indexCode string) (list []*BaseFromUsdaFasData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_usda_fas_data WHERE index_code=? `
+	_, err = o.Raw(sql, indexCode).QueryRows(&list)
+	return
+}
+
+func GetBaseFromUsdaFasIndexByIndexCode(indexCode string) (list *BaseFromUsdaFasIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_index WHERE index_code=? `
+	err = o.Raw(sql, indexCode).QueryRow(&list)
+	return
+}
+
+type BaseFromUsdaFasIndexType struct {
+	Type2 string `orm:"column(type_2)"`
+	Type3 string `orm:"column(type_3)"`
+}
+
+// Update 更新UsdaFas指标基础信息
+func (item *BaseFromUsdaFasIndex) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(item, cols...)
+	return
+}
+
+// EditUsdaFasIndexInfoResp 新增指标的返回
+type EditUsdaFasIndexInfoResp struct {
+	BaseFromUsdaFasIndexId int    `description:"指标ID"`
+	IndexCode              string `description:"指标code"`
+}
+
+type UsdaFasIndexSource2EdbReq struct {
+	EdbCode       string
+	EdbName       string
+	Frequency     string
+	Unit          string
+	ClassifyId    int
+	AdminId       int
+	AdminRealName string
+}
+
+func GetUsdaFasFrequencyByClassifyId(classifyId int) (items []*GlFrequency, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT frequency FROM base_from_usda_fas_index WHERE classify_id = ? `
+	sql += ` GROUP BY frequency ORDER BY frequency ASC `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}

+ 238 - 0
models/data_manage/base_from_usda_fas_classify.go

@@ -0,0 +1,238 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// BaseFromUsdaFasClassify UsdaFas原始数据分类表
+type BaseFromUsdaFasClassify struct {
+	ClassifyId      int       `orm:"column(classify_id);pk"`
+	ClassifyName    string    `description:"分类名称"`
+	ParentId        int       `description:"父级id"`
+	SysUserId       int       `description:"创建人id"`
+	SysUserRealName string    `description:"创建人姓名"`
+	Level           int       `description:"层级"`
+	Sort            int       `description:"排序字段,越小越靠前,默认值:10"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+}
+
+// AddBaseFromUsdaFasClassify 添加UsdaFas原始数据分类
+func AddBaseFromUsdaFasClassify(item *BaseFromUsdaFasClassify) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	lastId, err = o.Insert(item)
+	return
+}
+
+// GetBaseFromUsdaFasClassifyCount 获取分类名称的个数
+func GetBaseFromUsdaFasClassifyCount(classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM base_from_usda_fas_classify WHERE classify_name=? AND parent_id=? `
+	err = o.Raw(sql, classifyName, parentId).QueryRow(&count)
+	return
+}
+
+// GetBaseFromUsdaFasClassifyById 通过分类id的获取分类信息
+func GetBaseFromUsdaFasClassifyById(classifyId int) (item *BaseFromUsdaFasClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_usda_fas_classify WHERE classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+// GetBaseFromUsdaFasClassifyById 通过分类id的获取分类信息
+func GetBaseFromUsdaFasClassifyByIds(classifyIds []int) (items []*BaseFromUsdaFasClassify, err error) {
+	if len(classifyIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_usda_fas_classify WHERE classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) `
+	_, err = o.Raw(sql, classifyIds).QueryRows(&items)
+	return
+}
+
+// EditBaseFromUsdaFasClassify 修改UsdaFas原始数据分类
+func EditBaseFromUsdaFasClassify(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_usda_fas_classify SET classify_name=?,modify_time=NOW() WHERE classify_id=? `
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+// UpdateBaseFromUsdaFasClassifySort 修改UsdaFas原始数据分类的排序
+func UpdateBaseFromUsdaFasClassifySort(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_usda_fas_classify SET sort=classify_id, modify_time=NOW() WHERE classify_id=? `
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}
+
+type BaseFromUsdaFasClassifyItems struct {
+	ClassifyId             int    `description:"分类ID"`
+	BaseFromUsdaFasIndexId int    `description:"指标类型ID"`
+	IndexCode              string `description:"指标唯一编码"`
+	ClassifyName           string `description:"分类名称"`
+	ClassifyNameEn         string `description:"分类名称"`
+	UniqueCode             string `description:"分类唯一编码"`
+	ParentId               int    `description:"父级id"`
+	Level                  int    `description:"层级"`
+	Sort                   int    `description:"排序字段,越小越靠前,默认值:10"`
+	Children               []*BaseFromUsdaFasClassifyItems
+}
+
+type BaseFromUsdaFasClassifyNameItems struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级id"`
+}
+
+type BaseFromUsdaFasClassifyResp struct {
+	List []*BaseFromUsdaFasClassifyItems
+}
+
+type BaseFromUsdaFasClassifyNameResp struct {
+	List []*BaseFromUsdaFasClassifyNameItems
+}
+
+type BaseFromUsdaFasClassifyItemsButton struct {
+	AddButton    bool `description:"是否可添加"`
+	OpButton     bool `description:"是否可编辑"`
+	DeleteButton bool `description:"是否可删除"`
+	MoveButton   bool `description:"是否可移动"`
+}
+
+// GetBaseFromUsdaFasClassifyByParentId 根据上级id获取当下的分类列表数据
+func GetBaseFromUsdaFasClassifyByParentId(parentId int) (items []*BaseFromUsdaFasClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_classify WHERE parent_id=? order by sort asc,classify_id asc`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}
+
+// GetAllBaseFromUsdaFasClassify 获取所有的分类列表数据
+func GetAllBaseFromUsdaFasClassify() (items []*BaseFromUsdaFasClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_classify order by parent_id asc, sort asc,classify_id asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+type DeleteBaseFromUsdaFasClassifyReq struct {
+	ClassifyId int `description:"分类id"`
+	EdbInfoId  int `description:"指标id"`
+}
+
+type BaseFromUsdaFasClassifyListResp struct {
+	AllNodes      []*BaseFromUsdaFasClassifyItems
+	CanOpClassify bool `description:"是否允许操作分类"`
+}
+
+type BaseFromUsdaFasClassifySimplify struct {
+	ClassifyId   int    `description:"分类id"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     int
+}
+
+// GetFirstBaseFromUsdaFasClassify 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstBaseFromUsdaFasClassify() (item *BaseFromUsdaFasClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_usda_fas_classify order by sort asc,classify_id asc limit 1`
+	err = o.Raw(sql).QueryRow(&item)
+	return
+}
+
+// UpdateBaseFromUsdaFasClassifySortByClassifyId 根据分类id更新排序
+func UpdateBaseFromUsdaFasClassifySortByClassifyId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update base_from_usda_fas_classify set sort = ` + updateSort + ` WHERE parent_id=? AND sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// MoveUpUsdaFasIndexClassifyBySort 往上移动
+func MoveUpUsdaFasIndexClassifyBySort(parentId, nextSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_usda_fas_classify set sort = sort + 1 where parent_id=? and sort >= ? and sort< ?`
+	_, err = o.Raw(sql, parentId, nextSort, currentSort).Exec()
+	return
+}
+
+// MoveDownUsdaFasIndexClassifyBySort 往下移动
+func MoveDownUsdaFasIndexClassifyBySort(parentId, prevSort, currentSort int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `update base_from_usda_fas_classify set sort = sort - 1 where parent_id=? and sort <= ? and sort> ? `
+	_, err = o.Raw(sql, parentId, prevSort, currentSort).Exec()
+	return
+}
+
+// GetUsdaFasIndexClassifyMinSort 获取最小不等于0的排序
+func GetUsdaFasIndexClassifyMinSort(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select min(sort) from base_from_usda_fas_classify where parent_id=? and sort <> 0 `
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+// Update 更新分类基础信息
+func (BaseFromUsdaFasClassify *BaseFromUsdaFasClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(BaseFromUsdaFasClassify, cols...)
+	return
+}
+
+type AddUsdaFasClassifyResp struct {
+	ClassifyId int
+}
+
+// DeleteUsdaFasClassifyByClassifyId 根据分类id删除对应的指标分类
+func DeleteUsdaFasClassifyByClassifyId(classifyIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	num := len(classifyIdList)
+	if num <= 0 {
+		return
+	}
+	//删除分类
+	sql := `DELETE FROM base_from_usda_fas_classify WHERE classify_id IN (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, classifyIdList).Exec()
+	return
+}
+
+// AddUsdaFasClassifyMulti 批量新增SMM类别
+func AddUsdaFasClassifyMulti(list []*BaseFromUsdaFasClassify) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(1, list)
+	return
+}
+
+// InitUsdaFasClassifySort 初始化sort值
+func InitUsdaFasClassifySort() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_usda_fas_classify 
+SET modify_time=NOW(), sort = classify_id`
+	_, err = o.Raw(sql).Exec()
+	return
+}
+
+// InitUsdaFasIndexClassifyId 历史数据的classifyId值
+func InitUsdaFasIndexClassifyId() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE base_from_UsdaFasindex s
+LEFT JOIN (
+SELECT
+	c1.classify_id,
+	CONCAT( c2.classify_name, c1.classify_name ) AS type_name 
+FROM
+	base_from_usda_fas_classify c1
+	LEFT JOIN base_from_usda_fas_classify c2 ON c1.parent_id = c2.classify_id 
+	) AS t ON CONCAT( s.type_2, s.type_3 ) = t.type_name
+	SET s.classify_id = t.classify_id, s.modify_time=NOW() where s.type_2 <>""`
+	_, err = o.Raw(sql).Exec()
+	return
+}

+ 9 - 1
models/data_manage/chart_classify.go

@@ -184,6 +184,14 @@ func GetChartClassifyAll(source int) (items []*ChartClassifyItems, err error) {
 	return
 }
 
+// GetChartClassifyBySource 获取图表分类列表
+func GetChartClassifyBySource(source int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_classify WHERE source = ? ORDER BY level ASC,chart_classify_id ASC`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+
 type ChartClassifyItems struct {
 	ChartClassifyId     int `description:"分类id"`
 	ChartInfoId         int `description:"指标id"`
@@ -582,7 +590,7 @@ SELECT
 FROM
 	chart_info 
 WHERE
-	chart_classify_id = ? AND sys_user_id = ?
+	chart_classify_id = ? AND sys_user_id = ? AND source = 1
 ORDER BY
 	sort ASC,
 	chart_classify_id ASC`

+ 26 - 4
models/data_manage/data_manage_permission/edb.go

@@ -107,11 +107,33 @@ func SetPermissionByEdbIdList(edbIdList []string, userIdList []int, edbInfoType
 
 	// 获取已经配置的指标权限用户
 	edbInfoPermissionList := make([]*EdbInfoPermission, 0)
-	sql := `SELECT * FROM edb_info_permission WHERE edb_info_type = ? AND edb_info_id in (` + utils.GetOrmInReplace(edbNum) + `) `
-	_, err = o.Raw(sql, edbInfoType, edbIdList).QueryRows(&edbInfoPermissionList)
-	if err != nil {
-		return
+	// 定义批次大小
+	batchSize := 500
+	var sql string
+	for i := 0; i < edbNum; i += batchSize {
+		// 确定当前批次的结束索引
+		end := i + batchSize
+		if end > edbNum {
+			end = edbNum
+		}
+
+		// 获取当前批次的 ID 列表
+		batch := edbIdList[i:end]
+
+		// 生成批次查询 SQL
+		sql = `SELECT * FROM edb_info_permission WHERE edb_info_type = ? AND edb_info_id in (` + utils.GetOrmInReplace(len(batch)) + `)`
+
+		// 执行查询
+		var batchResult []*EdbInfoPermission
+		_, err = o.Raw(sql, edbInfoType, batch).QueryRows(&batchResult)
+		if err != nil {
+			return
+		}
+
+		// 将批次结果追加到总列表中
+		edbInfoPermissionList = append(edbInfoPermissionList, batchResult...)
 	}
+
 	edbInfoPermissionMap := make(map[string]*EdbInfoPermission)
 	for _, v := range edbInfoPermissionList {
 		edbInfoPermissionMap[fmt.Sprint(v.EdbInfoId, "_", v.SysUserId)] = v

+ 2 - 0
models/data_manage/edb_classify.go

@@ -76,6 +76,7 @@ func GetEdbClassifyEnCount(classifyNameEn string, parentId int, classifyType uin
 type EditEdbClassifyReq struct {
 	ClassifyName string `description:"分类名称"`
 	ClassifyId   int    `description:"分类名称"`
+	ParentId     int    `description:"父级分类id"`
 }
 
 func GetEdbClassifyById(classifyId int) (item *EdbClassify, err error) {
@@ -253,6 +254,7 @@ type EdbClassifyItems struct {
 	Button           EdbClassifyItemsButton `description:"操作权限"`
 	IsJoinPermission int                    `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth    bool                   `description:"是否有数据权限"`
+	SelectDisable    bool                   `description:"是否可以被选中 前端用"`
 }
 
 type EdbClassifyIdItems struct {

+ 6 - 6
models/data_manage/edb_data_wind.go

@@ -80,14 +80,14 @@ func EdbInfoUpdateStatusByEdbInfoId(edbInfoIds []int, isStop int, calculateEdbIn
 
 	// 更改指标的更新状态
 	if len(edbInfoIds) == 1 {
-		sql := ` UPDATE edb_info SET no_update = ? WHERE edb_info_id=? `
-		_, err = o.Raw(sql, isStop, edbInfoIds[0]).Exec()
+		sql := ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE edb_info_id=? `
+		_, err = o.Raw(sql, isStop, time.Now(), edbInfoIds[0]).Exec()
 		if err != nil {
 			return
 		}
 	} else {
-		sql := ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
-		_, err = o.Raw(sql, isStop, edbInfoIds).Exec()
+		sql := ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, time.Now(), edbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -95,8 +95,8 @@ func EdbInfoUpdateStatusByEdbInfoId(edbInfoIds []int, isStop int, calculateEdbIn
 
 	if len(calculateEdbInfoIds) > 0 {
 		// 批量更新相关联的指标ID
-		sql := ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
-		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		sql := ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, time.Now(), calculateEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}

+ 57 - 35
models/data_manage/edb_info.go

@@ -38,35 +38,37 @@ type EdbInfo struct {
 	CreateTime       time.Time
 	ModifyTime       time.Time
 	BaseModifyTime   time.Time
-	MinValue         float64 `description:"指标最小值"`
-	MaxValue         float64 `description:"指标最大值"`
-	CalculateFormula string  `description:"计算公式"`
-	EdbType          int     `description:"指标类型:1:基础指标,2:计算指标"`
-	Sort             int     `description:"排序字段"`
-	LatestDate       string  `description:"数据最新日期(实际日期)"`
-	LatestValue      float64 `description:"数据最新值(实际值)"`
-	EndValue         float64 `description:"数据的最新值(预测日期的最新值)"`
-	MoveType         int     `description:"移动方式:1:领先(默认),2:滞后"`
-	MoveFrequency    string  `description:"移动频度"`
-	NoUpdate         int8    `description:"是否停止更新,0:继续更新;1:停止更新"`
-	ServerUrl        string  `description:"服务器地址"`
-	ChartImage       string  `description:"图表图片"`
-	Calendar         string  `description:"公历/农历" orm:"default(公历);"`
-	DataDateType     string  `orm:"column(data_date_type);size(255);null;default(交易日)"`
-	ManualSave       int     `description:"是否有手动保存过上下限: 0-否; 1-是"`
-	EmptyType        int     `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
-	MaxEmptyType     int     `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
-	TerminalCode     string  `description:"终端编码,用于配置在机器上"`
-	DataUpdateTime   string  `description:"最近一次数据发生变化的时间"`
-	ErDataUpdateDate string  `description:"本次更新,数据发生变化的最早日期"`
-	SourceIndexName  string  `description:"数据源中的指标名称"`
-	SubSource        int     `description:"子数据来源:0:经济数据库,1:日期序列"`
-	SubSourceName    string  `description:"子数据来源名称"`
-	IndicatorCode    string  `description:"指标代码"`
-	StockCode        string  `description:"证券代码"`
-	Extra            string  `description:"指标额外配置"`
-	IsJoinPermission int     `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
-	IsStaticData     int     `description:"是否是静态指标,0否,1是"`
+	MinValue         float64   `description:"指标最小值"`
+	MaxValue         float64   `description:"指标最大值"`
+	CalculateFormula string    `description:"计算公式"`
+	EdbType          int       `description:"指标类型:1:基础指标,2:计算指标"`
+	Sort             int       `description:"排序字段"`
+	LatestDate       string    `description:"数据最新日期(实际日期)"`
+	LatestValue      float64   `description:"数据最新值(实际值)"`
+	EndValue         float64   `description:"数据的最新值(预测日期的最新值)"`
+	MoveType         int       `description:"移动方式:1:领先(默认),2:滞后"`
+	MoveFrequency    string    `description:"移动频度"`
+	NoUpdate         int8      `description:"是否停止更新,0:继续更新;1:停止更新"`
+	ServerUrl        string    `description:"服务器地址"`
+	ChartImage       string    `description:"图表图片"`
+	Calendar         string    `description:"公历/农历" orm:"default(公历);"`
+	DataDateType     string    `orm:"column(data_date_type);size(255);null;default(交易日)"`
+	ManualSave       int       `description:"是否有手动保存过上下限: 0-否; 1-是"`
+	EmptyType        int       `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int       `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
+	TerminalCode     string    `description:"终端编码,用于配置在机器上"`
+	DataUpdateTime   string    `description:"最近一次数据发生变化的时间"`
+	ErDataUpdateDate string    `description:"本次更新,数据发生变化的最早日期"`
+	SourceIndexName  string    `description:"数据源中的指标名称"`
+	SubSource        int       `description:"子数据来源:0:经济数据库,1:日期序列"`
+	SubSourceName    string    `description:"子数据来源名称"`
+	IndicatorCode    string    `description:"指标代码"`
+	StockCode        string    `description:"证券代码"`
+	Extra            string    `description:"指标额外配置"`
+	IsJoinPermission int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	IsStaticData     int       `description:"是否是静态指标,0否,1是"`
+	SetUpdateTime    time.Time `description:"设置启用/禁用刷新状态的时间"`
+	EndDateType      int       `description:"预测指标截止日期类型:0:未来日期,1未来期数"`
 }
 
 type EdbInfoFullClassify struct {
@@ -225,6 +227,11 @@ func GetEdbInfoByIdList(edbInfoIdList []int) (items []*EdbInfo, err error) {
 
 // BatchAddCheckReq 指标批量添加校验
 type BatchAddCheckReq struct {
+	IndexCodes []string `form:"IndexCodes" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
+}
+
+// MysteelChemicalDataBatchAddCheckReq 钢联化工指标批量添加校验
+type MysteelChemicalDataBatchAddCheckReq struct {
 	// MysteelChemicalDataListReq
 	IndexCodes []string `form:"IndexCodes" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
 }
@@ -462,6 +469,10 @@ type EdbInfoList struct {
 	HaveOperaAuth    bool                    `description:"是否有数据权限,默认:false"`
 	IsStaticData     int                     `description:"是否是静态指标,0否,1是"`
 	IsSupplierStop   int                     `description:"是否供应商停更:1:停更,0:未停更"`
+	MoveType         int                     `description:"移动方式:1:领先(默认),2:滞后"`
+	MoveFrequency    string                  `description:"移动频度"`
+	MinValue         float64                 `description:"最小值"`
+	MaxValue         float64                 `description:"最大值"`
 }
 
 type EdbDataInsertConfigItem struct {
@@ -1457,9 +1468,12 @@ func GetEdbInfoListByCondition(condition string, pars []interface{}, startSize,
 
 	sql += ` ORDER BY edb_info_id `
 	sql += orderDesc
-	sql += ` LIMIT ?,? `
+	if pageSize > 0 {
+		sql += ` LIMIT ?,? `
+		pars = append(pars, startSize, pageSize)
+	}
 
-	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
 
@@ -1814,15 +1828,15 @@ func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int, calculateEdbInfoIds
 	}()
 
 	// 更改指标的更新状态
-	sql := ` UPDATE edb_info SET no_update = ? WHERE  edb_info_id IN (` + utils.GetOrmInReplace(idNum) + `) `
-	_, err = o.Raw(sql, isStop, edbIdList).Exec()
+	sql := ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE  edb_info_id IN (` + utils.GetOrmInReplace(idNum) + `) `
+	_, err = o.Raw(sql, isStop, time.Now(), edbIdList).Exec()
 	if err != nil {
 		return
 	}
 	if len(calculateEdbInfoIds) > 0 {
 		// 批量更新相关联的指标ID
-		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
-		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, time.Now(), calculateEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -2123,3 +2137,11 @@ type EdbNameCheckResult struct {
 	EdbName string
 	Exist   bool
 }
+
+// UpdateEdbClassifyIdByChartInfoId
+func UpdateEdbClassifyIdByChartInfoId(chartInfoIds []int, classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update edb_info set classify_id = ? WHERE edb_info_id in (` + utils.GetOrmInReplace(len(chartInfoIds)) + `) `
+	_, err = o.Raw(sql, classifyId, chartInfoIds).Exec()
+	return
+}

+ 11 - 11
models/data_manage/edb_info_relation.go

@@ -104,8 +104,8 @@ func AddOrUpdateEdbInfoRelation(objectId, objectType int, relationList []*EdbInf
 
 	if len(refreshEdbInfoIds) > 0 {
 		//todo 是否需要所有指标的刷新状态
-		sql := ` UPDATE edb_info SET no_update = 0 WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND  no_update = 1`
-		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		sql := ` UPDATE edb_info SET no_update = 0, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, time.Now(), refreshEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -172,8 +172,8 @@ func AddOrUpdateEdbInfoRelationMulti(relationList []*EdbInfoRelation, refreshEdb
 
 	if len(refreshEdbInfoIds) > 0 {
 		// todo 更新指标的刷新状态
-		sql := ` UPDATE edb_info SET no_update = 0 WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND no_update = 1`
-		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		sql := ` UPDATE edb_info SET no_update = 0, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, time.Now(), refreshEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -386,7 +386,7 @@ func ReplaceRelationEdbInfoId(oldEdbInfo, newEdbInfo *EdbInfo, edbRelationIds []
 		return
 	}
 
-	sourceWhere := ` and (refer_object_type in (1,2 ) or (refer_object_type=4 and refer_object_sub_type !=5) )` //平衡表和事件日历中的直接引用无需替换,
+	sourceWhere := ` and (refer_object_type in (1,2,5) or (refer_object_type=4 and refer_object_sub_type !=5) )` //平衡表和事件日历中的直接引用无需替换,
 	// 替换edb_info_id
 	sql = ` UPDATE edb_info_relation SET edb_info_id=?, source=?, edb_name=?, edb_code=?, modify_time=?, relation_time=?  WHERE edb_info_id=? ` + sourceWhere + ` and relation_type=0 and edb_info_relation_id in (` + utils.GetOrmInReplace(len(edbRelationIds)) + `)`
 	_, err = o.Raw(sql, newEdbInfo.EdbInfoId, newEdbInfo.Source, newEdbInfo.EdbName, newEdbInfo.EdbCode, now, now, oldEdbInfo.EdbInfoId, edbRelationIds).Exec()
@@ -416,8 +416,8 @@ func ReplaceRelationEdbInfoId(oldEdbInfo, newEdbInfo *EdbInfo, edbRelationIds []
 
 	if len(refreshEdbInfoIds) > 0 {
 		// todo 更新指标的刷新状态
-		sql := ` UPDATE edb_info SET no_update = 0 WHERE  edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND no_update = 1`
-		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		sql := ` UPDATE edb_info SET no_update = 0, set_update_time=? WHERE  edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, time.Now(), refreshEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -490,8 +490,8 @@ func UpdateSecondRelationEdbInfoId(edbRelationIds []int, relationList []*EdbInfo
 
 	if len(refreshEdbInfoIds) > 0 {
 		// todo 更新指标的刷新状态
-		sql := ` UPDATE edb_info SET no_update = 0 WHERE  edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) AND no_update = 1`
-		_, err = o.Raw(sql, refreshEdbInfoIds).Exec()
+		sql = ` UPDATE edb_info SET no_update = 0, set_update_time=? WHERE  edb_info_id IN (` + utils.GetOrmInReplace(len(refreshEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, time.Now(), refreshEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -500,7 +500,7 @@ func UpdateSecondRelationEdbInfoId(edbRelationIds []int, relationList []*EdbInfo
 	//更新数据源钢联化工指标
 	if len(indexCodeList) > 0 {
 		// 更改数据源的更新状态
-		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
+		sql = ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
 		_, err = o.Raw(sql, indexCodeList).Exec()
 		if err != nil {
 			return
@@ -513,7 +513,7 @@ func UpdateSecondRelationEdbInfoId(edbRelationIds []int, relationList []*EdbInfo
 			relationCodes = append(relationCodes, relationCode)
 		}
 		if len(relationCodes) > 0 {
-			sql := ` UPDATE edb_info_relation e1  
+			sql = ` UPDATE edb_info_relation e1  
 JOIN edb_info_relation e2 ON e1.relation_code = e2.relation_code   
 SET e1.parent_relation_id = e2.edb_info_relation_id  
 WHERE  

+ 45 - 0
models/data_manage/excel/referenced_excel_config.go

@@ -0,0 +1,45 @@
+package excel
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type ReferencedExcelConfig struct {
+	ReferencedExcelConfigId int       `orm:"column(referenced_excel_config_id);pk;auto" ` // excel表格配置id
+	UniqueCode              string    // 表格唯一编码
+	ReferencedID            int       // 被引用的id,报告就是报告id,pptId
+	FromScene               int       // 引用类型 1智能研报 2研报列表 3英文研报 4PPT
+	Uuid                    string    // 引用唯一标识
+	WidthList               string    // 宽度数组
+	HeightList              string    // 高度数组
+	OpUserID                int       // 当前编辑操作的用户id
+	OpUserName              string    // 当前编辑的用户名称(冗余字段,避免查表)
+	CreateTime              time.Time // 创建时间
+	Content                 string    // 内容
+	ModifyTime              time.Time // 修改时间
+}
+
+type ExcelReferencesReq struct {
+	UniqueCode   string `description:"表格唯一编码"`
+	ReferencedId int    `description:"被引用的ID"`
+	FromScene    int    `description:"引用类型 1智能研报 2研报列表 3英文研报 4PPT 5英文PPT"`
+	Uuid         string `description:"引用唯一标识"`
+	WidthList    string `description:"宽度数组"`
+	HeightList   string `description:"高度数组"`
+}
+
+// add
+func AddReferencedExcelConfig(items []*ReferencedExcelConfig) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+// getByCode
+func GetReferencedExcelConfigByUniqueCode(uniqueCode string) (item ReferencedExcelConfig, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM referenced_excel_config WHERE referenced_excel_unique_code = ? `
+	err = o.Raw(sql, uniqueCode).QueryRow(&item)
+	return
+}

+ 10 - 10
models/data_manage/mysteel_chemical_index.go

@@ -620,8 +620,8 @@ func ModifyMysteelChemicalUpdateStatus(edbIdList []int, indexCodeList []string,
 	}
 
 	// 更改指标的更新状态
-	sql = ` UPDATE edb_info SET no_update = ? WHERE source = ? AND sub_source= ? AND edb_code IN (` + utils.GetOrmInReplace(codeNum) + `) `
-	_, err = o.Raw(sql, isStop, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, 0, indexCodeList).Exec()
+	sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE source = ? AND sub_source= ? AND edb_code IN (` + utils.GetOrmInReplace(codeNum) + `) `
+	_, err = o.Raw(sql, isStop, time.Now(), utils.DATA_SOURCE_MYSTEEL_CHEMICAL, 0, indexCodeList).Exec()
 	if err != nil {
 		return
 	}
@@ -658,15 +658,15 @@ func ModifyMysteelChemicalUpdateStatusByEdbInfoId(edbInfoId, isStop int, edbCode
 	}
 
 	// 更改指标的更新状态
-	sql = ` UPDATE edb_info SET no_update = ? WHERE source = ? AND sub_source= ? AND edb_info_id=? `
-	_, err = o.Raw(sql, isStop, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, 0, edbInfoId).Exec()
+	sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE source = ? AND sub_source= ? AND edb_info_id=? `
+	_, err = o.Raw(sql, isStop, time.Now(), utils.DATA_SOURCE_MYSTEEL_CHEMICAL, 0, edbInfoId).Exec()
 	if err != nil {
 		return
 	}
 	if len(calculateEdbInfoIds) > 0 {
 		// 批量更新相关联的指标ID
-		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
-		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, time.Now(), calculateEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}
@@ -696,15 +696,15 @@ func ModifyMysteelChemicalUpdateStatusByEdbInfoIds(edbInfoIds []int, isStop int,
 	}
 
 	// 更改指标的更新状态
-	sql = ` UPDATE edb_info SET no_update = ? WHERE source = ? AND edb_info_id IN (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
-	_, err = o.Raw(sql, isStop, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, edbInfoIds).Exec()
+	sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE source = ? AND edb_info_id IN (` + utils.GetOrmInReplace(len(edbInfoIds)) + `) `
+	_, err = o.Raw(sql, isStop, time.Now(), utils.DATA_SOURCE_MYSTEEL_CHEMICAL, edbInfoIds).Exec()
 	if err != nil {
 		return
 	}
 	if len(calculateEdbInfoIds) > 0 {
 		// 批量更新相关联的指标ID
-		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
-		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, time.Now(), calculateEdbInfoIds).Exec()
 		if err != nil {
 			return
 		}

+ 3 - 0
models/data_manage/predict_edb_conf.go

@@ -19,6 +19,7 @@ type PredictEdbConf struct {
 	EndDate          time.Time `description:"截止日期"`
 	ModifyTime       time.Time `description:"修改时间"`
 	CreateTime       time.Time `description:"添加时间"`
+	EndNum           int       `description:"截止期数"`
 }
 
 // PredictEdbConfDetail 预测规则 和 规则相关联的指标
@@ -35,6 +36,7 @@ type PredictEdbConfDetail struct {
 	ModifyTime       time.Time                               `description:"修改时间"`
 	CreateTime       time.Time                               `description:"添加时间"`
 	CalculateList    []*PredictEdbConfCalculateMappingDetail `description:"配置与指标的关联信息"`
+	EndNum           int                                     `description:"截止期数"`
 }
 
 // PredictEdbConfAndData 预测规则和其对应的动态数据
@@ -46,6 +48,7 @@ type PredictEdbConfAndData struct {
 	FixedValue       float64        `description:"固定值"`
 	Value            string         `description:"配置的值"`
 	EndDate          time.Time      `description:"截止日期"`
+	EndNum           int            `description:"截止期数"`
 	ModifyTime       time.Time      `description:"修改时间"`
 	CreateTime       time.Time      `description:"添加时间"`
 	DataList         []*EdbDataList `description:"动态数据"`

+ 74 - 0
models/data_manage/request/clarksons_data.go

@@ -0,0 +1,74 @@
+package request
+
+type AddBaseFromClarksonsClassifyReq struct {
+	ParentId     int    `description:"上级id"`
+	ClassifyName string `description:"分类名称"`
+}
+
+// DelBaseFromClarksonsReq 删除卓创红期分类
+type DelBaseFromClarksonsClassifyReq struct {
+	BaseFromClassifyId int `description:"分类id"`
+}
+
+// EditBaseFromSciClassifyReq 编辑卓创红期分类
+type EditBaseFromClarksonsClassifyReq struct {
+	ClassifyName       string `description:"分类名称"`
+	BaseFromClassifyId int    `description:"分类id"`
+}
+
+// EditBaseFromClarksonsReq 编辑卓创红期指标所属分类
+type EditBaseFromClarksonsReq struct {
+	BaseFromClarksonsIndexId int `description:"指标id"`
+	BaseFromClassifyId       int `description:"分类id"`
+}
+
+// DelBaseFromClarksonsReq 删除卓创红期指标
+type DelBaseFromClarksonsReq struct {
+	BaseFromClarksonsIndexId int `description:"指标id"`
+}
+
+// ResetBaseFromClarksonsReq 重置指标所属分类
+type ResetBaseFromClarksonsReq struct {
+	BaseFromClarksonsIndexId int `description:"指标id"`
+}
+
+// ClarksonsDataBatchAddCheckReq 卓创红期指标批量添加校验
+type ClarksonsDataBatchAddCheckReq struct {
+	// MysteelChemicalDataListReq
+	IndexCodes []string `description:"指标编码"`
+}
+
+// ClarksonsDataBatchListReq 卓创红期指标批量列表
+type ClarksonsDataBatchListReq struct {
+	ClassifyIds  string `description:"分类id"`
+	KeyWord      string `description:"关键字"`
+	Frequencies  string `description:"频度"`
+	SelectedId   []int  `description:"已选指标id, 为true时表示反选"`
+	IsSelectAll  bool   `description:"是否查询全部, 默认false, true:全选, false:查询已选"`
+	PageSize     int    `description:"每页条数"`
+	CurrentIndex int    `description:"当前页"`
+}
+
+// MoveBaseFromClarksonsClassifyReq 移动分类请求参数
+type MoveBaseFromClarksonsClassifyReq struct {
+	BaseFromClassifyId int `description:"分类id"`
+	ParentClassifyId   int `description:"父级分类id"`
+	PrevClassifyId     int `description:"上一个兄弟节点分类id"`
+	NextClassifyId     int `description:"下一个兄弟节点分类id"`
+}
+
+// MoveBaseFromClarksonsReq 移动指标请求参数
+type MoveBaseFromClarksonsReq struct {
+	BaseFromClassifyId           int `description:"分类id"`
+	BaseFromClarksonsIndexId     int `description:"指标id"`
+	PrevBaseFromClarksonsIndexId int `description:"上一个兄弟节点id"`
+	NextBaseFromClarksonsIndexId int `description:"下一个兄弟节点id"`
+}
+
+// ExportClarksonsExcelReq导出卓创红期excel指标
+type ExportClarksonsExcelReq struct {
+	KeyWord            string   `description:"关键字, 指标编码或指标ID"`
+	IndexCode          []string `description:"指标编码,全选时,表示反选"`
+	IsSelectedAll      bool     `description:"是否全选:true:全选|false: 无"`
+	BaseFromClassifyId int      `description:"指标id"`
+}

+ 12 - 0
models/data_manage/request/edb_info.go

@@ -6,3 +6,15 @@ type ModifyEdbInfoReq struct {
 	MaxValue  float64 `description:"最大值"`
 	MinValue  float64 `description:"最小值"`
 }
+
+// ModifyEdbListReq 批量编辑指标请求参数
+type ModifyEdbListReq struct {
+	SelectAll        bool
+	SubClassify      bool `description:"是否关联指标子分类"`
+	EdbClassifyIds string
+	SysUserIds       string
+	KeyWord          string
+	EdbInfoIds       string `description:"图表ID"`
+	ClassifyId       int    `description:"新图表分类"`
+	Sources          string
+}

+ 9 - 7
models/data_manage/request/predict_edb_info.go

@@ -18,6 +18,7 @@ type PredictEdbInfoChartDataReq struct {
 	RuleList     []RuleConfig `description:"配置规则列表"`
 	DataDateType string       `description:"数据日期类型,枚举值:交易日、自然日"`
 	StartYear    int          `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	EndDateType  int          `description:"截止日期类型:0:未来日期,1未来期数"`
 }
 
 // AddPredictEdbInfoReq 添加预测指标请求
@@ -29,13 +30,13 @@ type AddPredictEdbInfoReq struct {
 	RuleType        int          `description:"预测规则,1:最新,2:固定值"`
 	FixedValue      float64      `description:"固定值"`
 	RuleList        []RuleConfig `description:"配置规则列表"`
-
-	DataDateType string  `description:"日期类型,枚举值:交易日、自然日"`
-	MaxValue     float64 `description:"最大值"`
-	MinValue     float64 `description:"最小值"`
-	EdbInfoId    int     `description:"指标ID"`
-	AdminId      int     `description:"添加人id"`
-	AdminName    string  `description:"添加人名称"`
+	DataDateType    string       `description:"日期类型,枚举值:交易日、自然日"`
+	MaxValue        float64      `description:"最大值"`
+	MinValue        float64      `description:"最小值"`
+	EdbInfoId       int          `description:"指标ID"`
+	AdminId         int          `description:"添加人id"`
+	AdminName       string       `description:"添加人名称"`
+	EndDateType     int          `description:"截止日期类型:0:未来日期,1未来期数"`
 }
 
 // RuleConfig 预测规则配置
@@ -45,6 +46,7 @@ type RuleConfig struct {
 	EmptyType    int                          `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
 	MaxEmptyType int                          `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	EndDate      string                       `description:"截止日期"`
+	EndNum       int                          `description:"截止期数"`
 	EdbInfoIdArr []data_manage.EdbInfoFromTag `description:"指标信息"`
 }
 

+ 35 - 0
models/data_manage/response/clarksons_data.go

@@ -0,0 +1,35 @@
+package response
+
+import (
+	"eta/eta_api/models/data_manage"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type ClarksonsIndexPageListResp struct {
+	List   []*data_manage.BaseFromClarksonsIndexView
+	Paging *paging.PagingItem
+}
+
+type EditClarksonsIndexInfoResp struct {
+	BaseFromClarksonsIndexId int
+	IndexCode            string
+}
+
+type ClarksonsSingleDataResp struct {
+	BaseFromClarksonsIndexId int
+	ClassifyId           int
+	EdbInfoId            int
+	Name                 string
+	IndexCode            string
+	IndexName            string
+	Frequency            string
+	Unit                 string
+	ApiStartTime         string
+	ApiUpdateTime        string
+	StartTime            string
+	FinishTime           string
+	CreateTime           string
+	ModifyTime           string
+	Data                 []*data_manage.BaseFromClarksonsData
+}

+ 3 - 2
models/data_manage/smm_data.go

@@ -4,6 +4,7 @@ import (
 	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
 )
 
 type SmmClassify struct {
@@ -341,8 +342,8 @@ func ModifySmmUpdateStatus(edbIdList []int, indexCodeList []string, isStop int)
 	}
 
 	// 更改指标的更新状态
-	sql = ` UPDATE edb_info SET no_update = ? WHERE source = ? AND sub_source= ? AND edb_code IN (` + utils.GetOrmInReplace(codeNum) + `) `
-	_, err = o.Raw(sql, isStop, utils.DATA_SOURCE_YS, 0, indexCodeList).Exec()
+	sql = ` UPDATE edb_info SET no_update = ?, set_update_time=? WHERE source = ? AND sub_source= ? AND edb_code IN (` + utils.GetOrmInReplace(codeNum) + `) `
+	_, err = o.Raw(sql, isStop, time.Now(), utils.DATA_SOURCE_YS, 0, indexCodeList).Exec()
 	if err != nil {
 		return
 	}

+ 9 - 8
models/data_manage/trade_analysis/trade_analysis.go

@@ -210,14 +210,15 @@ func GetTradeDataByClassifyAndCompany(exchange, classifyName string, contracts,
 		condSold += fmt.Sprintf(` AND sold_short_name IN (%s)`, utils.GetOrmInReplace(len(condCompanies)))
 		parsSold = append(parsSold, condCompanies)
 	} else {
+		// 这里rank=0或者999是因为大商所的数据并不只有999
 		if len(condCompanies) > 0 {
-			condBuy += fmt.Sprintf(` AND (rank = 999 OR buy_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
-			condSold += fmt.Sprintf(` AND (rank = 999 OR sold_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
+			condBuy += fmt.Sprintf(` AND (rank = 999 OR rank = 0 OR buy_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
+			condSold += fmt.Sprintf(` AND (rank = 999 OR rank = 0 OR sold_short_name IN (%s))`, utils.GetOrmInReplace(len(condCompanies)))
 			parsBuy = append(parsBuy, condCompanies)
 			parsSold = append(parsSold, condCompanies)
 		} else {
-			condBuy += ` AND rank = 999`
-			condSold += ` AND rank = 999`
+			condBuy += ` AND (rank = 999 OR rank = 0)`
+			condSold += ` AND (rank = 999 OR rank = 0)`
 		}
 	}
 
@@ -361,10 +362,10 @@ const (
 	WarehouseDefaultFrequency = "日度"
 
 	GuangZhouTopCompanyAliasName = "日成交持仓排名" // 广期所TOP20对应的公司名称
-	GuangZhouSeatNameBuy         = "持买单量"       // 广期所指标名称中的多单名称
-	GuangZhouSeatNameSold        = "持卖单量"       // 广期所指标名称中的空单名称
-	GuangZhouTopSeatNameBuy      = "持买单量总计"   // 广期所指标名称中的TOP20多单名称
-	GuangZhouTopSeatNameSold     = "持卖单量总计"   // 广期所指标名称中的TOP20空单名称
+	GuangZhouSeatNameBuy         = "持买单量"    // 广期所指标名称中的多单名称
+	GuangZhouSeatNameSold        = "持卖单量"    // 广期所指标名称中的空单名称
+	GuangZhouTopSeatNameBuy      = "持买单量总计"  // 广期所指标名称中的TOP20多单名称
+	GuangZhouTopSeatNameSold     = "持卖单量总计"  // 广期所指标名称中的TOP20空单名称
 )
 
 const (

+ 18 - 0
models/db.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
 	"eta/eta_api/models/ai_summary"
 	"eta/eta_api/models/aimod"
 	"eta/eta_api/models/bi_dashboard"
@@ -230,6 +231,9 @@ func init() {
 
 	// 智能看板
 	initBiDashBoard()
+
+	// AI预测模型表
+	initAiPredictModel()
 }
 
 // initSystem 系统表 数据表
@@ -379,6 +383,9 @@ func initEdbData() {
 		new(data_manage.BaseFromSciHqClassify),
 		new(data_manage.BaseFromSciHqIndex),
 		new(data_manage.BaseFromSciHqData),
+		new(data_manage.BaseFromClarksonsClassify),
+		new(data_manage.BaseFromClarksonsIndex),
+		new(data_manage.BaseFromClarksonsData),
 	)
 }
 
@@ -701,6 +708,17 @@ func initBiDashBoard() {
 	)
 }
 
+// initAiPredictModel AI预测模型表
+func initAiPredictModel() {
+	orm.RegisterModel(
+		new(aiPredictModel.AiPredictModelClassify),
+		new(aiPredictModel.AiPredictModelIndex),
+		new(aiPredictModel.AiPredictModelData),
+		new(aiPredictModel.AiPredictModelDashboard),
+		new(aiPredictModel.AiPredictModelDashboardDetail),
+	)
+}
+
 // afterInitTable
 // @Description: 初始化表结构的的后置操作
 // @author: Roc

+ 19 - 9
models/document_manage_model/outside_report.go

@@ -50,33 +50,43 @@ func init() {
 }
 
 // GetOutsideReportListByConditionCount 根据条件查询列表条数
-func GetOutsideReportListByConditionCount(condition string, pars []interface{}) (count int, err error) {
+func GetOutsideReportListByConditionCount(condition string, pars []interface{}, chartPermissionIdList []string) (count int, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `select count(DISTINCT t1.outside_report_id) from outside_report t1 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
+	var sql string
+	if len(chartPermissionIdList) > 0 {
+		sql = `select count(1) from (`
+	}
+	sql += `SELECT COUNT( DISTINCT t1.outside_report_id ) 
+			FROM outside_report t1 
+			    LEFT JOIN chart_permission_search_key_word_mapping t2 ON t1.classify_id = t2.classify_id 
+			WHERE 1 = 1 `
 	sql += condition
+	if len(chartPermissionIdList) > 0 {
+		sql += ` ) t`
+	}
+
 	err = o.Raw(sql, pars).QueryRow(&count)
-	if err != nil {
+	if err != nil && err != orm.ErrNoRows {
 		return 0, err
 	}
 
-	return count, err
+	return count, nil
 }
 
 // GetOutsideReportListByCondition 根据条件查询列表
-func GetOutsideReportListByCondition(condition string, pars []interface{}, currentIndex int, pageSize int) (list []OutsideReport, err error) {
+func GetOutsideReportListByCondition(condition string, pars []interface{}) (list []OutsideReport, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `select DISTINCT t1.outside_report_id, t1.source, t1.title, t1.abstract, t1.classify_id, 
 t1.classify_name, t1.sys_user_id, t1.sys_user_name, t1.email_message_uid, t1.report_update_time, 
 t1.modify_time, t1.create_time, t1.report_code from outside_report t1 
 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
 	sql += condition
-	sql += ` limit ?, ?`
-	_, err = o.Raw(sql, pars, (currentIndex-1)*pageSize, pageSize).QueryRows(&list)
-	if err != nil {
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	if err != nil && err != orm.ErrNoRows {
 		return nil, err
 	}
 
-	return list, err
+	return list, nil
 }
 
 // SaveOutsideReport 保存报告

+ 7 - 2
models/permission.go

@@ -3,6 +3,7 @@ package models
 import (
 	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
+	"strconv"
 )
 
 // ChartPermissionSearchKeyWordMapping 权限相关
@@ -130,7 +131,11 @@ func GetPermissionByClassifyId(classifyId int) (items []*ChartPermissionSearchKe
 
 func GetClassifyIdsByPermissionId(chartPermissionIdList []string) (classifyIds []string, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := "SELECT classify_id FROM chart_permission_search_key_word_mapping WHERE `from` =  'rddp' and chart_permission_id IN (" + utils.GetOrmInReplace(len(chartPermissionIdList)) + ") and classify_id <> 0; "
+	sql := `SELECT classify_id 
+			FROM chart_permission_search_key_word_mapping 
+				WHERE  chart_permission_id IN (` + utils.GetOrmInReplace(len(chartPermissionIdList)) + `) 
+			GROUP BY classify_id
+			HAVING COUNT(DISTINCT chart_permission_id) = ` + strconv.Itoa(len(chartPermissionIdList)) + ``
 	_, err = o.Raw(sql, chartPermissionIdList).QueryRows(&classifyIds)
 	return
-}
+}

+ 8 - 0
models/report_chapter.go

@@ -605,3 +605,11 @@ func GetCountReportChapterByCondition(condition string, pars []interface{}) (cou
 
 	return
 }
+
+// GetNewestPreReportChapterByClassifyIdAndTypeId 获取分类下往期中最新发布的系统章节
+func GetNewestPreReportChapterByClassifyIdAndTypeId(classifyId, typeId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.* FROM report_chapter AS a JOIN report AS b ON a.report_id = b.id WHERE a.classify_id_first = ? AND a.type_id = ? AND a.publish_state = 2 AND b.state IN (2,6) ORDER BY a.publish_time DESC LIMIT 1`
+	err = o.Raw(sql, classifyId, typeId).QueryRow(&item)
+	return
+}

+ 205 - 0
models/residual_analysis_model/calculate_residual_analysis_config.go

@@ -0,0 +1,205 @@
+package residual_analysis_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type CalculateResidualAnalysisConfig struct {
+	CalculateResidualAnalysisConfigId int       `orm:"column(calculate_residual_analysis_config_id);pk;auto" description:"自增id"`
+	Config                            string    `orm:"column(config)" description:"计算参数配置"`
+	SysUserId                         int       `orm:"column(sys_user_id)" description:"操作人id"`
+	CreateTime                        time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime                        time.Time `orm:"column(modify_time)" description:"修改时间"`
+}
+
+func init() {
+	orm.RegisterModel(new(CalculateResidualAnalysisConfig))
+}
+
+// ResidualAnalysisReq 残差分析预览请求
+type ResidualAnalysisReq struct {
+	EdbInfoIdA       int     `description:"指标A"`
+	EdbInfoIdB       int     `description:"指标B"`
+	EdbInfoId        int     `description:"残差指标id"`
+	ConfigId         int     `description:"配置id"`
+	QueryType        int     `description:"查询类型区分 避免查询过慢 1-查询第一个表格 2-查询需要计算的表格"`
+	ResidualType     int     `description:"残差类型: 1-映射残差 2-拟合残差"`
+	DateType         int     `description:"时间类型 -1-自定义时间 0-至今 n-枚举时间(近n年)"`
+	StartDate        string  `description:"自定义开始日期"`
+	EndDate          string  `description:"自定义结束日期"`
+	IsOrder          bool    `description:"true:正序,false:逆序"`
+	IndexType        int     `description:"1-标准指标 2-领先指标"`
+	LeadValue        int     `description:"领先值"`
+	LeadFrequency    string  `description:"领先频度"`
+	LeftIndexMin     float64 `description:"指标A左侧下限"`
+	LeftIndexMax     float64 `description:"指标A左侧上限"`
+	RightIndexMin    float64 `description:"指标B右侧下限"`
+	RightIndexMax    float64 `description:"指标B右侧上限"`
+	ResidualIndexMin float64 `description:"残差指标下限"`
+	ResidualIndexMax float64 `description:"残差指标上限"`
+	ContrastIndexMin float64 `description:"对比指标下限"`
+	ContrastIndexMax float64 `description:"对比指标上限"`
+}
+
+// ResidualAnalysisResp 残差分析预览响应
+type ResidualAnalysisResp struct {
+	OriginalChartData ChartResp `description:"原始图数据"`
+	MappingChartData  ChartResp `description:"映射图数据"`
+	ResidualChartData ChartResp `description:"残差图数据"`
+	A                 float64   `description:"斜率"`
+	B                 float64   `description:"截距"`
+	R                 float64   `description:"相关系数"`
+	R2                float64   `description:"决定系数"`
+}
+
+type ChartResp struct {
+	ChartInfo   ResidualAnalysisChartInfo
+	EdbInfoList []ResidualAnalysisChartEdbInfoMapping
+}
+
+type ResidualAnalysisChartInfo struct {
+	ChartName     string  `description:"来源名称"`
+	ChartNameEn   string  `description:"英文图表名称"`
+	Unit          string  `description:"中文单位名称"`
+	UnitEn        string  `description:"英文单位名称"`
+	UniqueCode    string  `description:"图表唯一编码"`
+	DateType      int     `description:"时间类型 0-自定义时间 1-至今 n-枚举时间(近n年)"`
+	StartDate     string  `description:"自定义开始日期"`
+	EndDate       string  `description:"自定义结束日期"`
+	ChartType     int     `description:"生成样式:1:曲线图,2:季节性图"`
+	ChartWidth    float64 `description:"线条大小"`
+	Calendar      string  `description:"公历/农历"`
+	Disabled      int     `description:"是否禁用,0:启用,1:禁用,默认:0"`
+	Source        int     `description:"1:ETA图库;2:商品价格曲线;3:相关性图表"`
+	ChartSource   string  `description:"图表来源str"`
+	ChartSourceEn string  `description:"图表来源(英文)"`
+	SourcesFrom   string  `description:"图表来源"`
+	Instructions  string  `description:"图表说明"`
+}
+
+type ResidualAnalysisChartEdbInfoMapping struct {
+	EdbInfoId           int     `description:"指标id"`
+	SourceName          string  `description:"来源名称"`
+	Source              int     `description:"来源id"`
+	EdbCode             string  `description:"指标编码"`
+	EdbName             string  `description:"指标名称"`
+	EdbNameEn           string  `description:"英文指标名称"`
+	EdbType             int     `description:"指标类型:1:基础指标,2:计算指标"`
+	Frequency           string  `description:"频率"`
+	FrequencyEn         string  `description:"英文频率"`
+	Unit                string  `description:"单位"`
+	UnitEn              string  `description:"英文单位"`
+	StartDate           string  `description:"起始日期"`
+	EndDate             string  `description:"终止日期"`
+	ModifyTime          string  `description:"指标最后更新时间"`
+	MaxData             float64 `description:"上限"`
+	MinData             float64 `description:"下限"`
+	IsOrder             bool    `description:"true:逆序:,false:正序"`
+	IsAxis              int     `description:"1:左轴,0:右轴"`
+	EdbInfoType         int     `description:"1:标准指标,0:领先指标"`
+	EdbInfoCategoryType int     `description:"0:普通指标,1:预测指标"`
+	LeadValue           int     `description:"领先值"`
+	LeadUnit            string  `description:"领先单位"`
+	LeadUnitEn          string  `description:"领先英文单位"`
+	ChartStyle          string  `description:"图表类型"`
+	ChartColor          string  `description:"颜色"`
+	PredictChartColor   string  `description:"预测数据的颜色"`
+	ChartWidth          float64 `description:"线条大小"`
+	ChartType           int     `description:"生成样式:1:曲线图,2:季节性图,3:面积图,4:柱状图,5:散点图,6:组合图,7:柱方图,8:商品价格曲线图,9:相关性图"`
+	LatestDate          string  `description:"数据最新日期"`
+	LatestValue         float64 `description:"数据最新值"`
+	MinValue            float64 `description:"最小值"`
+	MaxValue            float64 `description:"最大值"`
+	DataList            interface{}
+}
+
+type ResidualAnalysisIndexSaveReq struct {
+	EdbInfoIdA   int                       `description:"指标A"`
+	EdbInfoIdB   int                       `description:"指标B"`
+	Source       int                       `description:"99-映射残差 100-拟合残差"`
+	EdbCode      string                    `description:"指标编码"`
+	EdbName      string                    `description:"指标名称"`
+	EdbNameEn    string                    `description:"英文指标名称"`
+	EdbType      int                       `description:"指标类型:1:基础指标,2:计算指标"`
+	Frequency    string                    `description:"频率"`
+	FrequencyEn  string                    `description:"英文频率"`
+	Unit         string                    `description:"单位"`
+	UnitEn       string                    `description:"英文单位"`
+	Calendar     string                    `description:"公历/农历"`
+	ClassifyId   int                       `description:"分类id"`
+	ConfigId     int                       `description:"残差配置id"`
+	ResidualType int                       `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
+	IndexType    int                       `orm:"column(index_type)" description:"指标类型:1-映射指标 2-残差指标 3-因变量指标 4-自变量指标"`
+	DataList     []edbDataResidualAnalysis `description:"指标数据"`
+}
+
+// ResidualAnalysisConfigVo 残差分析配置vo
+type ResidualAnalysisConfigVo struct {
+	DateType         int     `description:"时间类型 -1-自定义时间 0-至今 n-枚举时间(近n年)"`
+	StartDate        string  `description:"自定义开始日期"`
+	EndDate          string  `description:"自定义结束日期"`
+	IsOrder          bool    `description:"true:正序,false:逆序"`
+	IndexType        int     `description:"1-标准指标 2-领先指标"`
+	LeadValue        int     `description:"领先值"`
+	LeadFrequency    string  `description:"领先频度"`
+	LeftIndexMin     float64 `description:"指标A左侧下限"`
+	LeftIndexMax     float64 `description:"指标A左侧上限"`
+	RightIndexMin    float64 `description:"指标B右侧下限"`
+	RightIndexMax    float64 `description:"指标B右侧上限"`
+	ResidualIndexMin float64 `description:"残差指标下限"`
+	ResidualIndexMax float64 `description:"残差指标上限"`
+	ContrastIndexMin float64 `description:"对比指标下限"`
+	ContrastIndexMax float64 `description:"对比指标上限"`
+}
+
+type DetailEdbInfoList struct {
+	EdbInfoId   int    `orm:"column(edb_info_id);pk"`
+	EdbInfoType int    `description:"指标类型,0:普通指标,1:预测指标"`
+	IndexType   int    `orm:"column(index_type)" description:"指标类型:1-映射指标 2-残差指标 3-因变量指标 4-自变量指标"`
+	SourceName  string `description:"来源名称"`
+	Source      int    `description:"来源id"`
+	EdbCode     string `description:"指标编码"`
+	EdbNameEn   string `description:"英文指标名称"`
+	EdbName     string `description:"指标名称"`
+	Frequency   string `description:"频率"`
+	FrequencyEn string `description:"英文频率"`
+	Unit        string `description:"单位"`
+	UnitEn      string `description:"英文单位"`
+	ClassifyId  int    `description:"分类id"`
+}
+
+// ResidualAnalysisDetailResp 详情响应接口
+type ResidualAnalysisDetailResp struct {
+	ConfigInfo   *CalculateResidualAnalysisConfig
+	EdbInfoList  []*DetailEdbInfoList
+	ResidualType int `description:"残差类型: 1-映射残差 2-拟合残差"`
+}
+
+// GetResidualAnalysisConfigById 根据配置id查询配置信息
+func GetResidualAnalysisConfigById(configId int) (residualAnalysisConfig CalculateResidualAnalysisConfig, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM calculate_residual_analysis_config WHERE calculate_residual_analysis_config_id=?"
+	err = o.Raw(sql, configId).QueryRow(&residualAnalysisConfig)
+	return residualAnalysisConfig, nil
+}
+
+// UpdateResidualAnalysisConfig 更新配置信息
+func UpdateResidualAnalysisConfig(config CalculateResidualAnalysisConfig) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(&config)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// SaveResidualAnalysisConfig 保存配置信息
+func SaveResidualAnalysisConfig(config CalculateResidualAnalysisConfig) (id int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err = o.Insert(&config)
+	if err != nil {
+		return 0, err
+	}
+	return id, nil
+}

+ 50 - 0
models/residual_analysis_model/calculate_residual_analysis_config_mapping.go

@@ -0,0 +1,50 @@
+package residual_analysis_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type CalculateResidualAnalysisConfigMapping struct {
+	CalculateResidualAnalysisConfigMappingId int       `orm:"column(calculate_residual_analysis_config_mapping_id);pk;auto" description:"自增id"`
+	CalculateResidualAnalysisConfigId        int       `orm:"column(calculate_residual_analysis_config_id)" description:"残差分析配置id"`
+	EdbInfoId                                int64     `orm:"column(edb_info_id)" description:"指标id"`
+	ResidualType                             int       `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
+	IndexType                                int       `orm:"column(index_type)" description:"指标类型:1-映射指标 2-残差指标 3-因变量指标 4-自变量指标"`
+	CreateTime                               time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime                               time.Time `orm:"column(modify_time)" description:"修改时间"`
+}
+
+func init() {
+	orm.RegisterModel(new(CalculateResidualAnalysisConfigMapping))
+}
+
+// GetConfigMappingListByConfigId 根据配置配置id查询配置信息
+func GetConfigMappingListByConfigId(configId int) (items []CalculateResidualAnalysisConfigMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select * from calculate_residual_analysis_config_mapping where calculate_residual_analysis_config_id = ?`
+
+	_, err = o.Raw(sql, configId).QueryRows(&items)
+	return items, nil
+}
+
+// GetConfigMappingListByCondition 通过条件查询指标配置映射
+func GetConfigMappingListByCondition(condition string, pars []interface{}) (items []CalculateResidualAnalysisConfigMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `select * from calculate_residual_analysis_config_mapping where 1=1 `
+
+	sql += condition
+
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return items, nil
+}
+
+// SaveConfigMapping 保存指标和配置的映射关系
+func SaveConfigMapping(mapping CalculateResidualAnalysisConfigMapping) (id int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err = o.Insert(&mapping)
+	if err != nil {
+		return 0, err
+	}
+	return id, nil
+}

+ 40 - 0
models/residual_analysis_model/edb_data_residual_analysis.go

@@ -0,0 +1,40 @@
+package residual_analysis_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type edbDataResidualAnalysis struct {
+	EdbDataId     int       `orm:"column(edb_data_id);pk;auto" description:"自增id"`
+	EdbInfoId     int       `orm:"column(edb_info_id)" description:"指标id"`
+	EdbCode       string    `orm:"column(edb_code)" description:"指标编码"`
+	DataTime      string    `orm:"column(data_time)" description:"数据日期"`
+	Value         float64   `orm:"column(value)" description:"数据值"`
+	CreateTime    time.Time `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime    time.Time `orm:"column(modify_time)" description:"修改时间"`
+	DataTimeStamp int64     `orm:"column(data_timestamp)"`
+}
+
+func init() {
+	orm.RegisterModel(new(edbDataResidualAnalysis))
+}
+
+// DeleteResidualAnalysisDataByEdbCode 根据指标编码删除数据
+func DeleteResidualAnalysisDataByEdbCode(edbCode string) error {
+	o := orm.NewOrmUsingDB("data")
+	sql := `delete from edb_data_residual_analysis where edb_code = ?`
+	_, err := o.Raw(sql, edbCode).Exec()
+	return err
+}
+
+// AddResidualAnalysisData 新增指标数据
+func AddResidualAnalysisData(dataList []edbDataResidualAnalysis) (num int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	num, err = o.InsertMulti(len(dataList), dataList)
+	if err != nil {
+		return 0, err
+	}
+
+	return num, nil
+}

+ 45 - 0
models/yb/yb_poster_resource.go

@@ -0,0 +1,45 @@
+package yb
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// YbPosterResource 研报小程序海报
+type YbPosterResource struct {
+	Id         int       `orm:"column(id);pk" description:"价格驱动ID"`
+	Path       string    `description:"请求路径"`
+	ImgUrl     string    `description:"图片地址"`
+	Type       string    `description:"类型 poster-海报; qrcode-太阳码"`
+	Version    string    `description:"版本号"`
+	CreateTime time.Time `description:"创建时间"`
+}
+
+func (m *YbPosterResource) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*YbPosterResource, err error) {
+	o := orm.NewOrmUsingDB("weekly")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM yb_poster_resource WHERE 1=1 %s %s`, fields, condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *YbPosterResource) RemovePosters(ids []int) (err error) {
+	num := len(ids)
+	if num == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("weekly")
+	sql := fmt.Sprintf(`DELETE FROM yb_poster_resource WHERE id IN (%s) LIMIT %d`, utils.GetOrmInReplace(num), num)
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}

+ 558 - 0
routers/commentsRouter.go

@@ -241,6 +241,114 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/classify/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "RemoveCheck",
+            Router: `/classify/remove_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "DashboardDetail",
+            Router: `/index/dashboard/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "DashboardSave",
+            Router: `/index/dashboard/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/index/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "Import",
+            Router: `/index/import`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "Save",
+            Router: `/index/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"],
         beego.ControllerComments{
             Method: "AddChartClassify",
@@ -2635,6 +2743,87 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdClassify",
+            Router: `/rzd/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "GetRzdFrequencyList",
+            Router: `/rzd/frequency/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "GetRzdIndexInfo",
+            Router: `/rzd/get/index/info`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdIndexAdd",
+            Router: `/rzd/index/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdIndexAddValidate",
+            Router: `/rzd/index/add/validate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdIndexData",
+            Router: `/rzd/index/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdIndexDataExport",
+            Router: `/rzd/index/data/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdIndexDetail",
+            Router: `/rzd/index/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
+        beego.ControllerComments{
+            Method: "RzdIndexList",
+            Router: `/rzd/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
         beego.ControllerComments{
             Method: "ClassifyAdd",
@@ -2797,6 +2986,123 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "BatchAddEdbCheck",
+            Router: `/usda_fas/batch/add/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasBatchAdd",
+            Router: `/usda_fas/batch_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasBatchSearch",
+            Router: `/usda_fas/batch_search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasClassify",
+            Router: `/usda_fas/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasIndexList",
+            Router: `/usda_fas/classify/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasEdbInfoAdd",
+            Router: `/usda_fas/edb_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasAddCheck",
+            Router: `/usda_fas/edb_info/add_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasNameCheck",
+            Router: `/usda_fas/edb_info/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "ExportUsdaFasList",
+            Router: `/usda_fas/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "GetFrequency",
+            Router: `/usda_fas/frequency`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasIndexData",
+            Router: `/usda_fas/index/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasSearchList",
+            Router: `/usda_fas/search_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromUsdaFasController"],
+        beego.ControllerComments{
+            Method: "UsdaFasSingleData",
+            Router: `/usda_fas/single_data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BloombergDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BloombergDataController"],
         beego.ControllerComments{
             Method: "AddCheck",
@@ -3643,6 +3949,186 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "Classify",
+            Router: `/clarksons/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "AddClassify",
+            Router: `/clarksons/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "DelClassify",
+            Router: `/clarksons/classify/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "EditClassify",
+            Router: `/clarksons/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "MoveClassify",
+            Router: `/clarksons/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "DeleteClarksonsData",
+            Router: `/clarksons/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "AddCheck",
+            Router: `/clarksons/edb_info/add_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "BatchAdd",
+            Router: `/clarksons/edb_info/batch_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "BatchDelete",
+            Router: `/clarksons/edb_info/batch_delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "BatchEdit",
+            Router: `/clarksons/edb_info/batch_edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "NameCheck",
+            Router: `/clarksons/edb_info/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "EditClarksons",
+            Router: `/clarksons/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "GetClarksonsIndexInfo",
+            Router: `/clarksons/get/index/info`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "ClarksonsData",
+            Router: `/clarksons/index/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "IndexList",
+            Router: `/clarksons/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "IndexPageList",
+            Router: `/clarksons/index/page/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "MoveClarksonsData",
+            Router: `/clarksons/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "SearchList",
+            Router: `/clarksons/search_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "SingleData",
+            Router: `/clarksons/single_data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ClarksonsDataController"],
+        beego.ControllerComments{
+            Method: "ExportClarksonsList",
+            Router: `/export/clarksonsList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbBusinessController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbBusinessController"],
         beego.ControllerComments{
             Method: "AddCheck",
@@ -4174,6 +4660,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: "ClassifyEdbInfoList",
+            Router: `/classify/edb/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: "ComTradeCountryList",
@@ -4966,6 +5461,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: "ModifyEdbList",
+            Router: `/modify/edbList`,
+            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: "BatchAdd",
@@ -7927,6 +8431,60 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "CheckResidualAnalysisExist",
+            Router: `/check/residual/analysis/exist`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "ContrastPreview",
+            Router: `/contrast/preview`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "ResidualAnalysisDetail",
+            Router: `/residual/analysis/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "ResidualAnalysisPreview",
+            Router: `/residual/analysis/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "SaveResidualAnalysis",
+            Router: `/save/residual/analysis`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/residual_analysis:ResidualAnalysisController"],
+        beego.ControllerComments{
+            Method: "SaveResidualAnalysisConfig",
+            Router: `/save/residual/analysis/config`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/roadshow:CalendarController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/roadshow:CalendarController"],
         beego.ControllerComments{
             Method: "ResearcherList",

+ 16 - 1
routers/router.go

@@ -11,6 +11,7 @@ import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/controllers/ai"
 	"eta/eta_api/controllers/data_manage"
+	"eta/eta_api/controllers/data_manage/ai_predict_model"
 	"eta/eta_api/controllers/data_manage/correlation"
 	"eta/eta_api/controllers/data_manage/cross_variety"
 	"eta/eta_api/controllers/data_manage/data_manage_permission"
@@ -30,13 +31,13 @@ import (
 	"eta/eta_api/controllers/fe_calendar"
 	"eta/eta_api/controllers/material"
 	"eta/eta_api/controllers/report_approve"
+	"eta/eta_api/controllers/residual_analysis"
 	"eta/eta_api/controllers/roadshow"
 	"eta/eta_api/controllers/sandbox"
 	"eta/eta_api/controllers/semantic_analysis"
 	"eta/eta_api/controllers/smart_report"
 	"eta/eta_api/controllers/speech_recognition"
 	"eta/eta_api/controllers/trade_analysis"
-
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/filter/cors"
 )
@@ -183,6 +184,9 @@ func init() {
 				&data_manage.SciHqDataController{},
 				&data_manage.BaseFromLyClassifyController{},
 				&data_manage.BaseFromLyIndexController{},
+				&data_manage.BaseFromUsdaFasController{},
+				&data_manage.BaseFromRzdIndexController{},
+				&data_manage.ClarksonsDataController{},
 			),
 		),
 		web.NSNamespace("/my_chart",
@@ -422,6 +426,17 @@ func init() {
 				&controllers.BIDaShboardController{},
 			),
 		),
+		web.NSNamespace("/ai_predict_model",
+			web.NSInclude(
+				&ai_predict_model.AiPredictModelClassifyController{},
+				&ai_predict_model.AiPredictModelIndexController{},
+			),
+		),
+		web.NSNamespace("/residual_analysis",
+			web.NSInclude(
+				&residual_analysis.ResidualAnalysisController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 513 - 0
services/ai_predict_model_classify.go

@@ -0,0 +1,513 @@
+package services
+
+import (
+	"errors"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiPredictModelMoveClassify 移动分类
+func AiPredictModelMoveClassify(req aiPredictModel.AiPredictModelClassifyMoveReq, sysUser *system.Admin) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	parentClassifyId := req.ParentClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	itemId := req.ItemId
+	prevItemId := req.PrevItemId
+	nextItemId := req.NextItemId
+
+	//首先确定移动的对象是分类还是指标
+	//判断上一个节点是分类还是指标
+	//判断下一个节点是分类还是指标
+	//同时更新分类目录下的分类sort和指标sort
+	//更新当前移动的分类或者指标sort
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+
+	var parentClassify *aiPredictModel.AiPredictModelClassify
+	if parentClassifyId > 0 {
+		parentClassify, err = classifyOb.GetItemById(parentClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		edbClassifyInfo *aiPredictModel.AiPredictModelClassify
+		prevClassify    *aiPredictModel.AiPredictModelClassify
+		nextClassify    *aiPredictModel.AiPredictModelClassify
+
+		edbInfo     *aiPredictModel.AiPredictModelIndex
+		prevEdbInfo *aiPredictModel.AiPredictModelIndex
+		nextEdbInfo *aiPredictModel.AiPredictModelIndex
+		prevSort    int
+		nextSort    int
+	)
+
+	// 移动对象为分类, 判断权限
+	if itemId == 0 {
+		edbClassifyInfo, err = classifyOb.GetItemById(classifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前分类不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId > 0 && parentClassify.Level == 6 {
+			errMsg = "最高只支持添加6级分类"
+			err = errors.New(errMsg)
+			return
+		}
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		exists, e := data_manage.GetEdbClassifyByParentIdAndName(parentClassifyId, edbClassifyInfo.ClassifyName, classifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取父级分类下的同名分类失败, Err: %s", e.Error())
+			return
+		}
+		if exists != nil {
+			errMsg = "移动失败,分类名称已存在"
+			return
+		}
+
+	} else {
+		edbInfo, err = indexOb.GetItemById(req.ItemId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前指标不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId == 0 {
+			errMsg = "移动失败,指标必须挂在分类下"
+			err = errors.New(errMsg)
+			return
+		}
+
+		//// 移动权限校验
+		//button := GetEdbOpButton(sysUser, edbInfo.SysUserId, edbInfo.EdbType, edbInfo.EdbInfoType, haveOperaAuth)
+		//if !button.MoveButton {
+		//	errMsg = "无操作权限"
+		//	err = errors.New(errMsg)
+		//	return
+		//}
+	}
+
+	if prevClassifyId > 0 {
+		prevClassify, err = classifyOb.GetItemById(prevClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	} else if prevItemId > 0 {
+		prevEdbInfo, err = indexOb.GetItemById(prevItemId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevEdbInfo.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = classifyOb.GetItemById(nextClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	} else if nextItemId > 0 {
+		//下一个兄弟节点
+		nextEdbInfo, err = indexOb.GetItemById(nextItemId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextEdbInfo.Sort
+	}
+
+	err, errMsg = moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, nextClassify, edbInfo, prevEdbInfo, nextEdbInfo, parentClassifyId, prevSort, nextSort)
+	return
+}
+
+// moveAiPredictModelClassify 移动指标分类
+func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, nextClassify *aiPredictModel.AiPredictModelClassify, edbInfo, prevEdbInfo, nextEdbInfo *aiPredictModel.AiPredictModelIndex, parentClassifyId int, prevSort, nextSort int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+
+	// 移动对象为分类, 判断分类是否存在
+	if edbClassifyInfo != nil {
+		oldParentId := edbClassifyInfo.ParentId
+		oldLevel := edbClassifyInfo.Level
+		var classifyIds []int
+		if oldParentId != parentClassifyId {
+			//更新子分类对应的level
+			childList, e, m := GetAiPredictModelChildClassifyByClassifyId(edbClassifyInfo.AiPredictModelClassifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子分类失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.AiPredictModelClassifyId == edbClassifyInfo.AiPredictModelClassifyId {
+						continue
+					}
+					classifyIds = append(classifyIds, v.AiPredictModelClassifyId)
+				}
+			}
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+			if edbClassifyInfo.Level != parentClassify.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			edbClassifyInfo.ParentId = parentClassify.AiPredictModelClassifyId
+			edbClassifyInfo.RootId = parentClassify.RootId
+			edbClassifyInfo.Level = parentClassify.Level + 1
+			edbClassifyInfo.ModifyTime = time.Now()
+			// 更改层级路径
+			edbClassifyInfo.LevelPath = fmt.Sprintf("%s,%d", parentClassify.LevelPath, edbClassifyInfo.AiPredictModelClassifyId)
+			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime", "LevelPath")
+		} else if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == edbClassifyInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevClassify.Sort, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+					}
+
+					//变更指标
+					if prevEdbInfo != nil {
+						//变更兄弟节点的排序
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更分类
+						if prevClassify != nil {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevSort, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+						}
+
+						//变更指标
+						if prevEdbInfo != nil {
+							//变更兄弟节点的排序
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+
+					}
+				}
+			}
+
+			edbClassifyInfo.Sort = prevSort + 1
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevEdbInfo == nil && nextEdbInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetAiPredictModelClassifyMaxSort(parentClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			edbClassifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := aiPredictModel.GetFirstAiPredictModelClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
+				//该分类下的所有指标也需要+1
+				_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在指标,且第一个指标的排序等于0,那么需要调整排序
+				firstEdb, tErr := aiPredictModel.GetFirstAiPredictModelIndexByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
+					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
+				}
+			}
+
+			edbClassifyInfo.Sort = 0 //那就是排在第一位
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = edbClassifyInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应分类的root_id和层级
+			if oldParentId != parentClassifyId {
+				if len(classifyIds) > 0 {
+					levelStep := edbClassifyInfo.Level - oldLevel
+					err = aiPredictModel.UpdateAiPredictModelClassifyChildByParentClassifyId(classifyIds, edbClassifyInfo.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子分类失败,Err:" + err.Error())
+						return
+					}
+				}
+			}
+		}
+	} else {
+		if edbInfo == nil {
+			errMsg = "当前指标不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了分类,那么移动该指标数据
+		if edbInfo.ClassifyId != parentClassifyId {
+			edbInfo.ClassifyId = parentClassifyId
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ClassifyId", "ModifyTime")
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == edbInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevClassify.Sort, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+					}
+
+					//变更指标
+					if prevEdbInfo != nil {
+						//变更兄弟节点的排序
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更分类
+						if prevClassify != nil {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevSort, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+						}
+
+						//变更指标
+						if prevEdbInfo != nil {
+							//变更兄弟节点的排序
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+					}
+				}
+			}
+
+			edbInfo.Sort = prevSort + 1
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevEdbInfo == nil && nextEdbInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			classifyOb := new(aiPredictModel.AiPredictModelClassify)
+			maxSort, err = classifyOb.GetSortMax(parentClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			edbInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := aiPredictModel.GetFirstAiPredictModelClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
+				//该分类下的所有指标也需要+1
+				_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在指标,且第一个指标的排序等于0,那么需要调整排序
+				firstEdb, tErr := aiPredictModel.GetFirstAiPredictModelIndexByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
+					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
+				}
+			}
+
+			edbInfo.Sort = 0 //那就是排在第一位
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = edbInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func GetAiPredictModelChildClassifyByClassifyId(targetClassifyId int) (targetList []*aiPredictModel.AiPredictModelClassify, err error, errMsg string) {
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	//判断是否是挂在顶级目录下
+	targetClassify, err := classifyOb.GetItemById(targetClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前分类不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	orderStr := ` order by level asc, sort asc, ai_predict_model_classify_id asc`
+	tmpList, err := aiPredictModel.GetAiPredictModelClassifyByRootIdLevel(targetClassify.RootId, orderStr)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		err = errors.New("获取数据失败,Err:" + err.Error())
+		return
+	}
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.AiPredictModelClassifyId == targetClassify.AiPredictModelClassifyId {
+				idMap[v.AiPredictModelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.AiPredictModelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.AiPredictModelClassifyId]; ok {
+				targetItem := new(aiPredictModel.AiPredictModelClassify)
+				targetItem.AiPredictModelClassifyId = v.AiPredictModelClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.RootId = v.RootId
+				//targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetItem.ClassifyName = v.ClassifyName
+				//targetItem.IsJoinPermission = v.IsJoinPermission
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+
+	return
+}
+
+func GetAiPredictModelClassifyMaxSort(parentId int) (maxSort int, err error) {
+	//获取该层级下最大的排序数
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	classifyMaxSort, err := classifyOb.GetSortMax(parentId)
+	if err != nil {
+		return
+	}
+	maxSort = classifyMaxSort
+	edbMaxSort, err := data_manage.GetThsHfIndexMaxSortByClassifyId(parentId)
+	if err != nil {
+		return
+	}
+	if maxSort < edbMaxSort {
+		maxSort = edbMaxSort
+	}
+	return
+}

+ 266 - 0
services/ai_predict_model_index.go

@@ -0,0 +1,266 @@
+package services
+
+import (
+	"encoding/json"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"time"
+)
+
+func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelImportData) (err error) {
+	if len(imports) == 0 {
+		return
+	}
+
+	// 查询已存在的标的
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexNameItem := make(map[string]*aiPredictModel.AiPredictModelIndex)
+	{
+		list, e := indexOb.GetItemsByCondition("", make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取标的失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			indexNameItem[v.IndexName] = v
+		}
+	}
+
+	updateCols := []string{indexOb.Cols().ClassifyId, indexOb.Cols().ModelFramework, indexOb.Cols().PredictDate, indexOb.Cols().PredictValue, indexOb.Cols().DirectionAccuracy, indexOb.Cols().AbsoluteDeviation, indexOb.Cols().ExtraConfig, indexOb.Cols().SysUserId, indexOb.Cols().SysUserRealName, indexOb.Cols().ModifyTime}
+	updateIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+	createIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+	for _, v := range imports {
+		exist := indexNameItem[v.Index.IndexName]
+		// 编辑
+		if exist != nil {
+			// 图例信息
+			if exist.ExtraConfig != "" && v.Index.ExtraConfig != "" {
+				var oldConfig, newConfig aiPredictModel.AiPredictModelIndexExtraConfig
+				if e := json.Unmarshal([]byte(exist.ExtraConfig), &oldConfig); e != nil {
+					err = fmt.Errorf("标的原配置解析失败, Config: %s, Err: %v", exist.ExtraConfig, e)
+					return
+				}
+				if e := json.Unmarshal([]byte(v.Index.ExtraConfig), &newConfig); e != nil {
+					err = fmt.Errorf("标的新配置解析失败, Config: %s, Err: %v", v.Index.ExtraConfig, e)
+					return
+				}
+				oldConfig.DailyChart.PredictLegendName = newConfig.DailyChart.PredictLegendName
+				b, _ := json.Marshal(oldConfig)
+				v.Index.ExtraConfig = string(b)
+			}
+
+			v.Index.AiPredictModelIndexId = exist.AiPredictModelIndexId
+			v.Index.IndexCode = exist.IndexCode
+			updateIndexes = append(updateIndexes, v)
+			continue
+		}
+
+		// 新增
+		indexCode, e := utils.GenerateEdbCode(1, "IPM")
+		if e != nil {
+			err = fmt.Errorf("生成标的编码失败, %v", e)
+			return
+		}
+		v.Index.IndexCode = indexCode
+		createIndexes = append(createIndexes, v)
+	}
+
+	// 新增/更新指标
+	if e := indexOb.ImportIndexAndData(createIndexes, updateIndexes, updateCols); e != nil {
+		err = fmt.Errorf("导入指标失败, %v", e)
+		return
+	}
+	return
+}
+
+func GetAiPredictChartDetailByData(indexItem *aiPredictModel.AiPredictModelIndex, indexData []*aiPredictModel.AiPredictModelData, source int) (resp *data_manage.ChartInfoDetailResp, err error) {
+	resp = new(data_manage.ChartInfoDetailResp)
+
+	// 标的配置
+	var extraConfig aiPredictModel.AiPredictModelIndexExtraConfig
+	if indexItem.ExtraConfig != "" {
+		if e := json.Unmarshal([]byte(indexItem.ExtraConfig), &extraConfig); e != nil {
+			err = fmt.Errorf("标的额外配置解析失败, Config: %s, Err: %v", indexItem.ExtraConfig, e)
+			return
+		}
+	}
+
+	// 图表信息
+	var predictLegendName, confLeftMin, confLeftMax, unit string
+	if source == aiPredictModel.ModelDataSourceDaily {
+		predictLegendName = extraConfig.DailyChart.PredictLegendName
+		if predictLegendName == "" {
+			predictLegendName = "Predicted"
+		}
+		unit = extraConfig.DailyChart.Unit
+		confLeftMin = extraConfig.DailyChart.LeftMin
+		confLeftMax = extraConfig.DailyChart.LeftMax
+	}
+	if source == aiPredictModel.ModelDataSourceMonthly {
+		predictLegendName = "预测值"
+		unit = extraConfig.MonthlyChart.Unit
+		confLeftMin = extraConfig.MonthlyChart.LeftMin
+		confLeftMax = extraConfig.MonthlyChart.LeftMax
+	}
+	// 这里简单兼容下吧,暂时就不修数据了
+	if confLeftMin == "" {
+		confLeftMin = indexItem.LeftMin
+	}
+	if confLeftMax == "" {
+		confLeftMax = indexItem.LeftMax
+	}
+
+	// 获取曲线图主题样式
+	chartView := new(data_manage.ChartInfoView)
+	chartView.ChartType = utils.CHART_SOURCE_DEFAULT
+	chartTheme, e := data.GetChartThemeConfig(0, chartView.ChartType, utils.CHART_TYPE_CURVE)
+	if e != nil {
+		err = fmt.Errorf("获取图表主题样式失败, %v", e)
+		return
+	}
+	chartView.ChartThemeStyle = chartTheme.Config
+	chartView.ChartThemeId = chartTheme.ChartThemeId
+
+	chartView.ChartName = indexItem.IndexName
+	chartView.ChartNameEn = indexItem.IndexName
+	chartView.DateType = 3
+	chartView.Calendar = "公历"
+	chartView.ChartSource = "AI预测模型"
+	chartView.ChartSourceEn = "AI预测模型"
+	chartView.Unit = unit
+	chartView.UnitEn = unit
+
+	// EdbList-固定一条为标的实际值、一条为预测值
+	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	edbActual, edbPredict := new(data_manage.ChartEdbInfoMapping), new(data_manage.ChartEdbInfoMapping)
+	edbActual.EdbName = indexItem.IndexName
+	edbActual.EdbNameEn = indexItem.IndexName
+	edbActual.IsAxis = 1
+	edbActual.Unit = unit
+	edbActual.UnitEn = unit
+
+	edbPredict.EdbName = predictLegendName
+	edbPredict.EdbNameEn = predictLegendName
+	edbPredict.IsAxis = 1
+	edbPredict.Unit = unit
+	edbPredict.UnitEn = unit
+	actualData, predictData := make([]*data_manage.EdbDataList, 0), make([]*data_manage.EdbDataList, 0)
+
+	var startDate, endDate time.Time
+	var actualValues, predictValues []float64
+	var actualNewest, predictNewest bool
+	var actualLatestTimestamp int64 // 实际值最后一天的时间戳,作为日度图表的分割线
+	for k, v := range indexData {
+		// 如果实际值和预测值都是null那么该日期无效直接忽略
+		if !v.Value.Valid && !v.PredictValue.Valid {
+			continue
+		}
+
+		// 将有效值加入[]float64,最后取极值
+		if v.Value.Valid {
+			actualValues = append(actualValues, v.Value.Float64)
+		}
+		if v.PredictValue.Valid {
+			predictValues = append(predictValues, v.PredictValue.Float64)
+		}
+
+		// 开始结束时间
+		if k == 0 {
+			startDate = v.DataTime
+			endDate = v.CreateTime
+		}
+		if v.DataTime.Before(startDate) {
+			startDate = v.DataTime
+		}
+		if v.DataTime.After(endDate) {
+			endDate = v.DataTime
+		}
+
+		// 指标数据
+		if v.Value.Valid {
+			if !actualNewest {
+				edbActual.LatestDate = v.DataTime.Format(utils.FormatDate)
+				edbActual.LatestValue = v.Value.Float64
+				actualLatestTimestamp = v.DataTime.UnixNano() / 1e6
+				actualNewest = true
+			}
+			actualData = append(actualData, &data_manage.EdbDataList{
+				DataTime:      v.DataTime.Format(utils.FormatDate),
+				Value:         v.Value.Float64,
+				DataTimestamp: v.DataTimestamp,
+			})
+		}
+		if v.PredictValue.Valid {
+			if !predictNewest {
+				edbPredict.LatestDate = v.DataTime.Format(utils.FormatDate)
+				edbPredict.LatestValue = v.Value.Float64
+				predictNewest = true
+			}
+			predictData = append(predictData, &data_manage.EdbDataList{
+				DataTime:      v.DataTime.Format(utils.FormatDate),
+				Value:         v.PredictValue.Float64,
+				DataTimestamp: v.DataTimestamp,
+			})
+		}
+	}
+
+	// 图表数据这里均做一个升序排序
+	sort.Slice(actualData, func(i, j int) bool {
+		return actualData[i].DataTimestamp < actualData[j].DataTimestamp
+	})
+	sort.Slice(predictData, func(i, j int) bool {
+		return predictData[i].DataTimestamp < predictData[j].DataTimestamp
+	})
+
+	// 极值
+	actualMin, actualMax := utils.FindMinMax(actualValues)
+	predictMin, predictMax := utils.FindMinMax(predictValues)
+	edbActual.MinData = actualMin
+	edbActual.MaxData = actualMax
+	edbPredict.MinData = predictMin
+	edbPredict.MaxData = predictMax
+
+	edbActual.DataList = actualData
+	edbPredict.DataList = predictData
+	edbList = append(edbList, edbActual, edbPredict)
+
+	// 上下限
+	if confLeftMin != "" {
+		chartView.LeftMin = confLeftMin
+	} else {
+		leftMin := actualMin
+		if leftMin > predictMin {
+			leftMin = predictMin
+		}
+		chartView.LeftMin = fmt.Sprint(leftMin)
+	}
+	if confLeftMax != "" {
+		chartView.LeftMax = confLeftMax
+	} else {
+		leftMax := actualMax
+		if leftMax < predictMax {
+			leftMax = predictMax
+		}
+		chartView.LeftMax = fmt.Sprint(leftMax)
+	}
+
+	chartView.StartDate = startDate.Format(utils.FormatDate)
+	chartView.EndDate = endDate.Format(utils.FormatDate)
+
+	// 日度图表的分割线日期
+	if source == aiPredictModel.ModelDataSourceDaily {
+		var dataResp struct {
+			ActualLatestTimestamp int64
+		}
+		dataResp.ActualLatestTimestamp = actualLatestTimestamp
+		resp.DataResp = dataResp
+	}
+
+	resp.ChartInfo = chartView
+	resp.EdbInfoList = edbList
+	return
+}

+ 2 - 2
services/binlog/handler.go

@@ -77,7 +77,7 @@ func (h *EdbEventHandler) Insert(e *canal.RowsEvent) error {
 				return err
 			}
 		} else {
-			ok, err := utils.Rc.SIsMember(edbmonitorSvr.EDB_MONITOR_ID_SET_CACHE, newEdbInfo)
+			ok, err := utils.Rc.SIsMember(edbmonitorSvr.EDB_MONITOR_ID_SET_CACHE, newEdbInfo.EdbInfoId)
 			if err != nil {
 				return err
 			}
@@ -114,7 +114,7 @@ func (h *EdbEventHandler) Update(e *canal.RowsEvent) error {
 				return err
 			}
 		} else {
-			ok, err := utils.Rc.SIsMember(edbmonitorSvr.EDB_MONITOR_ID_SET_CACHE, newEdbInfo)
+			ok, err := utils.Rc.SIsMember(edbmonitorSvr.EDB_MONITOR_ID_SET_CACHE, newEdbInfo.EdbInfoId)
 			if err != nil {
 				return err
 			}

+ 6 - 0
services/data/base_edb_lib.go

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

+ 822 - 0
services/data/base_from_clarksons.go

@@ -0,0 +1,822 @@
+package data
+
+import (
+	"errors"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+func AddClarksonsClassify(classifyName string, parentId int) (ok bool, msg string, err error) {
+	var count int
+	count, err = data_manage.GetBaseFromClarksonsClassifyCount(classifyName, parentId)
+	if err != nil {
+		return
+	}
+	if count > 0 {
+		return false, "分类已存在", nil
+	}
+
+	sort, err := data_manage.GetChildClarksonsClassifyMaxSortById(parentId)
+	if err != nil {
+		return false, "", err
+	}
+	if parentId > 0 {
+		count, err = data_manage.GetClarksonsClassifyCountById(parentId)
+		if err != nil {
+			return false, "", err
+		}
+		if count == 0 {
+			return false, "", errors.New("父分类不存在")
+		}
+		clarksonsClassify := &data_manage.BaseFromClarksonsClassify{
+			ClassifyName: classifyName,
+			ParentId:     parentId,
+			Level:        2,
+			Sort:         sort + 1,
+			ModifyTime:   time.Now(),
+			CreateTime:   time.Now(),
+		}
+		_, err = clarksonsClassify.Add()
+		if err != nil {
+			return
+		}
+	} else {
+		clarksonsClassify := &data_manage.BaseFromClarksonsClassify{
+			ClassifyName: classifyName,
+			ParentId:     0,
+			Level:        1,
+			Sort:         sort + 1,
+			ModifyTime:   time.Now(),
+			CreateTime:   time.Now(),
+		}
+		_, er := clarksonsClassify.Add()
+		if er != nil {
+			return false, "", er
+		}
+	}
+	return true, "", nil
+}
+
+func DelClarksonsClassify(classifyId int) (err error) {
+	classify, err := data_manage.GetClarksonsClassifyById(classifyId)
+	if err != nil {
+		return
+	}
+	var classifyIds []int
+	classifyIds = append(classifyIds, classify.BaseFromClassifyId)
+	if classify.ParentId == 0 {
+		tmpClassifyIds, er := data_manage.GetChildClarksonsClassifyIdsById(classify.BaseFromClassifyId)
+		if er != nil {
+			err = er
+			return
+		}
+		classifyIds = append(classifyIds, tmpClassifyIds...)
+	}
+	// 获取分类下的所有指标
+	count, err := data_manage.GetClarksonsIndexCountByClassifyIds(classifyIds)
+	if err != nil {
+		err = errors.New("获取分类下的指标信息失败,Err:" + err.Error())
+		return
+	}
+	if count > 0 {
+		err = errors.New("该分类下有指标,不可删除")
+		return
+	}
+	// 删除对应的分类
+	err = data_manage.DeleteClarksonsClassifyByClassifyId(classifyIds)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// ResetClarksonsIndex 指标数据清除分类接口
+func ResetClarksonsIndex(indexId int) (err error) {
+	clarksonsIndex, err := data_manage.GetClarksonsIndexByIndexId(indexId)
+	if err != nil {
+		return
+	}
+
+	// 更新指标在未分类下的排序,永远排在未分类的最后一个
+	//移动排序
+	classifyId := 0
+	updateCol := make([]string, 0)
+	var currentSort, prevSort int
+	currentSort = clarksonsIndex.Sort
+	//未分类的最大的sort值
+	prevSort, err = data_manage.GetClarksonsIndexMaxSortByClassifyId(classifyId)
+	if err != nil {
+		err = errors.New("获取上一个兄弟节点指标信息失败,Err:" + err.Error())
+		return
+	}
+
+	//如果前面一个节点的sort值比当前大,则当前节点往下移动
+	if prevSort >= currentSort {
+		//往下移动
+		err = data_manage.MoveDownSciIndexBySort(classifyId, prevSort, currentSort)
+		if err != nil {
+			err = errors.New("向下移动出错:" + err.Error())
+			return
+		}
+		clarksonsIndex.Sort = prevSort
+	}
+
+	//更新
+	clarksonsIndex.ModifyTime = time.Now()
+	clarksonsIndex.ClassifyId = classifyId
+	updateCol = append(updateCol, "Sort", "ModifyTime", "BaseFromClassifyId")
+	err = clarksonsIndex.Update(updateCol)
+	if err != nil {
+		err = errors.New("移动失败,Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// 批量删除克拉克森指标
+func BatchDelClarksonsData(indexIds []int) (existIndex []*data_manage.BaseFromClarksonsIndex, err error) {
+	clarksonsIndexList, err := data_manage.GetClarksonsIndexListByIndexIds(indexIds)
+	if err != nil {
+		return
+	}
+
+	indexCodes := make([]string, 0)
+	codeToIndex := make(map[string]*data_manage.BaseFromClarksonsIndex)
+	for _, v := range clarksonsIndexList {
+		indexCodes = append(indexCodes, v.IndexCode)
+		codeToIndex[v.IndexCode] = v
+	}
+
+	edbInfoList, err := data_manage.GetEdbInfoListByEdbCodes(utils.DATA_SOURCE_CLARKSONS, indexCodes)
+	if err != nil {
+		return
+	}
+	deleteIndexIds := make([]int, 0)
+	for _, v := range edbInfoList {
+		if index, ok := codeToIndex[v.EdbCode]; ok {
+			existIndex = append(existIndex, index)
+			delete(codeToIndex, v.EdbCode)
+		}
+	}
+	for _, v := range codeToIndex {
+		deleteIndexIds = append(deleteIndexIds, v.BaseFromClarksonsIndexId)
+	}
+
+	// 删除对应的指标
+	err = data_manage.DeleteClarksonsIndexByIds(deleteIndexIds)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// DelClarksonsData 删除克拉克森指标
+func DelClarksonsData(indexId int) (err error, errMsg string) {
+	baseFromClarksonsIndex, err := data_manage.GetClarksonsIndexByIndexId(indexId)
+	if err != nil {
+		errMsg = `获取数据失败`
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = `该指标未入库`
+			err = nil
+		}
+		return
+	}
+
+	// 获取已经加入到EDB指标库的clarksons指标
+	edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_CLARKSONS, baseFromClarksonsIndex.IndexCode)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "删除失败"
+		err = errors.New("获取分类下的指标信息失败,Err:" + err.Error())
+		return
+	}
+	if edbInfo != nil {
+		errMsg = "当前指标已被引用,不可删除"
+		err = nil
+		return
+	}
+
+	// 删除对应的指标
+	err = data_manage.DeleteClarksonsIndexById(indexId)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// EditClarksonsIndex 编辑克拉克森指标
+func EditClarksonsIndex(indexId, classifyId int) (baseFromClarksonsIndex *data_manage.BaseFromClarksonsIndex, errMsg string, err error) {
+	baseFromClarksonsIndex, err = data_manage.GetClarksonsIndexByIndexId(indexId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = `该指标未入库`
+			err = nil
+		}
+		return
+	}
+	if classifyId > 0 {
+		classify, e := data_manage.GetClarksonsClassifyById(classifyId)
+		if e != nil {
+			err = e
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = `该分类不存在或已删除`
+				err = nil
+			}
+			return
+		}
+		if classify.Level == 1 { // 顶级分类
+			errMsg = `不合法的编辑`
+			err = nil
+			return
+		}
+	}
+
+	maxSort, err := data_manage.GetChildClarksonsClassifyMaxSortById(classifyId)
+	if err != nil {
+		return
+	}
+
+	baseFromClarksonsIndex.ClassifyId = classifyId
+	baseFromClarksonsIndex.Sort = maxSort + 1
+	baseFromClarksonsIndex.ModifyTime = time.Now()
+	err = baseFromClarksonsIndex.Update([]string{"classify_id", "sort", "modify_time"})
+	return
+}
+
+type ClarksonsIndexSource2EdbReq struct {
+	EdbCode       string
+	EdbName       string
+	Frequency     string
+	Unit          string
+	ClassifyId    int
+	AdminId       int
+	AdminRealName string
+}
+
+// ClarksonsIndexSource2Edb 新增克拉克森数据从数据源到指标库
+func ClarksonsIndexSource2Edb(req ClarksonsIndexSource2EdbReq, lang string) (edb *data_manage.EdbInfo, errMsg string, skip bool, err error) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ClarksonsIndexSource2Edb新增失败, Err: %s", err.Error())
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_CLARKSONS
+
+	// 是否新增过指标
+	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
+}
+
+// MoveClarksonsClassify 移动克拉克森分类
+func MoveClarksonsClassify(classifyId, parentClassifyId, prevClassifyId, nextClassifyId int) (err error, errMsg string) {
+	//判断分类是否存在
+	classifyInfo, err := data_manage.GetClarksonsClassifyById(classifyId)
+	if err != nil {
+		errMsg = "移动失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if classifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+		// 校验移动的父级目录下是否有重名分类
+		count, e := data_manage.GetBaseFromClarksonsClassifyCount(classifyInfo.ClassifyName, parentClassifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = errors.New("获取父级目录下的同名分类失败, Err: " + e.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "移动失败,分类名称已存在"
+			err = errors.New("该父级目录下已存在同名分类,请重新命名")
+			return
+		}
+
+		parentClassifyInfo, tmpErr := data_manage.GetClarksonsClassifyById(parentClassifyId)
+		if tmpErr != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+		classifyInfo.ParentId = parentClassifyInfo.BaseFromClassifyId
+		classifyInfo.Level = parentClassifyInfo.Level + 1
+		updateCol = append(updateCol, "ParentId", "Level")
+	}
+	var currentSort, prevSort, nextSort int
+	currentSort = classifyInfo.Sort
+
+	var prevClassify *data_manage.BaseFromClarksonsClassify
+	var nextClassify *data_manage.BaseFromClarksonsClassify
+	//如果有传入 上一个兄弟节点分类id
+	if prevClassifyId > 0 {
+		prevClassify, err = data_manage.GetClarksonsClassifyById(prevClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("上一个兄弟节点分类信息不存在" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	}
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = data_manage.GetClarksonsClassifyById(nextClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("下一个兄弟节点分类信息不存在" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	}
+	// 修改历史数据中的排序为0的情况
+	if prevSort == 0 && nextSort == 0 { //目标是让当前分类处于目录的最顶部
+		//更新为0排序的数据为当前最小排序
+		if nextClassify != nil {
+			minSort, e := data_manage.GetClarksonsIndexClassifyMinSort(parentClassifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + e.Error())
+				return
+			}
+			if minSort > 1 {
+				minSort -= 1
+			}
+			nextClassify.Sort = minSort
+			err = nextClassify.Update([]string{"sort"})
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("更新下一个兄弟节点分类信息失败,Err:" + err.Error())
+				return
+			}
+			nextSort = minSort
+		}
+	}
+	//移到两个排序值中间操作
+	if prevSort >= currentSort {
+		//往下移动
+		err = data_manage.MoveDownClarksonsIndexClassifyBySort(parentClassifyId, prevSort, currentSort)
+		if err != nil {
+			err = errors.New("向下移动出错:" + err.Error())
+			return
+		}
+		classifyInfo.Sort = prevSort
+	} else if nextSort <= currentSort && nextSort != 0 {
+		//往上移动
+		err = data_manage.MoveUpClarksonsIndexClassifyBySort(parentClassifyId, nextSort, currentSort)
+		if err != nil {
+			err = errors.New("向上移动出错:" + err.Error())
+			return
+		}
+		classifyInfo.Sort = nextSort
+	}
+	classifyInfo.ModifyTime = time.Now()
+	updateCol = append(updateCol, "Sort", "ModifyTime")
+	err = classifyInfo.Update(updateCol)
+	if err != nil {
+		errMsg = "移动失败"
+		err = errors.New("修改失败,Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// MoveClarksonsData 移动克拉克森指标
+func MoveClarksonsData(indexId, classifyId, prevIndexId, nextIndexId int) (err error, errMsg string) {
+	//分类信息
+	clarksonsIndex, err := data_manage.GetClarksonsIndexByIndexId(indexId)
+	if err != nil {
+		errMsg = `获取数据失败`
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = `该指标未入库`
+			err = nil
+		}
+		return
+	}
+
+	//判断分类是否存在
+	if classifyId > 0 {
+		_, err = data_manage.GetClarksonsClassifyById(classifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "找不到该分类"
+				err = nil
+			}
+			return
+		}
+	}
+
+	//如果改变了分类,那么移动该图表数据
+	if clarksonsIndex.ClassifyId != classifyId {
+		tmpErr := data_manage.MoveClarksonsIndex(indexId, classifyId)
+		if tmpErr != nil {
+			errMsg = "移动失败"
+			err = errors.New("移动失败,Err:" + tmpErr.Error())
+			return
+		}
+	}
+
+	//移动排序
+	updateCol := make([]string, 0)
+	var currentSort, prevSort, nextSort int
+	currentSort = clarksonsIndex.Sort
+	//如果有传入 上一个兄弟节点分类id
+	var prevIndex *data_manage.BaseFromClarksonsIndex
+	var nextIndex *data_manage.BaseFromClarksonsIndex
+	if prevIndexId > 0 {
+		prevIndex, err = data_manage.GetClarksonsIndexByIndexId(prevIndexId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("上一个兄弟节点指标信息不存在" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点指标信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevIndex.Sort
+	}
+
+	if nextIndexId > 0 {
+		//下一个兄弟节点
+		nextIndex, err = data_manage.GetClarksonsIndexByIndexId(nextIndexId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("下一个兄弟节点指标信息不存在" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点指标信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextIndex.Sort
+	}
+
+	// 修改历史数据中的排序为0的情况
+	if prevSort == 0 && nextSort == 0 { //目标是让当前分类处于目录的最顶部, 但是历史数据里下个节点的sort为0的情况
+		//更新为0排序的数据为当前最小排序
+		if nextIndex != nil {
+			minSort, e := data_manage.GetClarksonsIndexMinSortByClassifyId(classifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + e.Error())
+				return
+			}
+			if minSort > 1 {
+				minSort -= 1
+			}
+			nextIndex.Sort = minSort
+			err = nextIndex.Update([]string{"Sort"})
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("更新下一个兄弟节点分类信息失败,Err:" + err.Error())
+				return
+			}
+			nextSort = minSort
+		}
+	}
+	//移到两个排序值中间操作
+	if prevSort >= currentSort {
+		//往下移动
+		err = data_manage.MoveDownClarksonsIndexBySort(classifyId, prevSort, currentSort)
+		if err != nil {
+			err = errors.New("向下移动出错:" + err.Error())
+			return
+		}
+		clarksonsIndex.Sort = prevSort
+	} else if nextSort <= currentSort && nextSort != 0 {
+		//往上移动
+		err = data_manage.MoveUpClarksonsIndexBySort(classifyId, nextSort, currentSort)
+		if err != nil {
+			err = errors.New("向上移动出错:" + err.Error())
+			return
+		}
+		clarksonsIndex.Sort = nextSort
+	}
+
+	//更新
+	clarksonsIndex.ModifyTime = time.Now()
+	updateCol = append(updateCol, "Sort", "ModifyTime")
+	err = clarksonsIndex.Update(updateCol)
+	if err != nil {
+		errMsg = "移动失败"
+		err = errors.New("修改失败,Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// GetClarksonsIndexInfo 获取指标信息-分页
+func GetClarksonsIndexInfo(keyWord string, classifyIdList []string, frequencyList []string, currentIndex, startSize, pageSize int) (rzdIndexInfoList *data_manage.BaseFromRzdIndexPage, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if keyWord != "" {
+		condition += ` AND (index_name like ? or index_code like ?)`
+		pars = append(pars, "%"+keyWord+"%", "%"+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] + `)`
+	}
+
+	count, err := data_manage.GetClarksonsIndexInfoCount(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	indexPage := data_manage.BaseFromRzdIndexPage{}
+	page := paging.GetPaging(currentIndex, pageSize, count)
+	if count <= 0 {
+		indexPage.Paging = page
+		return &indexPage, nil
+	}
+
+	condition += ` ORDER BY base_from_clarksons_index_id asc`
+
+	// 分页
+	condition += ` LIMIT ?, ?`
+	pars = append(pars, startSize, pageSize)
+
+	indexInfoPage, err := data_manage.GetClarksonsIndexInfoPage(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	var indexCodes []string
+	for _, indexInfo := range indexInfoPage {
+		indexCodes = append(indexCodes, indexInfo.IndexCode)
+	}
+	IndexDataList, err := data_manage.GetClarksonsLastUpdateTimeLastByIndexCode(indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	var indexDataMap = make(map[string]*data_manage.BaseFromClarksonsData, 0)
+	for _, data := range IndexDataList {
+		indexDataMap[data.IndexCode] = data
+	}
+	for _, indexInfo := range indexInfoPage {
+		if indexDataMap[indexInfo.IndexCode] == nil {
+			continue
+		}
+		indexInfo.ModifyTimeMax = indexDataMap[indexInfo.IndexCode].DataTime
+		indexInfo.Value = indexDataMap[indexInfo.IndexCode].Value
+	}
+
+	indexPage.List = indexInfoPage
+	indexPage.Paging = page
+	return &indexPage, nil
+}
+
+// GetClarkssonsIndexList 获取指标列表
+func GetClarkssonsIndexList(searchParams string) (rzdIndexInfoList []*data_manage.BaseFromRzdIndexList, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if searchParams != "" {
+		condition += ` and index_code like ? or index_name like ?`
+		pars = append(pars, "%"+searchParams+"%", "%"+searchParams+"%")
+	}
+	rzdIndexList, err := data_manage.GetRzdIndex(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	return rzdIndexList, nil
+}
+
+// MoveClarksonsClassify 移动克拉克森分类
+func MoveClarksonsClassifyV2(classifyId, parentClassifyId, prevClassifyId, nextClassifyId int) (err error, errMsg string) {
+	//判断分类是否存在
+	classifyInfo, err := data_manage.GetClarksonsClassifyById(classifyId)
+	if err != nil {
+		errMsg = "移动失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	if classifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+		// 校验移动的父级目录下是否有重名分类
+		count, e := data_manage.GetBaseFromClarksonsClassifyCount(classifyInfo.ClassifyName, parentClassifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = errors.New("获取父级目录下的同名分类失败, Err: " + e.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "移动失败,分类名称已存在"
+			err = errors.New("该父级目录下已存在同名分类,请重新命名")
+			return
+		}
+
+		parentClassifyInfo, tmpErr := data_manage.GetClarksonsClassifyById(parentClassifyId)
+		if tmpErr != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+		classifyInfo.ParentId = parentClassifyInfo.BaseFromClassifyId
+		classifyInfo.Level = parentClassifyInfo.Level + 1
+		updateCol = append(updateCol, "ParentId", "Level")
+	}
+	//var currentSort, prevSort, nextSort int
+	//currentSort = classifyInfo.Sort
+
+	var prevClassify *data_manage.BaseFromClarksonsClassify
+	var nextClassify *data_manage.BaseFromClarksonsClassify
+	//如果有传入 上一个兄弟节点分类id
+	if prevClassifyId > 0 {
+		prevClassify, err = data_manage.GetClarksonsClassifyById(prevClassifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("上一个兄弟节点分类信息不存在" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		//如果是移动在两个兄弟节点之间
+		if nextClassifyId > 0 {
+			//下一个兄弟节点
+			nextClassify, err = data_manage.GetClarksonsClassifyById(nextClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+				return
+			}
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == classifyInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+				_ = data_manage.UpdateClarksonsClassifySortByParentId(prevClassify.ParentId, prevClassify.BaseFromClassifyId, prevClassify.Sort, updateSortStr)
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextClassify.Sort-prevClassify.Sort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+					_ = data_manage.UpdateClarksonsClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+				}
+			}
+		}
+
+		classifyInfo.Sort = prevClassify.Sort + 1
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else {
+		firstClassify, err := data_manage.GetFirstClarksonsClassifyByParentId(classifyInfo.ParentId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstClassify != nil && firstClassify.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = data_manage.UpdateClarksonsClassifySortByParentId(firstClassify.ParentId, firstClassify.ChartClassifyId-1, 0, updateSortStr)
+		}
+
+		classifyInfo.Sort = 0 //那就是排在第一位
+		classifyInfo.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//if nextClassifyId > 0 {
+	//	//下一个兄弟节点
+	//	nextClassify, err = data_manage.GetClarksonsClassifyById(nextClassifyId)
+	//	if err != nil {
+	//		if err.Error() == utils.ErrNoRow() {
+	//			errMsg = "移动失败"
+	//			err = errors.New("下一个兄弟节点分类信息不存在" + err.Error())
+	//			return
+	//		}
+	//		errMsg = "移动失败"
+	//		err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+	//		return
+	//	}
+	//	nextSort = nextClassify.Sort
+	//}
+	//
+	//// 修改历史数据中的排序为0的情况
+	//if prevSort == 0 && nextSort == 0 { //目标是让当前分类处于目录的最顶部
+	//	//更新为0排序的数据为当前最小排序
+	//	if nextClassify != nil {
+	//		minSort, e := data_manage.GetClarksonsIndexClassifyMinSort(parentClassifyId)
+	//		if e != nil {
+	//			errMsg = "移动失败"
+	//			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + e.Error())
+	//			return
+	//		}
+	//		if minSort > 1 {
+	//			minSort -= 1
+	//		}
+	//		nextClassify.Sort = minSort
+	//		err = nextClassify.Update([]string{"sort"})
+	//		if err != nil {
+	//			errMsg = "移动失败"
+	//			err = errors.New("更新下一个兄弟节点分类信息失败,Err:" + err.Error())
+	//			return
+	//		}
+	//		nextSort = minSort
+	//	}
+	//}
+	////移到两个排序值中间操作
+	//if prevSort >= currentSort {
+	//	//往下移动
+	//	err = data_manage.MoveDownClarksonsIndexClassifyBySort(parentClassifyId, prevSort, currentSort)
+	//	if err != nil {
+	//		err = errors.New("向下移动出错:" + err.Error())
+	//		return
+	//	}
+	//	classifyInfo.Sort = prevSort
+	//} else if nextSort <= currentSort && nextSort != 0 {
+	//	//往上移动
+	//	err = data_manage.MoveUpClarksonsIndexClassifyBySort(parentClassifyId, nextSort, currentSort)
+	//	if err != nil {
+	//		err = errors.New("向上移动出错:" + err.Error())
+	//		return
+	//	}
+	//	classifyInfo.Sort = nextSort
+	//}
+	//
+	//classifyInfo.ModifyTime = time.Now()
+	//updateCol = append(updateCol, "Sort", "ModifyTime")
+	//err = classifyInfo.Update(updateCol)
+	//if err != nil {
+	//	errMsg = "移动失败"
+	//	err = errors.New("修改失败,Err:" + err.Error())
+	//	return
+	//}
+	//更新
+	if len(updateCol) > 0 {
+		err = classifyInfo.Update(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}

+ 93 - 0
services/data/base_from_rzd_classify_service.go

@@ -0,0 +1,93 @@
+// Package data
+// @Author gmy 2024/8/12 16:09:00
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"sort"
+)
+
+// RzdClassifyList 获取分类列表 包含 分类下的指标名称
+func RzdClassifyList() ([]*data_manage.BaseFromRzdClassifyResponse, error) {
+	// step_1 获取分类列表
+	rzdClassify, err := data_manage.GetAllRzdClassify()
+	if err != nil {
+		return nil, err
+	}
+
+	// step_2 分类id列表
+	classifyIds := make([]int, 0)
+	for _, v := range rzdClassify {
+		classifyIds = append(classifyIds, v.BaseFromRzdClassifyId)
+	}
+
+	// step_3 获取分类下的指标
+	rzdIndices, err := data_manage.GetRzdIndexByClassifyIds(classifyIds)
+	if err != nil {
+		return nil, err
+	}
+	// step_4 封装返回
+	classifyMap := make(map[int]*data_manage.BaseFromRzdClassifyResponse)
+	indexMap := make(map[int][]*data_manage.BaseFromRzdIndex)
+
+	// 创建分类响应映射
+	for _, classify := range rzdClassify {
+		classifyMap[classify.BaseFromRzdClassifyId] = &data_manage.BaseFromRzdClassifyResponse{
+			BaseFromRzdClassifyId: classify.BaseFromRzdClassifyId,
+			CreateTime:            classify.CreateTime,
+			ModifyTime:            classify.ModifyTime,
+			ClassifyName:          classify.ClassifyName,
+			ParentId:              classify.ParentId,
+			Sort:                  classify.Sort,
+			ClassifyNameEn:        classify.ClassifyNameEn,
+		}
+	}
+
+	// 将指标归类到各自的分类
+	for _, index := range rzdIndices {
+		indexMap[index.BaseFromRzdClassifyId] = append(indexMap[index.BaseFromRzdClassifyId], index)
+	}
+
+	// step_5 组装最终结果
+	rzdClassifyAndIndexInfos := make([]*data_manage.BaseFromRzdClassifyResponse, 0)
+
+	for _, classifyResponse := range classifyMap {
+		if classifyResponse.ParentId == 0 { // 根分类
+			// 添加指标信息
+			if indices, exists := indexMap[classifyResponse.BaseFromRzdClassifyId]; exists {
+				classifyResponse.IndexInfo = indices
+			}
+			rzdClassifyAndIndexInfos = append(rzdClassifyAndIndexInfos, classifyResponse)
+		} else {
+			// 查找父分类并添加到其子分类中
+			if parent, exists := classifyMap[classifyResponse.ParentId]; exists {
+				// 添加指标信息
+				if indices, exists := indexMap[classifyResponse.BaseFromRzdClassifyId]; exists {
+					classifyResponse.IndexInfo = indices
+				}
+				parent.Child = append(parent.Child, classifyResponse)
+			}
+		}
+	}
+	sort.Slice(rzdClassifyAndIndexInfos, func(i, j int) bool {
+		return rzdClassifyAndIndexInfos[i].Sort < rzdClassifyAndIndexInfos[j].Sort
+	})
+
+	for _, info := range rzdClassifyAndIndexInfos {
+		sort.Slice(info.Child, func(i, j int) bool {
+			return info.Child[i].Sort < info.Child[j].Sort
+		})
+	}
+
+	return rzdClassifyAndIndexInfos, nil
+}
+
+// GetRzdClassifyItemByParentId 根据分类id获取分类信息
+func GetRzdClassifyItemByParentId(classifyId int) ([]*data_manage.BaseFromRzdClassify, error) {
+	rzdClassify, err := data_manage.GetRzdClassifyItemByParentId(classifyId)
+	if err != nil {
+		return nil, err
+	}
+
+	return rzdClassify, nil
+}

+ 444 - 0
services/data/base_from_rzd_index_service.go

@@ -0,0 +1,444 @@
+// Package data
+// @Author gmy 2024/8/22 9:44:00
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// RzdIndexData 获取睿姿得指标数据
+func RzdIndexData(classifyId int, frequency string, currentIndex, startSize, pageSize int) (dataList []*data_manage.BaseFromRzdIndexList, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if classifyId >= 0 {
+		// 递归查询子集分类
+		classifyIdList, err := RzdClassifyRecursiveQuery(classifyId)
+		if err != nil {
+			return nil, err
+		}
+		classifyIdList = append(classifyIdList, classifyId)
+
+		interfaceClassifyIdList := make([]interface{}, len(classifyIdList))
+		for i, id := range classifyIdList {
+			interfaceClassifyIdList[i] = id
+		}
+
+		condition += ` AND base_from_rzd_classify_id in (` + utils.GetOrmInReplace(len(classifyIdList)) + `)`
+		pars = append(pars, interfaceClassifyIdList...)
+	}
+
+	if frequency != "" {
+		condition += ` AND frequency=? `
+		pars = append(pars, frequency)
+	}
+
+	indexes, err := data_manage.GetRzdIndex(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	indexCodes := make([]string, 0)
+	for _, v := range indexes {
+		indexCodes = append(indexCodes, v.IndexCode)
+	}
+	indexCounts, e := data_manage.GetRzdIndexDataCountGroup(indexCodes)
+	if e != nil {
+		return nil, err
+	}
+	countMap := make(map[string]int)
+	for _, v := range indexCounts {
+		countMap[v.IndexCode] = v.Count
+	}
+
+	// 判断是否存在于指标库
+	edbCodeList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_RZD, indexCodes)
+	if err != nil {
+		return
+	}
+	edbCodeMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range edbCodeList {
+		edbCodeMap[v.EdbCode] = v
+	}
+
+	resultList := make([]*data_manage.BaseFromRzdIndexList, 0)
+	for _, v := range indexes {
+		product := new(data_manage.BaseFromRzdIndexList)
+		product.BaseFromRzdIndexId = v.BaseFromRzdIndexId
+		product.BaseFromRzdClassifyId = v.BaseFromRzdClassifyId
+		product.Unit = v.Unit
+		product.IndexCode = v.IndexCode
+		product.IndexName = v.IndexName
+		product.Frequency = v.Frequency
+		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)
+		indexDataList, e := data_manage.GetRzdIndexData(v.IndexCode, startSize, pageSize)
+		if e != nil {
+			return nil, err
+		}
+		if indexDataList == nil {
+			indexDataList = make([]*data_manage.BaseFromRzdData, 0)
+		}
+		product.DataList = indexDataList
+		product.Paging = page
+		resultList = append(resultList, product)
+	}
+
+	return resultList, nil
+}
+
+// RzdClassifyRecursiveQuery 递归查询分类
+func RzdClassifyRecursiveQuery(classifyId int) ([]int, error) {
+	var classifyIdList []int
+
+	// 查询当前分类 ID 的子分类
+	rzdClassifyList, err := GetRzdClassifyItemByParentId(classifyId)
+	if err != nil {
+		return nil, err
+	}
+
+	// 遍历子分类
+	for _, classify := range rzdClassifyList {
+		// 将当前子分类的 ID 添加到列表中
+		classifyIdList = append(classifyIdList, classify.BaseFromRzdClassifyId)
+
+		// 递归查询当前子分类的子分类
+		childIds, err := RzdClassifyRecursiveQuery(classify.BaseFromRzdClassifyId)
+		if err != nil {
+			return nil, err
+		}
+
+		// 合并子分类的 ID
+		classifyIdList = append(classifyIdList, childIds...)
+	}
+
+	return classifyIdList, nil
+}
+
+func GetRzdIndexFrequency(classify int) ([]*string, error) {
+
+	classifyIdList, err := RzdClassifyRecursiveQuery(classify)
+	if err != nil {
+		return nil, err
+	}
+	classifyIdList = append(classifyIdList, classify)
+
+	frequencyNameList, err := data_manage.GetRzdIndexFrequency(classifyIdList)
+	if err != nil {
+		return nil, err
+	}
+	return frequencyNameList, nil
+}
+
+// RzdIndexAddValidate 指标添加校验
+func RzdIndexAddValidate(req *data_manage.BaseFromRzdIndexBatchAddCheckReq) ([]*data_manage.BaseFromRzdIndexAndData, error) {
+	var condition string
+	var pars []interface{}
+
+	if req.IsCheckAll {
+		if len(req.ClassifyIdList) > 0 {
+			condition += ` AND base_from_rzd_classify_id in (` + utils.GetOrmInReplace(len(req.ClassifyIdList)) + `)`
+			for _, id := range req.ClassifyIdList {
+				pars = append(pars, id)
+			}
+		}
+		if len(req.FrequencyList) > 0 {
+			condition += ` AND frequency in (` + utils.GetOrmInReplace(len(req.FrequencyList)) + `)`
+			for _, frequency := range req.FrequencyList {
+				pars = append(pars, frequency)
+			}
+		}
+		if req.SearchParams != "" {
+			condition += ` AND (index_name like ? or index_code like ?)`
+			pars = append(pars, "%"+req.SearchParams+"%", "%"+req.SearchParams+"%")
+		}
+		if len(req.IndexCodes) > 0 {
+			condition += ` AND index_code not in (` + utils.GetOrmInReplace(len(req.IndexCodes)) + `)`
+			for _, code := range req.IndexCodes {
+				pars = append(pars, code)
+			}
+		}
+	} else {
+		if len(req.IndexCodes) > 0 {
+			condition += ` AND index_code in (` + utils.GetOrmInReplace(len(req.IndexCodes)) + `)`
+			for _, code := range req.IndexCodes {
+				pars = append(pars, code)
+			}
+		}
+	}
+
+	count, err := data_manage.GetRzdIndexNotExistEdbInfoCount(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	if count > 30 {
+		return nil, fmt.Errorf("批量添加指标数量不得超过30个")
+	}
+
+	condition += ` ORDER BY base_from_rzd_index_id asc`
+
+	indexList, err := data_manage.GetRzdIndexNotExistEdbInfoPage(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	return indexList, nil
+}
+
+// RzdIndexNameCheck 指标名称校验
+func RzdIndexNameCheck(indexNames []string, resp []*data_manage.RzdNameCheckResult) ([]*data_manage.RzdNameCheckResult, 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
+}
+
+// RzdIndexAdd 批量添加指标
+func RzdIndexAdd(req data_manage.RzdIndexAddReq, 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("ruizide指标数据新增失败, Err: %s", err.Error())
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_RZD
+
+	// 是否已有指标数据
+	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
+}
+
+// GetRzdIndexInfo 获取指标信息-分页
+func GetRzdIndexInfo(keyWord string, classifyIdList []string, frequencyList []string, currentIndex, startSize, pageSize int) (rzdIndexInfoList *data_manage.BaseFromRzdIndexPage, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if keyWord != "" {
+		condition += ` AND (index_name like ? or index_code like ?)`
+		pars = append(pars, "%"+keyWord+"%", "%"+keyWord+"%")
+	}
+	if len(classifyIdList) > 0 {
+		condition += ` AND base_from_rzd_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] + `)`
+	}
+
+	count, err := data_manage.GetRzdIndexInfoCount(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	indexPage := data_manage.BaseFromRzdIndexPage{}
+	page := paging.GetPaging(currentIndex, pageSize, count)
+	if count <= 0 {
+		indexPage.Paging = page
+		return &indexPage, nil
+	}
+
+	condition += ` ORDER BY base_from_rzd_index_id asc`
+
+	// 分页
+	condition += ` LIMIT ?, ?`
+	pars = append(pars, startSize, pageSize)
+
+	indexInfoPage, err := data_manage.GetRzdIndexInfoPage(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	var indexCodes []string
+	for _, indexInfo := range indexInfoPage {
+		indexCodes = append(indexCodes, indexInfo.IndexCode)
+	}
+	IndexDataList, err := data_manage.GetRzdLastUpdateTimeLastByIndexCode(indexCodes)
+	if err != nil {
+		return nil, err
+	}
+	var indexDataMap = make(map[string]*data_manage.BaseFromRzdData, 0)
+	for _, data := range IndexDataList {
+		indexDataMap[data.IndexCode] = data
+	}
+	for _, indexInfo := range indexInfoPage {
+		if indexDataMap[indexInfo.IndexCode] == nil {
+			continue
+		}
+		indexInfo.ModifyTimeMax = indexDataMap[indexInfo.IndexCode].DataTime
+		indexInfo.Value = indexDataMap[indexInfo.IndexCode].Value
+	}
+
+	indexPage.List = indexInfoPage
+	indexPage.Paging = page
+	return &indexPage, nil
+}
+
+// GetRzdIndexDetail 获取指标详情
+func GetRzdIndexDetail(indexCode string, currentIndex, startSize, pageSize int) (rzdIndexInfoList *data_manage.BaseFromRzdIndexList, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if indexCode != "" {
+		condition += ` and index_code = ?`
+		pars = append(pars, indexCode)
+	}
+	rzdIndexList, err := data_manage.GetRzdIndex(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	var rzdIndex *data_manage.BaseFromRzdIndexList
+	if len(rzdIndexList) > 0 {
+		rzdIndex = rzdIndexList[0]
+		// 查询指标数据
+		var condition string
+		var pars []interface{}
+		condition += ` and index_code = ?`
+		pars = append(pars, indexCode)
+
+		count, err := data_manage.GetBaseFormRzdDataByConditionCount(condition, pars)
+		if err != nil {
+			return nil, err
+		}
+		pagingItem := paging.GetPaging(currentIndex, pageSize, count)
+		if count <= 0 {
+			rzdIndex.Paging = pagingItem
+		} else {
+			condition += ` ORDER BY data_time desc`
+
+			condition += ` LIMIT ?, ?`
+			pars = append(pars, startSize, pageSize)
+
+			dataList, err := data_manage.GetBaseFormRzdDataByCondition(condition, pars)
+			if err != nil {
+				return nil, err
+			}
+
+			rzdIndex.Paging = pagingItem
+			rzdIndex.DataList = dataList
+		}
+
+		// 查询是否在指标库
+		edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RZD, indexCode)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			return nil, err
+		}
+
+		if edbInfo != nil {
+			rzdIndex.EdbInfoId = edbInfo.EdbInfoId
+		}
+	}
+
+	return rzdIndex, nil
+}
+
+// GetRzdIndexList 获取指标列表
+func GetRzdIndexList(searchParams string) (rzdIndexInfoList []*data_manage.BaseFromRzdIndexList, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if searchParams != "" {
+		condition += ` and index_code like ? or index_name like ?`
+		pars = append(pars, "%"+searchParams+"%", "%"+searchParams+"%")
+	}
+	rzdIndexList, err := data_manage.GetRzdIndex(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	return rzdIndexList, nil
+}

+ 49 - 0
services/data/base_from_usda_fas.go

@@ -0,0 +1,49 @@
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+// UsdaFasIndexSource2Edb 新增涌益咨询数据源到指标库
+func UsdaFasIndexSource2Edb(req data_manage.UsdaFasIndexSource2EdbReq, 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("UsdaFasIndexSource2Edb新增失败, Err: %s", err.Error())
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_USDA_FAS
+
+	// 是否新增过指标
+	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
+}

+ 57 - 42
services/data/chart_classify.go

@@ -979,7 +979,7 @@ func GetChartClassifyParentRecursive(list []*data_manage.ChartClassifyItems, cla
 }
 
 // 修改图表分类,可以修改父级
-func EditChartClassifyV2(chartClassifyId, praentId, source int, chartClassifyName, lang string) (classifyInfo *data_manage.ChartClassify, err error, errMsg string, isSendEmail bool) {
+func EditChartClassifyV2(chartClassifyId, parentId, source int, chartClassifyName, lang string) (classifyInfo *data_manage.ChartClassify, err error, errMsg string, isSendEmail bool) {
 	isSendEmail = true
 	errMsg = "保存失败"
 
@@ -989,14 +989,15 @@ func EditChartClassifyV2(chartClassifyId, praentId, source int, chartClassifyNam
 		return
 	}
 
-	if praentId != classifyInfo.ParentId {
-		parentClassifyInfo, e := data_manage.GetChartClassifyById(praentId)
+	if parentId != classifyInfo.ParentId {
+		parentClassifyInfo, e := data_manage.GetChartClassifyById(parentId)
 		if e != nil {
 			err = e
 			return
 		}
-		if classifyInfo.Level != parentClassifyInfo.Level-1 {
+		if classifyInfo.Level != parentClassifyInfo.Level+1 {
 			err = errors.New("父级分类层级异常")
+			return
 		}
 	}
 
@@ -1011,8 +1012,8 @@ func EditChartClassifyV2(chartClassifyId, praentId, source int, chartClassifyNam
 	// 需要变更的字段
 	updateCols := make([]string, 0)
 
-	if praentId != classifyInfo.ParentId {
-		classifyInfo.ParentId = praentId
+	if parentId != classifyInfo.ParentId {
+		classifyInfo.ParentId = parentId
 		classifyInfo.ModifyTime = time.Now()
 		updateCols = append(updateCols, "ParentId")
 	}
@@ -1128,53 +1129,67 @@ func GetChartClassifyChildrenRecursiveByParentIds(list []*data_manage.ChartClass
 	return res
 }
 
-func GetChartClassifyByIsMe(adminId, parentId, source int, classifyList []*data_manage.ChartClassifyItems) (list []*data_manage.ChartClassifyItems, err error) {
-	chartInfoList, err := data_manage.GetChartInfoByAdminId([]int{source}, adminId)
+// GetChartClassifyRelation 获取图库分类的父级关系 map[classifyId]map[parentId]struct{} classifyId为图库classify_id, map[parentId]struct{}为当前分类id中所有的父级id
+func GetChartClassifyRelation() (classifyRelationMap map[int]map[int]struct{}, err error) {
+	classifyAll, err := data_manage.GetChartClassifyBySource(utils.CHART_SOURCE_DEFAULT)
 	if err != nil {
 		return
 	}
-	classifyIdList := make([]int, 0)
-	for _, chartInfo := range chartInfoList {
-		if chartInfo.ChartClassifyId > 0 {
-			classifyIdList = append(classifyIdList, chartInfo.ChartClassifyId)
+	classifyRelationMap = make(map[int]map[int]struct{})
+	for _, classify := range classifyAll {
+		if classify.ParentId == 0 {
+			classifyRelationMap[classify.ChartClassifyId] = make(map[int]struct{})
+			continue
 		}
-	}
-	if parentId > 0 {
-		for _, v := range classifyList {
-			if v.ChartClassifyId > 0 && v.ChartInfoId == 0 {
-				classifyItems, er, _ := GetChildChartClassifyByClassifyId(v.ChartClassifyId)
-				if er != nil {
-					err = er
-					return
-				}
-				existClassifyMap := make(map[int]struct{})
-				for _, classify := range classifyItems {
-					existClassifyMap[classify.ChartClassifyId] = struct{}{}
-				}
-				for _, chart := range chartInfoList {
-					if _, ok := existClassifyMap[chart.ChartClassifyId]; ok {
-						list = append(list, v)
-						break
-					}
+		if parent, ok := classifyRelationMap[classify.ParentId]; !ok {
+			continue
+		} else {
+			if _, ok := classifyRelationMap[classify.ChartClassifyId]; !ok {
+				classifyRelationMap[classify.ChartClassifyId] = make(map[int]struct{})
+				classifyRelationMap[classify.ChartClassifyId][classify.ParentId] = struct{}{}
+				for k, v := range parent {
+					classifyRelationMap[classify.ChartClassifyId][k] = v
 				}
 			} else {
-				list = append(list, v)
+				classifyRelationMap[classify.ChartClassifyId][classify.ParentId] = struct{}{}
+				for k, v := range parent {
+					classifyRelationMap[classify.ChartClassifyId][k] = v
+				}
 			}
 		}
-	} else {
-		chartClassifyList, er := data_manage.GetChartClassifyByIdList(classifyIdList)
-		if er != nil {
-			err = er
-			return
-		}
+	}
+	return
+}
+
+func GetChartClassifyByIsMe(adminId, parentId, source int, classifyList []*data_manage.ChartClassifyItems) (list []*data_manage.ChartClassifyItems, err error) {
+	chartInfoList, err := data_manage.GetChartInfoByAdminId([]int{source}, adminId)
+	if err != nil {
+		return
+	}
 
-		existClassifyIdMap := make(map[int]struct{})
-		for _, classify := range chartClassifyList {
-			existClassifyIdMap[classify.RootId] = struct{}{}
+	classifyRelation, err := GetChartClassifyRelation()
+	if err != nil {
+		return
+	}
+	chartInfoRelationClassifyMap := make(map[int]struct{})
+	for _, classify := range chartInfoList {
+		if classify.ChartClassifyId == 0 {
+			continue
+		} else {
+			if relationClassify, ok := classifyRelation[classify.ChartClassifyId]; ok {
+				chartInfoRelationClassifyMap[classify.ChartClassifyId] = struct{}{}
+				for k, v := range relationClassify {
+					chartInfoRelationClassifyMap[k] = v
+				}
+			}
 		}
+	}
 
-		for _, classify := range classifyList {
-			if _, ok := existClassifyIdMap[classify.ChartClassifyId]; ok {
+	for _, classify := range classifyList {
+		if classify.ChartInfoId > 0 {
+			list = append(list, classify)
+		} else {
+			if _, ok := chartInfoRelationClassifyMap[classify.ChartClassifyId]; ok {
 				list = append(list, classify)
 			}
 		}

+ 6 - 5
services/data/chart_info.go

@@ -11,12 +11,13 @@ import (
 	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/shopspring/decimal"
 	"math"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/shopspring/decimal"
 )
 
 func ChartInfoRefreshV1(chartInfoId int) (err error) {
@@ -3906,7 +3907,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 						continue
 					}
 					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-					chartLegend,_ := strconv.Atoi(v.ChartLegend)
+					chartLegend, _ := strconv.Atoi(v.ChartLegend)
 					if chartLegend < startYear {
 						continue
 					}
@@ -4042,7 +4043,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 							continue
 						}
 						// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-						chartLegend,_ := strconv.Atoi(v.ChartLegend)
+						chartLegend, _ := strconv.Atoi(v.ChartLegend)
 						if chartLegend < startYear {
 							continue
 						}
@@ -4215,7 +4216,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 						continue
 					}
 					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-					chartLegend,_ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					chartLegend, _ := strconv.Atoi(quarterDataList[i].ChartLegend)
 					if chartLegend < startYear {
 						continue
 					}
@@ -4303,7 +4304,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 						continue
 					}
 					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
-					chartLegend,_ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					chartLegend, _ := strconv.Atoi(quarterDataList[i].ChartLegend)
 					if chartLegend < startYear {
 						continue
 					}

+ 3 - 0
services/data/chart_info_excel_balance.go

@@ -650,6 +650,9 @@ func GetBalanceExcelChartDetail(chartInfo *data_manage.ChartInfoView, mappingLis
 			}
 		}
 	}
+	if chartInfo.ChartType == utils.CHART_TYPE_SEASON && chartInfo.DateType == utils.DateTypeNYears {
+		dateMax = time.Date(dateMax.Year()+1, 1, 1, 0, 0, 0, 0, time.Local)
+	}
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, dateMax)
 
 	if chartInfo.ChartType == 2 {

+ 7 - 0
services/data/data_manage_permission/edb_permission.go

@@ -51,6 +51,13 @@ func SetEdbChartPermission(source, subSource, userId int, authUserList []int, is
 		}
 	}
 
+	// dataIdList 做3000条限制
+	if len(dataIdList) > 3000 {
+		err = fmt.Errorf("数据量条数不要超过3000,请分批设置")
+		errMsg = err.Error()
+		return
+	}
+
 	if len(authUserList) <= 0 {
 		// 说明是取消权限管控
 	}

+ 70 - 11
services/data/edb_classify.go

@@ -53,11 +53,15 @@ func edbClassifyHaveChild(allNode []*data_manage.EdbClassifyItems, node *data_ma
 }
 
 // GetClassifyTreeRecursive 递归获取分类树形结构
-func GetClassifyTreeRecursive(list []*data_manage.EdbClassifyItems, parentId int) []*data_manage.EdbClassifyItems {
+func GetClassifyTreeRecursive(list []*data_manage.EdbClassifyItems, parentId, level int) []*data_manage.EdbClassifyItems {
 	res := make([]*data_manage.EdbClassifyItems, 0)
 	for _, v := range list {
+		v.SelectDisable = true
+		if v.Level == level {
+			v.SelectDisable = false
+		}
 		if v.ParentId == parentId {
-			v.Children = GetClassifyTreeRecursive(list, v.ClassifyId)
+			v.Children = GetClassifyTreeRecursive(list, v.ClassifyId, level)
 			res = append(res, v)
 		}
 	}
@@ -395,7 +399,7 @@ func AddEdbClassify(classifyName string, parentId, level int, classifyType uint8
 }
 
 // EditEdbClassify 编辑指标分类
-func EditEdbClassify(classifyId int, classifyName, lang string, sysUser *system.Admin) (err error, errMsg string) {
+func EditEdbClassify(classifyId, parentId int, classifyName, lang string, sysUser *system.Admin) (err error, errMsg string) {
 	item, err := data_manage.GetEdbClassifyById(classifyId)
 	if err != nil {
 		errMsg = `保存失败`
@@ -432,15 +436,33 @@ func EditEdbClassify(classifyId int, classifyName, lang string, sysUser *system.
 
 	}
 
+	if parentId != item.ParentId {
+		parentClassifyInfo, e := data_manage.GetEdbClassifyById(parentId)
+		if e != nil {
+			err = e
+			return
+		}
+		if item.Level != parentClassifyInfo.Level+1 {
+			err = errors.New("父级分类层级异常")
+			return
+		}
+	}
+
 	// 需要变更的字段
 	updateCols := make([]string, 0)
 
+	if parentId != item.ParentId {
+		item.ParentId = parentId
+		item.ModifyTime = time.Now()
+		updateCols = append(updateCols, "ParentId", "ModifyTime")
+	}
+
 	switch lang {
 	case utils.EnLangVersion:
-		// 名字相同,那么就直接返回
-		if item.ClassifyNameEn == classifyName {
-			return
-		}
+		//// 名字相同,那么就直接返回
+		//if item.ClassifyNameEn == classifyName {
+		//	return
+		//}
 
 		// 判断名称是否已存在
 		count, tmpErr := data_manage.GetEdbClassifyEnCount(classifyName, item.ParentId, item.ClassifyType)
@@ -460,10 +482,10 @@ func EditEdbClassify(classifyId int, classifyName, lang string, sysUser *system.
 		item.LastModifyUserRealName = sysUser.RealName
 		updateCols = append(updateCols, "ClassifyNameEn", "LastModifyUserId", "LastModifyUserRealName")
 	default:
-		// 名字相同,那么就直接返回
-		if item.ClassifyName == classifyName {
-			return
-		}
+		//// 名字相同,那么就直接返回
+		//if item.ClassifyName == classifyName {
+		//	return
+		//}
 
 		// 判断名称是否已存在
 		count, tmpErr := data_manage.GetEdbClassifyCount(classifyName, item.ParentId, item.ClassifyType)
@@ -1624,3 +1646,40 @@ func GetEdbClassifyByIsMe(adminId, parentId, edbInfoType int, classifyList []*da
 	}
 	return
 }
+
+// GetEdbClassifyChildrenRecursive 根据父目录递归子级目录
+func GetEdbClassifyChildrenRecursive(list []*data_manage.EdbClassifyItems, parentId int) []*data_manage.EdbClassifyItems {
+	var res []*data_manage.EdbClassifyItems
+
+	for _, v := range list {
+		if v.ParentId == parentId {
+			// 递归调用以获取更深层次的子级
+			children := GetEdbClassifyChildrenRecursive(list, v.ClassifyId)
+			// 将当前节点和所有子节点添加到结果中
+			res = append(res, v)
+			res = append(res, children...)
+		} else if v.ClassifyId == parentId {
+			// 将当前节点添加到结果中
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// GetEdbClassifyChildrenRecursiveByParentIds 根据父目录递归子级目录
+func GetEdbClassifyChildrenRecursiveByParentIds(list []*data_manage.EdbClassifyItems, parentIds []string) []*data_manage.EdbClassifyItems {
+	var res []*data_manage.EdbClassifyItems
+	for _, v := range list {
+		for _, id := range parentIds {
+			parentId, _ := strconv.Atoi(id)
+			if v.ParentId == parentId || v.ClassifyId == parentId {
+				// 递归调用以获取更深层次的子级
+				children := GetEdbClassifyChildrenRecursive(list, v.ClassifyId)
+				// 将当前节点和所有子节点添加到结果中
+				res = append(res, v)
+				res = append(res, children...)
+			}
+		}
+	}
+	return res
+}

+ 7 - 0
services/data/edb_info.go

@@ -3034,6 +3034,11 @@ func handleByDelEdbInfo(edbInfo *data_manage.EdbInfo) {
 	if edbInfo.Source == utils.DATA_SOURCE_MANUAL {
 		_ = models.UpdateManualIsJoinEdbStatus(edbInfo.EdbCode, 0)
 	}
+
+	if edbInfo.EdbInfoType == utils.PREDICT_EDB_INFO_TYPE && edbInfo.EdbType == 1 {
+		// 删除图表中的指标引用
+		_ = data_manage.DeleteEdbRelationByObjectId(edbInfo.EdbInfoId, utils.EDB_RELATION_PREDICT_EDB)
+	}
 }
 
 func EditBaseEdbInfo(req data_manage.EditEdbInfoReq, sysUser *system.Admin, lang, requestBody, uri string) (isSendEmail bool, err error, errMsg string) {
@@ -3236,6 +3241,7 @@ func GetMySteelSourceByEdbCode(edbCode string) (source int, item *data_manage.Ed
 		}
 		if tmpInfo != nil {
 			source = utils.DATA_SOURCE_MYSTEEL_CHEMICAL
+			return
 		}
 	}
 
@@ -3250,6 +3256,7 @@ func GetMySteelSourceByEdbCode(edbCode string) (source int, item *data_manage.Ed
 		}
 		if tmpInfo != nil {
 			source = utils.DATA_SOURCE_GL
+			return
 		}
 	}
 

+ 19 - 12
services/data/edb_info_relation.go

@@ -15,12 +15,12 @@ import (
 // SaveChartEdbInfoRelation 添加/编辑图表指标引用关联记录
 func SaveChartEdbInfoRelation(edbInfoIds []int, chartInfo *data_manage.ChartInfo) (err error) {
 	//更新指标刷新状态为启用
-	err = saveEdbInfoRelation(edbInfoIds, chartInfo.ChartInfoId, utils.EDB_RELATION_CHART, chartInfo.Source)
+	err = saveEdbInfoRelation(edbInfoIds, chartInfo.ChartInfoId, utils.EDB_RELATION_CHART, chartInfo.Source, false)
 	return
 }
 
 // saveEdbInfoRelation 添加/编辑图表指标引用关联记录
-func saveEdbInfoRelation(edbInfoIds []int, objectId, objectType, objectSubType int) (err error) {
+func saveEdbInfoRelation(edbInfoIds []int, objectId, objectType, objectSubType int, needPredict bool) (err error) {
 	// 实现添加引用记录的逻辑
 	if len(edbInfoIds) == 0 {
 		return
@@ -43,13 +43,13 @@ func saveEdbInfoRelation(edbInfoIds []int, objectId, objectType, objectSubType i
 	// 过滤预测指标
 	edbInfoList := make([]*data_manage.EdbInfo, 0)
 	for _, v := range edbInfoListTmp {
-		if v.EdbInfoType == 0 {
+		if v.EdbInfoType == 0 || (v.EdbType == 1 && v.EdbInfoType == 1 && needPredict) {
 			edbInfoList = append(edbInfoList, v)
 		}
 	}
 	// 查询计算指标信息,并且建立关联关系
 	// 查询间接引用的指标信息
-	calculateEdbMappingListMap, calculateEdbMappingIdsMap, e := GetEdbListByEdbInfoId(edbInfoList)
+	calculateEdbMappingListMap, calculateEdbMappingIdsMap, e := GetEdbListByEdbInfoId(edbInfoList, needPredict)
 	if e != nil {
 		err = fmt.Errorf("查询计算指标信息失败,%s", e.Error())
 		return
@@ -103,7 +103,7 @@ func saveEdbInfoRelation(edbInfoIds []int, objectId, objectType, objectSubType i
 			}
 			tmp.RelationCode = fmt.Sprintf("%d_%d_%d_%d", tmp.EdbInfoId, tmp.ReferObjectId, tmp.ReferObjectType, tmp.ReferObjectSubType)
 			addList = append(addList, tmp)
-			if edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0 {
+			if (edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0) || (edbInfo.EdbType == 1 && edbInfo.EdbInfoType == 1 && needPredict) {
 				childEdbMappingIds, ok1 := calculateEdbMappingIdsMap[edbInfo.EdbInfoId]
 				if !ok1 {
 					continue
@@ -165,7 +165,7 @@ func SaveSandBoxEdbInfoRelation(sandBoxId int, sandBoxContent string) (err error
 		return
 	}
 	//更新指标刷新状态为启用
-	err = saveEdbInfoRelation(edbInfoIds, sandBoxId, utils.EDB_RELATION_SANDBOX, 0)
+	err = saveEdbInfoRelation(edbInfoIds, sandBoxId, utils.EDB_RELATION_SANDBOX, 0, false)
 	return
 }
 
@@ -254,7 +254,7 @@ func SaveCalendarEdbInfoRelation(chartPermissionId int, matterDate string, editM
 	}
 	// 查询计算指标信息,并且建立关联关系
 	// 查询间接引用的指标信息
-	calculateEdbMappingListMap, calculateEdbMappingIdsMap, e := GetEdbListByEdbInfoId(edbInfoList)
+	calculateEdbMappingListMap, calculateEdbMappingIdsMap, e := GetEdbListByEdbInfoId(edbInfoList, false)
 	if e != nil {
 		err = fmt.Errorf("查询计算指标信息失败,%s", e.Error())
 		return
@@ -456,13 +456,13 @@ func GetEdbRelationList(source, edbType int, classifyId, sysUserId, frequency, k
 }
 
 // 查找当前计算指标的所有溯源指标
-func GetEdbListByEdbInfoId(edbInfoList []*data_manage.EdbInfo) (edbMappingListMap map[int]*data_manage.EdbInfoCalculateMapping, edbInfoMappingRootIdsMap map[int][]int, err error) {
+func GetEdbListByEdbInfoId(edbInfoList []*data_manage.EdbInfo, needPredict bool) (edbMappingListMap map[int]*data_manage.EdbInfoCalculateMapping, edbInfoMappingRootIdsMap map[int][]int, err error) {
 	if len(edbInfoList) == 0 {
 		return
 	}
 	edbInfoIds := make([]int, 0)
 	for _, v := range edbInfoList {
-		if v.EdbType == 2 && v.EdbInfoType == 0 { //普通计算指标,排除预算指标
+		if (v.EdbType == 2 && v.EdbInfoType == 0) || (v.EdbType == 1 && v.EdbInfoType == 1 && needPredict) { //普通计算指标,或者是基础预测指标
 			edbInfoIds = append(edbInfoIds, v.EdbInfoId)
 		}
 	}
@@ -492,7 +492,7 @@ func GetEdbListByEdbInfoId(edbInfoList []*data_manage.EdbInfo) (edbMappingListMa
 	edbInfoMappingRootIdsMap = make(map[int][]int, 0)
 	edbMappingMap := make(map[int]struct{})
 	for _, edbInfo := range edbInfoList {
-		if edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0 {
+		if (edbInfo.EdbType == 2 && edbInfo.EdbInfoType == 0) || (edbInfo.EdbType == 1 && edbInfo.EdbInfoType == 1 && needPredict) {
 			edbInfoId := edbInfo.EdbInfoId
 			edbMappingList, err = getCalculateEdbInfoByEdbInfoId(allEdbMappingMap, edbInfoId, hasFindMap, edbInfoIdMap, edbMappingList, edbMappingMap, edbInfoMappingRootIdsMap, edbInfoId)
 			if err != nil {
@@ -636,7 +636,7 @@ func SaveExcelEdbInfoRelation(excelInfoId, source int, addChildExcel bool) (err
 				if !ok {
 					continue
 				}
-				err = saveEdbInfoRelation(edbInfoIds, v.ExcelInfoId, utils.EDB_RELATION_TABLE, source)
+				err = saveEdbInfoRelation(edbInfoIds, v.ExcelInfoId, utils.EDB_RELATION_TABLE, source, false)
 			}
 			//更新
 		}
@@ -656,7 +656,14 @@ func SaveExcelEdbInfoRelation(excelInfoId, source int, addChildExcel bool) (err
 	if len(edbInfoIds) == 0 {
 		return
 	}
-	err = saveEdbInfoRelation(edbInfoIds, excelInfoId, utils.EDB_RELATION_TABLE, source)
+	err = saveEdbInfoRelation(edbInfoIds, excelInfoId, utils.EDB_RELATION_TABLE, source, false)
+	return
+}
+
+// SavePredictEdbInfoRelation 添加/编辑预测指标引用关联记录
+func SavePredictEdbInfoRelation(edbInfoIds []int, edbInfoId int) (err error) {
+	//更新指标刷新状态为启用
+	err = saveEdbInfoRelation(edbInfoIds, edbInfoId, utils.EDB_RELATION_PREDICT_EDB, 1, true)
 	return
 }
 

+ 138 - 55
services/data/predict_edb_info.go

@@ -571,10 +571,14 @@ func MovePredictEdbInfo(edbInfoId, classifyId, prevEdbInfoId, nextEdbInfoId int,
 }
 
 // GetChartPredictEdbInfoDataListByConfList 获取图表的预测指标的未来数据
-func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.PredictEdbConfAndData, filtrateStartDateStr, latestDateStr, endDateStr, frequency, dataDateType string, realPredictEdbInfoData []*data_manage.EdbDataList) (predictEdbInfoData []*data_manage.EdbDataList, minValue, maxValue float64, err error, errMsg string) {
-	endDate, err := time.ParseInLocation(utils.FormatDate, endDateStr, time.Local)
-	if err != nil {
-		return
+func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.PredictEdbConfAndData, filtrateStartDateStr, latestDateStr, endDateStr string, endDateType int, frequency, dataDateType string, realPredictEdbInfoData []*data_manage.EdbDataList) (predictEdbInfoData []*data_manage.EdbDataList, minValue, maxValue float64, err error, errMsg string) {
+	hasEndNum := 0 //如果选择了未来期数,用来保存已经预测过的期数
+	var endDate time.Time
+	if endDateStr != `` {
+		endDate, err = time.ParseInLocation(utils.FormatDate, endDateStr, time.Local)
+		if err != nil {
+			return
+		}
 	}
 
 	latestDate, err := time.ParseInLocation(utils.FormatDate, latestDateStr, time.Local)
@@ -612,13 +616,18 @@ func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.P
 
 	for _, predictEdbConf := range predictEdbConfList {
 		dataEndTime := endDate
-		if predictEdbConf.EndDate.Before(dataEndTime) {
+		if !predictEdbConf.EndDate.IsZero() && predictEdbConf.EndDate.Before(dataEndTime) {
 			dataEndTime = predictEdbConf.EndDate
 		}
 
 		var tmpMinValue, tmpMaxValue float64 // 当前预测结果中的最大/最小值
+		endNum := predictEdbConf.EndNum - hasEndNum
+		if endNum <= 0 && endDateType == 1 {
+			err = fmt.Errorf("预测期数不能小于等于0")
+			return
+		}
+		dayList := getPredictEdbDayList(startDate, dataEndTime, frequency, dataDateType, endDateType, endNum)
 
-		dayList := getPredictEdbDayList(startDate, dataEndTime, frequency, dataDateType)
 		if len(dayList) <= 0 { // 如果未来没有日期的话,那么就退出当前循环,进入下一个循环
 			continue
 		}
@@ -811,6 +820,9 @@ func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.P
 		{
 			lenPredictEdbInfoData := len(predictEdbInfoData)
 			if lenPredictEdbInfoData > 0 {
+				if endDateType == 1 {
+					hasEndNum = predictEdbConf.EndNum
+				}
 				tmpDataEndTime, _ := time.ParseInLocation(utils.FormatDate, predictEdbInfoData[lenPredictEdbInfoData-1].DataTime, time.Local)
 				if startDate.Before(tmpDataEndTime) {
 					startDate = tmpDataEndTime
@@ -830,70 +842,141 @@ func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.P
 }
 
 // GetPredictEdbDayList 获取预测指标日期列表
-func getPredictEdbDayList(startDate, endDate time.Time, frequency, dataDateType string) (dayList []time.Time) {
-	//if !utils.InArrayByStr([]string{"日度", "周度", "月度"}, frequency)
+func getPredictEdbDayList(startDate, endDate time.Time, frequency, dataDateType string, endDateType, endNum int) (dayList []time.Time) {
 	if dataDateType == `` {
 		dataDateType = `交易日`
 	}
-	switch frequency {
-	case "日度":
-		for currDate := startDate.AddDate(0, 0, 1); currDate.Before(endDate) || currDate.Equal(endDate); currDate = currDate.AddDate(0, 0, 1) {
-			// 如果日期类型是交易日的时候,那么需要将周六、日排除
-			if dataDateType == `交易日` && (currDate.Weekday() == time.Sunday || currDate.Weekday() == time.Saturday) {
-				continue
+
+	if endDateType == 0 { // 截止日期
+		switch frequency {
+		case "日度":
+			for currDate := startDate.AddDate(0, 0, 1); currDate.Before(endDate) || currDate.Equal(endDate); currDate = currDate.AddDate(0, 0, 1) {
+				// 如果日期类型是交易日的时候,那么需要将周六、日排除
+				if dataDateType == `交易日` && (currDate.Weekday() == time.Sunday || currDate.Weekday() == time.Saturday) {
+					continue
+				}
+				dayList = append(dayList, currDate)
 			}
-			dayList = append(dayList, currDate)
-		}
-	case "周度":
-		//nextDate := startDate.AddDate(0, 0, 7)
-		for currDate := startDate.AddDate(0, 0, 7); currDate.Before(endDate) || currDate.Equal(endDate); currDate = currDate.AddDate(0, 0, 7) {
-			dayList = append(dayList, currDate)
-		}
-	case "旬度":
-		for currDate := startDate.AddDate(0, 0, 1); currDate.Before(endDate) || currDate.Equal(endDate); {
-			nextDate := currDate.AddDate(0, 0, 1)
-			//每个月的10号、20号、最后一天,那么就写入
-			if nextDate.Day() == 11 || nextDate.Day() == 21 || nextDate.Day() == 1 {
+		case "周度":
+			//nextDate := startDate.AddDate(0, 0, 7)
+			for currDate := startDate.AddDate(0, 0, 7); currDate.Before(endDate) || currDate.Equal(endDate); currDate = currDate.AddDate(0, 0, 7) {
 				dayList = append(dayList, currDate)
 			}
-			currDate = nextDate
+		case "旬度":
+			for currDate := startDate.AddDate(0, 0, 1); currDate.Before(endDate) || currDate.Equal(endDate); {
+				nextDate := currDate.AddDate(0, 0, 1)
+				//每个月的10号、20号、最后一天,那么就写入
+				if nextDate.Day() == 11 || nextDate.Day() == 21 || nextDate.Day() == 1 {
+					dayList = append(dayList, currDate)
+				}
+				currDate = nextDate
+			}
+		case "月度":
+			for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
+				currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+				if !currDate.After(endDate) && !currDate.Equal(startDate) {
+					dayList = append(dayList, currDate)
+				}
+				currDate = currDate.AddDate(0, 0, 1)
+			}
+		case "季度":
+			for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
+				// 每月的最后一天
+				currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+				if !currDate.After(endDate) && !currDate.Equal(startDate) {
+					// 季度日期就写入,否则不写入
+					if currDate.Month() == 3 || currDate.Month() == 6 || currDate.Month() == 9 || currDate.Month() == 12 {
+						dayList = append(dayList, currDate)
+					}
+				}
+				currDate = currDate.AddDate(0, 0, 1)
+			}
+		case "半年度":
+			for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
+				// 每月的最后一天
+				currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+				if !currDate.After(endDate) && !currDate.Equal(startDate) {
+					// 半年度日期就写入,否则不写入
+					if currDate.Month() == 6 || currDate.Month() == 12 {
+						dayList = append(dayList, currDate)
+					}
+				}
+				currDate = currDate.AddDate(0, 0, 1)
+			}
+		case "年度":
+			for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
+				currDate = time.Date(currDate.Year()+1, 12, 31, 0, 0, 0, 0, time.Now().Location())
+				if !currDate.After(endDate) && !currDate.Equal(startDate) {
+					dayList = append(dayList, currDate)
+				}
+			}
 		}
-	case "月度":
-		for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
-			currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
-			if !currDate.After(endDate) && !currDate.Equal(startDate) {
+	} else { // 截止期数
+		switch frequency {
+		case "日度":
+			for i := 1; i <= endNum; i++ {
+				currDate := startDate.AddDate(0, 0, i)
+				// 如果日期类型是交易日的时候,那么需要将周六、日排除
+				if dataDateType == `交易日` && (currDate.Weekday() == time.Sunday || currDate.Weekday() == time.Saturday) {
+					continue
+				}
 				dayList = append(dayList, currDate)
 			}
-			currDate = currDate.AddDate(0, 0, 1)
-		}
-	case "季度":
-		for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
-			// 每月的最后一天
-			currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
-			if !currDate.After(endDate) && !currDate.Equal(startDate) {
-				// 季度日期就写入,否则不写入
-				if currDate.Month() == 3 || currDate.Month() == 6 || currDate.Month() == 9 || currDate.Month() == 12 {
+		case "周度":
+			for i := 1; i <= endNum; i++ {
+				currDate := startDate.AddDate(0, 0, i*7)
+				dayList = append(dayList, currDate)
+			}
+		case "旬度":
+			currDate := startDate
+			for i := 1; len(dayList) < endNum; i++ {
+				currDate = currDate.AddDate(0, 0, 1)
+				nextDate := currDate.AddDate(0, 0, 1)
+				//每个月的10号、20号、最后一天,那么就写入
+				if nextDate.Day() == 11 || nextDate.Day() == 21 || nextDate.Day() == 1 {
 					dayList = append(dayList, currDate)
 				}
 			}
-			currDate = currDate.AddDate(0, 0, 1)
-		}
-	case "半年度":
-		for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
-			// 每月的最后一天
-			currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
-			if !currDate.After(endDate) && !currDate.Equal(startDate) {
-				// 半年度日期就写入,否则不写入
-				if currDate.Month() == 6 || currDate.Month() == 12 {
+		case "月度":
+			currDate := startDate
+			for i := 0; i <= endNum; i++ {
+				currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+				if !currDate.Equal(startDate) {
 					dayList = append(dayList, currDate)
 				}
+				currDate = currDate.AddDate(0, 0, 1)
 			}
-			currDate = currDate.AddDate(0, 0, 1)
-		}
-	case "年度":
-		for currDate := startDate; currDate.Before(endDate) || currDate.Equal(endDate); {
-			currDate = time.Date(currDate.Year()+1, 12, 31, 0, 0, 0, 0, time.Now().Location())
-			if !currDate.After(endDate) && !currDate.Equal(startDate) {
+
+		case "季度":
+			currDate := startDate
+			endNum = endNum * 3
+			for i := 0; i <= endNum; i++ {
+				currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+				if currDate.After(startDate) {
+					// 季度日期就写入,否则不写入
+					if currDate.Month() == 3 || currDate.Month() == 6 || currDate.Month() == 9 || currDate.Month() == 12 {
+						dayList = append(dayList, currDate)
+					}
+				}
+				currDate = currDate.AddDate(0, 0, 1)
+			}
+		case "半年度":
+			currDate := startDate
+			endNum = endNum * 6
+			for i := 0; i <= endNum; i++ {
+				currDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+				if currDate.After(startDate) {
+					// 季度日期就写入,否则不写入
+					if currDate.Month() == 6 || currDate.Month() == 12 {
+						dayList = append(dayList, currDate)
+					}
+				}
+				currDate = currDate.AddDate(0, 0, 1)
+			}
+		case "年度":
+			for i := 1; i <= endNum; i++ {
+				currDate := startDate.AddDate(i, 0, 0)
+				currDate, _ = time.ParseInLocation(utils.FormatDate, fmt.Sprintf(`%d-12-31`, currDate.Year()), time.Local)
 				dayList = append(dayList, currDate)
 			}
 		}

+ 7 - 2
services/data/predict_edb_info_rule.go

@@ -1221,6 +1221,7 @@ type RuleLineNhConf struct {
 	EndDate   string `description:"结束日期"`
 	MoveDay   int    `description:"移动天数"`
 	EdbInfoId int    `description:"指标id"`
+	DateType  int    `description:"时间类型:0:开始日期至截止日期,1开始日期-至今"`
 }
 
 //	GetChartPredictEdbInfoDataListByRuleLineNh 根据 一元线性拟合 的计算规则获取预测数据
@@ -1277,8 +1278,12 @@ func getCalculateNhccData(secondDataList []*data_manage.EdbDataList, ruleConf Ru
 	firstEdbInfoId := ruleConf.EdbInfoId
 	moveDay := ruleConf.MoveDay
 	startDate, _ := time.ParseInLocation(utils.FormatDate, ruleConf.StartDate, time.Local)
-	endDate, _ := time.ParseInLocation(utils.FormatDate, ruleConf.EndDate, time.Local)
-
+	var endDate time.Time
+	if ruleConf.DateType == 0 {
+		endDate, _ = time.ParseInLocation(utils.FormatDate, ruleConf.EndDate, time.Local)
+	} else {
+		endDate, _ = time.ParseInLocation(utils.FormatDate, time.Now().Format(utils.FormatDate), time.Local)
+	}
 	//查询当前指标现有的数据
 	edbInfo, err := data_manage.GetEdbInfoById(firstEdbInfoId)
 	if err != nil {

+ 4 - 4
services/data/stl/stl.go

@@ -169,7 +169,7 @@ func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.
 		msg = "解析Excel失败"
 		return
 	}
-	// defer os.Remove(saveFilePath)
+	defer os.Remove(saveFilePath)
 	resp = new(response.StlPreviewResp)
 	resp.OriginEdbInfo.EdbInfoId = edbInfo.EdbInfoId
 	resp.OriginEdbInfo.Title = edbInfo.EdbName
@@ -423,15 +423,15 @@ type STLResult struct {
 
 func execStlPythonCode(path, toPath string, period, seasonal, trend, trendDeg, seasonalDeg, lowPassDeg int, fraction float64, robust bool) (stlResult *STLResult, err error) {
 	pythonCode := `
+import json
+import warnings
+warnings.filterwarnings('ignore')
 import pandas as pd
 from statsmodels.tsa.seasonal import STL
 from statsmodels.nonparametric.smoothers_lowess import lowess
 from statsmodels.tsa.stattools import adfuller
 from statsmodels.stats.diagnostic import acorr_ljungbox
 import numpy as np
-import json
-import warnings
-warnings.filterwarnings('ignore')
 
 file_path = r"%s"
 df = pd.read_excel(file_path, parse_dates=['日期'], engine='openpyxl')

+ 3 - 3
services/data/trade_analysis/trade_analysis_data.go

@@ -67,7 +67,7 @@ func FormatCompanyTradeData2EdbMappings(companyTradeData []*tradeAnalysisModel.C
 				endTime, _ = time.ParseInLocation(utils.FormatDate, ed, time.Local)
 			}
 			if endTime.IsZero() {
-				endTime = v.EndDate
+				endTime = time.Now().Local()
 			}
 		}
 
@@ -217,9 +217,9 @@ func GetOriginTradeData(exchange, classifyName string, contracts, companies []st
 	keyDateData := make(map[string]*tradeAnalysisModel.ContractCompanyTradeDataList)
 	keyDateDataExist := make(map[string]bool)
 	for _, v := range originList {
-		// Rank999-对应的是TOP20
+		// Rank999和0对应的是TOP20
 		companyName := v.CompanyName
-		if v.Rank == 999 {
+		if v.Rank == 999 || v.Rank == 0 {
 			companyName = tradeAnalysisModel.TradeFuturesCompanyTop20
 		}
 

+ 9 - 5
services/document_manage_service/document_manage_service.go

@@ -135,9 +135,7 @@ func DocumentReportList(documentType int, chartPermissionIdList []string, classi
 				return nil, err
 			}
 
-			if id == 0 {
-				classifyIdList = append(classifyIdList, classifyId)
-			} else {
+			if id > 0 {
 				childrenClassifyIdList, err := GetAllClassifyIdsByParentId(id)
 				if err != nil {
 					return nil, err
@@ -160,8 +158,11 @@ func DocumentReportList(documentType int, chartPermissionIdList []string, classi
 		condition += ` and (t1.title like ? or t1.sys_user_name like ?) `
 		pars = append(pars, "%"+keyword+"%", "%"+keyword+"%")
 	}
+	if len(chartPermissionIdList) > 0 {
+		condition += ` GROUP BY t1.outside_report_id HAVING COUNT(DISTINCT t2.chart_permission_id) = ` + strconv.Itoa(len(chartPermissionIdList))
+	}
 
-	count, err := document_manage_model.GetOutsideReportListByConditionCount(condition, pars)
+	count, err := document_manage_model.GetOutsideReportListByConditionCount(condition, pars, chartPermissionIdList)
 	if err != nil {
 		return nil, err
 	}
@@ -179,7 +180,10 @@ func DocumentReportList(documentType int, chartPermissionIdList []string, classi
 		condition += ` order by t1.report_update_time desc`
 	}
 
-	outsideReportList, err := document_manage_model.GetOutsideReportListByCondition(condition, pars, startSize, pageSize)
+	condition += ` LIMIT ?,?`
+	pars = append(pars, (startSize-1)*pageSize, pageSize)
+
+	outsideReportList, err := document_manage_model.GetOutsideReportListByCondition(condition, pars)
 	if err != nil {
 		return nil, err
 	}

+ 2 - 2
services/edb_info_replace.go

@@ -288,7 +288,7 @@ func ReplaceEdbInRelation(oldEdbInfo, newEdbInfo *data_manage.EdbInfo) {
 	if newEdbInfo.EdbType == 2 {
 		edbInfoList := make([]*data_manage.EdbInfo, 0)
 		edbInfoList = append(edbInfoList, newEdbInfo)
-		calculateEdbMappingListMap, calculateEdbMappingIdsMap, err = data.GetEdbListByEdbInfoId(edbInfoList)
+		calculateEdbMappingListMap, calculateEdbMappingIdsMap, err = data.GetEdbListByEdbInfoId(edbInfoList, false)
 		if err != nil {
 			err = fmt.Errorf("查询指标关联指标列表失败 Err:%s", err)
 			return
@@ -379,7 +379,7 @@ func ReplaceEdbInRelation(oldEdbInfo, newEdbInfo *data_manage.EdbInfo) {
 						return
 					}
 				}
-				calculateEdbMappingListMap, calculateEdbMappingIdsMap, err = data.GetEdbListByEdbInfoId(edbInfoList)
+				calculateEdbMappingListMap, calculateEdbMappingIdsMap, err = data.GetEdbListByEdbInfoId(edbInfoList, false)
 				if err != nil {
 					err = fmt.Errorf("查询指标关联指标列表失败 Err:%s", err)
 					return

+ 23 - 3
services/excel/lucky_sheet.go

@@ -3,6 +3,7 @@ package excel
 import (
 	"encoding/json"
 	"errors"
+	"eta/eta_api/models/data_manage/excel"
 	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/utils"
 	"fmt"
@@ -1719,15 +1720,26 @@ func GetTableDataByCustomData(excelType int, data request.TableDataReq, lang str
 }
 
 // GetTableDataByMixedTableData 通过混合表格数据获取表格数据
-func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hideMerged bool) (selfTableData TableData, err error) {
+func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hideMerged bool, excelInfoId int) (selfTableData TableData, err error) {
 	tableDataList := make([][]LuckySheetDataValue, 0)
 	mergeList := make([]TableDataMerge, 0)
 
+	// 获取管理规则
+	excelRuleMappingList, err := excel.GetExcelRuleMappingByExcelInfoId(excelInfoId)
+	if err != nil {
+		return
+	}
+	excelRuleMap := make(map[int]*excel.ExcelInfoRuleMappingView)
+	for _, v := range excelRuleMappingList {
+		excelRuleMap[v.ExcelInfoRuleMappingId] = v
+	}
+	ruleScopeMap := generateRuleScopeIndexMap(excelRuleMappingList)
+
 	// 开始文本行了
 	{
-		for _, row := range config {
+		for i, row := range config {
 			dataCol := make([]LuckySheetDataValue, 0)
-			for _, cell := range row {
+			for j, cell := range row {
 				tmp := LuckySheetDataValue{
 					Value:     cell.Value,
 					Monitor:   cell.ShowValue,
@@ -1846,6 +1858,14 @@ func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hide
 					// }
 
 				}
+				if ruleIds, ok := ruleScopeMap[i][j]; ok {
+					for _, ruleId := range ruleIds {
+						if checkCellRule(excelRuleMap[ruleId], cell.ShowValue, config) {
+							tmp.Background = strings.TrimSpace(excelRuleMap[ruleId].BackgroundColor)
+							tmp.FontColor = strings.TrimSpace(excelRuleMap[ruleId].FontColor)
+						}
+					}
+				}
 				dataCol = append(dataCol, tmp)
 			}
 			tableDataList = append(tableDataList, dataCol)

+ 46 - 43
services/excel/lucky_sheet_table.go

@@ -2,6 +2,7 @@ package excel
 
 import (
 	"eta/eta_api/models/data_manage/excel"
+	"eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/utils"
 	"fmt"
 	"sort"
@@ -106,42 +107,42 @@ func handleCellVal(tmpTableColData LuckySheetDataValue) (valueStr string) {
 }
 
 // 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 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) {
+func getCellValueByType(value string, valueType int, tableDataList [][]request.MixedTableCellDataReq) (float64, bool) {
 	if valueType == 2 {
 		coords := strings.Split(value, ",")
 		var coordIntArr []int
@@ -151,7 +152,7 @@ func getCellValueByType(value string, valueType int, tableDataList [][]LuckyShee
 		}
 		if len(coordIntArr) == 2 {
 			x, y := coordIntArr[0]-1, coordIntArr[1]-1
-			conditionValue, err := strconv.ParseFloat(tableDataList[y][x].Monitor, 64)
+			conditionValue, err := strconv.ParseFloat(tableDataList[y][x].ShowValue, 64)
 			if err != nil {
 				return 0, false
 			}
@@ -167,7 +168,7 @@ func getCellValueByType(value string, valueType int, tableDataList [][]LuckyShee
 	return 0, false
 }
 
-func checkCellRule(ruleInfo *excel.ExcelInfoRuleMappingView, value string, tableDataList [][]LuckySheetDataValue) bool {
+func checkCellRule(ruleInfo *excel.ExcelInfoRuleMappingView, value string, tableDataList [][]request.MixedTableCellDataReq) bool {
 	var tableValue float64
 	var tableTime time.Time
 	var err error
@@ -292,8 +293,8 @@ func checkCellRule(ruleInfo *excel.ExcelInfoRuleMappingView, value string, table
 	return false
 }
 
-func generateRuleScopeIndexMap(items []*excel.ExcelInfoRuleMappingView) (ruleScopeMap map[int]map[int]int) {
-	ruleScopeMap = make(map[int]map[int]int)
+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
@@ -307,18 +308,20 @@ func generateRuleScopeIndexMap(items []*excel.ExcelInfoRuleMappingView) (ruleSco
 			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] = make(map[int][]int)
 					}
-					ruleScopeMap[i][j] = item.ExcelInfoRuleMappingId
+					ruleScopeMap[i][j] = append(ruleScopeMap[i][j], item.ExcelInfoRuleMappingId)
+					// 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] = make(map[int][]int)
 			}
-			ruleScopeMap[y][x] = item.ExcelInfoRuleMappingId
+			ruleScopeMap[y][x] = append(ruleScopeMap[y][x], item.ExcelInfoRuleMappingId)
+			// ruleScopeMap[y][x] = item.ExcelInfoRuleMappingId
 		}
 	}
 	return

+ 3 - 3
services/minio.go

@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"github.com/minio/minio-go/v7"
 	"github.com/minio/minio-go/v7/pkg/credentials"
-	"log"
 	"time"
 )
 
@@ -425,13 +424,14 @@ func (m *MinioOss) UploadFile(fileName, filePath, savePath string) (string, erro
 		Secure: useSSL,
 	})
 	if err != nil {
-		log.Fatalln(err)
+		utils.FileLog.Error("MinIo连接失败:", err)
 		return "1", err
 	}
 	bucketName := utils.MinIoBucketname
 	exists, errBucketExists := minioClient.BucketExists(ctx, bucketName)
 	if errBucketExists != nil || !exists {
 		err = fmt.Errorf("BucketExists: %v; err: %v", exists, errBucketExists)
+		utils.FileLog.Error("BucketExists: %v; err: %v", exists, errBucketExists)
 		return "2", err
 	}
 
@@ -441,7 +441,7 @@ func (m *MinioOss) UploadFile(fileName, filePath, savePath string) (string, erro
 	}
 	_, err = minioClient.FPutObject(ctx, bucketName, path, filePath, minio.PutObjectOptions{})
 	if err != nil {
-		log.Fatalln(err)
+		utils.FileLog.Error("MinIo上传文件失败:", err)
 		return "3", err
 	}
 	resourceUrl := utils.MinIoImghost + path

+ 6 - 1
services/report.go

@@ -731,7 +731,12 @@ func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId in
 	item.IsPublicPublish = req.IsPublicPublish
 	item.ReportCreateTime = time.Now()
 
-	err, errMsg = AddReportAndChapter(item, 0, req.GrantAdminIdList)
+	reportDate := time.Now()
+	t, _ := time.ParseInLocation(utils.FormatDate, req.CreateTime, time.Local)
+	if !t.IsZero() {
+		reportDate = t
+	}
+	err, errMsg = AddReportAndChapter(item, 0, req.GrantAdminIdList, reportDate)
 
 	return
 }

+ 137 - 29
services/report_v2.go

@@ -8,6 +8,8 @@ import (
 	"eta/eta_api/models/report"
 	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/system"
+	"eta/eta_api/models/yb"
+	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/file"
@@ -27,7 +29,7 @@ import (
 // @param inheritReportId int
 // @return err error
 // @return errMsg string
-func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAdminIdList []int) (err error, errMsg string) {
+func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAdminIdList []int, reportDate time.Time) (err error, errMsg string) {
 	// 根据审批开关及审批流判断当前报告状态
 	state, e := CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperateAdd)
 	if e != nil {
@@ -94,7 +96,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 	}
 
 	// 获取待生成的报告章节
-	addChapterList, allGrantUserList, err, errMsg := getAddChapter(reportInfo, minClassifyId, inheritReportId, grantAdminIdList)
+	addChapterList, allGrantUserList, err, errMsg := getAddChapter(reportInfo, minClassifyId, inheritReportId, grantAdminIdList, reportDate)
 
 	// 新增报告及章节
 	var reportId int64
@@ -276,7 +278,7 @@ func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.A
 // @return chapterList []*models.ReportChapter
 // @return err error
 // @return errMsg string
-func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int, grantAdminIdList []int) (chapterList []models.AddReportChapter, allGrantUserList []*report.ReportGrant, err error, errMsg string) {
+func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int, grantAdminIdList []int, reportDate time.Time) (chapterList []models.AddReportChapter, allGrantUserList []*report.ReportGrant, err error, errMsg string) {
 	// 待生成的报告章节内容
 	chapterList = make([]models.AddReportChapter, 0)
 
@@ -320,7 +322,7 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 	// 待添加的章节类型id列表
 	chapterTypeIdList := make([]int, 0)
 
-	nowTime := time.Now().Local()
+	//nowTime := time.Now().Local()
 	for _, chapterType := range allTypeList {
 		// 如果被永久暂停更新了
 		if chapterType.Enabled == 0 { //该章节已被永久禁用,那么就不允许继承或者新增该章节
@@ -338,8 +340,8 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 				utils.FileLog.Error("更新规则时间转换失败4002, Err: " + timeErr.Error())
 				continue
 			}
-			// 暂停更新
-			if nowTime.After(startTime) && nowTime.Before(endTime.AddDate(0, 0, 1)) {
+			// 暂停更新(此处用报告日期去判断是否需要停更,而不是报告创建日期)
+			if !reportDate.Before(startTime) && !reportDate.After(endTime) {
 				continue
 			}
 		}
@@ -548,32 +550,104 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 				tmpChapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
 			}
 		} else {
-			chapterItem.AddType = 1
-			chapterItem.Title = typeItem.ReportChapterTypeName
-			chapterItem.ReportType = typeItem.ResearchType
-			chapterItem.ClassifyIdFirst = minClassifyId
-			chapterItem.ClassifyNameFirst = minClassifyName
-			chapterItem.TypeId = typeItem.ReportChapterTypeId
-			chapterItem.TypeName = typeItem.ReportChapterTypeName
-			chapterItem.Stage = reportInfo.Stage
-			chapterItem.PublishState = 1
-			chapterItem.Sort = typeItem.Sort
-			chapterItem.CreateTime = reportInfo.CreateTime
-			chapterItem.ModifyTime = time.Now()
-
-			chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
-			chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
-			chapterItem.ContentModifyTime = time.Now()
-			//chapterItem.ContentStruct = v.ContentStruct
-			chapterItem.ReportLayout = reportInfo.ReportLayout
-			chapterItem.ReportCreateTime = time.Now()
+			// 如果系统章节未从继承的报告中找到,那么获取往期中最新版本的此章节内容
+			var findChapter bool
+			if inheritReportId > 0 {
+				chapterNewest, e := models.GetNewestPreReportChapterByClassifyIdAndTypeId(minClassifyId, typeItem.ReportChapterTypeId)
+				if e != nil && e.Error() == utils.ErrNoRow() {
+					errMsg = "继承最新内容的章节失败"
+					err = fmt.Errorf("获取最新内容的章节失败, %v", e)
+					return
+				}
+				if chapterNewest != nil {
+					chapterItem.AddType = 2
+					chapterItem.Title = chapterNewest.Title
+					chapterItem.ReportType = chapterNewest.ReportType
+					chapterItem.ClassifyIdFirst = minClassifyId
+					chapterItem.ClassifyNameFirst = minClassifyName
+					chapterItem.TypeId = typeItem.ReportChapterTypeId
+					chapterItem.TypeName = typeItem.ReportChapterTypeName
+					chapterItem.Content = chapterNewest.Content
+					chapterItem.ContentSub = chapterNewest.ContentSub
+					chapterItem.Stage = reportInfo.Stage
+					chapterItem.PublishState = 1
+					chapterItem.Sort = typeItem.Sort
+					chapterItem.CreateTime = reportInfo.CreateTime
+					chapterItem.ModifyTime = time.Now()
+					chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
+					chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
+					chapterItem.ContentModifyTime = time.Now()
+					chapterItem.ContentStruct = chapterNewest.ContentStruct
+					chapterItem.ReportLayout = chapterNewest.ReportLayout
+					chapterItem.ReportCreateTime = time.Now()
+
+					// 这里的授权和品种权限需要额外查询
+					tmpGrantList = make([]*report.ReportChapterGrant, 0)
+					tmpChapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+					{
+						grantOb := report.ReportChapterGrant{}
+						chapterGrant, e := grantOb.GetGrantListById(chapterNewest.ReportChapterId)
+						if e != nil {
+							errMsg = "获取待继承的报告章节的授权用户列表失败"
+							err = fmt.Errorf("获取待继承的报告章节的授权用户列表失败, ChapterId: %d, Err: %v", chapterNewest.ReportChapterId, e)
+							return
+						}
+						for _, cg := range chapterGrant {
+							// 如果不在报告授权的用户ID里面,那么该章节就不继承该授权用户
+							if _, ok := needAdminIdMap[cg.AdminId]; !ok {
+								continue
+							}
+							cg.ReportChapterId = 0
+							cg.GrantId = 0
+							tmpGrantList = append(tmpGrantList, cg)
+						}
+
+						permissionOb := report.ReportChapterPermissionMapping{}
+						permissions, e := permissionOb.GetPermissionListById(chapterNewest.ReportChapterId)
+						if e != nil {
+							errMsg = "获取待继承的报告章节的品种权限失败"
+							err = fmt.Errorf("获取待继承的报告章节的品种权限失败, ChapterId: %d, Err: %v", chapterNewest.ReportChapterId, e)
+							return
+						}
+						for _, ps := range permissions {
+							ps.ReportChapterId = 0
+							ps.ReportChapterPermissionMappingId = 0
+							tmpChapterPermissionList = append(tmpChapterPermissionList, ps)
+						}
+					}
 
-			// 默认配置:从当前分类下配置的章节类型id所关联的品种列表
-			tmpChapterPermissionList, ok = currChapterTypePermissionListMap[typeItem.ReportChapterTypeId]
-			if !ok {
-				tmpChapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+					findChapter = true
+				}
 			}
 
+			// 没找到那就新增一篇空白的
+			if !findChapter {
+				chapterItem.AddType = 1
+				chapterItem.Title = typeItem.ReportChapterTypeName
+				chapterItem.ReportType = typeItem.ResearchType
+				chapterItem.ClassifyIdFirst = minClassifyId
+				chapterItem.ClassifyNameFirst = minClassifyName
+				chapterItem.TypeId = typeItem.ReportChapterTypeId
+				chapterItem.TypeName = typeItem.ReportChapterTypeName
+				chapterItem.Stage = reportInfo.Stage
+				chapterItem.PublishState = 1
+				chapterItem.Sort = typeItem.Sort
+				chapterItem.CreateTime = reportInfo.CreateTime
+				chapterItem.ModifyTime = time.Now()
+
+				chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
+				chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
+				chapterItem.ContentModifyTime = time.Now()
+				//chapterItem.ContentStruct = v.ContentStruct
+				chapterItem.ReportLayout = reportInfo.ReportLayout
+				chapterItem.ReportCreateTime = time.Now()
+
+				// 默认配置:从当前分类下配置的章节类型id所关联的品种列表
+				tmpChapterPermissionList, ok = currChapterTypePermissionListMap[typeItem.ReportChapterTypeId]
+				if !ok {
+					tmpChapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+				}
+			}
 		}
 
 		if typeItem.Sort > maxSort {
@@ -1496,3 +1570,37 @@ func GetReportWaterMarkPdf(reportInfo *models.Report, sysUser *system.Admin) {
 	waterMarkStr := fmt.Sprintf("%s - %s", sysUser.RealName, sysUser.Mobile)
 	GeneralWaterMarkPdf(filePath, waterMarkStr)
 }
+
+// ResetMiniProgramReportDetailCover 重置小程序报告封面
+func ResetMiniProgramReportDetailCover(reportId int) (err error) {
+	if utils.MYSQL_WEEKLY_URL == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ResetMiniProgramReportDetailCover-重置小程序报告封面失败, %v", err)
+			utils.FileLog.Info(tips)
+			alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+
+	ob := new(yb.YbPosterResource)
+	cond := fmt.Sprintf(` AND path LIKE 'reportDetailCover?ReportId=%d&%%'`, reportId)
+	pars := make([]interface{}, 0)
+	list, e := ob.GetItemsByCondition(cond, pars, []string{"id"}, "")
+	if e != nil {
+		err = fmt.Errorf("获取报告海报失败, %v", e)
+		return
+	}
+	if len(list) == 0 {
+		return
+	}
+	var removeIds []int
+	for _, v := range list {
+		removeIds = append(removeIds, v.Id)
+	}
+	if e = ob.RemovePosters(removeIds); e != nil {
+		err = fmt.Errorf("清除报告海报失败, %v", e)
+	}
+	return
+}

+ 1048 - 0
services/residual_analysis_service/residual_analysis_service.go

@@ -0,0 +1,1048 @@
+package residual_analysis_service
+
+import (
+	"encoding/json"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/residual_analysis_model"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"math"
+	"sort"
+	"strconv"
+	"time"
+)
+
+func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (residual_analysis_model.ResidualAnalysisResp, error) {
+
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList([]int{req.EdbInfoIdA, req.EdbInfoIdB})
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("获取图表,指标信息失败,Err:%s", err.Error())
+	}
+
+	var edbInfoMappingA, edbInfoMappingB *data_manage.ChartEdbInfoMapping
+	for _, v := range mappingList {
+		if v.Unit == "无" {
+			v.Unit = ""
+		}
+		if v.EdbInfoId == req.EdbInfoIdA {
+			edbInfoMappingA = v
+		}
+		if v.EdbInfoId == req.EdbInfoIdB {
+			edbInfoMappingB = v
+		}
+	}
+	if edbInfoMappingA == nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标A不存在")
+	}
+	if edbInfoMappingB == nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标B不存在")
+	}
+
+	// 时间处理
+	var startDate, endDate string
+	switch req.DateType {
+	case 0:
+		startDate = req.StartDate
+		endDate = req.EndDate
+	case 1:
+		startDate = req.StartDate
+		endDate = ""
+	default:
+		startDate = utils.GetPreYearTime(req.DateType)
+		endDate = ""
+	}
+
+	resp := residual_analysis_model.ResidualAnalysisResp{}
+
+	// 原始图表信息
+	originalEdbList := make([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, 0)
+
+	originalEdbList, fullADataList, fullBDataList, err := fillOriginalChart(req, mappingList, startDate, endDate, edbInfoMappingA, edbInfoMappingB, originalEdbList)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, err
+	}
+
+	originalChartInfo := createChartInfoResp(req, startDate, endDate, edbInfoMappingA.EdbName+"与"+edbInfoMappingB.EdbName)
+
+	resp.OriginalChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   originalChartInfo,
+		EdbInfoList: originalEdbList,
+	}
+	// 如果只需要第一张图表的数据 直接返回,避免继续处理
+	if req.QueryType == 1 {
+		return resp, nil
+	}
+
+	dataAList, ok := edbInfoMappingA.DataList.([]*data_manage.EdbDataList)
+	if !ok {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("数据类型转换失败")
+	}
+	indexADataMap := map[string]*data_manage.EdbDataList{}
+	for _, indexData := range dataAList {
+		indexADataMap[indexData.DataTime] = indexData
+	}
+
+	// 映射图表信息
+	mappingEdbList, a, b, r, err := fillMappingChartInfo(req, edbInfoMappingA, edbInfoMappingB, originalEdbList, indexADataMap, startDate, endDate, fullADataList, fullBDataList)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, err
+	}
+
+	mappingChartInfo := createChartInfoResp(req, startDate, endDate, edbInfoMappingA.EdbName+"与"+edbInfoMappingB.EdbName+"映射"+edbInfoMappingA.EdbName)
+
+	resp.MappingChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   mappingChartInfo,
+		EdbInfoList: mappingEdbList,
+	}
+
+	// 残差图表信息
+	residualEdbList, R2, err := fillResidualChartInfo(req, edbInfoMappingA, edbInfoMappingB, mappingEdbList)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, err
+	}
+
+	residualChartInfo := createChartInfoResp(req, startDate, endDate, edbInfoMappingA.EdbName+"与"+edbInfoMappingA.EdbName+"映射残差/"+edbInfoMappingB.EdbName)
+
+	resp.ResidualChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   residualChartInfo,
+		EdbInfoList: residualEdbList,
+	}
+
+	if req.ResidualType == 2 {
+		resp.A = math.Round(a*10000) / 10000
+		resp.B = math.Round(b*10000) / 10000
+		resp.R = math.Round(r*10000) / 10000
+		resp.R2 = math.Round(R2*10000) / 10000
+	}
+
+	return resp, nil
+}
+
+func createChartInfoResp(req residual_analysis_model.ResidualAnalysisReq, startDate, endDate, chartName string) residual_analysis_model.ResidualAnalysisChartInfo {
+	return residual_analysis_model.ResidualAnalysisChartInfo{
+		Calendar:  `公历`,
+		Source:    utils.CHART_SOURCE_DEFAULT,
+		DateType:  req.DateType,
+		StartDate: startDate,
+		EndDate:   endDate,
+		ChartType: utils.CHART_TYPE_CURVE,
+		ChartName: chartName,
+	}
+}
+
+func fillResidualChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, float64, error) {
+	// 计算公式 映射残差 = 因变量指标 - 映射指标
+	var edbInfoA, edbInfoB residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+	if mappingEdbList[0].EdbInfoId == edbInfoMappingA.EdbInfoId {
+		edbInfoA = mappingEdbList[0]
+		edbInfoB = mappingEdbList[1]
+	} else {
+		edbInfoA = mappingEdbList[1]
+		edbInfoB = mappingEdbList[0]
+	}
+	dataAList, ok := edbInfoA.DataList.([]*data_manage.EdbDataList)
+	if !ok {
+		return nil, 0, fmt.Errorf("数据类型转换失败")
+	}
+	edbData := make([]*data_manage.EdbDataList, len(dataAList))
+	for i, data := range dataAList {
+		edbData[i] = &data_manage.EdbDataList{
+			Value:         data.Value, // 确保为每个元素创建一个新的对象
+			DataTimestamp: data.DataTimestamp,
+			DataTime:      data.DataTime,
+			EdbInfoId:     data.EdbInfoId,
+			EdbDataId:     data.EdbDataId,
+		}
+	}
+
+	dataBList, ok := edbInfoB.DataList.([]*data_manage.EdbDataList)
+	if !ok {
+		return nil, 0, fmt.Errorf("数据类型转换失败")
+	}
+
+	// 映射指标开始时间
+	var startTime string
+	if len(dataBList) > 0 {
+		startTime = dataBList[0].DataTime
+	}
+
+	var indexDataBMap = make(map[string]*data_manage.EdbDataList)
+	for _, data := range dataBList {
+		indexDataBMap[data.DataTime] = data
+	}
+	// 求R2
+	var valueB, sumValueA, averageValueA, residualQuadraticSum, totalQuadraticSum, R2 float64
+
+	for _, indexData := range edbData {
+		// 因变量的值总和
+		sumValueA += indexData.Value
+	}
+	// 因变量平均值
+	averageValueA = sumValueA / float64(len(edbData))
+
+	var indexMax, indexMin float64
+
+	var edbDataResp []*data_manage.EdbDataList
+	if len(edbData) > 0 {
+		indexMax = edbData[0].Value
+		indexMin = edbData[0].Value
+		for _, indexData := range edbData {
+			if dataB, ok := indexDataBMap[indexData.DataTime]; ok {
+				valueB = dataB.Value
+			} else {
+				continue
+			}
+
+			// 总因变量平方和
+			totalQuadraticSum += math.Pow(indexData.Value-averageValueA, 2)
+
+			// 补全残差值
+
+			indexData.Value = math.Round((indexData.Value-valueB)*10000) / 10000
+
+			// 残差平方和
+			residualQuadraticSum += math.Pow(indexData.Value, 2)
+
+			if indexData.Value > indexMax {
+				indexMax = indexData.Value
+			}
+			if indexData.Value < indexMin {
+				indexMin = indexData.Value
+			}
+
+			// 获取映射指标之后的数据
+			if startTime != "" && utils.CompareDate(startTime, indexData.DataTime) {
+				edbDataResp = append(edbDataResp, indexData)
+			}
+		}
+	}
+
+	// 计算R2 公式:R2=1-SSE/SST R2越大,越符合线性  R2 = 1 - 残差平方和/总平方和
+	R2 = 1 - residualQuadraticSum/totalQuadraticSum
+
+	mappingEdb := make([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, len(mappingEdbList))
+	copy(mappingEdb, mappingEdbList)
+
+	for i, mapping := range mappingEdb {
+		if mapping.EdbInfoId != edbInfoMappingA.EdbInfoId {
+			mappingEdb[i].DataList = edbDataResp
+			mappingEdb[i].EdbName = edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName
+			if req.IndexType == 2 {
+				if req.LeadValue > 0 {
+					mappingEdb[i].EdbName = edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName + "(领先" + strconv.Itoa(req.LeadValue) + req.LeadFrequency + ")"
+				}
+			}
+			mappingEdb[i].IsAxis = 1
+			mappingEdb[i].ChartColor = `#00F`
+			mappingEdb[i].IsOrder = false
+			mappingEdb[i].MinValue = indexMin
+			mappingEdb[i].MaxValue = indexMax
+		} else {
+			mappingEdb[i].IsAxis = 0
+			mappingEdb[i].ChartColor = `#F00`
+		}
+	}
+
+	return mappingEdb, R2, nil
+}
+
+func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]*data_manage.EdbDataList, startDate string, endDate string, fullADataList []*data_manage.EdbDataList, fullBDataList []*data_manage.EdbDataList) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, float64, float64, float64, error) {
+	// 计算公式:Y=aX+b,Y为映射后的指标,X为自变量指标
+	// 正序:a=(L2-L1)/(R2-R1)	b=L2-R2*a
+	// 逆序:a=(L2-L1)/(R1-R2)	b=L2-R1*a
+	// L2:左轴下限 R2:右轴上限 L1:左轴上限 R1:右轴下限
+	var a, b, r float64
+
+	// 映射残差 计算a,b
+	if req.ResidualType == 1 {
+		if req.IsOrder {
+			a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMin - req.RightIndexMax)
+			b = req.LeftIndexMax - req.RightIndexMin*a
+		} else {
+			a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
+			b = req.LeftIndexMax - req.RightIndexMax*a
+		}
+	}
+
+	//dataAList := edbInfoMappingA.DataList.([]*data_manage.EdbDataList)
+	dataList, ok := edbInfoMappingB.DataList.([]*data_manage.EdbDataList)
+	if !ok {
+		return nil, a, b, r, fmt.Errorf("数据类型转换失败")
+	}
+
+	// 指标B数据补充
+	// 新建一个切片来保存补充的数据
+	var replenishDataList []*data_manage.EdbDataList
+
+	for index := 0; index < len(dataList)-1; index++ {
+		// 获取当前数据和下一个数据
+		beforeIndexData := dataList[index]
+		afterIndexData := dataList[index+1]
+
+		// 从最早时间开始,补充时间为自然日
+		for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+			// 创建补充数据
+			nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+			toTime := utils.StringToTime(nextDay)
+			replenishIndexData := data_manage.EdbDataList{
+				DataTime:      nextDay, // 计算下一个自然日
+				DataTimestamp: toTime.UnixMilli(),
+				Value:         beforeIndexData.Value, // 可以选择使用前一天的值,或者其他逻辑来计算值
+			}
+
+			// 将补充数据加入补充数据列表
+			replenishDataList = append(replenishDataList, &replenishIndexData)
+
+			// 更新 beforeIndexData 为新创建的补充数据
+			beforeIndexData = &replenishIndexData
+		}
+	}
+
+	// 将补充数据插入原始数据列表
+	dataList = append(dataList, replenishDataList...)
+
+	// 排序
+	sort.Sort(ByDataTime(dataList))
+
+	// 拟合残差 计算a,b
+	var coordinateList []utils.Coordinate
+	var replenishADataList []*data_manage.EdbDataList
+	var replenishBDataList []*data_manage.EdbDataList
+	if req.ResidualType == 2 {
+		//
+
+		// 因变量指标也转换为日度
+		for index := 0; index < len(fullADataList)-1; index++ {
+			// 获取当前数据和下一个数据
+			beforeIndexData := fullADataList[index]
+			afterIndexData := fullADataList[index+1]
+
+			replenishADataList = append(replenishADataList, beforeIndexData)
+			// 从最早时间开始,补充时间为自然日
+			if utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+				for {
+					// 创建补充数据
+					nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+					toTime := utils.StringToTime(nextDay)
+					replenishIndexData := data_manage.EdbDataList{
+						DataTime:      nextDay, // 计算下一个自然日
+						DataTimestamp: toTime.UnixMilli(),
+						Value:         beforeIndexData.Value, // 可以选择使用前一天的值,或者其他逻辑来计算值
+					}
+
+					// 将补充数据加入补充数据列表
+					replenishADataList = append(replenishADataList, &replenishIndexData)
+
+					// 更新 beforeIndexData 为新创建的补充数据
+					beforeIndexData = &replenishIndexData
+
+					// 检查是否还需要继续补充数据
+					if !utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						break
+					}
+				}
+			}
+		}
+		replenishADataList = append(replenishADataList, fullADataList[len(fullADataList)-1])
+
+		// 自变量指标也转换为日度
+		for index := 0; index < len(fullBDataList)-1; index++ {
+			// 获取当前数据和下一个数据
+			beforeIndexData := fullBDataList[index]
+			afterIndexData := fullBDataList[index+1]
+
+			replenishBDataList = append(replenishBDataList, beforeIndexData)
+			// 从最早时间开始,补充时间为自然日
+			if utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+				for {
+					// 创建补充数据
+					nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+					toTime := utils.StringToTime(nextDay)
+					replenishIndexData := data_manage.EdbDataList{
+						DataTime:      nextDay, // 计算下一个自然日
+						DataTimestamp: toTime.UnixMilli(),
+						Value:         beforeIndexData.Value, // 可以选择使用前一天的值,或者其他逻辑来计算值
+					}
+
+					// 将补充数据加入补充数据列表
+					replenishBDataList = append(replenishBDataList, &replenishIndexData)
+
+					// 更新 beforeIndexData 为新创建的补充数据
+					beforeIndexData = &replenishIndexData
+
+					// 检查是否还需要继续补充数据
+					if !utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						break
+					}
+				}
+			}
+		}
+		replenishBDataList = append(replenishBDataList, fullBDataList[len(fullBDataList)-1])
+
+		// replenishADataList --> map
+		replenishADataMap := make(map[string]*data_manage.EdbDataList)
+		for _, indexData := range replenishADataList {
+			if (utils.StringToTime(indexData.DataTime).After(utils.StringToTime(startDate)) || utils.StringToTime(indexData.DataTime).Equal(utils.StringToTime(startDate))) && (endDate == "" || utils.StringToTime(indexData.DataTime).Before(utils.StringToTime(endDate))) {
+				replenishADataMap[indexData.DataTime] = indexData
+			}
+		}
+
+		for _, indexData := range replenishBDataList {
+			if _, ok = replenishADataMap[indexData.DataTime]; ok {
+
+				coordinate := utils.Coordinate{
+					X: indexData.Value,
+					Y: replenishADataMap[indexData.DataTime].Value,
+				}
+				coordinateList = append(coordinateList, coordinate)
+			}
+		}
+		a, b = utils.GetLinearResult(coordinateList)
+		r = utils.ComputeCorrelation(coordinateList)
+	}
+
+	// 填充映射指标值 使得时间长度一致
+	dataList = FillDataBList(dataList, edbInfoMappingA)
+
+	// 根据指标A的时间key,在B的映射指标中筛选出对应的值
+	var dataBList []*data_manage.EdbDataList
+
+	var indexMax, indexMin float64
+
+	if len(dataList) > 0 {
+		indexMax = dataList[0].Value
+		indexMin = dataList[0].Value
+		for _, indexData := range dataList {
+			if _, ok := indexADataMap[indexData.DataTime]; ok {
+				indexDataCopy := *indexData
+
+				// 计算指标B映射值
+				indexDataCopy.Value = math.Round((a*indexData.Value+b)*10000) / 10000
+
+				// 比较最大值
+				if indexData.Value > indexMax {
+					indexMax = indexData.Value
+				}
+
+				// 比较最小值
+				if indexData.Value < indexMin {
+					indexMin = indexData.Value
+				}
+
+				// 将副本添加到 dataBList
+				dataBList = append(dataBList, &indexDataCopy)
+			}
+		}
+	}
+
+	mappingEdbList := make([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, len(originalEdbList))
+	copy(mappingEdbList, originalEdbList)
+
+	for i, mapping := range mappingEdbList {
+		if mapping.EdbInfoId != req.EdbInfoIdA {
+			mappingEdbList[i].EdbInfoId = 0
+			mappingEdbList[i].EdbCode = ""
+			mappingEdbList[i].IsAxis = 1
+			mappingEdbList[i].EdbName = edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
+			if req.IndexType == 2 {
+				if req.LeadValue > 0 {
+					mappingEdbList[i].EdbName = edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName + "(领先" + strconv.Itoa(req.LeadValue) + req.LeadFrequency + ")"
+				}
+			}
+			mappingEdbList[i].DataList = dataBList
+			mappingEdbList[i].MinValue = indexMin
+			mappingEdbList[i].MaxValue = indexMax
+		}
+	}
+	return mappingEdbList, a, b, r, nil
+}
+
+// FillDataBList 填充B的数据 使得与A的时间保持一致
+func FillDataBList(dataList []*data_manage.EdbDataList, edbInfoMappingA *data_manage.ChartEdbInfoMapping) []*data_manage.EdbDataList {
+	dataAList, ok := edbInfoMappingA.DataList.([]*data_manage.EdbDataList)
+	if !ok {
+		return nil
+	}
+
+	for utils.StringToTime(dataList[len(dataList)-1].DataTime).Before(utils.StringToTime(dataAList[len(dataAList)-1].DataTime)) {
+		// 使用A的时间填充时间差
+		timeDiff := utils.GetNextDayN(dataList[len(dataList)-1].DataTime, 1)
+
+		// 创建新的数据点并填充 前值填充
+		newDataPoint := &data_manage.EdbDataList{
+			DataTime:      timeDiff,
+			Value:         dataList[len(dataList)-1].Value,
+			DataTimestamp: utils.StringToTime(timeDiff).UnixMilli(),
+		}
+
+		// 将新数据点添加到dataList末尾
+		dataList = append(dataList, newDataPoint)
+	}
+
+	return dataList
+}
+
+type ByDataTime []*data_manage.EdbDataList
+
+func (a ByDataTime) Len() int {
+	return len(a)
+}
+
+func (a ByDataTime) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a ByDataTime) Less(i, j int) bool {
+	t1 := utils.StringToTime(a[i].DataTime)
+	t2 := utils.StringToTime(a[j].DataTime)
+	return t1.Before(t2)
+}
+
+func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingList []*data_manage.ChartEdbInfoMapping, startDate string, endDate string, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, []*data_manage.EdbDataList, []*data_manage.EdbDataList, error) {
+
+	var fullADataList, fullBDataList []*data_manage.EdbDataList
+	for _, v := range mappingList {
+		var edbInfoMapping residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+		edbInfoMapping.EdbInfoType = 1
+		edbInfoMapping.IsOrder = false
+		edbInfoMapping.IsAxis = 1
+		edbInfoMapping.ChartColor = `#00F`
+		edbInfoMapping.ChartWidth = 3
+		edbInfoMapping.EdbName = v.EdbName
+
+		// 获取图表中的指标数据
+		dataList, err := data_manage.GetEdbDataList(v.Source, v.SubSource, v.EdbInfoId, startDate, endDate)
+		if err != nil {
+			return nil, nil, nil, fmt.Errorf("获取指标数据失败,Err:%s", err.Error())
+		}
+		// 重新获取指标数据 产品要求需要和计算指标-拟合残差逻辑保持一致
+		fullDataList, err := data_manage.GetEdbDataList(v.Source, v.SubSource, v.EdbInfoId, "", "")
+		if err != nil {
+			return nil, nil, nil, fmt.Errorf("获取指标数据失败,Err:%s", err.Error())
+		}
+
+		if v.EdbInfoId == req.EdbInfoIdB {
+			edbInfoMapping.LeadValue = req.LeadValue
+			edbInfoMapping.LeadUnit = req.LeadFrequency
+			edbInfoMapping.EdbInfoType = req.IndexType
+			edbInfoMapping.IsOrder = req.IsOrder
+			edbInfoMapping.IsAxis = 0
+			edbInfoMapping.ChartColor = `#F00`
+			edbInfoMapping.ChartWidth = 1
+
+			// 领先指标 dataList进行数据处理
+			if req.IndexType == 2 {
+				if req.LeadValue < 0 {
+					return nil, nil, nil, fmt.Errorf("领先值不能小于0")
+				} else if req.LeadValue > 0 {
+					edbInfoMapping.EdbName = v.EdbName + "(领先" + strconv.Itoa(req.LeadValue) + req.LeadFrequency + ")"
+					for _, indexData := range dataList {
+						switch req.LeadFrequency {
+						case "天":
+							indexData.DataTime = utils.GetNextDayN(indexData.DataTime, req.LeadValue)
+						case "周":
+							indexData.DataTime = utils.GetNextDayN(indexData.DataTime, req.LeadValue*7)
+						case "月":
+							indexData.DataTime = utils.TimeToString(utils.AddDate(utils.StringToTime(indexData.DataTime), 0, req.LeadValue), utils.YearMonthDay)
+						case "季":
+							indexData.DataTime = utils.TimeToString(utils.AddDate(utils.StringToTime(indexData.DataTime), 0, req.LeadValue*3), utils.YearMonthDay)
+						case "年":
+							indexData.DataTime = utils.TimeToString(utils.AddDate(utils.StringToTime(indexData.DataTime), req.LeadValue, 0), utils.YearMonthDay)
+						}
+						indexData.DataTimestamp = utils.StringToTime(indexData.DataTime).UnixMilli()
+					}
+				}
+			}
+
+			edbInfoMappingB.DataList = dataList
+			fullBDataList = fullDataList
+		} else {
+			edbInfoMappingA.DataList = dataList
+			fullADataList = fullDataList
+		}
+		edbInfoMapping.EdbInfoId = v.EdbInfoId
+		edbInfoMapping.EdbCode = v.EdbCode
+		edbInfoMapping.Unit = v.Unit
+		edbInfoMapping.Frequency = v.Frequency
+		edbInfoMapping.Source = v.Source
+		edbInfoMapping.SourceName = v.SourceName
+		edbInfoMapping.MinValue = v.MinValue
+		edbInfoMapping.MaxValue = v.MaxValue
+		edbInfoMapping.LatestDate = v.LatestDate
+		edbInfoMapping.LatestValue = v.LatestValue
+
+		edbInfoMapping.DataList = dataList
+		originalEdbList = append(originalEdbList, edbInfoMapping)
+	}
+	return originalEdbList, fullADataList, fullBDataList, nil
+}
+
+func ContrastPreview(indexCode string) (residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+	var condition string
+	var pars []interface{}
+
+	if indexCode != "" {
+		condition += " and edb_code=?"
+		pars = append(pars, indexCode)
+	}
+
+	edbInfo, err := data_manage.GetEdbInfoByCondition(condition, pars)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisChartEdbInfoMapping{}, err
+	}
+	if edbInfo == nil {
+		return residual_analysis_model.ResidualAnalysisChartEdbInfoMapping{}, fmt.Errorf("指标不存在")
+	}
+
+	dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, "", "")
+
+	var resp residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.EdbCode = edbInfo.EdbCode
+	resp.ChartColor = "#F00"
+	resp.SourceName = edbInfo.SourceName
+	resp.EdbName = edbInfo.EdbName
+	resp.Unit = edbInfo.Unit
+	resp.Frequency = edbInfo.Frequency
+	resp.MinValue = edbInfo.MinValue
+	resp.MaxValue = edbInfo.MaxValue
+	resp.DataList = dataList
+	return resp, nil
+}
+
+func SaveResidualAnalysis(req residual_analysis_model.ResidualAnalysisIndexSaveReq, sysUser *system.Admin) error {
+
+	// 验证分类是否存在
+	classifyCount, err := data_manage.GetEdbClassifyCountById(req.ClassifyId)
+	if err != nil {
+		return err
+	}
+	if classifyCount <= 0 {
+		return fmt.Errorf("分类不存在")
+	}
+
+	// 校验名称是否重复
+	var condition string
+	var pars []interface{}
+
+	condition += " and source = ? AND edb_name=?"
+	pars = append(pars, req.Source, req.EdbName)
+
+	edbInfoByCondition, err := data_manage.GetEdbInfoByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return err
+	}
+
+	// 获取指标数据最大值 最小值 最后更新时间 最后更新时间对应的值
+	var indexMax, indexMin, indexLatestValue float64
+	var indexLatestDate string
+	if len(req.DataList) > 0 {
+		latestTime, _ := time.Parse(utils.YearMonthDay, req.DataList[0].DataTime)
+		for _, data := range req.DataList {
+			// 比较最大值
+			if data.Value > indexMax {
+				indexMax = data.Value
+			}
+
+			// 比较最小值
+			if data.Value < indexMin {
+				indexMin = data.Value
+			}
+
+			// 比较最新时间和对应值
+			currentTime, err := time.Parse(utils.YearMonthDay, data.DataTime)
+			if err != nil {
+				// 时间解析失败,跳过此项
+				continue
+			}
+
+			// 如果当前时间更晚
+			if currentTime.After(latestTime) {
+				latestTime = currentTime
+				indexLatestDate = data.DataTime
+				indexLatestValue = data.Value
+			}
+		}
+	}
+
+	// 更新保存指标和配置的映射关系
+	mappingList, err := residual_analysis_model.GetConfigMappingListByConfigId(req.ConfigId)
+	if err != nil {
+		return err
+	}
+
+	// 判断是更新还是修改 看指标配置映射中,是否存在对应指标 存在 则更新 不存在 则新增
+	var edbInfoMapping residual_analysis_model.CalculateResidualAnalysisConfigMapping
+	for _, mapping := range mappingList {
+		if req.IndexType == mapping.IndexType && req.IndexType != 0 {
+			edbInfoMapping = mapping
+		}
+	}
+
+	var edbInfoId int64
+	var edbCode string
+	// 更新or新增
+	if edbInfoMapping.EdbInfoId > 0 {
+		// 查询指标库指标
+		edbInfo, err := data_manage.GetEdbInfoById(int(edbInfoMapping.EdbInfoId))
+		if err != nil {
+			return err
+		}
+		if edbInfo == nil {
+			return fmt.Errorf("指标不存在")
+		}
+		edbInfoId = int64(edbInfo.EdbInfoId)
+		edbCode = edbInfo.EdbCode
+
+		if edbInfoByCondition != nil && edbInfoByCondition.EdbInfoId != edbInfo.EdbInfoId {
+			return fmt.Errorf("指标名称重复")
+		}
+
+		// 须补充更新指标最大值,最小值,数据最新时间,数据最新值
+		edbInfo.MaxValue = indexMax
+		edbInfo.MinValue = indexMin
+		edbInfo.LatestDate = indexLatestDate
+		edbInfo.LatestValue = indexLatestValue
+		edbInfo.Unit = req.Unit
+		edbInfo.Frequency = req.Frequency
+		edbInfo.ClassifyId = req.ClassifyId
+		edbInfo.EdbName = req.EdbName
+		err = edbInfo.Update([]string{"min_value", "max_value", "latest_date", "latest_value", "unit", "frequency", "classify_id", "edb_name"})
+		if err != nil {
+			return err
+		}
+
+		// 删除对应得指标数据
+		err = residual_analysis_model.DeleteResidualAnalysisDataByEdbCode(edbInfo.EdbCode)
+		if err != nil {
+			return fmt.Errorf("删除指标数据失败")
+		}
+	} else {
+		if edbInfoByCondition != nil {
+			return fmt.Errorf("指标名称重复")
+		}
+
+		// 新增指标
+		edbCode, err = utils.GenerateEdbCode(1, "")
+		if err != nil {
+			return err
+		}
+
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+
+		edbInfoId, err = data_manage.AddEdbInfo(&data_manage.EdbInfo{
+			EdbCode:         edbCode,
+			UniqueCode:      utils.MD5(utils.CHART_PREFIX + "_" + timestamp),
+			EdbName:         req.EdbName,
+			EdbNameEn:       req.EdbNameEn,
+			ClassifyId:      req.ClassifyId,
+			EdbType:         req.EdbType,
+			Unit:            req.Unit,
+			UnitEn:          req.UnitEn,
+			Frequency:       req.Frequency,
+			Source:          req.Source,
+			SourceName:      "残差分析",
+			Calendar:        req.Calendar,
+			SysUserRealName: sysUser.RealName,
+			SysUserId:       sysUser.AdminId,
+			LatestDate:      indexLatestDate,
+			LatestValue:     indexLatestValue,
+			MinValue:        indexMin,
+			MaxValue:        indexMax,
+			CreateTime:      time.Now(),
+			ModifyTime:      time.Now(),
+		})
+		if err != nil {
+			return err
+		}
+
+		// 新增指标配置关系
+		_, err = residual_analysis_model.SaveConfigMapping(residual_analysis_model.CalculateResidualAnalysisConfigMapping{
+			CalculateResidualAnalysisConfigId: req.ConfigId,
+			EdbInfoId:                         edbInfoId,
+			ResidualType:                      req.ResidualType,
+			IndexType:                         req.IndexType,
+			CreateTime:                        time.Now(),
+			ModifyTime:                        time.Now(),
+		})
+		if err != nil {
+			return err
+		}
+	}
+
+	// 新增数据
+	for i := range req.DataList {
+		req.DataList[i].EdbDataId = 0
+		req.DataList[i].EdbInfoId = int(edbInfoId)
+		req.DataList[i].EdbCode = edbCode
+	}
+	_, err = residual_analysis_model.AddResidualAnalysisData(req.DataList)
+	if err != nil {
+		return err
+	}
+
+	// 新增自变量 因变量与配置得关系 配置中不存在该指标,则新增
+	var indexMap = make(map[int64]residual_analysis_model.CalculateResidualAnalysisConfigMapping)
+	for _, mapping := range mappingList {
+		indexMap[mapping.EdbInfoId] = mapping
+	}
+
+	if _, ok := indexMap[int64(req.EdbInfoIdA)]; !ok {
+		_, err = residual_analysis_model.SaveConfigMapping(residual_analysis_model.CalculateResidualAnalysisConfigMapping{
+			CalculateResidualAnalysisConfigId: req.ConfigId,
+			EdbInfoId:                         int64(req.EdbInfoIdA),
+			ResidualType:                      req.ResidualType,
+			IndexType:                         3,
+			CreateTime:                        time.Now(),
+			ModifyTime:                        time.Now(),
+		})
+		if err != nil {
+			return err
+		}
+	}
+	if _, ok := indexMap[int64(req.EdbInfoIdB)]; !ok {
+		_, err = residual_analysis_model.SaveConfigMapping(residual_analysis_model.CalculateResidualAnalysisConfigMapping{
+			CalculateResidualAnalysisConfigId: req.ConfigId,
+			EdbInfoId:                         int64(req.EdbInfoIdB),
+			ResidualType:                      req.ResidualType,
+			IndexType:                         4,
+			CreateTime:                        time.Now(),
+			ModifyTime:                        time.Now(),
+		})
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func ResidualAnalysisDetail(edbInfoId int) (residual_analysis_model.ResidualAnalysisDetailResp, error) {
+	// 通过指标配置映射表 拿到配置id,再获取关联的所有指标信息
+	var condition string
+	var pars []interface{}
+
+	condition += " and edb_info_id=?"
+	pars = append(pars, edbInfoId)
+
+	mappingList, err := residual_analysis_model.GetConfigMappingListByCondition(condition, pars)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisDetailResp{}, err
+	}
+
+	if len(mappingList) <= 0 {
+		return residual_analysis_model.ResidualAnalysisDetailResp{}, fmt.Errorf("指标不存在")
+	}
+
+	mapping := mappingList[0]
+
+	configMappingList, err := residual_analysis_model.GetConfigMappingListByConfigId(mapping.CalculateResidualAnalysisConfigId)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisDetailResp{}, err
+	}
+
+	var edbInfoIdList []int64
+	var edbInfoMap = make(map[int64]residual_analysis_model.CalculateResidualAnalysisConfigMapping)
+	var mappgingFlag = false
+	var residualFlag = false
+	for _, v := range configMappingList {
+		edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+
+		edbInfoMap[v.EdbInfoId] = v
+
+		if v.IndexType == 1 {
+			mappgingFlag = true
+		} else if v.IndexType == 2 {
+			residualFlag = true
+		}
+	}
+
+	condition = ""
+	pars = []interface{}{}
+
+	condition += ` and edb_info_id in(` + utils.GetOrmInReplace(len(edbInfoIdList)) + `)`
+	for _, id := range edbInfoIdList {
+		pars = append(pars, id)
+	}
+
+	edbInfoList, err := data_manage.GetEdbInfoListByCond(condition, pars)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisDetailResp{}, err
+	}
+
+	// 获取配置
+	configInfo, err := residual_analysis_model.GetResidualAnalysisConfigById(mapping.CalculateResidualAnalysisConfigId)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisDetailResp{}, err
+	}
+
+	var edbInfoListResp []*residual_analysis_model.DetailEdbInfoList
+	var dependentEdbInfo residual_analysis_model.DetailEdbInfoList
+	var independentEdbInfo residual_analysis_model.DetailEdbInfoList
+	for _, edbInfo := range edbInfoList {
+		var indexType int
+		if _, ok := edbInfoMap[int64(edbInfo.EdbInfoId)]; ok {
+			indexType = edbInfoMap[int64(edbInfo.EdbInfoId)].IndexType
+		}
+
+		info := residual_analysis_model.DetailEdbInfoList{
+			EdbInfoId:   edbInfo.EdbInfoId,
+			EdbInfoType: edbInfo.EdbInfoType,
+			IndexType:   indexType,
+			SourceName:  edbInfo.SourceName,
+			Source:      edbInfo.Source,
+			EdbCode:     edbInfo.EdbCode,
+			EdbName:     edbInfo.EdbName,
+			EdbNameEn:   edbInfo.EdbNameEn,
+			Unit:        edbInfo.Unit,
+			UnitEn:      edbInfo.UnitEn,
+			Frequency:   edbInfo.Frequency,
+			FrequencyEn: edbInfo.FrequencyEn,
+			ClassifyId:  edbInfo.ClassifyId,
+		}
+		edbInfoListResp = append(edbInfoListResp, &info)
+
+		if indexType == 3 {
+			dependentEdbInfo = info
+		} else if indexType == 4 {
+			independentEdbInfo = info
+		}
+	}
+
+	// 补充表格中 映射指标或者残差指标
+	if mappgingFlag && !residualFlag {
+		info := residual_analysis_model.DetailEdbInfoList{
+			IndexType:   2,
+			EdbName:     independentEdbInfo.EdbName + "映射残差/" + dependentEdbInfo.EdbName,
+			EdbNameEn:   dependentEdbInfo.EdbNameEn,
+			Unit:        dependentEdbInfo.Unit,
+			UnitEn:      dependentEdbInfo.UnitEn,
+			Frequency:   dependentEdbInfo.Frequency,
+			FrequencyEn: dependentEdbInfo.FrequencyEn,
+			ClassifyId:  dependentEdbInfo.ClassifyId,
+		}
+		edbInfoListResp = append(edbInfoListResp, &info)
+	} else if !mappgingFlag && residualFlag {
+		info := residual_analysis_model.DetailEdbInfoList{
+			IndexType:   1,
+			EdbName:     dependentEdbInfo.EdbName + "映射" + independentEdbInfo.EdbName,
+			EdbNameEn:   dependentEdbInfo.EdbNameEn,
+			Unit:        dependentEdbInfo.Unit,
+			UnitEn:      dependentEdbInfo.UnitEn,
+			Frequency:   dependentEdbInfo.Frequency,
+			FrequencyEn: dependentEdbInfo.FrequencyEn,
+			ClassifyId:  dependentEdbInfo.ClassifyId,
+		}
+		edbInfoListResp = append(edbInfoListResp, &info)
+	}
+
+	resp := residual_analysis_model.ResidualAnalysisDetailResp{
+		ConfigInfo:   &configInfo,
+		EdbInfoList:  edbInfoListResp,
+		ResidualType: mapping.ResidualType,
+	}
+
+	return resp, nil
+}
+
+func SaveResidualAnalysisConfig(req residual_analysis_model.ResidualAnalysisReq, sysUser *system.Admin) (int64, error) {
+
+	config := residual_analysis_model.ResidualAnalysisConfigVo{
+		DateType:         req.DateType,
+		StartDate:        req.StartDate,
+		EndDate:          req.EndDate,
+		IsOrder:          req.IsOrder,
+		IndexType:        req.IndexType,
+		LeadValue:        req.LeadValue,
+		LeadFrequency:    req.LeadFrequency,
+		LeftIndexMin:     req.LeftIndexMin,
+		LeftIndexMax:     req.LeftIndexMax,
+		RightIndexMin:    req.RightIndexMin,
+		RightIndexMax:    req.RightIndexMax,
+		ResidualIndexMin: req.ResidualIndexMin,
+		ResidualIndexMax: req.ResidualIndexMax,
+		ContrastIndexMin: req.ContrastIndexMin,
+		ContrastIndexMax: req.ContrastIndexMax,
+	}
+
+	// 转换为json格式
+	configJson, err := json.Marshal(config)
+	if err != nil {
+		return 0, err
+	}
+
+	// 新增or更新
+	/*
+		var condition string
+			var pars []interface{}
+		if req.EdbInfoId > 0 {
+			condition += " and edb_info_id=?"
+			pars = append(pars, req.EdbInfoId)
+			configMappings, err := residual_analysis_model.GetConfigMappingListByCondition(condition, pars)
+			if err != nil {
+				return 0, err
+			}
+			if len(configMappings) > 0 {
+				mapping := configMappings[0]
+				configInfo, err := residual_analysis_model.GetResidualAnalysisConfigById(mapping.CalculateResidualAnalysisConfigId)
+				if err != nil {
+					return 0, err
+				}
+				configInfo.Config = string(configJson)
+				err = residual_analysis_model.UpdateResidualAnalysisConfig(configInfo)
+				if err != nil {
+					return 0, err
+				}
+			}
+		}*/
+	var configId int64
+	if req.ConfigId > 0 {
+		configInfo, err := residual_analysis_model.GetResidualAnalysisConfigById(req.ConfigId)
+		if err != nil {
+			return 0, err
+		}
+		if configInfo.CalculateResidualAnalysisConfigId == 0 {
+			return 0, fmt.Errorf("未找到配置信息")
+		}
+
+		configId = int64(configInfo.CalculateResidualAnalysisConfigId)
+		configInfo.Config = string(configJson)
+
+		err = residual_analysis_model.UpdateResidualAnalysisConfig(configInfo)
+		if err != nil {
+			return 0, err
+		}
+	} else {
+		analysisConfig := residual_analysis_model.CalculateResidualAnalysisConfig{
+			Config:     string(configJson),
+			SysUserId:  sysUser.AdminId,
+			CreateTime: time.Now(),
+			ModifyTime: time.Now(),
+		}
+
+		configId, err = residual_analysis_model.SaveResidualAnalysisConfig(analysisConfig)
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	return configId, nil
+}
+
+func CheckResidualAnalysisExist(configId int) (int64, error) {
+
+	configMappingList, err := residual_analysis_model.GetConfigMappingListByConfigId(configId)
+	if err != nil {
+		return 0, err
+	}
+
+	var configMapping residual_analysis_model.CalculateResidualAnalysisConfigMapping
+	for _, mapping := range configMappingList {
+		if mapping.IndexType == 2 {
+			configMapping = mapping
+		}
+	}
+
+	return configMapping.EdbInfoId, nil
+}

+ 4 - 1
static/ErrMsgConfig.json

@@ -61,5 +61,8 @@
   "指标数据异常,请检查": "The metric data is abnormal, please check.",
   "原指标与替换指标存在引用关系,不允许替换": "Original indicators and replacement indicators have a referencing relationship, which is not allowed to be replaced.",
   "该分类下关联素材库不可删除": "The category associated with the material library cannot be deleted.",
-  "请输入图片名称": "Please enter the image name"
+  "请输入图片名称": "Please enter the image name",
+  "指标名称已存在,请重新填写": "The metric name already exists, please re-enter.",
+  "请选择分类": "Please Select a Category",
+  "分类已存在": "The category already exists."
 }

+ 36 - 1
utils/common.go

@@ -748,6 +748,17 @@ func TimeToStr(data time.Time, format string) string {
 	return data.Format(format)
 }
 
+func FormatDateString(format string) string {
+	// 解析源时间
+	t, err := time.Parse(FormatDateTime, format)
+	if err != nil {
+		return ""
+	}
+
+	// 格式化为目标时间格式
+	return t.Format(FormatDate)
+}
+
 // 字符串类型时间转周几
 func StrDateTimeToWeek(strTime string) string {
 	var WeekDayMap = map[string]string{
@@ -1802,7 +1813,11 @@ func GetDateByDateTypeV2(dateType int, tmpStartDate, tmpEndDate string, startYea
 			startDate = startDate + "-01"
 		}
 		if strings.Count(endDate, "-") == 1 {
-			endDate = endDate + "-01"
+			endTime, err := time.Parse(FormatYearMonthDate, endDate)
+			if err != nil {
+				return
+			}
+			endDate = endTime.AddDate(0, 1, -1).Format(FormatDate)
 		}
 	}
 
@@ -2769,3 +2784,23 @@ func RoundNumber(num string, decimalPlaces int, hasPercent bool) string {
 	}
 	return numStr
 }
+
+// FindMinMax 取出数组中的最小值和最大值
+func FindMinMax(numbers []float64) (min float64, max float64) {
+	if len(numbers) == 0 {
+		return 0, 0 // 如果切片为空,返回0, 0
+	}
+
+	min, max = numbers[0], numbers[0] // 初始化 min 和 max 为切片的第一个元素
+
+	for _, num := range numbers {
+		if num < min {
+			min = num
+		}
+		if num > max {
+			max = num
+		}
+	}
+
+	return min, max
+}

+ 10 - 4
utils/constants.go

@@ -185,7 +185,12 @@ const (
 	DATA_SOURCE_LY                                   = 91       // 粮油商务网
 	DATA_SOURCE_TRADE_ANALYSIS                       = 92       // 持仓分析
 	DATA_SOURCE_HISUGAR                              = 93       // 泛糖科技 -> 93
+	DATA_SOURCE_USDA_FAS                             = 96       //美国农业部
 	DATA_SOURCE_CALCULATE_STL                        = 98       // STL趋势分解 -> 98
+	DATA_SOURCE_RZD                                  = 97       // 睿姿得数据
+	DATA_SOURCE_MAPPING_RESIDUAL                     = 99       // 映射残差
+	DATA_SOURCE_FIT_RESIDUAL                         = 100      // 拟合残差
+	DATA_SOURCE_CLARKSONS                            = 101      // 克拉克森数据
 )
 
 // 数据刷新频率
@@ -469,10 +474,11 @@ const (
 
 // 指标引用对象
 const (
-	EDB_RELATION_CHART    = 1 // 图表
-	EDB_RELATION_SANDBOX  = 2 // ETA逻辑
-	EDB_RELATION_CALENDAR = 3 // 事件日历
-	EDB_RELATION_TABLE    = 4 // 表格
+	EDB_RELATION_CHART       = 1 // 图表
+	EDB_RELATION_SANDBOX     = 2 // ETA逻辑
+	EDB_RELATION_CALENDAR    = 3 // 事件日历
+	EDB_RELATION_TABLE       = 4 // 表格
+	EDB_RELATION_PREDICT_EDB = 5 // 预测指标
 )
 
 // 指标计算方式

+ 129 - 0
utils/date_util.go

@@ -0,0 +1,129 @@
+package utils
+
+import (
+	"time"
+)
+
+// 定义时间格式常量
+const (
+	YearMonthDay     = "2006-01-02"                     // yyyy-MM-dd
+	YearMonthDayTime = "2006-01-02 15:04:05"            // yyyy-MM-dd HH:mm:ss
+	MonthDay         = "01-02"                          // MM-dd
+	DayMonthYear     = "02-01-2006"                     // dd-MM-yyyy
+	YearMonth        = "2006-01"                        // yyyy-MM
+	FullDate         = "Monday, 02-Jan-06 15:04:05 PST" // 完整日期:例如:Monday, 02-Jan-06 15:04:05 PST
+)
+
+// GetPreYearTime 获取当前时间 前n年的时间 返回yyyy-MM-dd 格式的时间
+func GetPreYearTime(n int) string {
+	// 获取当前时间
+	now := time.Now()
+	// 计算前n年的时间
+	preYearTime := now.AddDate(-n, 0, 0)
+	// 格式化时间
+	return preYearTime.Format("2006-01-02")
+}
+
+// IsMoreThanOneDay 判断两个yyyy-MM-dd类型的时间,相差是否大于1天
+func IsMoreThanOneDay(startDate, endDate string) bool {
+	startTime, _ := time.Parse("2006-01-02", startDate)
+	endTime, _ := time.Parse("2006-01-02", endDate)
+	diff := endTime.Sub(startTime)
+	days := diff.Hours() / 24
+	return days > 1
+}
+
+// GetNextDay 获取 yyyy-MM-dd类型的时间的下一天
+func GetNextDay(date string) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, 1)
+	return nextDay.Format("2006-01-02")
+}
+
+// GetNextDayN 获取 yyyy-MM-dd 类型的时间的下n天
+func GetNextDayN(date string, n int) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, n)
+	return nextDay.Format("2006-01-02")
+}
+
+var daysOfMonth = [...]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+
+// AddDate 解决 Go time 包 AddDate() 添加年份/月份溢出到下一个月的问题。
+// 例如:
+//
+//	2024-02-29 AddDate(1, 0, 0) 期望结果: 2025-02-28
+//	2024-08-31 AddDate(0, 1, 1) 期望结果: 2024-09-30
+func AddDate(t time.Time, years, months int) time.Time {
+	month := t.Month()
+
+	// 规范年份和月份
+	years, months = norm(years, months, 12)
+
+	// 计算目标月份
+	targetMonth := int(month) + months
+	if targetMonth <= 0 {
+		// 处理负值月份
+		targetMonth += 12 * ((-targetMonth)/12 + 1)
+	}
+	// 取余计算目标月份
+	targetMonth = (targetMonth-1)%12 + 1
+
+	// 计算目标年份
+	targetYear := t.Year() + years + (int(month)+months-1)/12
+
+	// 计算目标月份最大天数
+	maxDayOfTargetMonth := daysOfMonth[targetMonth-1]
+	if isLeap(targetYear) && targetMonth == 2 {
+		maxDayOfTargetMonth++ // 闰年2月多一天
+	}
+
+	// 计算目标日期
+	targetDay := t.Day()
+	if targetDay > maxDayOfTargetMonth {
+		// 如果目标日期超出该月的天数,设置为该月的最后一天
+		targetDay = maxDayOfTargetMonth
+	}
+
+	// 返回新的日期
+	return time.Date(targetYear, time.Month(targetMonth), targetDay, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+}
+
+// norm 规范化年份和月份
+func norm(hi, lo, base int) (nhi, nlo int) {
+	if lo < 0 {
+		n := (-lo-1)/base + 1
+		hi -= n
+		lo += n * base
+	}
+	if lo >= base {
+		n := lo / base
+		hi += n
+		lo -= n * base
+	}
+	return hi, lo
+}
+
+// isLeap 判断是否为闰年
+func isLeap(year int) bool {
+	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
+
+// StringToTime string 类型时间 转换为 time.Time 类型
+func StringToTime(date string) time.Time {
+	t, _ := time.Parse("2006-01-02", date)
+	return t
+}
+
+// TimeToString time.Time 类型时间 转换为 string 类型
+func TimeToString(t time.Time, format string) string {
+	formattedTime := t.Format(format)
+	return formattedTime
+}
+
+// CompareDate 判断传入的两个字符串时间的前后顺序
+func CompareDate(data1, data2 string) bool {
+	t1, _ := time.Parse("2006-01-02", data1)
+	t2, _ := time.Parse("2006-01-02", data2)
+	return !t1.After(t2)
+}