Эх сурвалжийг харах

Merge branch 'debug' of http://8.136.199.33:3000/eta_server/eta_api into bzq1/mysteel_chemical_custom_debug

zqbao 8 сар өмнө
parent
commit
ddb885f2a6
100 өөрчлөгдсөн 14948 нэмэгдсэн , 2351 устгасан
  1. 1 2
      .gitignore
  2. 2 0
      cache/replace_edb_info.go
  3. 10 1
      controllers/ai/ai_file.go
  4. 96 0
      controllers/ai/ai_pormpt.go
  5. 817 0
      controllers/ai/ai_summary.go
  6. 0 1
      controllers/base_auth.go
  7. 151 0
      controllers/business_conf.go
  8. 188 411
      controllers/classify.go
  9. 10 0
      controllers/data_manage/chart_classify.go
  10. 1 0
      controllers/data_manage/chart_common.go
  11. 277 47
      controllers/data_manage/chart_info.go
  12. 158 0
      controllers/data_manage/chart_info_section.go
  13. 29 1
      controllers/data_manage/chart_theme.go
  14. 266 139
      controllers/data_manage/correlation/correlation_chart_classify.go
  15. 1306 137
      controllers/data_manage/correlation/correlation_chart_info.go
  16. 29 26
      controllers/data_manage/cross_variety/chart_info.go
  17. 27 9
      controllers/data_manage/edb_info.go
  18. 40 8
      controllers/data_manage/edb_info_calculate.go
  19. 232 2
      controllers/data_manage/edb_info_refresh.go
  20. 298 0
      controllers/data_manage/edb_info_relation.go
  21. 1 1
      controllers/data_manage/excel/balance_table.go
  22. 185 11
      controllers/data_manage/excel/excel_info.go
  23. 816 0
      controllers/data_manage/factor_edb_series.go
  24. 270 69
      controllers/data_manage/future_good/future_good_chart_info.go
  25. 155 111
      controllers/data_manage/future_good/future_good_profit_chart_info.go
  26. 29 26
      controllers/data_manage/line_equation/line_chart_info.go
  27. 29 26
      controllers/data_manage/line_feature/chart_info.go
  28. 35 6
      controllers/data_manage/multiple_graph_config.go
  29. 13 2
      controllers/data_manage/predict_edb_info.go
  30. 331 0
      controllers/data_manage/wind_data.go
  31. 0 1
      controllers/data_source/guagnzhouqihuo.go
  32. 6 2
      controllers/data_source/icpi.go
  33. 67 3
      controllers/english_report/email.go
  34. 7 8
      controllers/english_report/report.go
  35. 2 0
      controllers/fe_calendar/fe_calendar_matter.go
  36. 55 4
      controllers/ppt_english.go
  37. 11 1
      controllers/ppt_english_group.go
  38. 59 5
      controllers/ppt_v2.go
  39. 11 1
      controllers/ppt_v2_group.go
  40. 434 499
      controllers/report.go
  41. 18 4
      controllers/report_approve/report_approve.go
  42. 38 32
      controllers/report_approve/report_approve_flow.go
  43. 1 1
      controllers/report_author.go
  44. 1400 0
      controllers/report_chapter.go
  45. 50 63
      controllers/report_chapter_type.go
  46. 1902 0
      controllers/report_v2.go
  47. 9 1
      controllers/sandbox/sandbox.go
  48. 8 9
      controllers/smart_report/smart_report.go
  49. 55 27
      controllers/sys_admin.go
  50. 21 1
      controllers/sys_role.go
  51. 3 3
      controllers/sys_team.go
  52. 776 421
      controllers/target.go
  53. 89 28
      controllers/voice.go
  54. 1 1
      go.mod
  55. 2 3
      go.sum
  56. 57 0
      models/ai_summary/ai_pormpt.go
  57. 83 0
      models/ai_summary/ai_summary.go
  58. 249 0
      models/ai_summary/ai_summary_classify.go
  59. 67 35
      models/business_conf.go
  60. 29 0
      models/chart_permission.go
  61. 120 36
      models/classify.go
  62. 46 0
      models/company/company_config.go
  63. 231 0
      models/data_manage/base_from_business_data.go
  64. 3 2
      models/data_manage/base_from_trade_index.go
  65. 47 0
      models/data_manage/chart_classify.go
  66. 25 0
      models/data_manage/chart_edb_mapping.go
  67. 516 79
      models/data_manage/chart_info.go
  68. 218 0
      models/data_manage/chart_info_correlation.go
  69. 251 0
      models/data_manage/chart_series.go
  70. 31 0
      models/data_manage/chart_series_edb_mapping.go
  71. 84 0
      models/data_manage/chart_theme/request/theme.go
  72. 38 0
      models/data_manage/chart_type.go
  73. 22 0
      models/data_manage/correlation/request/chart.go
  74. 8 0
      models/data_manage/edb_classify.go
  75. 16 6
      models/data_manage/edb_data_base.go
  76. 1 1
      models/data_manage/edb_data_insert_config.go
  77. 8 0
      models/data_manage/edb_data_mysteel_chemical.go
  78. 41 0
      models/data_manage/edb_data_wind.go
  79. 81 8
      models/data_manage/edb_info.go
  80. 11 0
      models/data_manage/edb_info_calculate.go
  81. 11 0
      models/data_manage/edb_info_calculate_mapping.go
  82. 28 0
      models/data_manage/edb_info_record.go
  83. 518 0
      models/data_manage/edb_info_relation.go
  84. 22 0
      models/data_manage/edb_refresh/request/edb_info_refresh.go
  85. 9 0
      models/data_manage/excel/excel_edb_mapping.go
  86. 1 1
      models/data_manage/excel/excel_info.go
  87. 12 0
      models/data_manage/excel/request/excel_info.go
  88. 10 0
      models/data_manage/excel/request/mixed_table.go
  89. 15 7
      models/data_manage/excel/response/excel_info.go
  90. 8 4
      models/data_manage/excel_style.go
  91. 351 0
      models/data_manage/factor_edb_series.go
  92. 190 0
      models/data_manage/factor_edb_series_calculate_data.go
  93. 153 0
      models/data_manage/factor_edb_series_calculate_func.go
  94. 211 0
      models/data_manage/factor_edb_series_chart_mapping.go
  95. 167 0
      models/data_manage/factor_edb_series_mapping.go
  96. 2 0
      models/data_manage/future_good/request/future_good_chart.go
  97. 8 8
      models/data_manage/my_chart.go
  98. 85 0
      models/data_manage/mysteel_chemical_index.go
  99. 25 0
      models/data_manage/request/factor_edb_series.go
  100. 12 10
      models/data_manage/request/multiple_graph_config.go

+ 1 - 2
.gitignore

@@ -3,7 +3,6 @@
 /.idea
 /routers/.DS_Store
 /rdlucklog
-/etalogs
 /conf/*.conf
 /binlog/*
 /*.pdf
@@ -19,4 +18,4 @@ eta_api.exe
 eta_api.exe~
 /static/tmpFile/*
 etalogs/
-/.vscode
+/.vscode

+ 2 - 0
cache/replace_edb_info.go

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

+ 10 - 1
controllers/ai/ai_file.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/models/aimod"
 	"eta/eta_api/services"
 	"eta/eta_api/services/aiser"
+	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
 	"os"
@@ -122,7 +123,6 @@ func (this *AiFileController) FileUpload() {
 		assistantId = topic.AssistantId
 		threadId = topic.ThreadId
 	}
-
 	if aiChatTopicId <= 0 { //新增
 		topic := new(aimod.AiChatTopic)
 		var filenameWithSuffix string
@@ -159,6 +159,15 @@ func (this *AiFileController) FileUpload() {
 		chatItem.OpenaiFilePath = resourceUrl
 		chatItem.CreateTime = time.Now()
 		chatItem.ModifyTime = time.Now()
+
+		bchat, err := json.Marshal(chatItem)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "生成话题失败,Err:" + err.Error()
+			return
+		}
+		alarm_msg.SendAlarmMsg(string(bchat), 1)
+		alarm_msg.SendAlarmMsg(string("cur model:"+model), 1)
 		_, err = aimod.AddAiChat(chatItem)
 		if err != nil {
 			br.Msg = "获取数据失败!"

+ 96 - 0
controllers/ai/ai_pormpt.go

@@ -0,0 +1,96 @@
+package ai
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/ai_summary"
+	"eta/eta_api/models/system"
+)
+
+// AiPromptList
+// @Title 获取所有ai纪要分类接口-包含沙盘
+// @Description 获取所有ai纪要分类接口-包含沙盘
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /prompt/list [get]
+func (this *AiController) AiPromptList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	list, err := ai_summary.GetAiPromptList(this.SysUser.AdminId, 0)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// PromptGroupList
+// @Title 获取可见的目录列表
+// @Description 获取可见的目录列表接口
+// @Success 200 {object} ppt_english.RespGroupList
+// @router /prompt/groups [get]
+func (this *AiController) PromptGroupList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	list, err := ai_summary.GetAiPromptShareList()
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	resp := make([]*ai_summary.RespGroupListItem, 0)
+	publicAdminIdList := make([]int, 0)
+	publicPromptListMap := make(map[int][]*ai_summary.AiPromptItem)
+	systemAdminMap := make(map[int]*system.Admin)
+
+	for _, v := range list {
+		publicPromptList, ok := publicPromptListMap[v.SysAdminId]
+		if !ok {
+			publicPromptList = make([]*ai_summary.AiPromptItem, 0)
+			publicAdminIdList = append(publicAdminIdList, v.SysAdminId)
+
+		}
+		systemAdminMap[v.SysAdminId] = &system.Admin{
+			AdminId: v.SysAdminId,
+			RealName:v.SysAdminName,
+		}
+		publicPromptList = append(publicPromptList, v)
+		publicPromptListMap[v.SysAdminId] = publicPromptList
+	}
+	for _, v := range publicAdminIdList {
+		systemAdmin, ok := systemAdminMap[v]
+		if !ok {
+			continue
+		}
+		respGroupPptNameListItemList, ok := publicPromptListMap[v]
+		if !ok {
+			respGroupPptNameListItemList = make([]*ai_summary.AiPromptItem, 0)
+		}
+
+		//  分组信息
+		tmpRespGroupListItem := &ai_summary.RespGroupListItem{
+			GroupId:   int64(systemAdmin.AdminId),
+			GroupName: systemAdmin.RealName + "的PPT",
+			AdminId:   systemAdmin.AdminId,
+			IsShare:   1,
+			PromptList:   respGroupPptNameListItemList,
+		}
+		resp = append(resp, tmpRespGroupListItem)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询成功"
+	br.Data = resp
+	return
+}

+ 817 - 0
controllers/ai/ai_summary.go

@@ -0,0 +1,817 @@
+package ai
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/ai_summary"
+	"eta/eta_api/models/aimod"
+	"eta/eta_api/models/sandbox"
+	aiSummaryService "eta/eta_api/services/ai_summary"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiSummaryClassifyItems
+// @Title 获取所有ai纪要分类接口
+// @Description 获取所有ai纪要分类接口
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /summary/classify/list [get]
+func (this *AiController) AiSummaryClassifyItems() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := new(ai_summary.AiSummaryClassifyListResp)
+	aiSummaryClassifyId, _ := this.GetInt("AiSummaryClassifyId")
+
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		errMsg, err := aiSummaryService.GetAiSummaryClassifyListForMe(*this.SysUser, resp, aiSummaryClassifyId)
+		if err != nil {
+			br.Msg = errMsg
+			br.ErrMsg = err.Error()
+			return
+		}
+
+
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		fmt.Println("source my classify")
+		return
+	}
+
+	rootList, err := ai_summary.GetAiSummaryClassifyAndInfoByParentId(aiSummaryClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	classifyAll, err := ai_summary.GetAiSummaryClassifyAndInfoByParentId(aiSummaryClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	nodeAll := make([]*ai_summary.AiSummaryClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		aiSummaryService.AiSummaryClassifyItemsMakeTreeV2(this.SysUser, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+
+	resp.AllNodes = nodeAll
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 新增ai纪要分类
+// @Description 新增ai纪要分类接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /summary/classify/add [post]
+func (this *AiController) AddSandboxClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.AddAiSummaryClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	count, err := ai_summary.GetAiSummaryClassifyCount(req.ClassifyName, req.ParentId)
+	if err != nil {
+		br.Msg = "判断名称是否已存在失败"
+		br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+		return
+	}
+	if count > 0 {
+		br.Msg = "分类名称已存在,请重新输入"
+		br.IsSendEmail = false
+		return
+	}
+	//获取该层级下最大的排序数
+	maxSort, err := ai_summary.GetAiSummaryClassifyMaxSort(req.ParentId)
+
+	classify := new(ai_summary.AiSummaryClassify)
+	classify.ParentId = req.ParentId
+	classify.ClassifyName = req.ClassifyName
+	classify.HasData = 0
+	classify.CreateTime = time.Now()
+	classify.ModifyTime = time.Now()
+	classify.SysUserId = this.SysUser.AdminId
+	classify.SysUserRealName = this.SysUser.RealName
+	classify.Level = req.Level + 1
+	classify.Sort = maxSort + 1
+
+	_, err = ai_summary.AddAiSummaryClassify(classify)
+	if err != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+}
+
+// @Title 修改ai纪要分类
+// @Description 修改ai纪要分类接口
+// @Param	request	body data_manage.EditChartClassifyReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /summary/classify/edit [post]
+func (this *AiController) EditAiSummaryClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.EditAiSummaryClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	if req.AiSummaryClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	err = ai_summary.EditSandboxClassify(req.AiSummaryClassifyId, req.ClassifyName)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// @Title 删除纪要分类检测接口
+// @Description 删除纪要分类检测接口
+// @Param	request	body data_manage.ChartClassifyDeleteCheckResp true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /summary/classify/delete/check [post]
+func (this *AiController) DeleteSandboxClassifyCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.AiSummaryClassifyDeleteCheckReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.AiSummaryClassifyId < 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	var deleteStatus int
+	var tipsMsg string
+	//删除分类
+	if req.AiSummaryClassifyId > 0 {
+		//判断分类下,是否含有纪要
+		count, err := ai_summary.GetAiSummaryInfoCountByClassifyId(req.AiSummaryClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有指标失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联沙盘不可删除"
+		}
+	}
+
+	if deleteStatus != 1 {
+		classifyCount, err := ai_summary.GetAiSummaryInfoCountByClassifyId(req.AiSummaryClassifyId)
+		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(sandbox.SandboxClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// @Title 删除纪要分类/纪要
+// @Description 删除纪要分类/纪要接口
+// @Param	request	body data_manage.DeleteChartClassifyReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /summary/classify/delete [post]
+func (this *AiController) DeleteAiSummaryClassify() {
+	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 ai_summary.DeleteAiSummaryClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.AiSummaryClassifyId < 0 && req.AiSummaryId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+
+	//删除分类
+	if req.AiSummaryClassifyId > 0 && req.AiSummaryId == 0 {
+		//判断是否含有纪要
+		count, err := ai_summary.GetAiSummaryInfoCountByClassifyId(req.AiSummaryClassifyId)
+		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 = ai_summary.DeleteAiSummaryClassify(req.AiSummaryClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	//删除沙盘
+	if req.AiSummaryId > 0 {
+		summaryInfo, err := ai_summary.GetAiSummaryById(req.AiSummaryId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "沙盘已删除,请刷新页面"
+				br.ErrMsg = "指标不存在,Err:" + err.Error()
+				return
+			} else {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,获取指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+		if summaryInfo == nil {
+			br.Msg = "纪要已删除,请刷新页面"
+			return
+		}
+		err = ai_summary.DelAiSummaryById(req.AiSummaryId)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+	}
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// AiSummaryClassifyMove
+// @Title 纪要分类移动接口
+// @Description 纪要分类移动接口
+// @Success 200 {object} data_manage.MoveChartClassifyReq
+// @router /summary/classify/move [post]
+func (this *AiController) AiSummaryClassifyMove() {
+	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 ai_summary.MoveAiSummaryClassifyReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.AiSummaryClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "分类id小于等于0"
+		return
+	}
+	//判断分类是否存在
+	aiSummaryClassifyInfo, err := ai_summary.GetAiSummaryClassifyById(req.AiSummaryClassifyId)
+	if err != nil {
+		br.Msg = "移动失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	updateCol := make([]string, 0)
+
+	// 判断移动的是分类还是沙盘
+	if req.AiSummaryId > 0 {
+		//判断分类是否存在
+		count, _ := ai_summary.GetAiSummaryClassifyCountById(req.AiSummaryClassifyId)
+		if count <= 0 {
+			br.Msg = "分类已被删除,不可移动,请刷新页面"
+			return
+		}
+
+		aiSummaryInfo, err := ai_summary.GetAiSummaryById(req.AiSummaryId)
+		if err != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = "获取沙盘信息失败,Err:" + err.Error()
+			return
+		}
+
+		//如果改变了分类,那么移动该图表数据
+		// 11/22 ETA逻辑优化去除名称重复限制
+		if aiSummaryInfo.ClassifyId != req.ParentClassifyId {
+			////查询需要修改的分类下是否存在同一个图表名称
+			//tmpSandboxInfo, tmpErr := sandbox.GetSandboxByClassifyIdAndName(req.ParentClassifyId, aiSummaryInfo.Name)
+			//if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			//	br.Msg = "移动失败"
+			//	br.ErrMsg = "移动失败,Err:" + tmpErr.Error()
+			//	return
+			//}
+			//if tmpSandboxInfo != nil {
+			//	br.Msg = "移动失败,同一个分类下沙盘名称不允许重复"
+			//	br.ErrMsg = "移动失败,同一个分类下沙盘名称不允许重复"
+			//	return
+			//}
+			err = ai_summary.MoveAiSummary(req.AiSummaryId, req.ParentClassifyId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "移动失败,Err:" + err.Error()
+				return
+			}
+		}
+
+		//移动排序
+		updateCol := make([]string, 0)
+		//如果有传入 上一个兄弟节点分类id
+		if req.PrevId > 0 {
+			if req.PrevType == 1 {
+				//上一个兄弟节点
+				prevClassify, err := ai_summary.GetAiSummaryClassifyById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是分类 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := ai_summary.GetAiSummaryClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+						if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == aiSummaryClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, prevClassify.AiSummaryClassifyId, prevClassify.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是分类 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := ai_summary.GetAiSummaryById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(分类)与下一个兄弟(沙盘)的排序权重是一致的,那么需要将下一个兄弟(沙盘)(以及下个兄弟(沙盘)的同样排序权重)的排序权重+2,自己变成上一个兄弟(分类)的排序权重+1
+						if prevClassify.Sort == nextChartInfo.Sort || prevClassify.Sort == aiSummaryInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, prevClassify.AiSummaryClassifyId, prevClassify.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟(沙盘)的排序权重正好是上个兄弟节点(分类)的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.AiSummaryClassifyId, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					}
+
+				}
+
+				aiSummaryInfo.Sort = prevClassify.Sort + 1
+				aiSummaryInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+
+			} else {
+				prevAiSumary, err := ai_summary.GetAiSummaryById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是沙盘 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := ai_summary.GetAiSummaryClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevAiSumary.Sort == nextClassify.Sort || prevAiSumary.Sort == aiSummaryClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevAiSumary.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是沙盘 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := ai_summary.GetAiSummaryById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevAiSumary.Sort == nextChartInfo.Sort || prevAiSumary.Sort == aiSummaryInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevAiSumary.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+							}
+						}
+					}
+
+				}
+
+				aiSummaryInfo.Sort = prevAiSumary.Sort + 1
+				aiSummaryInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+			}
+
+		} else {
+			// prevId为0,也就是沙盘移到最前端
+			firstClassify, err := ai_summary.GetFirstAiSummaryByClassifyId(req.AiSummaryClassifyId)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = ai_summary.UpdateAiSummarySortByClassifyId(firstClassify.ClassifyId, 0, firstClassify.AiSummaryId-1, updateSortStr)
+			}
+
+			aiSummaryInfo.Sort = 0 //那就是排在第一位
+			aiSummaryInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = aiSummaryInfo.Update(updateCol)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "修改失败,Err:" + err.Error()
+				return
+			}
+		}
+	} else {
+		//移动的是分类
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if aiSummaryClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+			parentChartClassifyInfo, err := ai_summary.GetAiSummaryClassifyById(req.ParentClassifyId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+				return
+			}
+			aiSummaryClassifyInfo.ParentId = parentChartClassifyInfo.AiSummaryClassifyId
+			aiSummaryClassifyInfo.Level = parentChartClassifyInfo.Level + 1
+			aiSummaryClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+		} else if aiSummaryClassifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId == 0 {
+			//改为一级分类
+			aiSummaryClassifyInfo.ParentId = req.ParentClassifyId
+			aiSummaryClassifyInfo.Level = 1
+			aiSummaryClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ParentId", "Level", "ModifyTime")
+		}
+
+		//如果有传入 上一个兄弟节点分类id
+		if req.PrevId > 0 {
+			if req.PrevType == 1 {
+				//上一个节点是分类
+				//上一个兄弟节点
+				prevClassify, err := ai_summary.GetAiSummaryClassifyById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是分类 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := ai_summary.GetAiSummaryClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+						if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == aiSummaryClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, prevClassify.AiSummaryClassifyId, prevClassify.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是分类 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := ai_summary.GetAiSummaryById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(分类)与下一个兄弟(沙盘)的排序权重是一致的,那么需要将下一个兄弟(沙盘)(以及下个兄弟(沙盘)的同样排序权重)的排序权重+2,自己变成上一个兄弟(分类)的排序权重+1
+						if prevClassify.Sort == nextChartInfo.Sort || prevClassify.Sort == aiSummaryClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, prevClassify.AiSummaryClassifyId, prevClassify.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+						} else {
+							//如果下一个兄弟(沙盘)的排序权重正好是上个兄弟节点(分类)的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevClassify.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.AiSummaryClassifyId, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevClassify.AiSummaryClassifyId, prevClassify.Sort, 0, updateSortStr)
+							}
+						}
+					}
+
+				}
+
+				aiSummaryClassifyInfo.Sort = prevClassify.Sort + 1
+				aiSummaryClassifyInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+
+			} else {
+				//上一个节点是沙盘
+				prevAiSumary, err := ai_summary.GetAiSummaryById(req.PrevId)
+				if err != nil {
+					br.Msg = "移动失败"
+					br.ErrMsg = "获取上一个兄弟节点分类信息失败,Err:" + err.Error()
+					return
+				}
+
+				//如果是移动在两个兄弟节点之间
+				if req.NextId > 0 {
+					if req.NextType == 1 {
+						//上一个节点是沙盘 下一个节点是分类的情况
+						//下一个兄弟节点
+						nextClassify, err := ai_summary.GetAiSummaryClassifyById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevAiSumary.Sort == nextClassify.Sort || prevAiSumary.Sort == aiSummaryClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextClassify.Sort-prevAiSumary.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+							}
+						}
+					} else {
+						//上一个节点是沙盘 下一个节点是沙盘的情况
+						//下一个兄弟节点
+						nextChartInfo, err := ai_summary.GetAiSummaryById(req.NextId)
+						if err != nil {
+							br.Msg = "移动失败"
+							br.ErrMsg = "获取下一个兄弟节点分类信息失败,Err:" + err.Error()
+							return
+						}
+						//如果上一个兄弟(沙盘)与下一个兄弟(分类)的排序权重是一致的,那么需要将下一个兄弟(分类)(以及下个兄弟(分类)的同样排序权重)的排序权重+2,自己变成上一个兄弟(沙盘)的排序权重+1
+						if prevAiSumary.Sort == nextChartInfo.Sort || prevAiSumary.Sort == aiSummaryClassifyInfo.Sort {
+							//变更兄弟节点的排序
+							updateSortStr := `sort + 2`
+							_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+							_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+						} else {
+							//如果下一个兄弟(分类)的排序权重正好是上个兄弟(沙盘)节点的下一层,那么需要再加一层了
+							if nextChartInfo.Sort-prevAiSumary.Sort == 1 {
+								//变更兄弟节点的排序
+								updateSortStr := `sort + 1`
+								_ = ai_summary.UpdateAiSummaryClassifySortByParentId(prevAiSumary.ClassifyId, 0, prevAiSumary.Sort, updateSortStr)
+								_ = ai_summary.UpdateAiSummarySortByClassifyId(prevAiSumary.ClassifyId, prevAiSumary.Sort, prevAiSumary.AiSummaryId, updateSortStr)
+							}
+						}
+					}
+
+				}
+				aiSummaryClassifyInfo.Sort = prevAiSumary.Sort + 1
+				aiSummaryClassifyInfo.ModifyTime = time.Now()
+				updateCol = append(updateCol, "Sort", "ModifyTime")
+
+			}
+
+		} else {
+			firstClassify, err := ai_summary.GetFirstAiSummaryClassifyByParentId(aiSummaryClassifyInfo.ParentId)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + err.Error()
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = ai_summary.UpdateAiSummaryClassifySortByParentId(firstClassify.ParentId, firstClassify.AiSummaryClassifyId-1, 0, updateSortStr)
+			}
+
+			aiSummaryClassifyInfo.Sort = 0 //那就是排在第一位
+			aiSummaryClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = aiSummaryClassifyInfo.Update(updateCol)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "修改失败,Err:" + err.Error()
+				return
+			}
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+
+// @Title 获取话题列表
+// @Description 获取话题列表接口
+// @Success 200 {object} aimod.AiChatTopicListResp
+// @router /summary/list [get]
+func (this *AiController) SummaryList() {
+	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
+	}
+	list, err := aimod.GetAiChatTopicList(sysUser.AdminId)
+	if err != nil {
+		br.Msg = "获取数据失败!"
+		br.ErrMsg = "获取主题记录信息失败,Err:" + err.Error()
+		return
+	}
+	resp := new(aimod.AiChatTopicListResp)
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 0 - 1
controllers/base_auth.go

@@ -290,7 +290,6 @@ func (c *BaseAuthController) ServeJSON(encoding ...bool) {
 		}
 	}
 
-
 	//新增uuid记录
 	{
 		if _, ok := AdminOperateRecordMap[urlPath]; !ok && !strings.Contains(urlPath, "cygx") {

+ 151 - 0
controllers/business_conf.go

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

+ 188 - 411
controllers/classify.go

@@ -7,7 +7,6 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
-	"time"
 )
 
 // 分类
@@ -52,175 +51,19 @@ func (this *ClassifyController) Add() {
 		br.Msg = "分类名称不可为空"
 		return
 	}
-	/*if menuMap[system.MenuSpecialHandleClassifyShowType] && req.ParentId != 0 && req.ShowType == 0 {
-		br.Msg = "展示类型不可为空"
-		return
-	}
-	if menuMap[system.MenuSpecialHandleClassifyChildMenu] && len(req.MenuList) > 0 && req.ParentId != 0 {
-		br.Msg = "非一级分类不可添加子目录"
-		return
-	}
-	if menuMap[system.MenuSpecialHandleClassifyReportImgs] && (req.ShowType == 1 || req.ShowType == 3) && req.YbRightBanner == "" && req.ParentId == 0 { //当一级报告分类为列表、品种时,增加“报告合集配图”的配置项
-		br.Msg = "报告合集配图不可为空"
-		return
-	}*/
-
-	item, err := models.GetClassifyByName(req.ClassifyName, req.ParentId)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = "获取分类信息失败"
-		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
-		return
-	}
-	if item != nil {
-		br.Msg = "分类名称:" + req.ClassifyName + "已存在"
-		return
-	}
 
-	nowTime := time.Now().Local()
-	classify := new(models.Classify)
-
-	maxSort, e := classify.GetMaxSort()
-	if e != nil {
-		br.Msg = "操作失败"
-		br.ErrMsg = "查询品种排序失败, Err: " + e.Error()
-		return
-	}
-	classify.ClassifyName = req.ClassifyName
-	classify.ParentId = req.ParentId
-	classify.CreateTime = nowTime
-	classify.ModifyTime = nowTime
-	classify.Sort = maxSort + 1
-	classify.Enabled = 1
-	classify.ShowType = 1 //默认列表格式
-	classify.IsShow = 1
-	/*classify.Abstract = req.Abstract
-	classify.Descript = req.Descript
-	classify.Abstract = req.Abstract
-	classify.Descript = req.Descript
-	classify.ReportAuthor = req.ReportAuthor
-	classify.AuthorDescript = req.AuthorDescript
-	classify.ColumnImgUrl = req.ColumnImgUrl
-	classify.ReportImgUrl = req.ReportImgUrl
-	classify.HeadImgUrl = req.HeadImgUrl
-	classify.AvatarImgUrl = req.AvatarImgUrl
-	classify.HomeImgUrl = req.HomeImgUrl
-	classify.ClassifyLabel = req.ClassifyLabel
-	classify.ShowType = req.ShowType
-	classify.HasTeleconference = req.HasTeleconference
-	classify.VipTitle = req.VipTitle
-
-	classify.IsShow = req.IsShow
-	classify.YbFiccSort = req.YbFiccSort
-	classify.YbFiccIcon = req.YbFiccIcon
-	classify.YbFiccPcIcon = req.YbFiccPcIcon
-	classify.YbIconUrl = req.YbIconUrl
-	classify.YbBgUrl = req.YbBgUrl
-	classify.YbListImg = req.YbListImg
-	classify.YbShareBgImg = req.YbShareBgImg
-	classify.YbRightBanner = req.YbRightBanner
-	classify.RelateTel = req.RelateTel
-	classify.RelateVideo = req.RelateVideo
-	if req.ParentId > 0 {
-		parentClassify := new(models.Classify)
-		if parentClassify, err = models.GetClassifyById(req.ParentId); err != nil {
-			br.Msg = "获取父级分类信息失败"
-			br.ErrMsg = "获取父级分类信息失败, Err:" + err.Error()
-			return
-		}
-		updateParent := false
-		updateCols := make([]string, 0)
-		updateCols = append(updateCols, "HasTeleconference")
-		if req.HasTeleconference == 1 {
-			// 二级分类包含电话会,则一级分类也默认包含电话会
-			if parentClassify.HasTeleconference == 0 {
-				parentClassify.HasTeleconference = 1
-				updateParent = true
-			}
-		} else {
-			// 二级分类均无电话会,则一级分类也无电话会
-			if parentClassify.HasTeleconference == 1 {
-				child, err := models.GetClassifyChild(parentClassify.Id, "")
-				if err != nil {
-					br.Msg = "获取子分类失败"
-					br.ErrMsg = "获取子分类失败,Err:" + err.Error()
-					return
-				}
-				// 存在同一级分类下的二级分类有电话会则不变动
-				hasTel := false
-				for i := 0; i < len(child); i++ {
-					if child[i].HasTeleconference == 1 {
-						hasTel = true
-						break
-					}
-				}
-				if !hasTel {
-					parentClassify.HasTeleconference = 0
-					updateParent = true
-				}
-			}
-		}
-		if updateParent {
-			if err = parentClassify.UpdateClassify(updateCols); err != nil {
-				br.Msg = "更新父级分类失败"
-				br.ErrMsg = "更新父级分类失败, Err:" + err.Error()
-				return
-			}
-		}
-	}*/
-	err = models.AddClassify(classify)
+	// 新增分类
+	err, errMsg, isSentEmail := services.AddReportClassify(req.ClassifyName, req.ParentId, req.ChartPermissionIdList)
 	if err != nil {
-		br.Msg = "新增失败"
-		br.ErrMsg = "新增失败,Err:" + err.Error()
-		return
-	}
-
-	// 一级分类-新增子目录
-	/*if classify.ParentId == 0 && len(req.MenuList) > 0 {
-		menus := make([]*models.ClassifyMenu, 0)
-		for i := range req.MenuList {
-			menus = append(menus, &models.ClassifyMenu{
-				MenuName:   req.MenuList[i].MenuName,
-				ClassifyId: classify.Id,
-				Sort:       i + 1,
-				CreateTime: nowTime,
-				ModifyTime: nowTime,
-			})
-		}
-		menuObj := new(models.ClassifyMenu)
-		if e := menuObj.InsertMulti(menus); e != nil {
-			br.Msg = "新增子目录失败"
-			br.ErrMsg = "批量新增子目录失败, Err:" + e.Error()
-			return
-		}
-	}
-	// 二级分类-新增子目录关联
-	if classify.ParentId > 0 && req.ClassifyMenuId > 0 {
-		if e := models.DeleteAndInsertClassifyMenuRelation(classify.Id, req.ClassifyMenuId); e != nil {
-			br.Msg = "新增子目录关联失败"
-			br.ErrMsg = "新增子目录关联失败, Err:" + e.Error()
-			return
-		}
-	}*/
-	//获取报告分类权限列表
-	if classify.ParentId > 0 { //二级分类才能修改权限
-		err = models.EditChartPermissionSearchKeyWordMappingMulti(req.ClassifyName, req.ChartPermissionIdList, classify.Id)
-		if err != nil {
-			br.Msg = "修改分类权限失败"
-			br.ErrMsg = "修改分类权限失败,Err:" + err.Error()
-			return
+		br.Msg = "添加失败"
+		if errMsg != "" {
+			br.Msg = errMsg
 		}
-		go func() {
-			_ = services.EditClassifyChartPermissionSync(req.ClassifyName, classify.Id)
-		}()
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		br.IsSendEmail = isSentEmail
+		return
 	}
 
-	// 新增关联了电话会的二级分类时, 同步FICC活动分类
-	//if req.ParentId > 0 && req.RelateTel == 1 {
-	//	go func() {
-	//		_ = yb.SyncClassifyAndFiccActivityType()
-	//	}()
-	//}
-
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "新增成功"
@@ -360,6 +203,11 @@ func (this *ClassifyController) Delete() {
 		br.Msg = "参数错误"
 		return
 	}
+
+	br.Msg = "报告分类不允许删除"
+	br.IsSendEmail = false
+	return
+
 	item, err := models.GetClassifyById(req.ClassifyId)
 	if err != nil {
 		br.Msg = "获取信息失败"
@@ -407,20 +255,6 @@ func (this *ClassifyController) Edit() {
 		return
 	}
 
-	// 获取系统菜单, 如果没有对应的字段的特殊处理项, 则忽略必填
-	/*menus, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
-	if e != nil {
-		br.Msg = "保存失败"
-		br.ErrMsg = "获取菜单列表失败, Err: " + e.Error()
-		return
-	}
-	menuMap := make(map[string]bool)
-	for _, m := range menus {
-		if m.ButtonCode != "" {
-			menuMap[m.ButtonCode] = true
-		}
-	}*/
-
 	if req.ClassifyId <= 0 {
 		br.Msg = "参数错误"
 		return
@@ -429,199 +263,25 @@ func (this *ClassifyController) Edit() {
 		br.Msg = "分类名称不可为空"
 		return
 	}
-	/*if menuMap[system.MenuSpecialHandleClassifyShowType] && req.ParentId != 0 && req.ShowType == 0 {
-		br.Msg = "展示类型不可为空"
-		return
-	}
-	if req.ParentId == req.ClassifyId {
-		br.Msg = "上级分类不能选择自己"
-		return
-	}
-	if menuMap[system.MenuSpecialHandleClassifyReportImgs] && (req.ShowType == 1 || req.ShowType == 3) && req.YbRightBanner == "" && req.ParentId == 0 { //当一级报告分类为列表、品种时,增加“报告合集配图”的配置项
-		br.Msg = "报告合集配图不可为空"
-		return
-	}*/
 
-	item, err := models.GetClassifyById(req.ClassifyId)
+	// 修改分类
+	err, errMsg, isSentEmail := services.EditReportClassify(req.ClassifyId, req.ClassifyName, req.ChartPermissionIdList)
 	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "分类不存在, 或已被删除"
-			br.ErrMsg = "获取分类信息失败, Err: " + err.Error()
-			return
-		}
-		br.Msg = "获取信息失败"
-		br.ErrMsg = "获取信息失败,Err:" + err.Error()
-		return
-	}
-	originName := item.ClassifyName
-	oldParentId := item.ParentId
-	//originRelateTel := item.RelateTel
-
-	// 重名校验
-	existName, e := models.GetClassifyByName(req.ClassifyName, item.ParentId)
-	if e != nil && e.Error() != utils.ErrNoRow() {
-		br.Msg = "获取信息失败"
-		br.ErrMsg = "获取重名分类失败, Err: " + err.Error()
-		return
-	}
-	if existName != nil && existName.Id != item.Id {
-		br.Msg = "分类名称:" + req.ClassifyName + "已存在"
-		return
-	}
-	item.ClassifyName = req.ClassifyName
-	item.ParentId = req.ParentId
-	item.ModifyTime = time.Now().Local()
-	cols := make([]string, 0)
-	cols = append(cols, "ClassifyName", "ParentId", "ModifyTime")
-	if e := item.UpdateClassify(cols); e != nil {
 		br.Msg = "修改失败"
-		br.ErrMsg = "修改失败,Err:" + e.Error()
-		return
-	}
-
-	/*
-		item.Abstract = req.Abstract
-		item.Descript = req.Descript
-		item.ReportAuthor = req.ReportAuthor
-		item.AuthorDescript = req.AuthorDescript
-		item.ColumnImgUrl = req.ColumnImgUrl
-		item.HeadImgUrl = req.HeadImgUrl
-		item.AvatarImgUrl = req.AvatarImgUrl
-		item.ReportImgUrl = req.ReportImgUrl
-		item.HomeImgUrl = req.HomeImgUrl
-		item.ClassifyLabel = req.ClassifyLabel
-		item.ShowType = req.ShowType
-		item.HasTeleconference = req.HasTeleconference
-		item.VipTitle = req.VipTitle
-		//	item.Sort = req.Sort
-		item.IsShow = req.IsShow
-		item.YbFiccSort = req.YbFiccSort
-		item.YbFiccIcon = req.YbFiccIcon
-		item.YbFiccPcIcon = req.YbFiccPcIcon
-		item.YbIconUrl = req.YbIconUrl
-		item.YbBgUrl = req.YbBgUrl
-		item.YbListImg = req.YbListImg
-		item.YbShareBgImg = req.YbShareBgImg
-		item.YbRightBanner = req.YbRightBanner
-		item.RelateTel = req.RelateTel
-		item.RelateVideo = req.RelateVideo
-		item.ModifyTime = time.Now().Local()
-		cols := make([]string, 0)
-		cols = append(cols, "ClassifyName", "Abstract", "ParentId", "Descript", "ReportAuthor", "AuthorDescript", "ColumnImgUrl",
-			"HeadImgUrl", "AvatarImgUrl", "ReportImgUrl", "HomeImgUrl", "ClassifyLabel", "ShowType", "HasTeleconference", "VipTitle", "Sort",
-			"IsShow", "YbFiccSort", "YbFiccIcon", "YbFiccPcIcon", "YbIconUrl", "YbBgUrl", "YbListImg", "YbShareBgImg", "YbRightBanner",
-			"RelateTel", "RelateVideo", "ModifyTime")
-		if e := item.UpdateClassify(cols); e != nil {
-			br.Msg = "修改失败"
-			br.ErrMsg = "修改失败,Err:" + e.Error()
-			return
-		}
-		// 一级分类关联设置会强制修改二级分类的所有关联设置
-		if item.ParentId == 0 {
-			if e = models.UpdateChildClassifyRelateSetting(item.Id, req.RelateTel, req.RelateVideo); e != nil {
-				br.Msg = "更新二级分类关联设置失败"
-				br.ErrMsg = "更新二级分类关联设置失败, Err: " + e.Error()
-				return
-			}
+		if errMsg != "" {
+			br.Msg = errMsg
 		}
-	*/
-	// 为二级分类时, 更新父级分类是否含电话会字段
-	if req.ParentId > 0 {
-		//二级分类才能修改权限
-		err = models.EditChartPermissionSearchKeyWordMappingMulti(item.ClassifyName, req.ChartPermissionIdList, req.ClassifyId)
-		if err != nil {
-			br.Msg = "修改分类权限失败"
-			br.ErrMsg = "修改分类权限失败,Err:" + err.Error()
-			return
-		}
-		go func() {
-			_ = services.EditClassifyChartPermissionSync(item.ClassifyName, req.ClassifyId)
-		}()
-		/*go func() {
-			_ = services.UpdateParentClassifyHasTel(req.ClassifyId, req.ParentId, req.HasTeleconference)
-		}()*/
-	}
-
-	// 更新报告分类名称/父级分类后
-	go func() {
-		_ = services.AfterUpdateClassifyNameOrParent(item.Id, item.ParentId, oldParentId, originName, item.ClassifyName)
-	}()
-
-	// 获取编辑前子目录列表
-	/*classifyId := item.Id
-	var menuCond string
-	var menuPars []interface{}
-	menuCond += ` AND classify_id = ?`
-	menuPars = append(menuPars, classifyId)
-	menuList, e := models.GetClassifyMenuList(menuCond, menuPars)
-	if e != nil {
-		br.Msg = "保存失败"
-		br.ErrMsg = "获取分类子目录列表失败, Err:" + e.Error()
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		br.IsSendEmail = isSentEmail
 		return
 	}
-	oriMenuIds := make([]int, 0)
-	for i := range menuList {
-		oriMenuIds = append(oriMenuIds, menuList[i].MenuId)
-	}
-
-	// 一级分类-新增/编辑/删除子目录
-	if item.ParentId == 0 && len(req.MenuList) > 0 {
-		nowTime := time.Now().Local()
-		insertMenus := make([]*models.ClassifyMenu, 0)
-		editMenus := make([]*models.ClassifyMenu, 0)
-		deleteMenuIds := make([]int, 0)
-		menuIds := make([]int, 0)
-		for i := range req.MenuList {
-			m := req.MenuList[i]
-
-			v := new(models.ClassifyMenu)
-			v.MenuName = req.MenuList[i].MenuName
-			v.ClassifyId = classifyId
-			v.Sort = i + 1
-			v.MenuId = m.MenuId
-			v.ModifyTime = nowTime
-			if v.MenuId > 0 {
-				// 编辑
-				editMenus = append(editMenus, v)
-				menuIds = append(menuIds, m.MenuId)
-			} else {
-				// 新增
-				v.CreateTime = nowTime
-				insertMenus = append(insertMenus, v)
-			}
-		}
-		// 编辑前存在子目录则取"编辑前子目录IDs与编辑时子目录IDs的差集"作为删除IDs
-		if len(oriMenuIds) > 0 {
-			deleteMenuIds = utils.MinusInt(oriMenuIds, menuIds)
-		}
-		if e = models.InsertAndUpdateClassifyMenu(insertMenus, editMenus, deleteMenuIds); e != nil {
-			br.Msg = "保存失败"
-			br.ErrMsg = "新增/编辑/删除分类子目录失败, Err:" + e.Error()
-			return
-		}
-	}
-
-	// 二级分类-新增子目录关联
-	if item.ParentId > 0 {
-		if e := models.DeleteAndInsertClassifyMenuRelation(classifyId, req.ClassifyMenuId); e != nil {
-			br.Msg = "新增子目录关联失败"
-			br.ErrMsg = "新增子目录关联失败, Err:" + e.Error()
-			return
-		}
-	}*/
-
-	// 关联电话会选项被更改时, 同步FICC活动分类
-	//if originRelateTel != req.RelateTel {
-	//	go func() {
-	//		_ = yb.SyncClassifyAndFiccActivityType()
-	//	}()
-	//}
 
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "修改成功"
 }
 
+// ParentClassify
 // @Title 获取父级分类接口
 // @Description 获取父级分类
 // @Success 200 {object} models.Classify
@@ -632,7 +292,7 @@ func (this *ClassifyController) ParentClassify() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	items, err := models.ParentClassify()
+	items, err := models.GetAllClassify()
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -678,6 +338,8 @@ func (this *ClassifyController) ParentClassify() {
 		})
 	}
 
+	resp = services.GetClassifyTreeRecursive(resp, 0)
+
 	br.Data = resp
 	br.Ret = 200
 	br.Success = true
@@ -729,31 +391,54 @@ func (this *ClassifyController) ListClassify() {
 	}()
 
 	keyWord := this.GetString("KeyWord")
-	companyType := this.GetString("CompanyType")
-	hideDayWeek, _ := this.GetInt("HideDayWeek")
-
 	reqEnabled, _ := this.GetInt("Enabled", -1)
-	// 商家不隐藏晨周报
-	if utils.BusinessCode != utils.BusinessCodeRelease {
-		hideDayWeek = 0
-	}
+
 	enabled := -1
 	if reqEnabled == 1 {
 		enabled = reqEnabled
 	}
 
-	list, err := models.GetClassifyList(keyWord, companyType, hideDayWeek, enabled)
+	list, err := models.GetClassifyListByKeyword(keyWord, enabled)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		return
 	}
 
-	parentIds := make([]int, 0)
+	if keyWord != `` {
+		idMap := make(map[int]bool)
+
+		currParentClassifyIdList := make([]int, 0)
+		for _, v := range list {
+			idMap[v.Id] = true
+			if v.ParentId > 0 {
+				currParentClassifyIdList = append(currParentClassifyIdList, v.ParentId)
+			}
+		}
+
+		findList := list
+		list = make([]*models.ClassifyList, 0)
+
+		tmpList, tmpErr := services.GetParentClassifyListByParentIdList(currParentClassifyIdList)
+		if tmpErr != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.Id]; !ok {
+				list = append(list, v)
+			}
+		}
+
+		list = append(list, findList...)
+	}
+
+	classifyIdList := make([]int, 0)
 	for i := range list {
-		parentIds = append(parentIds, list[i].Id)
+		classifyIdList = append(classifyIdList, list[i].Id)
 	}
-	parentIdLen := len(parentIds)
+	parentIdLen := len(classifyIdList)
 	if parentIdLen == 0 {
 		resp := &models.ClassifyListResp{
 			List: list,
@@ -765,12 +450,12 @@ func (this *ClassifyController) ListClassify() {
 		return
 	}
 
-	// 获取一级分类-子目录列表
+	// 获取子目录列表
 	menuListMap := make(map[int][]*models.ClassifyMenu, 0)
 	var menuCond string
 	var menuPars []interface{}
 	menuCond += ` AND classify_id IN (` + utils.GetOrmInReplace(parentIdLen) + `)`
-	menuPars = append(menuPars, parentIds)
+	menuPars = append(menuPars, classifyIdList)
 	parentMenus, e := models.GetClassifyMenuList(menuCond, menuPars)
 	if e != nil {
 		br.Msg = "获取失败"
@@ -784,26 +469,13 @@ func (this *ClassifyController) ListClassify() {
 		menuListMap[parentMenus[i].ClassifyId] = append(menuListMap[parentMenus[i].ClassifyId], parentMenus[i])
 	}
 
-	// 获取子分类
-	children, e := models.GetClassifyChildByParentIds(parentIds, keyWord, enabled)
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取子分类失败"
-		return
-	}
-	childrenIds := make([]int, 0)
-	for i := range children {
-		childrenIds = append(childrenIds, children[i].Id)
-	}
-	childrenIdsLen := len(childrenIds)
-
-	// 获取二级分类-子目录关联
+	// 分类与子目录关联
 	relateMap := make(map[int]int, 0)
-	if childrenIdsLen > 0 {
+	{
 		var relateCond string
 		var relatePars []interface{}
-		relateCond += ` AND classify_id IN (` + utils.GetOrmInReplace(childrenIdsLen) + `)`
-		relatePars = append(relatePars, childrenIds)
+		relateCond += ` AND classify_id IN (` + utils.GetOrmInReplace(parentIdLen) + `)`
+		relatePars = append(relatePars, classifyIdList)
 		relates, e := models.GetClassifyMenuRelationList(relateCond, relatePars)
 		if e != nil {
 			br.Msg = "获取失败"
@@ -817,34 +489,26 @@ func (this *ClassifyController) ListClassify() {
 
 	// 查询分类绑定的权限
 	permissionList, _ := models.GetAllPermissionMapping()
-	classifyPermissionMap := make(map[int][]int, 0)
+	classifyPermissionMap := make(map[int][]int)
 	if len(permissionList) > 0 {
 		for _, v := range permissionList {
 			classifyPermissionMap[v.ClassifyId] = append(classifyPermissionMap[v.ClassifyId], v.ChartPermissionId)
 		}
 	}
-	// 二级分类
-	childrenMap := make(map[int][]*models.ClassifyItem, 0)
-	for i := range children {
+	// 遍历分类并绑定子目录和权限
+	for i, v := range list {
+		list[i].ClassifyMenuList = menuListMap[v.Id]
 
-		if childrenMap[children[i].ParentId] == nil {
-			childrenMap[children[i].ParentId] = make([]*models.ClassifyItem, 0)
+		list[i].ClassifyMenuId = relateMap[v.Id]
+		if permissionIds, ok := classifyPermissionMap[v.Id]; ok {
+			list[i].ChartPermissionIdList = permissionIds
 		}
-		tmp := &models.ClassifyItem{
-			Classify:       *children[i],
-			ClassifyMenuId: relateMap[children[i].Id],
-		}
-		if permissionIds, ok := classifyPermissionMap[children[i].Id]; ok {
-			tmp.ChartPermissionIdList = permissionIds
-		}
-		childrenMap[children[i].ParentId] = append(childrenMap[children[i].ParentId], tmp)
 	}
 
-	// 一级分类
-	for i := range list {
-		list[i].ClassifyMenuList = menuListMap[list[i].Id]
-		list[i].Child = childrenMap[list[i].Id]
-	}
+	// 先将分类列表排序
+	services.SortClassifyListBySortAndCreateTime(list)
+	// 接着转换结构
+	list = services.GetClassifyListTreeRecursive(list, 0)
 
 	resp := new(models.ClassifyListResp)
 	resp.List = list
@@ -882,7 +546,7 @@ func (this *ClassifyController) ClassifyPermission() {
 	}
 
 	//获取报告分类权限列表
-	list, err := models.GetPermission(classifyInfo.Id)
+	list, err := models.GetPermissionByClassifyId(classifyInfo.Id)
 	if err != nil {
 		br.Msg = "获取分类信息失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1086,3 +750,116 @@ func (this *ClassifyController) SetEnabled() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// ClassifyPermissionV2
+// @Title 获取权限设置基础信息
+// @Description 获取权限设置基础信息接口
+// @Param   CompanyType   query   string  true       "客户类型:传空字符串或者不传为全部,'ficc','权益'"
+// @Param   NoUpgrade   query   bool  false       "是否不展示升级权限,默认为 false"
+// @Success 200 {object} company.PermissionSetResp
+// @router /permission/list [get]
+func (this *ClassifyController) ClassifyPermissionV2() {
+	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 = "参数错误"
+		return
+	}
+	resp := make([]*models.ChartPermissionItem, 0)
+
+	// 拥有的品种ID列表
+	permissionIdMap := make(map[int]bool)
+	{
+		//获取报告分类详情
+		classifyInfo, err := models.GetClassifyById(classifyId)
+		if err != nil {
+			br.Msg = "获取分类信息失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+
+		//获取报告分类权限列表
+		list, err := models.GetPermission(classifyInfo.Id)
+		if err != nil {
+			br.Msg = "获取分类信息失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+
+		for _, v := range list {
+			permissionIdMap[v.ChartPermissionId] = true
+		}
+	}
+
+	// 没有关联品种时,直接返回
+	num := len(permissionIdMap)
+	if num <= 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+	cond := ` and product_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, 1)
+	list, e := services.GetChartPermissionList(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
+		return
+	}
+
+	// 品种树
+	childMap := make(map[int][]*models.ChartPermissionItem)
+
+	tmpResp := make([]*models.ChartPermissionItem, 0)
+	for _, v := range list {
+		t := new(models.ChartPermissionItem)
+		t.PermissionId = v.ChartPermissionId
+		t.PermissionName = v.PermissionName
+		t.ParentId = v.ParentId
+		t.IsPublic = v.IsPublic
+		t.Enabled = v.Enabled
+		t.Sort = v.Sort
+		t.CreateTime = v.CreatedTime.Format(utils.FormatDateTime)
+		t.Child = make([]*models.ChartPermissionItem, 0)
+		if v.ParentId == 0 {
+			tmpResp = append(tmpResp, t)
+			continue
+		}
+		if v.ParentId > 0 {
+			if _, ok := permissionIdMap[v.ChartPermissionId]; ok {
+				if childMap[v.ParentId] == nil {
+					childMap[v.ParentId] = make([]*models.ChartPermissionItem, 0)
+				}
+				childMap[v.ParentId] = append(childMap[v.ParentId], t)
+			}
+		}
+	}
+	for _, r := range tmpResp {
+		childList, ok := childMap[r.PermissionId]
+		if !ok {
+			continue
+		}
+		r.Child = childList
+		resp = append(resp, r)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 10 - 0
controllers/data_manage/chart_classify.go

@@ -654,6 +654,14 @@ func (this *ChartClassifyController) DeleteChartClassify() {
 			br.ErrMsg = "删除失败,Err:" + err.Error()
 			return
 		}
+		if chartInfo.ChartType == utils.CHART_TYPE_SECTION_COMBINE {
+			err = data_manage.DeleteChartSeriesAndEdbMapping(req.ChartInfoId)
+			if err != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = "删除失败,Err:" + err.Error()
+				return
+			}
+		}
 		//删除ES
 		{
 			go data.EsDeleteChartInfo(req.ChartInfoId)
@@ -709,6 +717,8 @@ func (this *ChartClassifyController) DeleteChartClassify() {
 				}
 			}
 		}
+		// 删除图表中的指标引用
+		_ = data_manage.DeleteEdbRelationByObjectId(req.ChartInfoId, utils.EDB_RELATION_CHART)
 		//新增操作日志
 		{
 			chartLog := new(data_manage.ChartInfoLog)

+ 1 - 0
controllers/data_manage/chart_common.go

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

+ 277 - 47
controllers/data_manage/chart_info.go

@@ -15,13 +15,14 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"os"
 	"os/exec"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // 图表管理
@@ -1014,6 +1015,7 @@ func (this *ChartInfoController) ChartInfoDetail() {
 			br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
 			return
 		}
+
 		chartInfo.ChartThemeStyle = chartTheme.Config
 		chartInfo.ChartThemeId = chartTheme.ChartThemeId
 
@@ -1378,6 +1380,8 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 			return
 		}
 		extraConfigStr = chartInfo.BarConfig
+	} else if chartInfo != nil && chartInfo.ChartType == utils.CHART_TYPE_SECTION_COMBINE {
+		extraConfigStr = req.ExtraConfig
 	}
 
 	// 获取图表中的指标数据
@@ -1503,6 +1507,7 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
 		return
 	}
+
 	chartInfo.ChartThemeStyle = chartTheme.Config
 	chartInfo.ChartThemeId = chartTheme.ChartThemeId
 
@@ -2479,7 +2484,6 @@ func (this *ChartInfoController) ChartInfoDetailFromUniqueCode() {
 
 func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool, sysUser *system.Admin) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
 	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
-
 	msg = `获取失败`
 	adminId := sysUser.AdminId
 
@@ -2502,6 +2506,28 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
 
 			// 图表权限校验
 			{
@@ -2597,50 +2623,6 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 				if err != nil || resp == nil {
 					return
 				}
-				// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-				var myCond string
-				var myPars []interface{}
-				myCond += ` AND a.admin_id=? `
-				myPars = append(myPars, adminId)
-				myCond += ` AND a.chart_info_id=? `
-				myPars = append(myPars, chartInfo.ChartInfoId)
-				myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-				if err != nil && err.Error() != utils.ErrNoRow() {
-					msg = "获取失败"
-					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-					return
-				}
-				resp.ChartInfo.IsAdd = false
-				resp.ChartInfo.MyChartId = 0
-				resp.ChartInfo.MyChartClassifyId = ""
-				if myList != nil && len(myList) > 0 {
-					resp.ChartInfo.IsAdd = true
-					resp.ChartInfo.MyChartId = myList[0].MyChartId
-					resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-				}
-
-				//判断是否加入我的图库
-				{
-					var myChartCondition string
-					var myChartPars []interface{}
-					myChartCondition += ` AND a.admin_id=? `
-					myChartPars = append(myChartPars, adminId)
-					myChartCondition += ` AND a.chart_info_id=? `
-					myChartPars = append(myChartPars, chartInfo.ChartInfoId)
-
-					myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					if myChartList != nil && len(myChartList) > 0 {
-						chartInfo.IsAdd = true
-						chartInfo.MyChartId = myChartList[0].MyChartId
-						chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
-					}
-				}
-
 				isOk = true
 				fmt.Println("source redis")
 				return
@@ -2655,6 +2637,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		errMsg = "获取主题信息失败,Err:" + err.Error()
 		return
 	}
+
 	chartInfo.ChartThemeStyle = chartTheme.Config
 	chartInfo.ChartThemeId = chartTheme.ChartThemeId
 
@@ -3906,6 +3889,7 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 
 	edbInfoId := this.GetString("EdbInfoId")
 	chartType, _ := this.GetInt("ChartType")
+	isAxis, _ := this.GetInt("IsAxis")
 
 	calendar := this.GetString("Calendar")
 	if calendar == "" {
@@ -3946,6 +3930,7 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 			br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
 			return
 		}
+
 		chartInfo.ChartThemeStyle = chartTheme.Config
 		chartInfo.ChartThemeId = chartTheme.ChartThemeId
 
@@ -4048,7 +4033,7 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 		startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
 
 		// 获取图表中的指标数据
-		edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartConvertEdbData(chartInfoId, chartType, calendar, startDate, endDate, mappingList, extraConfigStr, chartInfo.SeasonExtraConfig)
+		edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartConvertEdbData(chartInfoId, chartType, calendar, startDate, endDate, mappingList, extraConfigStr, chartInfo.SeasonExtraConfig, isAxis)
 		if err != nil {
 			br.Msg = "获取失败"
 			if errMsg != `` {
@@ -4262,3 +4247,248 @@ func (this *ChartInfoController) ForumDelete() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// PreviewSeasonChartInfo
+// @Title 图表-获取预览的季节性图
+// @Description 图表-获取预览的季节性图
+// @Param	ExtraConfig	body data_manage.PreviewRadarChartReq true "type json string"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview/season [post]
+func (this *ChartInfoController) PreviewSeasonChartInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req data_manage.PreviewSeasonChartReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	chartType := utils.CHART_TYPE_SEASON
+	if len(req.ChartEdbInfoList) <= 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	edbInfoIdArr := make([]int, 0)
+	edbInfoNameMap := make(map[int]string)
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+		edbInfoNameMap[v.EdbInfoId] = v.EdbAliasName
+	}
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdArr)
+	for _, v := range mappingList {
+		if name, ok := edbInfoNameMap[v.EdbInfoId]; ok {
+			v.EdbAliasName = name
+		}
+	}
+
+	var seasonExtraConfig string
+	seasonExtra, tErr := json.Marshal(req.SeasonExtraConfig)
+	if tErr != nil {
+		br.Msg = "季节性图表配置信息异常"
+		br.ErrMsg = "季节性图表配置信息异常,Err:" + tErr.Error()
+		return
+	}
+	seasonExtraConfig = string(seasonExtra)
+	// 获取图表中的指标数据
+	edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartEdbData(0, chartType, "", "", "", mappingList, "", seasonExtraConfig)
+	if err != nil {
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	warnEdbList := make([]string, 0)
+	for _, v := range edbList {
+		if v.IsNullData {
+			warnEdbList = append(warnEdbList, v.EdbName+"("+v.EdbCode+")")
+		}
+	}
+	if len(warnEdbList) > 0 {
+		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, utils.CHART_SOURCE_DEFAULT, chartType)
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
+	}
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ChartInfoImgSetByBase64
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_base64 [post]
+func (this *ChartInfoController) ChartInfoImgSetByBase64() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	uploadDir := "static/images/"
+	if !utils.FileIsExist(uploadDir) {
+		err := os.MkdirAll(uploadDir, utils.DIR_MOD)
+		if err != nil {
+			br.Msg = "图表保存失败"
+			br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	//var saveToOssPath string
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	var fileName, outFileName string
+	fileName = randStr + ".txt"
+	fileName = uploadDir + fileName
+	err := utils.SaveToFile(imgData, fileName)
+	if err != nil {
+		br.Msg = "图片保存失败"
+		br.ErrMsg = "图片保存失败,Err:" + err.Error()
+		return
+	}
+
+	// 删除临时存储的svg文件
+	defer func() {
+		err = os.Remove(fileName)
+		if err != nil {
+			utils.FileLog.Info("删除临时存储的svg文件失败, err: " + err.Error())
+		}
+	}()
+	outFileName = randStr + ".png"
+
+	doneChannel := make(chan bool, 1)
+	errorChannel := make(chan error, 1)
+
+	cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
+
+	go func() {
+		output, err := cmd.CombinedOutput()
+		if err != nil {
+			utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
+			errorChannel <- err
+			return
+		}
+		doneChannel <- true
+	}()
+	defer func() {
+		_ = os.Remove(outFileName)
+		if err != nil {
+			utils.FileLog.Info("删除生产的图片文件失败, err: " + err.Error())
+		}
+	}()
+
+	select {
+	case <-time.After(30 * time.Second):
+		utils.FileLog.Info("执行超过30秒 杀死超时进程")
+		e := cmd.Process.Kill()
+		if e != nil {
+			fmt.Println("cmd kill err: ", e.Error())
+			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+			br.Msg = "图片生成失败"
+			br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+			return
+		}
+		fmt.Println("timeout kill process")
+	case <-doneChannel:
+		fmt.Println("done")
+	case err := <-errorChannel:
+		br.Msg = "文件上传失败"
+		br.ErrMsg = fmt.Sprintf("execute command failure err: %s", err.Error())
+		fmt.Println("execute command failure err:" + err.Error())
+		return
+	}
+
+	//上传到阿里云 和 minio
+	resourceUrl := ``
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		br.Msg = "上传失败"
+		br.ErrMsg = "初始化OSS服务失败"
+		return
+	}
+	resourceUrl, err = ossClient.UploadFile(outFileName, outFileName, "")
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 158 - 0
controllers/data_manage/chart_info_section.go

@@ -0,0 +1,158 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"strings"
+)
+
+// GetChartTypeList
+// @Title 获取所有图表类型
+// @Description 获取所有图表类型
+// @Success 200 {object} data_manage.AddChartInfoResp
+// @router /chart_info/type_list [get]
+func (this *ChartInfoController) GetChartTypeList() {
+	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
+	}
+
+	listTmp, err := data_manage.GetChartTypeList()
+	if err != nil {
+		br.Msg = "获取图表类型失败"
+		br.ErrMsg = "获取图表类型失败,Err:" + err.Error()
+		return
+	}
+	//遍历list,将id和name组成map
+	chartTypeMap := make(map[int][]data_manage.ChartType)
+	for _, v := range listTmp {
+		if v.ParentId > 0 {
+			chartTypeMap[v.ParentId] = append(chartTypeMap[v.ParentId], v)
+		}
+	}
+	list := make([]data_manage.ChartTypeList, 0)
+	for _, v := range listTmp {
+		if v.ParentId == 0 {
+			tmp := data_manage.ChartTypeList{
+				ChartTypeId:     v.ChartTypeId,
+				ChartTypeName:   v.ChartTypeName,
+				ChartTypeNameEn: v.ChartTypeNameEn,
+				ParentId:        v.ParentId,
+			}
+			child, ok := chartTypeMap[v.ChartTypeId]
+			if ok {
+				tmp.Child = child
+			}
+			list = append(list, tmp)
+		}
+	}
+
+	resp := data_manage.ChartTypeListResp{List: list}
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取图表类型成功"
+
+}
+
+// PreviewSectionCombineChartInfo
+// @Title 图表-获取预览的截面组合图数据
+// @Description 图表-获取预览的截面组合图数据
+// @Param	request	body data_manage.PreviewSectionCombineChartReq true "type json string"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview/section_combine [post]
+func (this *ChartInfoController) PreviewSectionCombineChartInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req data_manage.PreviewSectionCombineChartReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	chartType := utils.CHART_TYPE_SECTION_COMBINE
+	edbInfoIdArr, err, errMsg := data.CheckChartExtraConfig(chartType, req.ExtraConfig)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "添加失败:" + err.Error()
+		return
+	}
+	chartInfo.ChartType = chartType
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdArr)
+
+	// 获取图表中的指标数据
+	edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartEdbData(0, chartType, "", "", "", mappingList, req.ExtraConfig, "")
+	if err != nil {
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	warnEdbList := make([]string, 0)
+	for _, v := range edbList {
+		if v.IsNullData {
+			warnEdbList = append(warnEdbList, v.EdbName+"("+v.EdbCode+")")
+		}
+	}
+	if len(warnEdbList) > 0 {
+		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
+	}
+
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, utils.CHART_SOURCE_DEFAULT, chartType)
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
+	}
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 29 - 1
controllers/data_manage/chart_theme.go

@@ -53,6 +53,15 @@ func (c *ChartThemeController) List() {
 		return
 	}
 
+	// 兼容历史数据,加入新字段LineOptionList
+	for i, v := range list {
+		newConfig, e := data.ConvertOldChartOptions(v.Config)
+		if e != nil {
+			continue
+		}
+		list[i].Config = newConfig
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -133,6 +142,7 @@ func (c *ChartThemeController) GetThemePreviewData() {
 	chartInfo := new(data_manage.ChartInfoView)
 	// 图表额外数据参数
 	extraConfigStr := ``
+	var barConfig data_manage.BarChartInfoReq
 
 	// 开始时间,结束时间
 	var tmpStartDate, tmpEndDate string
@@ -161,6 +171,8 @@ func (c *ChartThemeController) GetThemePreviewData() {
 		chartInfo.ChartName = "散点图"
 	case 7: // 柱形图
 		edbInfoIdList = []int{1, 2, 3, 4, 5}
+		chartInfo.LeftMin = "260"
+		chartInfo.LeftMax = "430"
 		extraConfigStr = `{"EdbInfoIdList":[{"EdbInfoId":1,"Name":"指标1","NameEn":"","Source":0},{"EdbInfoId":2,"Name":"指标2","NameEn":"","Source":0},{"EdbInfoId":3,"Name":"指标3","NameEn":"","Source":0},{"EdbInfoId":4,"Name":"指标4","NameEn":"","Source":0},{"EdbInfoId":5,"Name":"指标5","NameEn":"","Source":0}],"DateList":[{"Type":2,"Date":"","Value":100,"Color":"","Name":""},{"Type":1,"Date":"","Value":0,"Color":"","Name":""}],"Sort":{"Sort":0,"DateIndex":0},"XEdbList":null,"YEdbList":null,"Unit":"千桶","UnitEn":""}`
 		chartInfo.ChartName = "柱形图"
 	case 10: // 截面散点图
@@ -173,6 +185,12 @@ func (c *ChartThemeController) GetThemePreviewData() {
 		chartInfo.LeftMax = "60000"
 		extraConfigStr = `{"DateList":[{"Type":3,"Date":"2023-11-01","Value":0,"Color":"","Name":""},{"Type":1,"Date":"","Value":0,"Color":"","Name":""}]}`
 		chartInfo.ChartName = "雷达图"
+	case utils.CHART_TYPE_SECTION_COMBINE:
+		edbInfoIdList = []int{19, 20, 21, 22, 23, 24}
+		chartInfo.LeftMin = "0"
+		chartInfo.LeftMax = "4000"
+		extraConfigStr = `{"DateConfList":[],"IsHeap":1,"XDataList":[{"Name":"内销","NameEn":""},{"Name":"出口","NameEn":""},{"Name":"销量","NameEn":""},{"Name":"产量","NameEn":""}],"UnitList":{"LeftName":"万台","LeftNameEn":"","RightName":"%","RightNameEn":"%","RightTwoName":"","RightTwoNameEn":""},"BaseChartSeriesName":"增量","SortType":0,"SeriesList":[{"ChartSeriesId":1,"SeriesName":"增量","SeriesNameEn":"","ChartStyle":"bar","ChartColor":"#FF0000","ChartWidth":2,"IsPoint":1,"IsNumber":1,"IsAxis":0,"MaxData":4000,"MinData":0,"IsOrder":true,"EdbInfoList":[{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":0,"DateConf":{},"DateConfName":""}],"DataList":[1455,1076,2532,3729],"NoDataEdbIdList":[]},{"ChartSeriesId":2,"SeriesName":"增速","SeriesNameEn":"","ChartStyle":"line","ChartColor":"#FF0000","ChartWidth":2,"IsPoint":1,"IsNumber":1,"IsAxis":1,"MaxData":15,"MinData":10,"IsOrder":true,"EdbInfoList":[{"ChartSeriesEdbMappingId":0,"ChartSeriesId":0,"EdbInfoId":0,"DateConf":{},"DateConfName":""}],"DataList":[13,12,12,14],"NoDataEdbIdList":[]}]}`
+		chartInfo.ChartName = "图表标题"
 	default:
 		br.Msg = "暂不支持该类型"
 		br.IsSendEmail = false
@@ -246,6 +264,7 @@ func (c *ChartThemeController) GetThemePreviewData() {
 	resp.XEdbIdValue = xEdbIdValue
 	resp.YDataList = yDataList
 	resp.DataResp = dataResp
+	resp.BarChartInfo = barConfig
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
@@ -329,7 +348,7 @@ func (c *ChartThemeController) Add() {
 }
 
 // Edit
-// @Title 新增主题
+// @Title 编辑主题
 // @Description
 // @Param	request	body request.DeleteThemeConfReq true "type json string"
 // @Success 200 Ret=200 修改成功
@@ -661,6 +680,15 @@ func (c *ChartThemeController) ListBySource() {
 		return
 	}
 
+	// 兼容历史数据,加入新字段LineOptionList
+	for i, v := range list {
+		newConfig, e := data.ConvertOldChartOptions(v.Config)
+		if e != nil {
+			continue
+		}
+		list[i].Config = newConfig
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"

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

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

+ 1306 - 137
controllers/data_manage/correlation/correlation_chart_info.go

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

+ 29 - 26
controllers/data_manage/cross_variety/chart_info.go

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

+ 27 - 9
controllers/data_manage/edb_info.go

@@ -2,7 +2,6 @@ package data_manage
 
 import (
 	"encoding/json"
-	"eta/eta_api/cache"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/company"
@@ -11,7 +10,6 @@ import (
 	request2 "eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/models/data_manage/request"
 	"eta/eta_api/models/data_manage/response"
-	"eta/eta_api/models/mgo"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
@@ -22,12 +20,12 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"go.mongodb.org/mongo-driver/bson"
 	"strconv"
 	"strings"
 	"sync"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // EdbInfoController 数据管理
@@ -1735,8 +1733,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				return
 			}
 
-			obj := mgo.BaseFromBusinessData{}
-			tmpDataList, err := obj.GetLimitDataList(bson.M{"index_code": edbCode}, utils.EDB_DATA_LIMIT, []string{"-data_time"})
+			_, tmpDataList, err := data.GetPageBaseBusinessIndexData(edbCode, 1, utils.EDB_DATA_LIMIT)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "获取失败"
 				br.ErrMsg = "获取自有数据已存在信息失败,Err:" + err.Error()
@@ -1745,7 +1742,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 			dataItems := make([]*data_manage.EdbInfoSearchData, 0)
 			for _, v := range tmpDataList {
 				dataItems = append(dataItems, &data_manage.EdbInfoSearchData{
-					DataTime: v.DataTime.Format(utils.FormatDate),
+					DataTime: v.DataTime,
 					Value:    v.Value,
 				})
 			}
@@ -2433,6 +2430,11 @@ func (this *EdbInfoController) EdbInfoEdit() {
 		return
 	}
 
+	// 记录旧的指标基本信息
+	oldEdbName := edbInfo.EdbName
+	oldFrequency := edbInfo.Frequency
+	oldUnit := edbInfo.Unit
+
 	var haveOperaAuth bool
 	// 权限校验
 	{
@@ -2526,6 +2528,20 @@ func (this *EdbInfoController) EdbInfoEdit() {
 	//	}
 	//}
 
+	// 新增保存记录日志
+	oldEdbInfo := new(data_manage.EdbInfo)
+	oldEdbInfo.EdbInfoId = edbInfo.EdbInfoId
+	oldEdbInfo.EdbName = oldEdbName
+	oldEdbInfo.Frequency = oldFrequency
+	oldEdbInfo.Unit = oldUnit
+	newEdbInfoRecord := new(data_manage.EdbInfoEditRecord)
+	newEdbInfoRecord.EdbName = req.EdbName
+	newEdbInfoRecord.Frequency = req.Frequency
+	newEdbInfoRecord.Unit = req.Unit
+	newEdbInfoRecord.OperateUserId = sysUser.AdminId
+	newEdbInfoRecord.OperateUserRealName = sysUser.RealName
+	go data.AddEditEdbInfoRcord(oldEdbInfo, newEdbInfoRecord)
+
 	//新增操作日志
 	{
 		edbLog := new(data_manage.EdbInfoLog)
@@ -3380,6 +3396,10 @@ func (this *EdbInfoController) EdbInfoFilterByEs() {
 			pars = append(pars, frequency)
 		}
 
+		if source > 0 && filterSource != 5 {
+			condition += ` AND source = ? `
+			pars = append(pars, source)
+		}
 		// 查询只允许添加预测指标的搜索
 		if isAddPredictEdb {
 			condition += ` AND frequency in ("日度","周度","月度") `
@@ -3889,8 +3909,6 @@ func (this *ChartInfoController) EdbInfoReplace() {
 		return
 	}
 
-	//加入到缓存队列中处理
-	go cache.AddReplaceEdbInfo(oldEdbInfo, newEdbInfo)
 	br.Msg = "替换成功"
 	br.ErrMsg = "替换成功"
 	br.Ret = 200

+ 40 - 8
controllers/data_manage/edb_info_calculate.go

@@ -9,11 +9,12 @@ import (
 	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"net/url"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // 计算指标
@@ -230,7 +231,6 @@ func (this *ChartInfoController) CalculateDetail() {
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		return
 	}
-
 	fullEdb := new(data_manage.EdbInfoFullClassify)
 	classifyList, err, errMsg := data.GetFullClassifyByClassifyId(edbInfo.ClassifyId)
 	if err != nil {
@@ -848,6 +848,8 @@ func (this *ChartInfoController) CalculateBatchEdit() {
 		Frequency:     req.Frequency,
 		Unit:          req.Unit,
 		ClassifyId:    req.ClassifyId,
+		AdminId:       this.SysUser.AdminId,
+		AdminName:     this.SysUser.RealName,
 		Formula:       req.Formula, //N数值移动平均计算、环比值、环差值
 		FromEdbInfoId: req.FromEdbInfoId,
 		Source:        req.Source,
@@ -1468,7 +1470,7 @@ func (this *EdbInfoController) QueryEdbDataTable() {
 	tableName := data_manage.GetEdbDataTableName(edbInfo.Source, edbInfo.SubSource)
 
 	var templateStr string
-	if edbInfo.Source == utils.DATA_SOURCE_BUSINESS {
+	if edbInfo.Source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		templateStr = fmt.Sprintf("# 查询条件\nquery = {\"edb_code\": \"%s\"}\n# 排序\nsort = [(\"data_time\", -1)]  # -1 表示降序排列,对应 SQL 的 DESC\nprojection = {\"data_time\": 1, \"value\": 1, \"_id\": 0}  # 只选择data_time和value字段,忽略_id字段\n# 使用 find() 方法获取数据,然后使用 aggregate() 转换为列表\nraw_data = list(collection.find(query, projection).sort(sort))\n# 将结果转换为 DataFrame\nraw = pd.DataFrame(raw_data)\n# 转换data_time字段为本地时区时间\nraw['data_time'] = raw['data_time'].apply(lambda x: x.replace(tzinfo=utc_tz)).dt.tz_convert(local_tz).dt.strftime('%s')", edbInfo.EdbCode, "%Y-%m-%d")
 	} else {
 		templateStr = fmt.Sprintf("sql1 = f\"\"\"SELECT data_time,`value` FROM %s WHERE edb_code = '%s' ORDER BY data_time DESC;\"\"\"\nraw = pandas_fetch_all(sql1, db)", tableName, edbInfo.EdbCode)
@@ -1948,6 +1950,10 @@ func (this *ChartInfoController) BatchCalculateBatchEdit() {
 		}
 		return
 	}
+
+	req.AdminId = sysUser.AdminId
+	req.AdminName = sysUser.RealName
+
 	req.EdbList = reqEdbList
 	// 调用指标库去更新
 	reqJson, err := json.Marshal(req)
@@ -2117,6 +2123,7 @@ func (this *ChartInfoController) CalculateComputeCorrelation() {
 // @Param   SysUserIds   query   int  true       "创建人"
 // @Param   Keyword   query   string  false       "关键词搜索"
 // @Param   Frequency   query   string  false       "频度"
+// @Param   EdbInfoType  query  int  false  "指标类型: 0-普通指标; 1-预测指标"
 // @Success 200 {object} data_manage.EdbInfoSearchResp
 // @router /edb_info/calculate/multi/choice [get]
 func (this *ChartInfoController) CalculateMultiChoice() {
@@ -2134,13 +2141,28 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 	edbInfoIds := this.GetString("EdbInfoIds")
 	selectAll, _ := this.GetBool("SelectAll")
 	notFrequency := this.GetString("NotFrequency")
+	edbInfoType, _ := this.GetInt("EdbInfoType", 0)
 	var edbIdArr []int
+	var filterEdbIds []int
+	if edbInfoIds != "" {
+		arr := strings.Split(edbInfoIds, ",")
+		for _, v := range arr {
+			t, e := strconv.Atoi(v)
+			if e != nil {
+				br.Msg = "参数有误"
+				br.ErrMsg = fmt.Sprintf("指标ID有误, %s", v)
+				return
+			}
+			filterEdbIds = append(filterEdbIds, t)
+		}
+	}
 
 	if selectAll {
 		// 如果勾了列表全选,那么EdbCode传的就是排除的code
 
-		var condition string
-		var pars []interface{}
+		condition := ` AND edb_info_type = ? `
+		pars := make([]interface{}, 0)
+		pars = append(pars, edbInfoType)
 
 		if classifyIds != "" {
 			classifyIdsArr := strings.Split(classifyIds, ",")
@@ -2175,14 +2197,19 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 				}
 			}
 		}
-		edbList, e := data_manage.GetEdbInfoFilter(condition, pars)
+		edbList, e := data_manage.GetEdbInfoListByCond(condition, pars)
 		if e != nil {
 			br.Msg = "获取指标列表失败"
 			br.ErrMsg = "获取指标列表失败,Err:" + e.Error()
 			return
 		}
 
+		filterLen := len(filterEdbIds)
 		for _, v := range edbList {
+			// 全选-排除传入的指标
+			if filterLen > 0 && utils.InArrayByInt(filterEdbIds, v.EdbInfoId) {
+				continue
+			}
 			edbIdArr = append(edbIdArr, v.EdbInfoId)
 		}
 	} else {
@@ -2263,6 +2290,7 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 				EdbInfoId:     info.EdbInfoId,
 				ClassifyId:    info.ClassifyId,
 				HaveOperaAuth: haveOperaAuth,
+				EdbInfoType:   info.EdbInfoType,
 			}
 			searchItemList = append(searchItemList, searchItem)
 		}
@@ -2283,6 +2311,7 @@ func (this *ChartInfoController) CalculateMultiChoice() {
 // @Param   SysUserIds   query   int  true       "创建人"
 // @Param   Keyword   query   string  false       "关键词搜索"
 // @Param   Frequency   query   string  false       "频度"
+// @Param   EdbInfoType  query  int  false  "指标类型: 0-普通指标; 1-预测指标"
 // @Success 200 {object} data_manage.EdbInfoSearchResp
 // @router /edb_info/calculate/multi/search [get]
 func (this *ChartInfoController) CalculateMultiSearch() {
@@ -2301,6 +2330,7 @@ func (this *ChartInfoController) CalculateMultiSearch() {
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
 	notFrequency := this.GetString("NotFrequency")
+	edbInfoType, _ := this.GetInt("EdbInfoType", 0)
 
 	var startSize int
 	if pageSize <= 0 {
@@ -2313,8 +2343,9 @@ func (this *ChartInfoController) CalculateMultiSearch() {
 
 	var edbIdArr []int
 
-	condition := ` AND edb_info_type = 0 `
-	var pars []interface{}
+	condition := ` AND edb_info_type = ? `
+	pars := make([]interface{}, 0)
+	pars = append(pars, edbInfoType)
 
 	if classifyIds != "" {
 		classifyIdsArr := strings.Split(classifyIds, ",")
@@ -2422,6 +2453,7 @@ func (this *ChartInfoController) CalculateMultiSearch() {
 				EndDate:         info.EndDate,
 				EndValue:        info.EndValue,
 				HaveOperaAuth:   haveOperaAuth,
+				EdbInfoType:     info.EdbInfoType,
 			}
 			searchItemList = append(searchItemList, searchItem)
 		}

+ 232 - 2
controllers/data_manage/edb_info_refresh.go

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

+ 298 - 0
controllers/data_manage/edb_info_relation.go

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

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

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

+ 185 - 11
controllers/data_manage/excel/excel_info.go

@@ -248,6 +248,7 @@ func (c *ExcelInfoController) Add() {
 		ParentId:           req.ParentId,
 		UpdateUserId:       sysUser.AdminId,
 		UpdateUserRealName: sysUser.RealName,
+		SourcesFrom:        req.SourcesFrom,
 	}
 
 	excelEdbMappingList := make([]*excel3.ExcelEdbMapping, 0)
@@ -307,6 +308,8 @@ func (c *ExcelInfoController) Add() {
 	resp.ExcelInfoId = excelInfo.ExcelInfoId
 	resp.UniqueCode = excelInfo.UniqueCode
 
+	//新增指标引用记录
+	_ = data.SaveExcelEdbInfoRelation(excelInfo.ExcelInfoId, excelInfo.Source, false)
 	//新增操作日志
 	//{
 	//	excelLog := &data_manage.ExcelInfoLog{
@@ -662,6 +665,70 @@ func (c *ExcelInfoController) Detail() {
 		excelDetail.Editor = markStatus.Editor
 	}
 
+	// 图表的指标来源
+	if excelDetail.Source == utils.TIME_TABLE {
+		jsonStrByte, err := json.Marshal(excelDetail.TableData)
+		if err != nil {
+			br.Msg = "自定义表格数据获取失败"
+			br.ErrMsg = "自定义表格数据获取失败,转json失败,Err:" + err.Error()
+			return
+		}
+		var tableData request.TableDataReq
+		err = json.Unmarshal(jsonStrByte, &tableData)
+		if err != nil {
+			br.Msg = "自定义表格数据获取失败"
+			br.ErrMsg = "自定义表格数据获取失败,json转结构体失败,Err:" + err.Error()
+			return
+		}
+		sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList(tableData.EdbInfoIdList)
+		if err != nil {
+			br.Msg = "自定义表格数据获取失败"
+			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			return
+		}
+		excelDetail.ExcelSource = strings.Join(sourceNameList, ",")
+		excelDetail.ExcelSourceEn = strings.Join(sourceNameEnList, ",")
+
+	}
+
+	// 数据刷新-混合表格
+	if excelDetail.Source == utils.MIXED_TABLE {
+		// todo 刷新动态表的所有子表中关联的指标数据
+		jsonByte, e := json.Marshal(excelDetail.TableData)
+		if e != nil {
+			br.Msg = "刷新失败"
+			br.ErrMsg = "JSON格式化混合表格数据失败, Err: " + e.Error()
+			return
+		}
+		var tableData request.MixedTableReq
+		if e = json.Unmarshal(jsonByte, &tableData); e != nil {
+			br.Msg = "刷新失败"
+			br.ErrMsg = "解析混合表格数据失败, Err: " + e.Error()
+			return
+		}
+		edbInfoIds := make([]int, 0)
+		edbInfoIdExist := make(map[int]bool)
+		if len(tableData.Data) > 0 {
+			for _, t := range tableData.Data {
+				for _, v := range t {
+					if v.EdbInfoId > 0 && !edbInfoIdExist[v.EdbInfoId] {
+						edbInfoIdExist[v.EdbInfoId] = true
+						edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+					}
+				}
+			}
+		}
+		sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList(edbInfoIds)
+		if err != nil {
+			br.Msg = "自定义表格数据获取失败"
+			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			return
+		}
+		excelDetail.ExcelSource = strings.Join(sourceNameList, ",")
+		excelDetail.ExcelSourceEn = strings.Join(sourceNameEnList, ",")
+	}
+
+
 	// excel表格按钮权限
 	if excelDetail.Source != utils.BALANCE_TABLE {
 		excelDetail.Button = excel2.GetExcelInfoOpButton(sysUser, excelDetail.SysUserId, excelDetail.Source, excelDetail.HaveOperaAuth)
@@ -936,12 +1003,14 @@ func (c *ExcelInfoController) Edit() {
 	excelInfo.UpdateUserId = sysUser.AdminId
 	excelInfo.UpdateUserRealName = sysUser.RealName
 	excelInfo.Content = content
+	excelInfo.SourcesFrom = req.SourcesFrom
+
 	// 自动保存时不会传缩略图,也就不更新这个字段
 	var updateExcelInfoParams []string
 	if req.ExcelImage != "" {
-		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "ExcelImage", "Content", "UpdateUserId", "UpdateUserRealName"}
+		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "ExcelImage", "Content", "SourcesFrom"}
 	} else {
-		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "Content", "UpdateUserId", "UpdateUserRealName"}
+		updateExcelInfoParams = []string{"ModifyTime", "ExcelName", "ExcelType", "ExcelClassifyId", "Content", "SourcesFrom"}
 	}
 
 	excelEdbMappingList := make([]*excel3.ExcelEdbMapping, 0)
@@ -997,7 +1066,8 @@ func (c *ExcelInfoController) Edit() {
 		ExcelInfoId: excelInfo.ExcelInfoId,
 		UniqueCode:  excelInfo.UniqueCode,
 	}
-
+	//新增指标引用记录
+	_ = data.SaveExcelEdbInfoRelation(excelInfo.ExcelInfoId, excelInfo.Source, false)
 	//删除公共图库那边的缓存
 	_ = utils.Rc.Delete(utils.HZ_CHART_LIB_EXCEL_TABLE_DETAIL + ":" + excelInfo.UniqueCode)
 
@@ -1479,6 +1549,7 @@ func (c *ExcelInfoController) GetExcelTableData() {
 	}
 
 	var tableData excel.TableData
+	var excelSource, excelSourceEn string
 	switch excelInfo.Source {
 	case utils.EXCEL_DEFAULT:
 		luckySheetData, err := excel.GetLuckySheetData(excelInfo.Content)
@@ -1513,6 +1584,14 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
 			return
 		}
+		sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList(result.EdbInfoIdList)
+		if err != nil {
+			br.Msg = "自定义表格数据获取失败"
+			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			return
+		}
+		excelSource = strings.Join(sourceNameList, ",")
+		excelSourceEn = strings.Join(sourceNameEnList, ",")
 	case utils.MIXED_TABLE:
 		var result request.MixedTableReq
 		err = json.Unmarshal([]byte(excelInfo.Content), &result)
@@ -1530,13 +1609,32 @@ func (c *ExcelInfoController) GetExcelTableData() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, true)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
 			return
 		}
-
+		edbInfoIds := make([]int, 0)
+		edbInfoIdExist := make(map[int]bool)
+		if len(newResult) > 0 {
+			for _, t := range newResult {
+				for _, v := range t {
+					if v.EdbInfoId > 0 && !edbInfoIdExist[v.EdbInfoId] {
+						edbInfoIdExist[v.EdbInfoId] = true
+						edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+					}
+				}
+			}
+		}
+		sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList(edbInfoIds)
+		if err != nil {
+			br.Msg = "自定义表格数据获取失败"
+			br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+			return
+		}
+		excelSource = strings.Join(sourceNameList, ",")
+		excelSourceEn = strings.Join(sourceNameEnList, ",")
 	}
 
 	tableData = excel.HandleTableCell(tableData)
@@ -1558,11 +1656,14 @@ func (c *ExcelInfoController) GetExcelTableData() {
 	}
 
 	resp := response.ExcelTableDetailResp{
-		UniqueCode: excelInfo.UniqueCode,
-		ExcelImage: excelInfo.ExcelImage,
-		ExcelName:  excelInfo.ExcelName,
-		TableInfo:  tableData,
-		Config:     config,
+		UniqueCode:    excelInfo.UniqueCode,
+		ExcelImage:    excelInfo.ExcelImage,
+		ExcelName:     excelInfo.ExcelName,
+		TableInfo:     tableData,
+		Config:        config,
+		SourcesFrom:   excelInfo.SourcesFrom,
+		ExcelSource:   excelSource,
+		ExcelSourceEn: excelSourceEn,
 	}
 	br.Ret = 200
 	br.Success = true
@@ -1743,12 +1844,23 @@ func (c *ExcelInfoController) GetFirstEdbData() {
 		}
 	}
 
+	sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{edbInfoId})
+	if err != nil {
+		br.Msg = "自定义表格数据获取失败"
+		br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+		return
+	}
+	excelSource := strings.Join(sourceNameList, ",")
+	excelSourceEn := strings.Join(sourceNameEnList, ",")
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
 	br.Data = response.TableDataItem{
 		EdbInfoId: edbInfoId,
 		Data:      dataList,
+		ExcelSource: excelSource,
+		ExcelSourceEn: excelSourceEn,
 	}
 }
 
@@ -1811,12 +1923,23 @@ func (c *ExcelInfoController) GetOtherEdbData() {
 		}
 	}
 
+	sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{req.EdbInfoId})
+	if err != nil {
+		br.Msg = "自定义表格数据获取失败"
+		br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+		return
+	}
+	excelSource := strings.Join(sourceNameList, ",")
+	excelSourceEn := strings.Join(sourceNameEnList, ",")
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
 	br.Data = response.TableDataItem{
 		EdbInfoId: req.EdbInfoId,
 		Data:      dataList,
+		ExcelSource: excelSource,
+		ExcelSourceEn: excelSourceEn,
 	}
 }
 
@@ -2344,7 +2467,7 @@ func (c *ExcelInfoController) Download() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + err.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, false)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()
@@ -2469,6 +2592,8 @@ func (c *ExcelInfoController) Copy() {
 		br.IsSendEmail = isSendEmail
 		return
 	}
+	//新增指标引用记录
+	_ = data.SaveExcelEdbInfoRelation(excelInfo.ExcelInfoId, excelInfo.Source, true)
 
 	resp := new(response.AddExcelInfoResp)
 	resp.ExcelInfoId = excelInfo.ExcelInfoId
@@ -2788,3 +2913,52 @@ func (c *ExcelInfoController) GetBatchChartRefreshResult() {
 	br.Ret = 200
 	br.Success = true
 }
+
+
+// GetBatchChartRefreshResult
+// @Title 获取批量刷新表格结果
+// @Description 获取批量刷新表格结果
+// @Param   EdbInfoId   query   int  true       "edb id"
+// @Success Ret=200 刷新成功
+// @router /excel_info/get_edb_source [get]
+func (c *ExcelInfoController) GetEdbSource() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	edbInfoId, _ := c.GetInt("EdbInfoId")
+	if edbInfoId <= 0  {
+		br.Msg = "请选择指标"
+		br.ErrMsg = "请选择指标"
+		br.IsSendEmail = false
+		return
+	}
+	sourceNameList, sourceNameEnList, err := excel2.GetEdbSourceByEdbInfoIdList([]int{edbInfoId})
+	if err != nil {
+		br.Msg = "自定义表格数据获取失败"
+		br.ErrMsg = "自定义表格数据获取失败,Err:" + err.Error()
+		return
+	}
+	excelSource := strings.Join(sourceNameList, ",")
+	excelSourceEn := strings.Join(sourceNameEnList, ",")
+
+	var resp struct {
+		ExcelSource string `description:"表格来源"`
+		ExcelSourceEn string `description:"表格来源(英文)"`
+	}
+
+	resp.ExcelSource = excelSource
+	resp.ExcelSourceEn = excelSourceEn
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+}

+ 816 - 0
controllers/data_manage/factor_edb_series.go

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

+ 270 - 69
controllers/data_manage/future_good/future_good_chart_info.go

@@ -15,13 +15,14 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"os"
 	"os/exec"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // FutureGoodChartInfoController 商品价格图表管理
@@ -324,7 +325,7 @@ func (this *FutureGoodChartInfoController) ChartInfoAdd() {
 		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
 		return
 	}
-	var req data_manage.AddChartInfoReq
+	var req data_manage.AddFutureGoodChartInfoReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
 		br.Msg = "参数解析异常!"
@@ -544,6 +545,9 @@ func (this *FutureGoodChartInfoController) ChartInfoAdd() {
 	resp.UniqueCode = chartInfo.UniqueCode
 	resp.ChartType = req.ChartType
 
+	chartInfo.ChartInfoId = chartInfoId
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfoId)
 
@@ -606,7 +610,7 @@ func (this *FutureGoodChartInfoController) ChartInfoEdit() {
 		return
 	}
 
-	var req data_manage.EditChartInfoReq
+	var req data_manage.EditFutureGoodChartInfoReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
 		br.Msg = "参数解析异常!"
@@ -767,6 +771,9 @@ func (this *FutureGoodChartInfoController) ChartInfoEdit() {
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
+
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据
@@ -1289,8 +1296,8 @@ func (this *FutureGoodChartInfoController) ChartInfoDetail() {
 func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dateType, startYear int, startDate, endDate string, sysUser *system.Admin, br *models.BaseResponse) {
 	chartInfoId := chartInfo.ChartInfoId
 
-	var edbInfoMapping, futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping
-	edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartInfoId)
+	var futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping
+	edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -1303,7 +1310,7 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 		return
 	}
 
-	if edbInfoMapping == nil {
+	if len(edbInfoMappingList) == 0 {
 		br.Msg = "请选择ETA指标"
 		br.ErrMsg = "请选择ETA指标"
 		br.IsSendEmail = false
@@ -1317,16 +1324,18 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 	}
 
 	maxYear := 0
-	if edbInfoMapping.LatestDate != "" {
-		latestDateT, _ := time.Parse(utils.FormatDate, edbInfoMapping.LatestDate)
-		maxYear = latestDateT.Year()
+	for _, v := range edbInfoMappingList {
+		if v.LatestDate != "" {
+			latestDateT, _ := time.Parse(utils.FormatDate, v.LatestDate)
+			if maxYear < latestDateT.Year() {
+				maxYear = latestDateT.Year()
+			}
+		}
 	}
 	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, maxYear)
 
 	// 商品价格曲线图的一些配置
-	var barConfig data_manage.BarChartInfoReq
-	barChartInfoDateList := make([]data_manage.BarChartInfoDateReq, 0)
-
+	var barConfig data_manage.FutureGoodBarChartInfoReq
 	if chartInfo.BarConfig == `` {
 		br.Msg = "商品价格曲线图未配置"
 		br.ErrMsg = "商品价格曲线图未配置"
@@ -1339,10 +1348,24 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 		return
 	}
 
-	barChartInfoDateList = barConfig.DateList
+	baseEdbInfoId := barConfig.BaseEdbInfoId
+	var baseEdbInfoMapping *data_manage.ChartEdbInfoMapping
+	// todo 兼容历史数据,
+	if baseEdbInfoId == 0 {
+		// 默认取第一个现货指标
+		baseEdbInfoId = edbInfoMappingList[0].EdbInfoId
+		baseEdbInfoMapping = edbInfoMappingList[0]
+	} else {
+		baseEdbInfoMapping, err = data_manage.GetChartEdbMappingByEdbInfoId(baseEdbInfoId)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+			return
+		}
+	}
 
 	// 获取图表中的指标数据
-	barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(chartInfoId, startDate, endDate, edbInfoMapping, futureGoodEdbInfoMapping, barChartInfoDateList, true)
+	barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(chartInfoId, startDate, endDate, baseEdbInfoMapping, edbInfoMappingList, futureGoodEdbInfoMapping, barConfig, true)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -1410,7 +1433,7 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
 	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 
-	resp := new(data_manage.ChartInfoDetailResp)
+	resp := new(data_manage.FutureGoodChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.XEdbIdValue = xEdbIdValue
@@ -1492,14 +1515,37 @@ func (this *FutureGoodChartInfoController) ChartInfoDetailFromUniqueCode() {
 }
 
 // GetChartInfoDetailFromUniqueCode 根据编码获取图表详情
-func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool, sysUser *system.Admin) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
-	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool, sysUser *system.Admin) (resp *data_manage.FutureChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
+	resp = new(data_manage.FutureChartInfoDetailFromUniqueCodeResp)
 
 	adminId := sysUser.AdminId
 	// 指标数据map
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1569,33 +1615,12 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}
@@ -1625,7 +1650,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
-	edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartInfoId)
+	edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		msg = "获取失败"
 		errMsg = "获取图表,现货指标信息失败,Err:" + err.Error()
@@ -1639,8 +1664,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	}
 
 	// 商品价格曲线图的一些配置
-	var barConfig data_manage.BarChartInfoReq
-	barChartInfoDateList := make([]data_manage.BarChartInfoDateReq, 0)
+	var barConfig data_manage.FutureGoodBarChartInfoReq
 
 	if chartInfo.BarConfig == `` {
 		msg = "商品价格曲线图未配置"
@@ -1654,10 +1678,25 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		return
 	}
 
-	barChartInfoDateList = barConfig.DateList
+	baseEdbInfoId := barConfig.BaseEdbInfoId
+	var baseEdbInfoMapping *data_manage.ChartEdbInfoMapping
+	// todo 兼容历史数据,
+	if baseEdbInfoId == 0 {
+		// 默认取第一个现货指标
+		baseEdbInfoId = edbInfoMappingList[0].EdbInfoId
+		baseEdbInfoMapping = edbInfoMappingList[0]
+		barConfig.BaseEdbInfoId = baseEdbInfoId
+	} else {
+		baseEdbInfoMapping, err = data_manage.GetChartEdbMappingByEdbInfoId(baseEdbInfoId)
+		if err != nil {
+			msg = "获取失败"
+			errMsg = "获取图表,指标信息失败,Err:" + err.Error()
+			return
+		}
+	}
 
 	// 获取图表中的指标数据
-	barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(chartInfoId, startDate, endDate, edbInfoMapping, futureGoodEdbInfoMapping, barChartInfoDateList, true)
+	barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(chartInfoId, startDate, endDate, baseEdbInfoMapping, edbInfoMappingList, futureGoodEdbInfoMapping, barConfig, true)
 	if err != nil {
 		msg = "获取失败"
 		errMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -1669,7 +1708,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		errMsg = "商品价格曲线图表异常"
 		return
 	}
-	baseEdbInfo := edbList[0] //现货指标
+	baseEdbInfo := baseEdbInfoMapping //现货指标
 
 	for _, v := range edbList {
 		if v.IsNullData {
@@ -2071,16 +2110,13 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 	switch chartInfo.Source {
 	case utils.CHART_SOURCE_FUTURE_GOOD, utils.CHART_SOURCE_FUTURE_GOOD_PROFIT:
 		// 现货指标
-		edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartInfo.ChartInfoId)
+		edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(chartInfo.ChartInfoId)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取图表,现货指标信息失败,Err:" + err.Error()
 			return
 		}
-		edbList := []*data_manage.ChartEdbInfoMapping{
-			edbInfoMapping,
-		}
-
+		edbList := edbInfoMappingList
 		// 期货指标
 		futureGoodEdbInfoMapping, err := data_manage.GetFutureGoodEdbChartEdbMapping(chartInfo.ChartInfoId)
 		if err != nil {
@@ -2091,8 +2127,7 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 		edbList = append(edbList, futureGoodEdbInfoMapping)
 
 		// 商品价格曲线图的一些配置
-		var barConfig data_manage.BarChartInfoReq
-		barChartInfoDateList := make([]data_manage.BarChartInfoDateReq, 0)
+		var barConfig data_manage.FutureGoodBarChartInfoReq
 
 		if chartInfo.BarConfig == `` {
 			br.Msg = "商品价格曲线图未配置"
@@ -2106,8 +2141,6 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 			return
 		}
 
-		barChartInfoDateList = barConfig.DateList
-
 		startDate := chartInfo.StartDate
 		endDate := chartInfo.EndDate
 
@@ -2121,8 +2154,24 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 			}
 		}
 
+		baseEdbInfoId := barConfig.BaseEdbInfoId
+		var baseEdbInfoMapping *data_manage.ChartEdbInfoMapping
+		// todo 兼容历史数据,
+		if baseEdbInfoId == 0 {
+			// 默认取第一个现货指标
+			baseEdbInfoId = edbInfoMappingList[0].EdbInfoId
+			baseEdbInfoMapping = edbInfoMappingList[0]
+		} else {
+			baseEdbInfoMapping, err = data_manage.GetChartEdbMappingByEdbInfoId(baseEdbInfoId)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
+
 		// 获取图表中的指标数据
-		barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(chartInfo.ChartInfoId, startDate, endDate, edbInfoMapping, futureGoodEdbInfoMapping, barChartInfoDateList, false)
+		barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(chartInfo.ChartInfoId, startDate, endDate, baseEdbInfoMapping, edbInfoMappingList, futureGoodEdbInfoMapping, barConfig, false)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -2134,7 +2183,7 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 			br.ErrMsg = "商品价格曲线图表异常"
 			return
 		}
-		baseEdbInfo := edbList[0] //现货指标
+		baseEdbInfo := baseEdbInfoMapping //现货指标
 
 		for _, v := range edbList {
 			if v.IsNullData {
@@ -2180,10 +2229,12 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 		chartInfo.Button.IsEdit = chartInfo.IsEdit
 		chartInfo.Button.IsCopy = true
 		//判断是否需要展示英文标识
-		chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
+		edbListTmp := make([]*data_manage.ChartEdbInfoMapping, 0)
+		edbListTmp = append(edbListTmp, baseEdbInfo)
+		chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbListTmp, chartInfo.Source, chartInfo.ChartType)
 		chartInfo.UnitEn = baseEdbInfo.UnitEn
 
-		resp := data_manage.ChartInfoDetailFromUniqueCodeResp{}
+		resp := data_manage.FutureChartInfoDetailFromUniqueCodeResp{}
 		resp.ChartInfo = chartInfo
 		resp.EdbInfoList = edbList
 		resp.XEdbIdValue = xEdbIdValue
@@ -2893,7 +2944,7 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 		this.ServeJSON()
 	}()
 
-	var req data_manage.BarChartInfoReq
+	var req data_manage.FutureGoodBarChartInfoReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
 		br.Msg = "参数解析异常!"
@@ -2917,13 +2968,14 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 		br.Msg = "请选择日期"
 		return
 	}
-	chartInfo := new(data_manage.ChartInfoView)
-
-	if len(req.EdbInfoIdList) != 2 {
-		br.Msg = "指标数量异常"
+	if req.BaseEdbInfoId == 0 {
+		br.Msg = "请选择日期基准指标"
 		return
 	}
+	chartInfo := new(data_manage.ChartInfoView)
+
 	var edbInfoMapping, futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping
+	edbInfoMappingList := make([]*data_manage.ChartEdbInfoMapping, 0)
 	for _, v := range req.EdbInfoIdList {
 		if v.Source == utils.CHART_SOURCE_DEFAULT {
 			edbInfoMapping, err = data_manage.GetChartEdbMappingByEdbInfoId(v.EdbInfoId)
@@ -2932,6 +2984,7 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 				br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
 				return
 			}
+			edbInfoMappingList = append(edbInfoMappingList, edbInfoMapping)
 		} else if v.Source == utils.CHART_SOURCE_FUTURE_GOOD {
 			futureGoodEdbInfoMapping, err = data_manage.GetChartEdbMappingByFutureGoodEdbInfoId(v.EdbInfoId)
 			if err != nil {
@@ -2947,7 +3000,7 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 		}
 	}
 
-	if edbInfoMapping == nil {
+	if len(edbInfoMappingList) == 0 {
 		br.Msg = "请选择ETA指标"
 		br.ErrMsg = "请选择ETA指标"
 		br.IsSendEmail = false
@@ -2960,8 +3013,17 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 		return
 	}
 
+	baseEdbInfoId := req.BaseEdbInfoId
+
+	baseEdbInfoMapping, err := data_manage.GetChartEdbMappingByEdbInfoId(baseEdbInfoId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+
 	// 获取图表中的指标数据
-	barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(0, "", "", edbInfoMapping, futureGoodEdbInfoMapping, req.DateList, true)
+	barConfigEdbInfoIdList, edbList, xEdbIdValue, xDataList, yDataList, err := future_goodServ.GetChartEdbData(0, "", "", baseEdbInfoMapping, edbInfoMappingList, futureGoodEdbInfoMapping, req, true)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -2990,7 +3052,7 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 	//判断是否需要展示英文标识
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, chartInfo.Source, chartInfo.ChartType)
 
-	resp := new(data_manage.ChartInfoDetailResp)
+	resp := new(data_manage.FutureGoodChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.XEdbIdValue = xEdbIdValue
@@ -3219,3 +3281,142 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 	br.Msg = "编辑成功"
 	br.IsAddLog = true
 }
+
+// ChartInfoImgSetByBase64
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_base64 [post]
+func (this *FutureGoodChartInfoController) ChartInfoImgSetByBase64() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	uploadDir := "static/images/"
+	if !utils.FileIsExist(uploadDir) {
+		err := os.MkdirAll(uploadDir, utils.DIR_MOD)
+		if err != nil {
+			br.Msg = "图表保存失败"
+			br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	//var saveToOssPath string
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	var fileName, outFileName string
+	fileName = randStr + ".txt"
+	fileName = uploadDir + fileName
+	err := utils.SaveToFile(imgData, fileName)
+	if err != nil {
+		br.Msg = "图片保存失败"
+		br.ErrMsg = "图片保存失败,Err:" + err.Error()
+		return
+	}
+
+	// 删除临时存储的svg文件
+	defer func() {
+		err = os.Remove(fileName)
+		if err != nil {
+			utils.FileLog.Info("删除临时存储的svg文件失败, err: " + err.Error())
+		}
+	}()
+	outFileName = randStr + ".png"
+
+	doneChannel := make(chan bool, 1)
+	errorChannel := make(chan error, 1)
+
+	cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
+
+	go func() {
+		output, err := cmd.CombinedOutput()
+		if err != nil {
+			utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
+			errorChannel <- err
+			return
+		}
+		doneChannel <- true
+	}()
+	defer func() {
+		_ = os.Remove(outFileName)
+		if err != nil {
+			utils.FileLog.Info("删除生产的图片文件失败, err: " + err.Error())
+		}
+	}()
+
+	select {
+	case <-time.After(30 * time.Second):
+		utils.FileLog.Info("执行超过30秒 杀死超时进程")
+		e := cmd.Process.Kill()
+		if e != nil {
+			fmt.Println("cmd kill err: ", e.Error())
+			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+			br.Msg = "图片生成失败"
+			br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+			return
+		}
+		fmt.Println("timeout kill process")
+	case <-doneChannel:
+		fmt.Println("done")
+	case err := <-errorChannel:
+		br.Msg = "文件上传失败"
+		br.ErrMsg = fmt.Sprintf("execute command failure err: %s", err.Error())
+		fmt.Println("execute command failure err:" + err.Error())
+		return
+	}
+
+	//上传到阿里云 和 minio
+	resourceUrl := ``
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		br.Msg = "上传失败"
+		br.ErrMsg = "初始化OSS服务失败"
+		return
+	}
+	resourceUrl, err = ossClient.UploadFile(outFileName, outFileName, "")
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 155 - 111
controllers/data_manage/future_good/future_good_profit_chart_info.go

@@ -99,6 +99,11 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 		br.IsSendEmail = false
 		return
 	}
+	if len(extraReq.EdbInfoIdList) == 0 {
+		br.Msg = "请选择ETA指标"
+		br.IsSendEmail = false
+		return
+	}
 	if len(extraReq.FutureGoodEdbInfoIdList) <= 0 {
 		br.Msg = "请选择期货商品指标"
 		br.IsSendEmail = false
@@ -113,34 +118,31 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 	extraConf := string(extraConfByte)
 
 	var edbInfoIdArr []int
+	baseEdbInfo := new(data_manage.EdbInfo)
 	// ETA指标
-	edbInfo, err := data_manage.GetEdbInfoById(extraReq.BaseEdbInfoId)
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(extraReq.EdbInfoIdList)
 	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "指标不存在!"
-			br.ErrMsg = "指标不存在,edbInfoId:" + strconv.Itoa(extraReq.BaseEdbInfoId)
-			return
-		} else {
-			br.Msg = "获取指标信息失败!"
-			br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
-			return
+		br.Msg = "获取指标信息失败!"
+		br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoListMap := make(map[int]*data_manage.EdbInfo)
+	for k, v := range edbInfoList {
+		edbInfoList[k].EdbNameSource = v.EdbName
+		edbInfoListMap[v.EdbInfoId] = v
+		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+		if v.EdbInfoId == extraReq.BaseEdbInfoId {
+			baseEdbInfo = v
 		}
 	}
-	if edbInfo == nil {
-		br.Msg = "指标已被删除,请重新选择!"
-		br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(extraReq.BaseEdbInfoId)
-		return
-	} else {
-		if edbInfo.EdbInfoId <= 0 {
+	for _, v := range extraReq.EdbInfoIdList {
+		if _, ok := edbInfoListMap[v]; !ok {
 			br.Msg = "指标已被删除,请重新选择!"
-			br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(extraReq.BaseEdbInfoId)
+			br.ErrMsg = "指标不存在,edbInfoId:" + strconv.Itoa(v)
 			return
 		}
 	}
 
-	edbInfoIdArr = append(edbInfoIdArr, extraReq.BaseEdbInfoId)
-	edbInfo.EdbNameSource = edbInfo.EdbName
-
 	// 期货商品指标(主力合约)
 	futureGoodEdbInfoMap := make(map[int]*future_good.FutureGoodEdbInfo)
 	zlFutureGoodEdbInfoList := make([]*future_good.FutureGoodEdbInfo, 0)
@@ -234,25 +236,27 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 	// 关联指标
 	mapList := make([]*data_manage.ChartEdbMapping, 0)
 	{
-		mapList = append(mapList, &data_manage.ChartEdbMapping{
-			ChartEdbMappingId: 0,
-			EdbInfoId:         extraReq.BaseEdbInfoId,
-			CreateTime:        time.Now(),
-			ModifyTime:        time.Now(),
-			UniqueCode:        utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + strconv.Itoa(extraReq.BaseEdbInfoId)),
-			//MaxData:           0,
-			//MinData:           0,
-			IsOrder:     true,
-			IsAxis:      1,
-			EdbInfoType: 1,
-			//LeadValue:         0,
-			//LeadUnit:          "",
-			//ChartStyle:        "",
-			//ChartColor:        "",
-			//PredictChartColor: "",
-			//ChartWidth:        0,
-			Source: 1,
-		})
+		for _, v := range edbInfoList {
+			mapList = append(mapList, &data_manage.ChartEdbMapping{
+				ChartEdbMappingId: 0,
+				EdbInfoId:         v.EdbInfoId,
+				CreateTime:        time.Now(),
+				ModifyTime:        time.Now(),
+				UniqueCode:        utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + strconv.Itoa(v.EdbInfoId)),
+				//MaxData:           0,
+				//MinData:           0,
+				IsOrder:     true,
+				IsAxis:      1,
+				EdbInfoType: 1,
+				//LeadValue:         0,
+				//LeadUnit:          "",
+				//ChartStyle:        "",
+				//ChartColor:        "",
+				//PredictChartColor: "",
+				//ChartWidth:        0,
+				Source: 1,
+			})
+		}
 
 		for _, v := range futureGoodEdbInfoMap {
 			mapList = append(mapList, &data_manage.ChartEdbMapping{
@@ -278,7 +282,7 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 	}
 
 	// 获取图表中的指标数据
-	_, _, _, xDataList, yDataList, err := future_goodServ.GetProfitChartEdbData(edbInfo, zlFutureGoodEdbInfoList, extraReq.DateList, extraReq.CalculateFormula, extraReq.FutureGoodEdbInfoIdList)
+	_, _, _, xDataList, yDataList, err := future_goodServ.GetProfitChartEdbData(baseEdbInfo, edbInfoList, zlFutureGoodEdbInfoList, extraReq.DateList, extraReq.CalculateFormula, extraReq.FutureGoodEdbInfoIdList, extraReq.XDataList)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存商品利润失败,指标信息失败,Err:" + err.Error()
@@ -318,6 +322,8 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 	resp.UniqueCode = chartInfo.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 
@@ -445,6 +451,9 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 		br.IsSendEmail = false
 		return
 	}
+	if len(extraReq.EdbInfoIdList) == 0 {
+		extraReq.EdbInfoIdList = append(extraReq.EdbInfoIdList, extraReq.BaseEdbInfoId)
+	}
 	extraConfByte, err := json.Marshal(extraReq)
 	if err != nil {
 		br.Msg = "商品利润曲线图信息异常"
@@ -454,34 +463,32 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 	extraConf := string(extraConfByte)
 
 	var edbInfoIdArr []int
+	baseEdbInfo := new(data_manage.EdbInfo)
 	// ETA指标
-	edbInfo, err := data_manage.GetEdbInfoById(extraReq.BaseEdbInfoId)
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(extraReq.EdbInfoIdList)
 	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "指标不存在!"
-			br.ErrMsg = "指标不存在,edbInfoId:" + strconv.Itoa(extraReq.BaseEdbInfoId)
-			return
-		} else {
-			br.Msg = "获取指标信息失败!"
-			br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
-			return
+		br.Msg = "获取指标信息失败!"
+		br.ErrMsg = "获取图表的指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoListMap := make(map[int]*data_manage.EdbInfo)
+	for k, v := range edbInfoList {
+		edbInfoList[k].EdbNameSource = v.EdbName
+		edbInfoListMap[v.EdbInfoId] = v
+		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+		if v.EdbInfoId == extraReq.BaseEdbInfoId {
+			baseEdbInfo = v
 		}
 	}
-	if edbInfo == nil {
-		br.Msg = "指标已被删除,请重新选择!"
-		br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(extraReq.BaseEdbInfoId)
-		return
-	} else {
-		if edbInfo.EdbInfoId <= 0 {
+
+	for _, v := range extraReq.EdbInfoIdList {
+		if _, ok := edbInfoListMap[v]; !ok {
 			br.Msg = "指标已被删除,请重新选择!"
-			br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(extraReq.BaseEdbInfoId)
+			br.ErrMsg = "指标不存在,edbInfoId:" + strconv.Itoa(v)
 			return
 		}
 	}
 
-	edbInfoIdArr = append(edbInfoIdArr, extraReq.BaseEdbInfoId)
-	edbInfo.EdbNameSource = edbInfo.EdbName
-
 	// 期货商品指标(主力合约)
 	futureGoodEdbInfoMap := make(map[int]*future_good.FutureGoodEdbInfo)
 	zlFutureGoodEdbInfoList := make([]*future_good.FutureGoodEdbInfo, 0)
@@ -549,26 +556,27 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 	// 关联指标
 	mapList := make([]*data_manage.ChartEdbMapping, 0)
 	{
-		mapList = append(mapList, &data_manage.ChartEdbMapping{
-			ChartEdbMappingId: 0,
-			EdbInfoId:         extraReq.BaseEdbInfoId,
-			CreateTime:        time.Now(),
-			ModifyTime:        time.Now(),
-			UniqueCode:        utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + strconv.Itoa(extraReq.BaseEdbInfoId)),
-			//MaxData:           0,
-			//MinData:           0,
-			IsOrder:     true,
-			IsAxis:      1,
-			EdbInfoType: 1,
-			//LeadValue:         0,
-			//LeadUnit:          "",
-			//ChartStyle:        "",
-			//ChartColor:        "",
-			//PredictChartColor: "",
-			//ChartWidth:        0,
-			Source: 1,
-		})
-
+		for _, v := range edbInfoList {
+			mapList = append(mapList, &data_manage.ChartEdbMapping{
+				ChartEdbMappingId: 0,
+				EdbInfoId:         v.EdbInfoId,
+				CreateTime:        time.Now(),
+				ModifyTime:        time.Now(),
+				UniqueCode:        utils.MD5(utils.CHART_PREFIX + "_" + strconv.FormatInt(time.Now().UnixNano(), 10) + "_" + strconv.Itoa(v.EdbInfoId)),
+				//MaxData:           0,
+				//MinData:           0,
+				IsOrder:     true,
+				IsAxis:      1,
+				EdbInfoType: 1,
+				//LeadValue:         0,
+				//LeadUnit:          "",
+				//ChartStyle:        "",
+				//ChartColor:        "",
+				//PredictChartColor: "",
+				//ChartWidth:        0,
+				Source: 1,
+			})
+		}
 		for _, v := range futureGoodEdbInfoMap {
 			mapList = append(mapList, &data_manage.ChartEdbMapping{
 				ChartEdbMappingId: 0,
@@ -593,7 +601,7 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 	}
 
 	// 获取图表中的指标数据
-	_, _, _, xDataList, yDataList, err := future_goodServ.GetProfitChartEdbData(edbInfo, zlFutureGoodEdbInfoList, extraReq.DateList, extraReq.CalculateFormula, extraReq.FutureGoodEdbInfoIdList)
+	_, _, _, xDataList, yDataList, err := future_goodServ.GetProfitChartEdbData(baseEdbInfo, edbInfoList, zlFutureGoodEdbInfoList, extraReq.DateList, extraReq.CalculateFormula, extraReq.FutureGoodEdbInfoIdList, extraReq.XDataList)
 	if err != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = "保存商品利润失败,指标信息失败,Err:" + err.Error()
@@ -639,6 +647,8 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 	resp.UniqueCode = chartItem.UniqueCode
 	resp.ChartType = req.ChartType
 
+	// 添加指标引用记录
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartItem)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartItem.ChartInfoId)
 	//修改my eta es数据
@@ -819,7 +829,15 @@ func copyProfitChartInfo(oldChartInfo *data_manage.ChartInfo, chartClassifyId in
 		br.ErrMsg = "保存商品利润图表失败,Err:" + err.Error()
 		return
 	}
-
+	// 添加指标引用记录
+	edbInfoIdArr := make([]int, 0)
+	edbInfoIdArrStr := strings.Split(oldChartInfo.EdbInfoIds, ",")
+	for _, v := range edbInfoIdArrStr {
+		i, _ := strconv.Atoi(v)
+		edbInfoIdArr = append(edbInfoIdArr, i)
+	}
+	//保存图表与指标的关系
+	_ = data.SaveChartEdbInfoRelation(edbInfoIdArr, chartInfo)
 	//添加es数据
 	go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 	//修改my eta es数据
@@ -911,9 +929,22 @@ func (this *FutureGoodChartInfoController) PreviewProfitChartInfo() {
 		br.IsSendEmail = false
 		return
 	}
+	if len(req.EdbInfoIdList) == 0 {
+		br.Msg = "请选择ETA指标"
+		br.ErrMsg = "请选择ETA指标"
+		br.IsSendEmail = false
+		return
+	}
+	//查询基本指标信息
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(req.EdbInfoIdList)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
 
 	// 获取图表中的指标数据
-	barConfigEdbInfoIdList, _, _, xDataList, yDataList, err := future_goodServ.GetProfitChartEdbData(baseEdbInfo, zlFutureGoodEdbInfoList, req.DateList, req.CalculateFormula, req.FutureGoodEdbInfoIdList)
+	barConfigEdbInfoIdList, _, _, xDataList, yDataList, err := future_goodServ.GetProfitChartEdbData(baseEdbInfo, edbInfoList, zlFutureGoodEdbInfoList, req.DateList, req.CalculateFormula, req.FutureGoodEdbInfoIdList, req.XDataList)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
@@ -991,15 +1022,18 @@ func getFutureGoodProfitChartInfo(chartInfo *data_manage.ChartInfoView, sysUser
 		br.ErrMsg = "商品利润曲线图配置异常"
 		return
 	}
+	if len(extraConf.EdbInfoIdList) == 0 {
+		extraConf.EdbInfoIdList = append(extraConf.EdbInfoIdList, extraConf.BaseEdbInfoId)
+	}
 
 	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
-	edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartInfoId)
+	edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取商品利润图表,基础指标信息失败,Err:" + err.Error()
 		return
 	}
-	edbList = append(edbList, edbInfoMapping)
+	edbList = edbInfoMappingList
 	futureGoodEdbInfoMappingList, err := data_manage.GetFutureGoodEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		br.Msg = "获取失败"
@@ -1113,6 +1147,29 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 	edbClassifyPermissionMap := make(map[int]data_manage_permission.EdbClassifyPermission)
 	defer func() {
 		if isOk {
+			// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
+			{
+				//判断是否加入我的图库
+				var myChartCondition string
+				var myChartPars []interface{}
+				myChartCondition += ` AND a.admin_id=? `
+				myChartPars = append(myChartPars, adminId)
+				myChartCondition += ` AND a.chart_info_id=? `
+				myChartPars = append(myChartPars, chartInfo.ChartInfoId)
+
+				myChartList, err := data_manage.GetMyChartByCondition(myChartCondition, myChartPars)
+				if err != nil && err.Error() != utils.ErrNoRow() {
+					msg = "获取失败"
+					errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
+					return
+				}
+				if myChartList != nil && len(myChartList) > 0 {
+					chartInfo.IsAdd = true
+					chartInfo.MyChartId = myChartList[0].MyChartId
+					chartInfo.MyChartClassifyId = myChartList[0].MyChartClassifyId
+				}
+			}
+
 			resp.ChartInfo.HaveOperaAuth = true
 
 			// 指标权限
@@ -1182,33 +1239,12 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 		if utils.Re == nil && utils.Rc.IsExist(key) {
 			if chartData, err1 := utils.Rc.RedisBytes(key); err1 == nil {
 				err := json.Unmarshal(chartData, &resp)
-				if err == nil && resp != nil {
-					// 这里跟当前用户相关的信息重新查询写入resp, 不使用缓存中的
-					var myCond string
-					var myPars []interface{}
-					myCond += ` AND a.admin_id=? `
-					myPars = append(myPars, adminId)
-					myCond += ` AND a.chart_info_id=? `
-					myPars = append(myPars, chartInfo.ChartInfoId)
-					myList, err := data_manage.GetMyChartByCondition(myCond, myPars)
-					if err != nil && err.Error() != utils.ErrNoRow() {
-						msg = "获取失败"
-						errMsg = "获取我的图表信息失败,GetMyChartByCondition,Err:" + err.Error()
-						return
-					}
-					resp.ChartInfo.IsAdd = false
-					resp.ChartInfo.MyChartId = 0
-					resp.ChartInfo.MyChartClassifyId = ""
-					if myList != nil && len(myList) > 0 {
-						resp.ChartInfo.IsAdd = true
-						resp.ChartInfo.MyChartId = myList[0].MyChartId
-						resp.ChartInfo.MyChartClassifyId = myList[0].MyChartClassifyId
-					}
-
-					isOk = true
-					fmt.Println("source redis")
+				if err != nil || resp == nil {
 					return
 				}
+				isOk = true
+				fmt.Println("source redis")
+				return
 			}
 		}
 	}
@@ -1233,15 +1269,24 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 		errMsg = "商品利润曲线图配置异常,Err:" + err.Error()
 		return
 	}
+	if len(extraConf.EdbInfoIdList) == 0 {
+		extraConf.EdbInfoIdList = append(extraConf.EdbInfoIdList, extraConf.BaseEdbInfoId)
+	}
 
 	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
-	edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartInfoId)
+	edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		msg = "获取失败"
 		errMsg = "获取商品利润图表,基础指标信息失败,Err:" + err.Error()
 		return
 	}
-	edbList = append(edbList, edbInfoMapping)
+	baseEdbInfo := new(data_manage.ChartEdbInfoMapping)
+	for _, v := range edbInfoMappingList {
+		if v.EdbInfoId == extraConf.BaseEdbInfoId {
+			baseEdbInfo = v
+		}
+	}
+	edbList = edbInfoMappingList
 	futureGoodEdbInfoMappingList, err := data_manage.GetFutureGoodEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		msg = "获取失败"
@@ -1281,7 +1326,6 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 	}
 
 	warnEdbList := make([]string, 0)
-	baseEdbInfo := edbList[0] //现货指标
 
 	for _, v := range edbList {
 		if v.IsNullData {

+ 29 - 26
controllers/data_manage/line_equation/line_chart_info.go

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

+ 29 - 26
controllers/data_manage/line_feature/chart_info.go

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

+ 35 - 6
controllers/data_manage/multiple_graph_config.go

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

+ 13 - 2
controllers/data_manage/predict_edb_info.go

@@ -12,12 +12,13 @@ import (
 	"eta/eta_api/services/elastic"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/shopspring/decimal"
 	"sort"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/shopspring/decimal"
 )
 
 // PredictEdbInfoController 预测指标
@@ -770,6 +771,16 @@ func (this *PredictEdbInfoController) Edit() {
 	//修改es
 	data.AddOrEditEdbInfoToEs(resp.EdbInfoId)
 
+	// 添加操作日志
+	newEdbRecord := new(data_manage.EdbInfoEditRecord)
+	newEdbRecord.EdbName = req.EdbName
+	// 频度和单位在此接口不处理
+	newEdbRecord.Frequency = edbInfo.Frequency
+	newEdbRecord.Unit = edbInfo.Unit
+	newEdbRecord.OperateUserId = sysUser.AdminId
+	newEdbRecord.OperateUserRealName = sysUser.RealName
+	go data.AddEditEdbInfoRcord(edbInfo, newEdbRecord)
+
 	// 刷新关联指标
 	go data.EdbInfoRefreshAllFromBaseV2(resp.EdbInfoId, true, false)
 

+ 331 - 0
controllers/data_manage/wind_data.go

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

+ 0 - 1
controllers/data_source/guagnzhouqihuo.go

@@ -13,7 +13,6 @@ import (
 	"time"
 )
 
-// 广州期货交易所
 type DataSourceController struct {
 	controllers.BaseAuthController
 }

+ 6 - 2
controllers/data_source/icpi.go

@@ -1,6 +1,7 @@
 package data_source
 
 import (
+	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_source"
 	"eta/eta_api/utils"
@@ -13,13 +14,16 @@ import (
 )
 
 // 消费者价格指数
+type DataSourceIcpiController struct {
+	controllers.BaseAuthController
+}
 
 // ComTradeCountryList
 // @Title 获取居民消费价格指数分类
 // @Description 获取居民消费价格指数分类
 // @Success 200 {object} []data_manage.ComTradeCountryItem
 // @router /icpi/classify/list [get]
-func (this *DataSourceController) IcpiClassifyList() {
+func (this *DataSourceIcpiController) IcpiClassifyList() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		this.Data["json"] = br
@@ -54,7 +58,7 @@ func (this *DataSourceController) IcpiClassifyList() {
 // @Param   KeyWord   query   string  true       "关键词"
 // @Success 200 {object} data_source.BaseFromIcpiIndexView
 // @router /icpi/index/data [get]
-func (this *DataSourceController) IcpiData() {
+func (this *DataSourceIcpiController) IcpiData() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		this.Data["json"] = br

+ 67 - 3
controllers/english_report/email.go

@@ -9,14 +9,15 @@ import (
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/beego/beego/v2/server/web"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/tealeg/xlsx"
 	"os"
 	"path"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/server/web"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
 )
 
 // EnglishReportEmailController 英文研报邮箱/英文客户联系人
@@ -843,6 +844,69 @@ func (this *EnglishReportEmailController) PvList() {
 	br.Data = resp
 }
 
+// UvList
+// @Title 英文研报邮箱UV列表
+// @Description 英文研报邮箱UV列表
+// @Param   ReportId	query	int	false	"英文研报ID"
+// @Param   ReportType	query	int	false	"类型:0英文研报,1英文线上路演"
+// @Success 200 {object} models.EnglishReportEmailPvResp
+// @router /email/uv_list [get]
+func (this *EnglishReportEmailController) UvList() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	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
+	}
+
+	reportId, e := this.GetInt("ReportId", 0)
+	if reportId == 0 {
+		br.Msg = "参数有误"
+		return
+	}
+	reportType, e := this.GetInt("ReportType", 0)
+
+	var cond string
+	var pars []interface{}
+	cond += ` AND a.report_id = ? AND a.report_type= ?`
+	pars = append(pars, reportId, reportType)
+
+	var startSize int
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	total, list, e := models.GetEnglishReportEmailUvPageList(cond, pars, startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文研报邮箱uv列表失败, Err: " + e.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := &models.EnglishReportEmailUvPageListResp{
+		Paging: page,
+		List:   list,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 // ImportListMatch
 // @Title 英文邮箱/联系人批量导入
 // @Description 英文邮箱/联系人批量导入

+ 7 - 8
controllers/english_report/report.go

@@ -92,7 +92,7 @@ func (this *EnglishReportController) Add() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird, models.ReportOperateAdd)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -475,7 +475,6 @@ func (this *EnglishReportController) ListReport() {
 
 	var authOk bool
 	adminMap := make(map[int]string, 0) // 编辑中的研究员姓名
-
 	total, e := models.GetEnglishReportListCount(condition, pars, companyType)
 	if e != nil {
 		br.Msg = "获取失败"
@@ -676,7 +675,7 @@ func (this *EnglishReportController) PublishReport() {
 		}
 
 		// 根据审批开关及审批流判断当前报告状态
-		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, models.ReportOperatePublish)
+		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, 0, models.ReportOperatePublish)
 		if e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -779,7 +778,7 @@ func (this *EnglishReportController) PrePublishReport() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond)
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, 0)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
@@ -838,7 +837,7 @@ func (this *EnglishReportController) PublishCancleReport() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, models.ReportOperateCancelPublish)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, 0, models.ReportOperateCancelPublish)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -1301,7 +1300,7 @@ func (this *EnglishReportController) SubmitApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateSubmitApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1324,7 +1323,7 @@ func (this *EnglishReportController) SubmitApprove() {
 	}
 
 	// 提交审批
-	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, sysUser.AdminId, sysUser.RealName)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "提交审批失败, Err: " + e.Error()
@@ -1392,7 +1391,7 @@ func (this *EnglishReportController) CancelApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateCancelApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()

+ 2 - 0
controllers/fe_calendar/fe_calendar_matter.go

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

+ 55 - 4
controllers/ppt_english.go

@@ -92,6 +92,16 @@ func (this *PptEnglishController) ListPpt() {
 		} else {
 			list[i].IsAuth = false
 		}
+		if list[i].PptPage == 0 {
+			var pptContent []services.PPTContent
+			er := json.Unmarshal([]byte(list[i].Content), &pptContent)
+			if er != nil {
+				br.Msg = "content参数解析失败"
+				br.ErrMsg = "content参数解析失败, Err:" + er.Error()
+				return
+			}
+			list[i].PptPage = len(pptContent)
+		}
 	}
 	page := paging.GetPaging(currentIndex, pageSize, total)
 	resp := new(ppt_english.PptEnglishListResp)
@@ -126,6 +136,13 @@ func (this *PptEnglishController) AddPpt() {
 		br.Msg = "标题不能为空"
 		return
 	}
+	var pptContent []services.PPTContent
+	err = json.Unmarshal([]byte(req.Content), &pptContent)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
 
 	var newId int64
 	var msg string
@@ -190,9 +207,9 @@ func (this *PptEnglishController) AddPpt() {
 		pptInfo.Content = req.Content
 		pptInfo.CoverContent = req.CoverContent
 		pptInfo.ModifyTime = time.Now()
+		pptInfo.PptPage = len(pptContent)
 		pptInfo.TitleSetting = req.TitleSetting
-		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "TitleSetting"})
-
+		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "ppt_page", "TitleSetting"})
 		msg = "保存成功"
 	}
 	resp := ppt_english.AddPptEnglishResp{
@@ -223,6 +240,15 @@ func (this *PptEnglishController) EditPpt() {
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
+
+	var pptContent []services.PPTContent
+	err = json.Unmarshal([]byte(req.Content), &pptContent)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
 	if req.FirstPage.Title == "" {
 		br.Msg = "标题不能为空"
 		return
@@ -270,8 +296,10 @@ func (this *PptEnglishController) EditPpt() {
 	pptInfo.Content = req.Content
 	pptInfo.CoverContent = req.CoverContent
 	pptInfo.ModifyTime = time.Now()
+	pptInfo.PptPage = len(pptContent)
+
 	pptInfo.TitleSetting = req.TitleSetting
-	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "TitleSetting"})
+	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "ppt_page", "TitleSetting"})
 	if err != nil {
 		br.Msg = "编辑失败"
 		br.ErrMsg = "编辑失败,Err:" + err.Error()
@@ -500,6 +528,29 @@ func (this *PptEnglishController) Publish() {
 		br.ErrMsg = "发布失败,Err:" + err.Error()
 		return
 	}
+
+	pptMap, err := ppt_english.GetPptMappingByPptId(int64(req.PptId))
+	if err != nil {
+		br.Msg = `该PPT信息不存在, 保存失败`
+		br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	pptMapList, err := ppt_english.GetPptMappingListByGroupIdDesc(pptMap.GroupId)
+	if err != nil {
+		br.ErrMsg = "PPT目录信息异常"
+		return
+	}
+	if !pptMap.IsMoved && len(pptMapList) > 1 {
+		// 如果没有人为移动位置, 默认将当前ppt置顶
+		err = ppt.MoveGroupPptEnglish(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
+		if err != nil {
+			br.Msg = err.Error()
+			br.ErrMsg = "移动失败,Err:" + err.Error()
+			return
+		}
+	}
+
 	//添加发布记录
 	{
 		record := new(ppt_english.PptEnglishPublishRecord)
@@ -667,6 +718,7 @@ func (this *PptEnglishController) SaveLog() {
 		br.Msg = "标题不能为空"
 		return
 	}
+
 	var pageContent []services.PPTContent
 	err = json.Unmarshal([]byte(req.Content), &pageContent)
 	if err != nil {
@@ -674,7 +726,6 @@ func (this *PptEnglishController) SaveLog() {
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
-
 	// 获取ppt
 	item, err := ppt_english.GetPptEnglishByTitleAndId(req.FirstPage.Title, this.SysUser.AdminId)
 	if err != nil && err.Error() != utils.ErrNoRow() {

+ 11 - 1
controllers/ppt_english_group.go

@@ -301,7 +301,17 @@ func (this *PptEnglishGroupController) MoveGroupPpt() {
 		br.Msg = "请输入目录ID"
 		return
 	}
-	err = ppt.MoveGroupPptEnglish(req.GroupId, req.GroupPptId, req.PrevGroupPptId, req.NextGroupPptId, this.SysUser.AdminId)
+	// 由于是倒序, 所以需要颠倒NextGroupPptId和PrevGroupPptId的位置
+	err = ppt.MoveGroupPptEnglish(req.GroupId, req.GroupPptId, req.NextGroupPptId, req.PrevGroupPptId, this.SysUser.AdminId)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	// 增加人为移动的标识
+	mappingInfo := &ppt_english.PptEnglishGroupMapping{}
+	mappingInfo.GroupPptId = req.GroupPptId
+	mappingInfo.IsMoved = true
+	err = mappingInfo.Update([]string{"is_moved"})
 	if err != nil {
 		br.Msg = err.Error()
 		return

+ 59 - 5
controllers/ppt_v2.go

@@ -92,6 +92,16 @@ func (this *PptV2Controller) ListPpt() {
 		} else {
 			list[i].IsAuth = false
 		}
+		if list[i].PptPage == 0 {
+			var pptContent []services.PPTContent
+			er := json.Unmarshal([]byte(list[i].Content), &pptContent)
+			if er != nil {
+				br.Msg = "content参数解析失败"
+				br.ErrMsg = "content参数解析失败, Err:" + er.Error()
+				return
+			}
+			list[i].PptPage = len(pptContent)
+		}
 	}
 	page := paging.GetPaging(currentIndex, pageSize, total)
 	resp := new(models.PptV2ListResp)
@@ -127,6 +137,14 @@ func (this *PptV2Controller) AddPpt() {
 		return
 	}
 
+	var pptContent []services.PPTContent
+	err = json.Unmarshal([]byte(req.Content), &pptContent)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
 	var newId int64
 	var msg string
 	if req.PptId <= 0 {
@@ -159,6 +177,7 @@ func (this *PptV2Controller) AddPpt() {
 			AdminId:       this.SysUser.AdminId,
 			AdminRealName: this.SysUser.RealName,
 			PptVersion:    2,
+			PptPage:       len(pptContent),
 			TitleSetting:  req.TitleSetting,
 		}
 		newId, err = models.AddPptV2(pptInfo)
@@ -191,9 +210,9 @@ func (this *PptV2Controller) AddPpt() {
 		pptInfo.Content = req.Content
 		pptInfo.CoverContent = req.CoverContent
 		pptInfo.ModifyTime = time.Now()
+		pptInfo.PptPage = len(pptContent)
 		pptInfo.TitleSetting = req.TitleSetting
-		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "TitleSetting"})
-
+		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "ppt_page", "TitleSetting"})
 		msg = "保存成功"
 	}
 	resp := models.AddPptResp{
@@ -228,6 +247,15 @@ func (this *PptV2Controller) EditPpt() {
 		br.Msg = "标题不能为空"
 		return
 	}
+
+	var pptContent []services.PPTContent
+	err = json.Unmarshal([]byte(req.Content), &pptContent)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
 	item, err := models.GetPptV2ByTitleAndId(req.FirstPage.Title, this.SysUser.AdminId)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		br.Msg = "获取数据异常!"
@@ -271,8 +299,10 @@ func (this *PptV2Controller) EditPpt() {
 	pptInfo.Content = req.Content
 	pptInfo.CoverContent = req.CoverContent
 	pptInfo.ModifyTime = time.Now()
+	pptInfo.PptPage = len(pptContent)
+
 	pptInfo.TitleSetting = req.TitleSetting
-	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "TitleSetting"})
+	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent", "ppt_page", "TitleSetting"})
 	if err != nil {
 		br.Msg = "编辑失败"
 		br.ErrMsg = "编辑失败,Err:" + err.Error()
@@ -500,6 +530,29 @@ func (this *PptV2Controller) Publish() {
 		br.ErrMsg = "发布失败,Err:" + err.Error()
 		return
 	}
+
+	pptMap, err := models.GetPptMappingByPptId(int64(req.PptId))
+	if err != nil {
+		br.Msg = `该PPT信息不存在, 保存失败`
+		br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	pptMapList, err := models.GetPptMappingListByGroupId(pptMap.GroupId)
+	if err != nil {
+		br.ErrMsg = "PPT目录信息异常"
+		return
+	}
+	if !pptMap.IsMoved && len(pptMapList) > 1 {
+		// 如果没有人为移动位置, 默认将当前ppt置顶
+		err = ppt.MoveGroupPpt(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
+		if err != nil {
+			br.Msg = err.Error()
+			br.ErrMsg = "移动失败,Err:" + err.Error()
+			return
+		}
+	}
+
 	//添加发布记录
 	{
 		record := new(models.PptV2PublishRecord)
@@ -688,7 +741,8 @@ func (this *PptV2Controller) SaveLog() {
 	pptItem.ModifyTime = time.Now()
 	pptItem.TitleSetting = req.TitleSetting
 	pptItem.PptPage = len(pptContent)
-	err = pptItem.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "TitleSetting", "ppt_page"})
+
+	err = pptItem.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "ppt_page", "TitleSetting"})
 
 	// 将更新后的PPT, 置顶
 	pptMap, err := models.GetPptMappingByPptId(int64(req.PptId))
@@ -813,7 +867,7 @@ func (this *PptV2Controller) ToReport() {
 		br.Msg = "参数有误"
 		return
 	}
-	reportId, reportCode, msg, e := services.SavePPTReport(req.PptId, req.ClassifyIdSecond, req.Title, sysUser)
+	reportId, reportCode, msg, e := services.SavePPTReport(req.PptId, req.ClassifyId, req.Title, sysUser)
 	if e != nil {
 		br.Msg = msg
 		br.ErrMsg = "PPT转报告失败, Err: " + e.Error()

+ 11 - 1
controllers/ppt_v2_group.go

@@ -303,7 +303,17 @@ func (this *PptV2GroupController) MoveGroupPpt() {
 		br.Msg = "请输入目录ID"
 		return
 	}
-	err = ppt.MoveGroupPpt(req.GroupId, req.GroupPptId, req.PrevGroupPptId, req.NextGroupPptId, this.SysUser.AdminId)
+	// 由于是倒序, 所以需要颠倒NextGroupPptId和PrevGroupPptId的位置
+	err = ppt.MoveGroupPpt(req.GroupId, req.GroupPptId, req.NextGroupPptId, req.PrevGroupPptId, this.SysUser.AdminId)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	// 增加人为移动的标识
+	mappingInfo := &models.PptV2GroupMapping{}
+	mappingInfo.GroupPptId = req.GroupPptId
+	mappingInfo.IsMoved = true
+	err = mappingInfo.Update([]string{"is_moved"})
 	if err != nil {
 		br.Msg = err.Error()
 		return

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 434 - 499
controllers/report.go


+ 18 - 4
controllers/report_approve/report_approve.go

@@ -174,7 +174,7 @@ func (this *ReportApproveController) List() {
 
 	// 已处理
 	if params.ListType == 2 {
-		cond := fmt.Sprintf(` AND a.%s = ? AND a.%s IN (%s)`, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.State, utils.GetOrmInReplace(2))
+		cond := fmt.Sprintf(` AND a.%s = ? AND a.%s IN (%s)`, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.NodeState, utils.GetOrmInReplace(2))
 		pars := make([]interface{}, 0)
 		pars = append(pars, sysUser.AdminId, []int{report_approve.ReportApproveStatePass, report_approve.ReportApproveStateRefuse})
 		order := ""
@@ -236,6 +236,12 @@ func (this *ReportApproveController) List() {
 			br.ErrMsg = "GetApprovedReportApprovePageList err: " + e.Error()
 			return
 		}
+
+		for _, v := range list {
+			// 这个时候的状态,用审批状态
+			v.RecordState = v.NodeState
+			v.ApproveTime = v.NodeApproveTime
+		}
 		ormList = list
 	}
 
@@ -317,7 +323,15 @@ func (this *ReportApproveController) List() {
 		if v.ReportType == report_approve.FlowReportTypeEnglish {
 			t.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], enClassifyIdName[enRootIdMap[v.ClassifySecondId]], enClassifyIdName[v.ClassifyFirstId], enClassifyIdName[v.ClassifySecondId])
 		} else {
-			t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+			//t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+			reportClassify := fmt.Sprintf("%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId])
+			if v.ClassifySecondId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifySecondId])
+			}
+			if v.ClassifyThirdId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifyThirdId])
+			}
+			t.ReportClassify = reportClassify
 		}
 		respList = append(respList, t)
 	}
@@ -513,7 +527,7 @@ func (this *ReportApproveController) Detail() {
 		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], enClassifyIdName[enRootIdMap[approveItem.ClassifySecondId]], enClassifyIdName[approveItem.ClassifyFirstId], enClassifyIdName[approveItem.ClassifySecondId])
 	}
 	if approveItem.ReportType == report_approve.FlowReportTypeSmart {
-		detail.Report.ReportCode = 	utils.MD5(fmt.Sprint("smart_", approveItem.ReportId))
+		detail.Report.ReportCode = utils.MD5(fmt.Sprint("smart_", approveItem.ReportId))
 		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], cnClassifyIdName[approveItem.ClassifyFirstId], cnClassifyIdName[approveItem.ClassifySecondId])
 	}
 
@@ -922,7 +936,7 @@ func (this *ReportApproveController) CheckApproveOpen() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()

+ 38 - 32
controllers/report_approve/report_approve_flow.go

@@ -56,6 +56,8 @@ func (this *ReportApproveFlowController) List() {
 
 	var cond, orderRule string
 	var pars []interface{}
+	cond += fmt.Sprintf(` AND %s = ? `, report_approve.ReportApproveFlowCols.Enabled)
+	pars = append(pars, 1)
 	// 筛选项
 	{
 		keyword := strings.TrimSpace(params.Keyword)
@@ -68,6 +70,10 @@ func (this *ReportApproveFlowController) List() {
 			cond += fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifySecondId)
 			pars = append(pars, params.ReportType, params.ClassifySecondId)
 		}
+		if params.ClassifyThirdId > 0 {
+			cond += fmt.Sprintf(` AND %s = ? `, report_approve.ReportApproveFlowCols.ClassifyThirdId)
+			pars = append(pars, params.ReportType, params.ClassifyThirdId)
+		}
 		if params.SortRule > 0 {
 			orderMap := map[int]string{1: "ASC", 2: "DESC"}
 			orderRule = fmt.Sprintf("%s %s", report_approve.ReportApproveFlowCols.CreateTime, orderMap[params.SortRule])
@@ -137,7 +143,14 @@ func (this *ReportApproveFlowController) List() {
 		if v.ReportType == report_approve.FlowReportTypeEnglish {
 			t.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], enClassifyIdName[enRootIdMap[v.ClassifySecondId]], enClassifyIdName[v.ClassifyFirstId], enClassifyIdName[v.ClassifySecondId])
 		} else {
-			t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+			reportClassify := fmt.Sprintf("%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId])
+			if v.ClassifySecondId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifySecondId])
+			}
+			if v.ClassifyThirdId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifyThirdId])
+			}
+			t.ReportClassify = reportClassify
 		}
 		resp.List = append(resp.List, t)
 	}
@@ -221,9 +234,9 @@ func (this *ReportApproveFlowController) Add() {
 	// 审批流是否已存在
 	{
 		flowOb := new(report_approve.ReportApproveFlow)
-		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId)
 		existPars := make([]interface{}, 0)
-		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId)
 		exist, e := flowOb.GetItemByCondition(existCond, existPars, "")
 		if e != nil && e.Error() != utils.ErrNoRow() {
 			br.Msg = "获取失败"
@@ -241,7 +254,9 @@ func (this *ReportApproveFlowController) Add() {
 	flowItem.ReportType = req.ReportType
 	flowItem.ClassifyFirstId = req.ClassifyFirstId
 	flowItem.ClassifySecondId = req.ClassifySecondId
+	flowItem.ClassifyThirdId = req.ClassifyThirdId
 	flowItem.CurrVersion = 1
+	flowItem.Enabled = 1
 	flowItem.CreateTime = time.Now().Local()
 	flowItem.ModifyTime = time.Now().Local()
 
@@ -273,7 +288,7 @@ func (this *ReportApproveFlowController) Add() {
 
 	// 更新审批对应的报告状态:未发布->待提交
 	go func() {
-		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
 	}()
 
 	br.Data = detail
@@ -369,9 +384,9 @@ func (this *ReportApproveFlowController) Edit() {
 
 	// 审批流是否已存在
 	{
-		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s <> ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ReportApproveFlowId)
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?  AND %s = ? AND %s <> ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId, report_approve.ReportApproveFlowCols.ReportApproveFlowId)
 		existPars := make([]interface{}, 0)
-		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ReportApproveFlowId)
+		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId, req.ReportApproveFlowId)
 		exist, e := flowOb.GetItemByCondition(existCond, existPars, "")
 		if e != nil && e.Error() != utils.ErrNoRow() {
 			br.Msg = "操作失败"
@@ -403,8 +418,8 @@ func (this *ReportApproveFlowController) Edit() {
 	}
 
 	// 变更了报告分类时, 判断是否允许变更
-	if req.ReportType != flowItem.ReportType || req.ClassifyFirstId != flowItem.ClassifyFirstId || req.ClassifySecondId != flowItem.ClassifySecondId {
-		checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+	if req.ReportType != flowItem.ReportType || req.ClassifyFirstId != flowItem.ClassifyFirstId || req.ClassifySecondId != flowItem.ClassifySecondId || req.ClassifyThirdId != flowItem.ClassifyThirdId {
+		checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId)
 		if e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "校验审批流是否可变更失败, Err: " + e.Error()
@@ -420,6 +435,7 @@ func (this *ReportApproveFlowController) Edit() {
 	flowItem.ReportType = req.ReportType
 	flowItem.ClassifyFirstId = req.ClassifyFirstId
 	flowItem.ClassifySecondId = req.ClassifySecondId
+	flowItem.ClassifyThirdId = req.ClassifyThirdId
 	flowItem.CurrVersion += 1
 	flowItem.ModifyTime = time.Now().Local()
 
@@ -566,7 +582,7 @@ func (this *ReportApproveFlowController) Remove() {
 	}
 
 	// 校验是否允许删除
-	checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+	checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验审批流是否可变更失败, Err: " + e.Error()
@@ -586,7 +602,7 @@ func (this *ReportApproveFlowController) Remove() {
 
 	// 更新审批对应的报告状态:待提交->未发布
 	go func() {
-		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, flowItem.ClassifyThirdId, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
 	}()
 
 	br.Ret = 200
@@ -645,7 +661,7 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 	}
 	hasFlowMap := make(map[string]bool)
 	for _, v := range flows {
-		k := fmt.Sprintf("%d-%d-%d", v.ReportType, v.ClassifyFirstId, v.ClassifySecondId)
+		k := fmt.Sprintf("%d-%d-%d-%d", v.ReportType, v.ClassifyFirstId, v.ClassifySecondId, v.ClassifyThirdId)
 		if k == flowKey {
 			// 当前审批流对应的分类标记为可选状态
 			continue
@@ -653,7 +669,7 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		hasFlowMap[k] = true
 	}
 
-	resp, cnTree, smartTree, enTree := make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0)
+	resp, cnTree, enTree := make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0)
 
 	var cnErr, enErr error
 	wg := sync.WaitGroup{}
@@ -672,17 +688,16 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		}
 		cnTree = services.GetReportClassifyTreeRecursive(classify, 0)
 		for _, v := range cnTree {
-			for _, v2 := range v.Children {
-				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId)
-				v2.HasFlow = hasFlowMap[k]
-			}
-		}
+			k1 := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, 0, 0)
+			v.HasFlow = hasFlowMap[k1]
 
-		smartTree = services.GetReportClassifyTreeRecursive(classify, 0)
-		for _, v := range smartTree {
 			for _, v2 := range v.Children {
-				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeSmart, v.ClassifyId, v2.ClassifyId)
-				v2.HasFlow = hasFlowMap[k]
+				k2 := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId, 0)
+				v2.HasFlow = hasFlowMap[k2]
+				for _, v3 := range v2.Children {
+					k3 := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId, v3.ClassifyId)
+					v3.HasFlow = hasFlowMap[k3]
+				}
 			}
 		}
 	}()
@@ -701,7 +716,7 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 		enTree = services.GetReportClassifyTreeRecursive(classify, 0)
 		for _, v := range enTree {
 			for _, v2 := range v.Children {
-				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeEnglish, v.ClassifyId, v2.ClassifyId)
+				k := fmt.Sprintf("%d-%d-%d-%d", report_approve.FlowReportTypeEnglish, v.ClassifyId, v2.ClassifyId, 0)
 				v2.HasFlow = hasFlowMap[k]
 			}
 		}
@@ -729,28 +744,19 @@ func (this *ReportApproveFlowController) ReportClassifyTree() {
 			ClassifyId:   report_approve.FlowReportTypeEnglish,
 			ClassifyName: "English Report",
 			Children:     enTree,
-		}, &report_approve.ReportClassifyTreeItem{
-			ClassifyId:   report_approve.FlowReportTypeSmart,
-			ClassifyName: "Smart Report",
-			Children:     smartTree,
 		})
 	} else {
 		resp = append(resp, &report_approve.ReportClassifyTreeItem{
 			ClassifyId:   report_approve.FlowReportTypeChinese,
-			ClassifyName: "研报列表",
+			ClassifyName: "研报",
 			Children:     cnTree,
 		}, &report_approve.ReportClassifyTreeItem{
 			ClassifyId:   report_approve.FlowReportTypeEnglish,
 			ClassifyName: "英文研报",
 			Children:     enTree,
-		}, &report_approve.ReportClassifyTreeItem{
-			ClassifyId:   report_approve.FlowReportTypeSmart,
-			ClassifyName: "智能研报",
-			Children:     smartTree,
 		})
 	}
 
-
 	br.Data = resp
 	br.Ret = 200
 	br.Success = true

+ 1 - 1
controllers/report_author.go

@@ -366,7 +366,7 @@ func (this *ReportAuthorController) DeleteAuthor() {
 		condition = " AND author = ? "
 		pars = append(pars, item.ReportAuthor)
 		if item.AuthorType == 1 {
-			count, err = models.GetReportListCount(condition, pars, "")
+			count, err = models.GetReportListCount(condition, pars)
 		} else {
 			count, err = models.GetEnglishReportListCount(condition, pars, "")
 		}

+ 1400 - 0
controllers/report_chapter.go

@@ -0,0 +1,1400 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/report"
+	"eta/eta_api/services"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/kgiannakakis/mp3duration/src/mp3duration"
+	"html"
+	"os"
+	"path"
+	"strconv"
+	"time"
+)
+
+// AddChapter
+// @Title 新增晨周报章节内容
+// @Description 新增晨周报章节内容
+// @Param	request	body models.AddReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/add [post]
+func (this *ReportController) AddChapter() {
+	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 models.AddReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ReportId <= 0 {
+		br.Msg = "报告ID有误"
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(req.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许新增"
+		br.ErrMsg = "该报告已发布,不允许新增"
+		return
+	}
+
+	//newContent := req.Content
+	//// 更新章节及指标
+	//contentSub := ""
+	//if req.Content != "" {
+	//	e := utils.ContentXssCheck(req.Content)
+	//	if e != nil {
+	//		br.Msg = "存在非法标签"
+	//		br.ErrMsg = "存在非法标签, Err: " + e.Error()
+	//		return
+	//	}
+	//	contentClean, e := services.FilterReportContentBr(req.Content)
+	//	if e != nil {
+	//		br.Msg = "内容去除前后空格失败"
+	//		br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+	//		return
+	//	}
+	//	req.Content = contentClean
+	//
+	//	contentSub, err = services.GetReportContentSub(req.Content)
+	//	if err != nil {
+	//		br.Msg = "内容分段解析失败"
+	//		br.ErrMsg = "编辑报告章节-解析 ContentSub 失败, Err: " + err.Error()
+	//		return
+	//	}
+	//}
+	//if req.Content == "" {
+	//	req.Content = newContent
+	//}
+
+	// 最小单元的分类
+	var minClassifyId int
+	var minClassifyName string
+	if reportInfo.ClassifyIdThird > 0 {
+		minClassifyId = reportInfo.ClassifyIdThird
+		minClassifyName = reportInfo.ClassifyNameThird
+	} else if reportInfo.ClassifyIdSecond > 0 {
+		minClassifyId = reportInfo.ClassifyIdSecond
+		minClassifyName = reportInfo.ClassifyNameSecond
+	} else {
+		minClassifyId = reportInfo.ClassifyIdFirst
+		minClassifyName = reportInfo.ClassifyNameFirst
+	}
+
+	reportChapterInfo := new(models.ReportChapter)
+	reportChapterInfo.ReportId = reportInfo.Id
+	reportChapterInfo.ClassifyIdFirst = minClassifyId
+	reportChapterInfo.ClassifyNameFirst = minClassifyName
+
+	reportChapterInfo.Title = req.Title
+	reportChapterInfo.AddType = 1
+	reportChapterInfo.PublishState = 1
+	//reportChapterInfo.Author = req.Author
+	//reportChapterInfo.Content = html.EscapeString(req.Content)
+	//reportChapterInfo.ContentSub = html.EscapeString(contentSub)
+	reportChapterInfo.IsEdit = 1
+	//reportChapterInfo.CreateTime = req.CreateTime
+	reportChapterInfo.CreateTime = reportInfo.CreateTime
+	reportChapterInfo.ModifyTime = time.Now()
+	reportChapterInfo.ReportCreateTime = time.Now()
+	reportChapterInfo.VideoKind = 2
+	reportChapterInfo.Stage = reportInfo.Stage
+
+	reportChapterInfo.LastModifyAdminId = sysUser.AdminId
+	reportChapterInfo.LastModifyAdminName = sysUser.RealName
+	reportChapterInfo.ContentModifyTime = time.Now()
+	//reportChapterInfo.ContentStruct = html.EscapeString(req.ContentStruct)
+	//reportChapterInfo.CanvasColor = req.CanvasColor
+	//reportChapterInfo.HeadResourceId = req.HeadResourceId
+	//reportChapterInfo.EndResourceId = req.EndResourceId
+
+	err, errMsg := services.AddChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, req.PermissionIdList, req.AdminIdList)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// EditChapterBaseInfoAndPermission
+// @Title 修改报告章节的基础信息、授权用户权限、品种权限
+// @Description 修改报告章节的基础信息、授权用户权限、品种权限
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/base_info/edit [post]
+func (this *ReportController) EditChapterBaseInfoAndPermission() {
+	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 models.EditReportChapterBaseInfoAndPermissionReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	err, errMsg := services.EditChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, req.Title, req.PermissionIdList, req.AdminIdList)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// EditDayWeekChapter
+// @Title 编辑晨周报章节内容
+// @Description 编辑晨周报章节内容
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editDayWeekChapter [post]
+func (this *ReportController) EditDayWeekChapter() {
+	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 models.EditReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+	if req.Content == "" {
+		br.Msg = "请输入内容"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	{
+		// 如果不是创建人,那么就要去查看是否授权
+		if reportInfo.AdminId != sysUser.AdminId {
+			// 授权用户权限校验
+			chapterGrantObj := report.ReportChapterGrant{}
+			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
+			if tmpErr != nil {
+				if tmpErr.Error() == utils.ErrNoRow() {
+					br.Msg = "没有权限"
+					br.ErrMsg = "没有权限"
+					br.IsSendEmail = false
+					return
+				}
+				br.Msg = "获取章节id授权用户失败"
+				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
+				return
+			}
+		}
+
+		// 标记更新中
+		{
+			markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+			if err != nil {
+				br.Msg = err.Error()
+				return
+			}
+			if markStatus.Status == 1 {
+				br.Msg = markStatus.Msg
+				br.IsSendEmail = false
+				return
+			}
+		}
+	}
+
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+	// 报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	reqTickerList := req.TickerList
+	// 更新章节及指标
+	contentSub := ""
+	if req.Content != "" {
+		e := utils.ContentXssCheck(req.Content)
+		if e != nil {
+			br.Msg = "存在非法标签"
+			br.ErrMsg = "存在非法标签, Err: " + e.Error()
+			return
+		}
+		contentClean, e := services.FilterReportContentBr(req.Content)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = contentClean
+
+		contentSub, err = services.GetReportContentSub(req.Content)
+		if err != nil {
+			br.Msg = "内容分段解析失败"
+			br.ErrMsg = "编辑报告章节-解析 ContentSub 失败, Err: " + err.Error()
+			return
+		}
+	}
+
+	reportChapterInfo.Title = req.Title
+	//reportChapterInfo.AddType = req.AddType
+	reportChapterInfo.Author = req.Author
+	reportChapterInfo.Content = html.EscapeString(req.Content)
+	reportChapterInfo.ContentSub = html.EscapeString(contentSub)
+	reportChapterInfo.IsEdit = 1
+	if req.CreateTime != `` {
+		reportChapterInfo.CreateTime = req.CreateTime
+	}
+
+	reportChapterInfo.LastModifyAdminId = sysUser.AdminId
+	reportChapterInfo.LastModifyAdminName = sysUser.RealName
+	reportChapterInfo.ContentModifyTime = time.Now()
+	reportChapterInfo.ContentStruct = html.EscapeString(req.ContentStruct)
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Title", "AddType", "Author", "Content", "ContentSub", "IsEdit", "CreateTime")
+
+	updateCols = append(updateCols, "LastModifyAdminId", "LastModifyAdminName", "ContentModifyTime", "ContentStruct")
+
+	// 章节报告更新指标
+	tickerList := make([]*models.ReportChapterTicker, 0)
+	if len(reqTickerList) > 0 {
+		nowTime := time.Now()
+		for i := 0; i < len(reqTickerList); i++ {
+			tickerList = append(tickerList, &models.ReportChapterTicker{
+				ReportChapterId: reportChapterInfo.ReportChapterId,
+				Sort:            reqTickerList[i].Sort,
+				Ticker:          reqTickerList[i].Ticker,
+				CreateTime:      nowTime,
+				UpdateTime:      nowTime,
+			})
+		}
+	}
+	err = models.UpdateChapterAndTicker(reportInfo, reportChapterInfo, updateCols, tickerList)
+	if err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 标记更新中
+	{
+		markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			br.Msg = markStatus.Msg
+			return
+		}
+	}
+
+	// 备份关键数据
+	chapters := make([]*models.ReportChapter, 0)
+	chapters = append(chapters, reportChapterInfo)
+	go services.SaveReportLogs(nil, chapters, sysUser.AdminId, sysUser.RealName)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// DelChapter
+// @Title 编辑晨周报章节内容
+// @Description 编辑晨周报章节内容
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/del [post]
+func (this *ReportController) DelChapter() {
+	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 models.DelReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportChapterId := req.ReportChapterId
+	if reportChapterId <= 0 {
+		br.Msg = "报告章节ID有误"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	{
+		// 如果不是创建人,那么就要去查看是否授权
+		if reportInfo.AdminId != sysUser.AdminId {
+			// 授权用户权限校验
+			chapterGrantObj := report.ReportChapterGrant{}
+			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(reportChapterInfo.ReportChapterId, sysUser.AdminId)
+			if tmpErr != nil {
+				if tmpErr.Error() == utils.ErrNoRow() {
+					br.Msg = "没有权限"
+					br.ErrMsg = "没有权限"
+					br.IsSendEmail = false
+					return
+				}
+				br.Msg = "获取章节id授权用户失败"
+				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
+				return
+			}
+		}
+
+		// 标记更新中
+		{
+			markStatus, err := services.UpdateReportEditMark(reportChapterInfo.ReportId, reportChapterInfo.ReportChapterId, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+			if err != nil {
+				br.Msg = err.Error()
+				return
+			}
+			if markStatus.Status == 1 {
+				br.Msg = markStatus.Msg
+				br.IsSendEmail = false
+				return
+			}
+		}
+	}
+
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 删除章节
+	err, errMsg := services.DelChapter(reportInfo, reportChapterInfo, sysUser)
+	if err != nil {
+		br.Msg = "删除失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 备份关键数据
+	chapters := make([]*models.ReportChapter, 0)
+	chapters = append(chapters, reportChapterInfo)
+	go services.SaveReportLogs(nil, chapters, sysUser.AdminId, sysUser.RealName)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// GetReportChapterList
+// @Title 获取报告章节列表
+// @Description 获取报告章节列表
+// @Param   ReportId	query	string	true	"报告ID"
+// @Success 200 {object} company.CompanyListResp
+// @router /getReportChapterList [get]
+func (this *ReportController) GetReportChapterList() {
+	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
+	}
+
+	reqReportId := this.GetString("ReportId")
+	reportId, _ := strconv.Atoi(reqReportId)
+	if reportId <= 0 {
+		br.Msg = "报告ID有误"
+		return
+	}
+
+	// 获取报告信息
+	reportInfo, err := models.GetReportByReportId(reportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 权限校验
+	isAuth, err := services.CheckReportAuthByReportChapterInfo(sysUser.AdminId, reportInfo.AdminId, reportId)
+	if err != nil {
+		br.Msg = "获取报告权限失败"
+		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		br.Msg = "无操作权限"
+		br.ErrMsg = "无操作权限"
+		return
+	}
+
+	// 获取章节列表
+	chapterList, err := models.GetChapterListByReportId(reportId)
+	if err != nil {
+		br.Msg = "获取章节列表失败"
+		br.ErrMsg = "获取章节列表失败, Err: " + err.Error()
+		return
+	}
+	typeList, err := models.GetReportChapterTypeList()
+	if err != nil {
+		br.Msg = "获取章节类型列表失败"
+		br.ErrMsg = "获取章节类型列表失败, Err: " + err.Error()
+		return
+	}
+	typeIdImg := make(map[int]string)
+	for i := 0; i < len(typeList); i++ {
+		typeIdImg[typeList[i].ReportChapterTypeId] = typeList[i].EditImgUrl
+	}
+
+	resp := make([]models.ReportChapterResp, 0)
+	if len(chapterList) > 0 {
+		chapterIdList := make([]int, 0)
+		// 章节id授权用户列表map
+		chapterIdGrandListMap := make(map[int][]int)
+		// 章节id关联品种id列表map
+		chapterIdPermissionListMap := make(map[int][]int)
+
+		for _, v := range chapterList {
+			chapterIdList = append(chapterIdList, v.ReportChapterId)
+		}
+
+		// 处理章节id授权用户列表
+		{
+			chapterGrantObj := report.ReportChapterGrant{}
+			chapterGrantList, tmpErr := chapterGrantObj.GetGrantListByIdList(chapterIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id授权用户列表失败"
+				br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+				return
+			}
+
+			for _, v := range chapterGrantList {
+				tmpChapterIdGrandList, ok := chapterIdGrandListMap[v.ReportChapterId]
+				if !ok {
+					tmpChapterIdGrandList = make([]int, 0)
+				}
+				chapterIdGrandListMap[v.ReportChapterId] = append(tmpChapterIdGrandList, v.AdminId)
+			}
+		}
+
+		// 处理章节id关联品种id列表
+		{
+			obj := report.ReportChapterPermissionMapping{}
+			chapterPermissionList, tmpErr := obj.GetPermissionListByIdList(chapterIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id关联品种列表失败"
+				br.ErrMsg = "获取章节id关联品种列表失败, Err: " + tmpErr.Error()
+				return
+			}
+
+			for _, v := range chapterPermissionList {
+				tmpChapterIdPermissionList, ok := chapterIdPermissionListMap[v.ReportChapterId]
+				if !ok {
+					tmpChapterIdPermissionList = make([]int, 0)
+				}
+				chapterIdPermissionListMap[v.ReportChapterId] = append(tmpChapterIdPermissionList, v.ChartPermissionId)
+			}
+		}
+
+		// 章节类型的字段
+		for _, item := range chapterList {
+			// 授权的用户列表
+			tmpChapterIdGrandList, ok := chapterIdGrandListMap[item.ReportChapterId]
+			if !ok {
+				tmpChapterIdGrandList = make([]int, 0)
+			}
+			// 关联的品种列表
+			tmpChapterIdPermissionList, ok := chapterIdPermissionListMap[item.ReportChapterId]
+			if !ok {
+				tmpChapterIdPermissionList = make([]int, 0)
+			}
+
+			tmpChapterItem := models.ReportChapterResp{
+				ReportChapterId:  item.ReportChapterId,
+				ReportId:         item.ReportId,
+				ReportType:       item.ReportType,
+				TypeId:           item.TypeId,
+				TypeName:         item.TypeName,
+				TypeEditImg:      typeIdImg[item.TypeId],
+				Title:            item.Title,
+				IsEdit:           item.IsEdit,
+				Trend:            item.Trend,
+				Sort:             item.Sort,
+				PublishState:     item.PublishState,
+				VideoUrl:         item.VideoUrl,
+				VideoName:        item.VideoName,
+				VideoPlaySeconds: item.VideoPlaySeconds,
+				VideoSize:        item.VideoSize,
+				VideoKind:        item.VideoKind,
+				ModifyTime:       item.ModifyTime.Format(utils.FormatDate),
+				GrandAdminIdList: tmpChapterIdGrandList,
+				PermissionIdList: tmpChapterIdPermissionList,
+			}
+			markStatus, err := services.UpdateReportEditMark(item.ReportId, item.ReportChapterId, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+			if err != nil {
+				br.Msg = "查询标记状态失败"
+				br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+				return
+			}
+
+			if markStatus.Status == 0 {
+				tmpChapterItem.CanEdit = true
+			} else {
+				tmpChapterItem.Editor = markStatus.Editor
+			}
+
+			// 报告章节的操作权限
+			tmpChapterItem.IsAuth = services.CheckChapterAuthByAdminIdList(sysUser.AdminId, reportInfo.AdminId, tmpChapterIdGrandList)
+
+			resp = append(resp, tmpChapterItem)
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// GetDayWeekChapter
+// @Title 获取晨周报章节信息
+// @Description 获取晨周报章节信息
+// @Param	ReportChapterId  query  int  true  "报告章节ID"
+// @Success 200 Ret=200 保存成功
+// @router /getDayWeekChapter [get]
+func (this *ReportController) GetDayWeekChapter() {
+	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
+	}
+
+	reportChapterId, _ := this.GetInt("ReportChapterId")
+	if reportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	chapterItem, err := models.GetReportChapterItemById(reportChapterId)
+	if err != nil {
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportById(chapterItem.ReportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 权限校验
+	isAuth, err := services.CheckReportAuthByReportChapterInfo(sysUser.AdminId, reportInfo.AdminId, reportInfo.Id)
+	if err != nil {
+		br.Msg = "获取报告权限失败"
+		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		br.Msg = "无操作权限"
+		br.ErrMsg = "无操作权限"
+		return
+	}
+
+	chapterItem.Content = html.UnescapeString(chapterItem.Content)
+	chapterItem.ContentSub = html.UnescapeString(chapterItem.ContentSub)
+	chapterItem.ContentStruct = html.UnescapeString(chapterItem.ContentStruct)
+
+	// 授权用户列表map
+	chapterGrantIdList := make([]int, 0)
+	// 关联品种id列表map
+	chapterPermissionIdList := make([]int, 0)
+	// 处理章节id授权用户列表
+	{
+		chapterGrantObj := report.ReportChapterGrant{}
+		chapterGrantList, tmpErr := chapterGrantObj.GetGrantListById(chapterItem.ReportChapterId)
+		if tmpErr != nil {
+			br.Msg = "获取章节id授权用户列表失败"
+			br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+			return
+		}
+
+		for _, v := range chapterGrantList {
+			chapterGrantIdList = append(chapterGrantIdList, v.AdminId)
+		}
+	}
+
+	// 处理章节id关联品种id列表
+	{
+		obj := report.ReportChapterPermissionMapping{}
+		chapterPermissionList, tmpErr := obj.GetPermissionListById(chapterItem.ReportChapterId)
+		if tmpErr != nil {
+			br.Msg = "获取章节id关联品种列表失败"
+			br.ErrMsg = "获取章节id关联品种列表失败, Err: " + tmpErr.Error()
+			return
+		}
+
+		for _, v := range chapterPermissionList {
+			chapterPermissionIdList = append(chapterPermissionIdList, v.ChartPermissionId)
+		}
+	}
+
+	resp := models.ReportChapterItemResp{
+		ReportChapterItem: *chapterItem,
+		GrandAdminIdList:  chapterGrantIdList,
+		PermissionIdList:  chapterPermissionIdList,
+	}
+
+	// 获取当前编辑状态
+	{
+		markStatus, err := services.UpdateReportEditMark(chapterItem.ReportId, chapterItem.ReportChapterId, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = "查询标记状态失败"
+			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+			return
+		}
+		if markStatus.Status == 0 {
+			resp.CanEdit = true
+		} else {
+			resp.Editor = markStatus.Editor
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ChapterMove
+// @Title 移动章节类型
+// @Description 移动章节类型
+// @Param	request	body models.PermissionMoveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /chapter/move [post]
+func (this *ReportController) ChapterMove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.ReportChapterMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.ReportChapterId == 0 {
+		br.Msg = "请选择要移动的章节"
+		return
+	}
+	e, msg := services.MoveReportChapter(&req)
+	if e != nil {
+		br.Msg = msg
+		br.ErrMsg = "移动品种失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// EditChapterTrendTag
+// @Title 编辑章节趋势标签
+// @Description 编辑章节趋势标签
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /editChapterTrendTag [post]
+func (this *ReportController) EditChapterTrendTag() {
+	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 models.EditChapterTrendTagReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "章节ID有误"
+		return
+	}
+	if req.Trend == "" {
+		br.Msg = "请输入标签"
+		return
+	}
+
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "获取章节信息失败"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, err := models.GetReportByReportId(chapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "报告信息有误"
+		br.ErrMsg = "报告信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 操作权限校验
+	{
+		// 如果不是创建人,那么就要去查看是否授权
+		if reportInfo.AdminId != sysUser.AdminId {
+			// 授权用户权限校验
+			chapterGrantObj := report.ReportChapterGrant{}
+			_, tmpErr := chapterGrantObj.GetGrantByIdAndAdmin(chapterInfo.ReportChapterId, sysUser.AdminId)
+			if tmpErr != nil {
+				if tmpErr.Error() == utils.ErrNoRow() {
+					br.Msg = "没有权限"
+					br.ErrMsg = "没有权限"
+					br.IsSendEmail = false
+					return
+				}
+				br.Msg = "获取章节id授权用户失败"
+				br.ErrMsg = "获取章节id授权用户失败, Err: " + tmpErr.Error()
+				return
+			}
+		}
+	}
+
+	// 更新章节标签
+	chapterInfo.Trend = req.Trend
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Trend")
+	if err = chapterInfo.UpdateChapter(updateCols); err != nil {
+		br.Msg = "更新标签失败"
+		br.ErrMsg = "更新标签失败, Err: " + err.Error()
+		return
+	}
+
+	// 添加关键词
+	if err = models.AddTrendTagKeyWord(req.Trend); err != nil {
+		br.Msg = "添加标签关键词失败"
+		br.ErrMsg = "添加标签关键词失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// GetSunCode 获取太阳码
+// @Title 公共模块
+// @Description 获取分享海报
+// @Param	request	body models.SunCodeReq true "type json string"
+// @Success 200 {object} string "获取成功"
+// @failure 400 {string} string "获取失败"
+// @router /getSunCode [post]
+func (this *ReportController) GetSunCode() {
+	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 models.SunCodeReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	var sunCodeUrl string
+	//先查,查不到再去生成上传
+	item, err := models.GetYbPcSunCode(req.CodeScene, req.CodePage)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "查询太阳码失败!"
+		br.ErrMsg = "查询太阳码失败,Err:" + err.Error()
+		return
+	}
+	if item != nil {
+		sunCodeUrl = item.SuncodeUrl
+	}
+
+	if sunCodeUrl == "" {
+		sunCodeUrl, err = services.PcCreateAndUploadSunCode(req.CodeScene, req.CodePage)
+		if err != nil {
+			br.Msg = "生成太阳码失败!"
+			br.ErrMsg = "生成太阳码失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	br.Data = sunCodeUrl
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// VoiceUpload
+// @Title 音频上传
+// @Description 音频上传接口
+// @Param   file   query   file  true       "文件"
+// @Param   ReportChapterId   query   int  true       "报告章节ID"
+// @Success Ret=200 上传成功
+// @router /chapter/voice/upload [post]
+func (this *ReportController) VoiceUpload() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	f, h, err := this.GetFile("file")
+	if err != nil {
+		br.Msg = "获取资源信息失败"
+		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 报告章节id
+	reportChapterId, err := this.GetInt("ReportChapterId", 0)
+	if err != nil {
+		br.Msg = "报告章节ID异常"
+		br.ErrMsg = "报告章节ID异常,Err:" + err.Error()
+		return
+	}
+
+	// 报告章节信息
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "获取报告章节信息失败"
+		br.ErrMsg = "获取报告章节信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 报告信息
+	reportInfo, err := models.GetReportByReportId(reportChapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 权限校验
+	isAuth, err := services.CheckChapterAuthByReportChapterInfo(this.SysUser.AdminId, reportInfo.AdminId, reportChapterInfo)
+	if err != nil {
+		br.Msg = "获取报告权限失败"
+		br.ErrMsg = "获取报告权限失败,Err:" + err.Error()
+		return
+	}
+	if !isAuth {
+		br.Msg = "无操作权限"
+		br.ErrMsg = "无操作权限"
+		return
+	}
+
+	ext := path.Ext(h.Filename)
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "hongze/" + dateDir
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		br.Msg = "存储目录创建失败"
+		br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName := randStr + ext
+	fPath := uploadDir + "/" + fileName
+	defer f.Close() //关闭上传文件
+	err = this.SaveToFile("file", fPath)
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	resourceUrl := ``
+	//上传到阿里云 和 minio
+	//if utils.ObjectStorageClient == "minio" {
+	//	resourceUrl, err = services.UploadAudioToMinIo(fileName, fPath)
+	//	if err != nil {
+	//		br.Msg = "文件上传失败"
+	//		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+	//		return
+	//	}
+	//} else {
+	//	resourceUrl, err = services.UploadAudioAliyun(fileName, fPath)
+	//	if err != nil {
+	//		br.Msg = "文件上传失败"
+	//		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
+	ossClient := services.NewOssClient()
+	if ossClient == nil {
+		br.Msg = "上传失败"
+		br.ErrMsg = "初始化OSS服务失败"
+		return
+	}
+	resourceUrl, err = ossClient.UploadFile(fileName, fPath, "")
+	if err != nil {
+		br.Msg = "文件上传失败"
+		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+		return
+	}
+
+	defer func() {
+		os.Remove(fPath)
+	}()
+	item := new(models.Resource)
+	item.ResourceUrl = resourceUrl
+	item.ResourceType = 2
+	item.CreateTime = time.Now()
+	newId, err := models.AddResource(item)
+	if err != nil {
+		br.Msg = "资源上传失败"
+		br.ErrMsg = "资源上传失败,Err:" + err.Error()
+		return
+	}
+
+	var playSeconds float64
+	playSeconds, err = mp3duration.Calculate(fPath)
+	if playSeconds <= 0 {
+		playSeconds, err = utils.GetVideoPlaySeconds(fPath)
+		if err != nil {
+			br.Msg = "获取音频时间失败"
+			br.ErrMsg = "获取音频时间失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	fileBody, err := os.ReadFile(fPath)
+	videoSize := len(fileBody)
+	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
+	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
+
+	{
+		reportChapterCreateTime, err := time.Parse(utils.FormatDateTime, reportChapterInfo.CreateTime)
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "上传失败,Err:" + err.Error()
+			return
+		}
+		createTimeStr := reportChapterCreateTime.Format("0102")
+		videoName := reportChapterInfo.Title + "(" + createTimeStr + ")"
+
+		reportChapterInfo.VideoUrl = resourceUrl
+		reportChapterInfo.VideoName = videoName
+		reportChapterInfo.VideoPlaySeconds = fmt.Sprint(playSeconds)
+		reportChapterInfo.VideoSize = sizeStr
+		reportChapterInfo.VideoKind = 1
+		reportChapterInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportChapterInfo.LastModifyAdminName = this.SysUser.RealName
+		reportChapterInfo.ModifyTime = time.Now()
+		err = reportChapterInfo.UpdateChapter([]string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "VideoKind", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告章节的音频信息失败,Err:" + err.Error()
+			return
+		}
+
+		// 修改报告的最近更新人信息
+		reportInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告最后更新人信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	// 处理报告中的音频文件分贝
+	go services.HandleVideoDecibel(reportChapterInfo)
+
+	resp := new(models.ResourceResp)
+	resp.Id = newId
+	resp.ResourceUrl = resourceUrl
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}
+
+// PublishDayWeekReportChapter
+// @Title 发布章节
+// @Description 发布章节
+// @Param	request	body models.PublishReportChapterReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /publishDayWeekReportChapter [post]
+func (this *ReportController) PublishDayWeekReportChapter() {
+	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 models.PublishReportChapterReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportChapterId <= 0 {
+		br.Msg = "参数有误"
+		return
+	}
+
+	// 获取报告详情
+	chapterInfo, err := models.GetReportChapterInfoById(req.ReportChapterId)
+	if err != nil {
+		br.Msg = "章节信息有误"
+		br.ErrMsg = "获取章节信息失败, Err: " + err.Error()
+		return
+	}
+
+	reportInfo, err := models.GetReportByReportId(chapterInfo.ReportId)
+	if err != nil {
+		br.Msg = "查询报告有误"
+		br.ErrMsg = "查询报告信息失败, Err: " + err.Error()
+		return
+	}
+	if reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 图表刷新状态
+	refreshResult := data.CheckBatchChartRefreshResult("report", chapterInfo.ReportId, chapterInfo.ReportChapterId)
+	if !refreshResult {
+		br.Msg = "图表刷新未完成,请稍后操作"
+		br.ErrMsg = "图表刷新未完成,请稍后操作"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 如果是系统章节,那么需要校验下是否已经停更
+	if chapterInfo.TypeId > 0 {
+		// 获取规则配置
+		reportChapterTypeRule, err := models.GetReportChapterTypeById(chapterInfo.TypeId)
+		if err != nil {
+			br.Msg = "获取配置信息异常"
+			br.ErrMsg = "获取配置信息异常, Err: " + err.Error()
+			return
+		}
+		if reportChapterTypeRule.Enabled == 0 {
+			br.Msg = "该章节已永久停更"
+			br.ErrMsg = "该章节已永久停更 "
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	var publishTime time.Time
+	if reportInfo.MsgIsSend == 1 && reportInfo.PublishTime.IsZero() { //如果报告曾经发布过,并且已经发送过模版消息,则章节的发布时间为报告的发布时间
+		publishTime = reportInfo.PublishTime
+	} else {
+		publishTime = time.Now()
+	}
+
+	// 更新报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+	err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "报告最后编辑人保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 更新章节信息
+	chapterInfo.IsEdit = 1
+	chapterInfo.PublishState = 2
+	chapterInfo.PublishTime = publishTime
+	chapterInfo.LastModifyAdminId = this.SysUser.AdminId
+	chapterInfo.LastModifyAdminName = this.SysUser.RealName
+	chapterInfo.ModifyTime = time.Now()
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "IsEdit", "PublishState", "PublishTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+	err = chapterInfo.UpdateChapter(updateCols)
+	if err != nil {
+		br.Msg = "发布失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+
+	// 修改报告的最近更新人信息
+	go func() {
+		reportInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告最后更新人信息失败,Err:" + err.Error()
+			return
+		}
+	}()
+
+	// 更新章节ES
+	{
+		go services.UpdateReportChapterEs(chapterInfo.ReportChapterId)
+	}
+
+	// 同时发布报告
+	//if req.PublishReport == 1 {
+	//	if _, e := services.PublishDayWeekReport(chapterInfo.ReportId); e != nil {
+	//		br.Msg = "章节发布成功,报告发布失败,请手动发布报告"
+	//		br.ErrMsg = "发布晨/周报失败, Err: " + e.Error()
+	//		return
+	//	}
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// GetUnPublishReportChapterList
+// @Title 获取报告中未发布的章节数
+// @Description 获取报告中未发布的章节数
+// @Param	ReportId  query  int  true  "报告ID"
+// @Success 200 Ret=200 获取成功
+// @router /chapter/un_publish/list [get]
+func (this *ReportController) GetUnPublishReportChapterList() {
+	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
+	}
+
+	reportId, _ := this.GetInt("ReportId")
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.IsSendEmail = false
+		return
+	}
+	reportInfo, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "报告有误"
+		br.ErrMsg = "报告有误, Err: " + err.Error()
+		return
+	}
+
+	if reportInfo.HasChapter == 0 {
+		br.Msg = "报告未包含章节"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取未发布的章节列表
+	unPublishedList, err := models.GetUnPublishedChapterList(reportInfo.Id)
+	if err != nil {
+		br.Msg = "获取未发布的章节数失败"
+		br.ErrMsg = "获取未发布的章节数失败, Err: " + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = unPublishedList
+}

+ 50 - 63
controllers/report_chapter_type.go

@@ -16,7 +16,7 @@ type ReportChapterTypeController struct {
 // List
 // @Title 报告章节列表
 // @Description 报告章节列表
-// @Param   ReportType  query  string  true  "报告类型: day-晨报; week-周报"
+// @Param   ClassifyId  query  int  true  "所属分类id"
 // @Success 200 {object} models.ReportChapterTypePageListResp
 // @router /chapter_type/list [get]
 func (this *ReportChapterTypeController) List() {
@@ -33,22 +33,27 @@ func (this *ReportChapterTypeController) List() {
 		br.Ret = 408
 		return
 	}
-	reportType := this.GetString("ReportType")
-	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
-	if !utils.InArrayByStr(typeArr, reportType) {
-		br.Msg = "请选择报告类型"
+	classifyId, _ := this.GetInt("ClassifyId", 0)
+	if classifyId <= 0 {
+		br.Msg = "请选择分类"
 		return
 	}
-	cond := ` AND research_type = ?`
+
+	cond := ` AND report_classify_id = ?`
 	pars := make([]interface{}, 0)
-	pars = append(pars, reportType)
+	pars = append(pars, classifyId)
 	list, e := models.GetReportChapterTypePageList(cond, pars)
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取报告章节列表失败, Err: " + e.Error()
 		return
 	}
-	mappingList, e := models.GetChapterTypePermissionByResearchType(reportType)
+	chapterTypeIdList := make([]int, 0)
+	for _, v := range list {
+		chapterTypeIdList = append(chapterTypeIdList, v.ReportChapterTypeId)
+	}
+
+	mappingList, e := models.GetChapterTypePermissionByChapterTypeIdList(chapterTypeIdList)
 	if e != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取章节类型权限列表失败, Err: " + e.Error()
@@ -116,15 +121,16 @@ func (this *ReportChapterTypeController) Add() {
 		br.Msg = "请输入章节名称"
 		return
 	}
-	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
-	if !utils.InArrayByStr(typeArr, req.ResearchType) {
-		br.Msg = "请选择报告类型"
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
 		return
 	}
+
 	// 重名校验
-	cond := ` AND report_chapter_type_name = ? AND research_type = ?`
+	cond := ` AND report_chapter_type_name = ? AND report_classify_id = ?`
 	pars := make([]interface{}, 0)
-	pars = append(pars, req.ReportChapterTypeName, req.ResearchType)
+	pars = append(pars, req.ReportChapterTypeName, req.ClassifyId)
 	exists, e := models.GetReportChapterTypeByCondition(cond, pars)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -149,7 +155,9 @@ func (this *ReportChapterTypeController) Add() {
 	item.Enabled = 1
 	item.CreatedTime = nowTime
 	item.LastUpdatedTime = nowTime
-	item.ResearchType = req.ResearchType
+	//item.ResearchType = req.ResearchType
+	item.ResearchType = "chapter"
+	item.ReportClassifyId = req.ClassifyId
 	item.IsSet = 0
 	item.ReportChapterTypeKey = req.ReportChapterTypeName
 	item.TickerTitle = req.ReportChapterTypeName
@@ -160,7 +168,6 @@ func (this *ReportChapterTypeController) Add() {
 		return
 	}
 
-	// todo 更新章节权限
 	cond = ` and product_id=1`
 	pars = make([]interface{}, 0)
 	permissionList, e := services.GetChartPermissionList(cond, pars)
@@ -174,29 +181,20 @@ func (this *ReportChapterTypeController) Add() {
 		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
 	}
 
-	researchType := item.ResearchType
-	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
-	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	newPermissions := make([]*models.ReportChapterTypePermission, 0) // 报告章节权限表(新)
 	for i := range req.ChartPermissionIdList {
 		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
 			ReportChapterTypeId:   item.ReportChapterTypeId,
 			ReportChapterTypeName: item.ReportChapterTypeName,
 			ChartPermissionId:     req.ChartPermissionIdList[i],
 			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
-			ResearchType:          researchType,
+			ResearchType:          item.ResearchType,
 			CreatedTime:           nowTime,
 		})
-		if researchType == utils.REPORT_TYPE_WEEK {
-			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
-				ChartPermissionId:   req.ChartPermissionIdList[i],
-				ReportChapterTypeId: item.ReportChapterTypeId,
-				ResearchType:        researchType,
-			})
-		}
 	}
 
 	// 设置权限
-	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, newPermissions)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
@@ -208,10 +206,9 @@ func (this *ReportChapterTypeController) Add() {
 		_ = utils.Rc.Delete(key)
 	}
 
-	//todo 同步更新crm章节权限和章节类型
 	go func() {
 		var syncReq services.ChapterTypeSyncReq
-		syncReq.ResearchType = researchType
+		syncReq.ResearchType = item.ResearchType
 		syncReq.ReportChapterTypeId = item.ReportChapterTypeId
 		_, _ = services.ReportChapterTypeSync(&syncReq)
 	}()
@@ -254,15 +251,16 @@ func (this *ReportChapterTypeController) Edit() {
 		br.Msg = "请输入章节名称"
 		return
 	}
-	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
-	if !utils.InArrayByStr(typeArr, req.ResearchType) {
-		br.Msg = "请选择报告类型"
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
 		return
 	}
+
 	// 重名校验
-	cond := ` AND report_chapter_type_name = ? AND research_type = ?`
+	cond := ` AND report_chapter_type_name = ? AND report_classify_id = ?`
 	pars := make([]interface{}, 0)
-	pars = append(pars, req.ReportChapterTypeName, req.ResearchType)
+	pars = append(pars, req.ReportChapterTypeName, req.ClassifyId)
 	exists, e := models.GetReportChapterTypeByCondition(cond, pars)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -281,8 +279,15 @@ func (this *ReportChapterTypeController) Edit() {
 		return
 	}
 	originName := item.ReportChapterTypeName
-	//更新章节权限
-	// todo 更新章节权限
+	if item.ReportChapterTypeName != req.ReportChapterTypeName {
+		item.ReportChapterTypeName = req.ReportChapterTypeName
+		err := item.Update([]string{"ReportChapterTypeName"})
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改章节类型失败, Err: " + err.Error()
+			return
+		}
+	}
 	cond = ` and product_id=1`
 	pars = make([]interface{}, 0)
 	permissionList, e := services.GetChartPermissionList(cond, pars)
@@ -297,29 +302,20 @@ func (this *ReportChapterTypeController) Edit() {
 		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
 	}
 
-	researchType := item.ResearchType
-	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
-	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	newPermissions := make([]*models.ReportChapterTypePermission, 0) // 报告章节权限表(新)
 	for i := range req.ChartPermissionIdList {
 		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
 			ReportChapterTypeId:   item.ReportChapterTypeId,
 			ReportChapterTypeName: item.ReportChapterTypeName,
 			ChartPermissionId:     req.ChartPermissionIdList[i],
 			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
-			ResearchType:          researchType,
+			ResearchType:          item.ResearchType,
 			CreatedTime:           nowTime,
 		})
-		if researchType == utils.REPORT_TYPE_WEEK {
-			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
-				ChartPermissionId:   req.ChartPermissionIdList[i],
-				ReportChapterTypeId: item.ReportChapterTypeId,
-				ResearchType:        researchType,
-			})
-		}
 	}
 
 	// 设置权限
-	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, newPermissions)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
@@ -340,7 +336,7 @@ func (this *ReportChapterTypeController) Edit() {
 
 	go func() {
 		var syncReq services.ChapterTypeSyncReq
-		syncReq.ResearchType = researchType
+		syncReq.ResearchType = item.ResearchType
 		syncReq.ReportChapterTypeId = item.ReportChapterTypeId
 		_, _ = services.ReportChapterTypeSync(&syncReq)
 	}()
@@ -474,30 +470,21 @@ func (this *ReportChapterTypeController) AuthSetting() {
 		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
 	}
 
-	researchType := item.ResearchType
 	nowTime := time.Now().Local()
-	newPermissions := make([]*models.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
-	newWeekPermissions := make([]*models.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	newPermissions := make([]*models.ReportChapterTypePermission, 0) // 报告章节权限表(新)
 	for i := range req.ChartPermissionIdList {
 		newPermissions = append(newPermissions, &models.ReportChapterTypePermission{
 			ReportChapterTypeId:   item.ReportChapterTypeId,
 			ReportChapterTypeName: item.ReportChapterTypeName,
 			ChartPermissionId:     req.ChartPermissionIdList[i],
 			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
-			ResearchType:          researchType,
-			CreatedTime:           nowTime,
+			//ResearchType:          researchType,
+			CreatedTime: nowTime,
 		})
-		if researchType == utils.REPORT_TYPE_WEEK {
-			newWeekPermissions = append(newWeekPermissions, &models.ChartPermissionChapterMapping{
-				ChartPermissionId:   req.ChartPermissionIdList[i],
-				ReportChapterTypeId: item.ReportChapterTypeId,
-				ResearchType:        researchType,
-			})
-		}
 	}
 
 	// 设置权限
-	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	e = models.SetReportChapterTypePermission(item.ReportChapterTypeId, newPermissions)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "设置章节类型权限失败, Err: " + e.Error()
@@ -546,7 +533,7 @@ func (this *ReportChapterTypeController) PermissionList() {
 		br.Msg = "章节不存在或已被删除"
 		return
 	}
-	list, e := models.GetChapterTypePermissionByTypeIdAndResearchType(typeId, item.ResearchType)
+	list, e := models.GetChapterTypePermissionByReportChapterTypeId(typeId)
 	if e != nil {
 		br.Msg = "章节不存在或已被删除"
 		br.ErrMsg = "获取章节类型权限列表失败, Err: " + e.Error()

+ 1902 - 0
controllers/report_v2.go

@@ -0,0 +1,1902 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/report"
+	"eta/eta_api/models/report_approve"
+	"eta/eta_api/models/smart_report"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ListReport
+// @Title 获取报告列表接口
+// @Description 获取报告列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   TimeType     query string true  "筛选的时间类别:publish_time(发布时间),modify_time(更新时间);approve_time(审批时间)"
+// @Param   StartDate   query   string  true       "开始时间"
+// @Param   EndDate   query   string  true       "结束时间"
+// @Param   Frequency   query   string  true       "频度"
+// @Param   ClassifyIdFirst   query   int  true       "一级分类id"
+// @Param   ClassifyIdSecond   query   int  true       "二级分类id"
+// @Param   ClassifyIdThird   query   int  true       "三级分类id"
+// @Param   State   query   int  true       "状态"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   PublishSort   query   string  true       "desc:降序,asc 升序(预留)"
+// @Param   FilterReportType   query   string  true       "筛选报告类型,1:公共研报,2:共享研报,3:我的研报"
+// @Success 200 {object} models.ReportListResp
+// @router /list [get]
+func (this *ReportController) ListReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	timeType := this.GetString("TimeType")
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	frequency := this.GetString("Frequency")
+	classifyIdFirst, _ := this.GetInt("ClassifyIdFirst", 0)
+	classifyIdSecond, _ := this.GetInt("ClassifyIdSecond", 0)
+	classifyIdThird, _ := this.GetInt("ClassifyIdThird", 0)
+	state, _ := this.GetInt("State")
+	keyWord := this.GetString("KeyWord")
+	msgIsSend, _ := this.GetInt("MsgIsSend")
+	filterReportType, _ := this.GetInt("FilterReportType", 1)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	if timeType == "" {
+		timeType = "publish_time"
+	}
+	if timeType != "publish_time" && timeType != "modify_time" && timeType != "approve_time" {
+		br.Msg = "请选择正确的时间"
+		br.ErrMsg = "请选择正确的时间"
+		return
+	}
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += ` AND (a.title LIKE ? OR a.admin_real_name LIKE ? ) `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
+	}
+	if startDate != "" {
+		condition += ` AND a.` + timeType + ` >= ? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		condition += ` AND a.` + timeType + ` <= ? `
+		pars = append(pars, endDate)
+	}
+	if frequency != "" {
+		condition += ` AND a.frequency = ? `
+		pars = append(pars, frequency)
+	}
+
+	if classifyIdFirst > 0 {
+		condition += ` AND a.classify_id_first = ? `
+		pars = append(pars, classifyIdFirst)
+	}
+	if classifyIdSecond > 0 {
+		condition += ` AND a.classify_id_second = ? `
+		pars = append(pars, classifyIdSecond)
+	}
+	if classifyIdThird > 0 {
+		condition += ` AND a.classify_id_third = ? `
+		pars = append(pars, classifyIdSecond)
+	}
+
+	if state > 0 {
+		condition += ` AND a.state = ? `
+		pars = append(pars, state)
+	}
+	// 消息是否已推送 1-未推送; 2-已推送
+	if msgIsSend == 1 {
+		condition += ` AND (a.msg_is_send = 0 OR a.ths_msg_is_send = 0) `
+	}
+	if msgIsSend == 2 {
+		condition += ` AND a.msg_is_send = 1 AND a.ths_msg_is_send = 1 `
+	}
+
+	var err error
+	var total int
+	var list []*models.ReportList
+
+	switch filterReportType {
+	// 筛选报告类型,1:公共研报,2:共享研报,3:我的研报
+	case 1:
+		condition += ` AND a.is_public_publish = ? `
+		pars = append(pars, 1)
+		condition += `  AND a.state in (2,6) `
+
+	case 3:
+		condition += ` AND a.admin_id = ? `
+		pars = append(pars, this.SysUser.AdminId)
+	case 2:
+		condition += ` AND (a.admin_id = ? or b.admin_id = ?) `
+		pars = append(pars, this.SysUser.AdminId, this.SysUser.AdminId)
+	}
+
+	// 共享报告需要连表查询,所以需要单独写
+	if filterReportType == 2 {
+		total, err = models.GetReportListCountByGrant(condition, pars)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		list, err = models.GetReportListByGrant(condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		total, err = models.GetReportListCountV1(condition, pars)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		list, err = models.GetReportListV1(condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	listLen := len(list)
+	if listLen > 0 {
+		pvMap := make(map[int]int)
+		uvMap := make(map[int]int)
+		reportIdArr := make([]string, 0)
+		syncReportIdArr := make([]string, 0)      // 同步过来的报告IDs
+		oldAndNewReportIdMap := make(map[int]int) // 旧报告和新报告的id对应关系
+		for i := 0; i < listLen; i++ {
+			reportIdArr = append(reportIdArr, strconv.Itoa(list[i].Id))
+			if list[i].OldReportId > 0 && list[i].ReportLayout == 1 {
+				syncReportIdArr = append(syncReportIdArr, strconv.Itoa(list[i].OldReportId))
+				oldAndNewReportIdMap[list[i].OldReportId] = list[i].Id
+			}
+			pvMap[list[i].Id] = list[i].Pv
+			uvMap[list[i].Id] = list[i].Uv
+		}
+
+		// 当下报告的pv,uv
+		if len(reportIdArr) > 0 {
+			pvList, e := models.GetReportPvUvByReportIdList(reportIdArr)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取同步报告对应的PV、UV失败, Err: " + e.Error()
+				return
+			}
+			for _, v := range pvList {
+				pv := pvMap[v.ReportId]
+				uv := uvMap[v.ReportId]
+				pvMap[v.ReportId] = v.PvTotal + pv
+				uvMap[v.ReportId] = v.UvTotal + uv
+			}
+		}
+
+		//reportIds := strings.Join(reportIdArr, ",")
+		//syncReportIds := strings.Join(syncReportIdArr, ",")
+
+		// 查询同步过来的报告对应的老报告PV+UV
+		if len(syncReportIdArr) > 0 {
+			puvList, e := models.GetPUVByResearchReportIds(syncReportIdArr)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取同步报告对应的PV、UV失败, Err: " + e.Error()
+				return
+			}
+			puvLen := len(puvList)
+			for i := 0; i < puvLen; i++ {
+				newReportId, ok := oldAndNewReportIdMap[puvList[i].ResearchReportId]
+				if ok {
+					pv := pvMap[newReportId]
+					uv := uvMap[newReportId]
+					pvMap[newReportId] = puvList[i].Pv + pv
+					uvMap[newReportId] = puvList[i].Uv + uv
+				}
+			}
+		}
+		// 晨周报音频列表
+		videoList, err := models.GetReportChapterVideoListByReportIds(reportIdArr)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取报告音频文件失败,Err:" + err.Error()
+			return
+		}
+		for i := 0; i < listLen; i++ {
+			list[i].Content = html.UnescapeString(list[i].Content)
+			list[i].ContentSub = html.UnescapeString(list[i].ContentSub)
+			// 除周报外其余报告均可推送客群
+			list[i].NeedThsMsg = 1
+			//if list[i].HasChapter == 1 && list[i].ChapterType == utils.REPORT_TYPE_WEEK {
+			//	list[i].NeedThsMsg = 0
+			//}
+			chapterList := make([]*models.ReportChapterVideoList, 0)
+			for ii := 0; ii < len(videoList); ii++ {
+				if list[i].Id == videoList[ii].ReportId {
+					chapterList = append(chapterList, videoList[ii])
+				}
+			}
+			list[i].ChapterVideoList = chapterList
+			list[i].Pv += pvMap[list[i].Id]
+			list[i].Uv += uvMap[list[i].Id]
+		}
+	}
+
+	for _, item := range list {
+		/*key := fmt.Sprint(`crm:report:edit:`, item.Id)
+		opUserId, _ := utils.Rc.RedisInt(key)
+		//如果当前没有人操作,获取当前操作人是本人,那么编辑按钮可用
+		if opUserId <= 0 || (opUserId == this.SysUser.AdminId) || item.ClassifyNameFirst == "周报" || item.ClassifyNameFirst == "晨报" {
+			item.CanEdit = true
+		} else {
+			adminInfo, errAdmin := system.GetSysUserById(opUserId)
+			if errAdmin != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + errAdmin.Error()
+				return
+			}
+			item.Editor = adminInfo.RealName
+		}*/
+		if item.HasChapter == 1 {
+			item.CanEdit = true
+			continue
+		}
+		markStatus, err := services.UpdateReportEditMark(item.Id, 0, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = "查询标记状态失败"
+			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+			return
+		}
+		if markStatus.Status == 0 {
+			item.CanEdit = true
+		} else {
+			item.Editor = markStatus.Editor
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ReportListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增报告接口
+// @Description 新增报告(不区分报告类型)
+// @Param	request	body models.AddReq true "type json string"
+// @Success 200 {object} models.AddResp
+// @router /add [post]
+func (this *ReportController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req models.AddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.Title == `` {
+		br.Msg = "标题不能为空"
+		br.ErrMsg = "标题不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ClassifyIdFirst <= 0 {
+		br.Msg = "分类必填"
+		br.ErrMsg = "分类必填"
+		br.IsSendEmail = false
+		return
+	}
+
+	var contentSub string
+	if req.Content != "" {
+		e := utils.ContentXssCheck(req.Content)
+		if e != nil {
+			br.Msg = "存在非法标签"
+			br.ErrMsg = "存在非法标签, Err: " + e.Error()
+			return
+		}
+		content, e := services.FilterReportContentBr(req.Content)
+		if e != nil {
+			br.Msg = "内容去除前后空格失败"
+			br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+			return
+		}
+		req.Content = content
+
+		contentSub, err = services.GetReportContentSub(req.Content)
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("ContentSub 失败,Err:"+err.Error(), 3)
+			//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
+		}
+	}
+
+	// 报告期数
+	maxStage, err := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
+	if err != nil {
+		br.Msg = "期数获取失败!"
+		br.ErrMsg = "期数获取失败,Err:" + err.Error()
+		return
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird, models.ReportOperateAdd)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 协作方式,1:个人,2:多人协作。默认:1
+	if req.CollaborateType == 0 {
+		req.CollaborateType = 1
+	}
+	// 报告布局,1:常规布局,2:智能布局。默认:1
+	if req.ReportLayout == 0 {
+		req.ReportLayout = 1
+	}
+	// 是否公开发布,1:是,2:否
+	if req.IsPublicPublish == 0 {
+		req.IsPublicPublish = 1
+	}
+
+	classifyItemList, err := models.GetClassifyListByIdList([]int{req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird})
+	classifyMap := make(map[int]string)
+	for _, v := range classifyItemList {
+		classifyMap[v.Id] = v.ClassifyName
+	}
+
+	item := new(models.Report)
+	item.AddType = req.AddType
+	item.ReportVersion = 2
+	item.ClassifyIdFirst = req.ClassifyIdFirst
+	item.ClassifyNameFirst = classifyMap[req.ClassifyIdFirst]
+	item.ClassifyIdSecond = req.ClassifyIdSecond
+	item.ClassifyNameSecond = classifyMap[req.ClassifyIdSecond]
+	item.Title = req.Title
+	item.Abstract = req.Abstract
+	item.Author = req.Author
+	item.Frequency = req.Frequency
+	item.State = state
+	item.Content = html.EscapeString(req.Content)
+	item.Stage = maxStage + 1
+	item.ContentSub = html.EscapeString(contentSub)
+	item.CreateTime = req.CreateTime
+	item.ModifyTime = time.Now()
+	item.ReportVersion = req.ReportVersion
+	item.AdminId = sysUser.AdminId
+	item.AdminRealName = sysUser.RealName
+
+	item.ClassifyIdThird = req.ClassifyIdThird
+	item.ClassifyNameThird = classifyMap[req.ClassifyIdThird]
+
+	// 产品要求,如果是多人协作,那么就是章节类型的报告
+	if req.CollaborateType == 2 {
+		item.HasChapter = 1
+		item.ChapterType = ""
+	}
+	item.LastModifyAdminId = sysUser.AdminId
+	item.LastModifyAdminName = sysUser.RealName
+	item.ContentModifyTime = time.Now()
+	item.NeedSplice = 1
+	item.ContentStruct = html.EscapeString(req.ContentStruct)
+	item.HeadImg = req.HeadImg
+	item.EndImg = req.EndImg
+	item.CanvasColor = req.CanvasColor
+	item.HeadResourceId = req.HeadResourceId
+	item.EndResourceId = req.EndResourceId
+	item.CollaborateType = req.CollaborateType
+	item.ReportLayout = req.ReportLayout
+	item.IsPublicPublish = req.IsPublicPublish
+	item.ReportCreateTime = time.Now()
+
+	err, errMsg := services.AddReportAndChapter(item, req.InheritReportId, req.GrantAdminIdList)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	recordItem := &models.ReportStateRecord{
+		ReportId:   item.Id,
+		ReportType: 1,
+		State:      1,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+
+	resp := new(models.AddResp)
+	resp.ReportId = int64(item.Id)
+	resp.ReportCode = item.ReportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// Edit
+// @Title 编辑报告基础信息接口
+// @Description 编辑报告基础信息(不区分报告类型)
+// @Param	request	body models.EditReq true "type json string"
+// @Success 200 {object} models.EditResp
+// @router /edit [post]
+func (this *ReportController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req models.EditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	//if req.Content == "" {
+	//	br.Msg = "报告内容不能为空"
+	//	return
+	//}
+	//更新标记key
+	markStatus, err := services.UpdateReportEditMark(int(req.ReportId), 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	if markStatus.Status == 1 {
+		br.Msg = markStatus.Msg
+		//br.Ret = 202 //202 服务器已接受请求,但尚未处理。
+		return
+	}
+
+	reportInfo, e := models.GetReportByReportId(int(req.ReportId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	if reportInfo.State == models.ReportStatePublished || reportInfo.State == models.ReportStatePass {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 编辑报告信息
+	err, errMsg := services.EditReport(reportInfo, req, sysUser)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
+	resp := new(models.EditResp)
+	resp.ReportId = req.ReportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// Detail
+// @Title 获取报告详情接口
+// @Description 获取报告详情
+// @Param	request	body models.ReportDetailReq true "type json string"
+// @Success 200 {object} models.Report
+// @router /detail [get]
+func (this *ReportController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	/*var req models.ReportDetailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}*/
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取参数失败!"
+		br.ErrMsg = "获取参数失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	item, err := models.GetReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	chapterList := make([]*models.ReportChapter, 0)
+	if item.HasChapter == 1 {
+		// 获取章节内容
+		tmpChapterList, err := models.GetPublishedChapterListByReportId(item.Id)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取晨/周报章节列表失败, Err: " + err.Error()
+			return
+		}
+
+		if len(tmpChapterList) > 0 {
+			// 章节类型的字段赋值
+			for _, item := range tmpChapterList {
+				item.Content = html.UnescapeString(item.Content)
+				item.ContentSub = html.UnescapeString(item.ContentSub)
+				chapterList = append(chapterList, item)
+			}
+		}
+
+		//item.Abstract = item.Title
+	}
+	item.Content = html.UnescapeString(item.Content)
+	item.ContentSub = html.UnescapeString(item.ContentSub)
+	item.ContentStruct = html.UnescapeString(item.ContentStruct)
+
+	if item.HeadResourceId > 0 {
+		headResource, err := smart_report.GetResourceItemById(item.HeadResourceId)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
+			return
+		}
+		item.HeadImg = headResource.ImgUrl
+		item.HeadStyle = headResource.Style
+	}
+
+	if item.EndResourceId > 0 {
+		endResource, err := smart_report.GetResourceItemById(item.EndResourceId)
+		if err != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
+			return
+		}
+		item.EndImg = endResource.ImgUrl
+		item.EndStyle = endResource.Style
+	}
+
+	resp := &models.ReportDetailView{
+		ReportDetail: item,
+		ChapterList:  chapterList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// SaveReportContent
+// @Title 保存草稿
+// @Description 保存草稿
+// @Param	request	body models.SaveReportContent true "type json string"
+// @Success 200 {object} models.ReportAuthorResp
+// @router /saveReportContent [post]
+func (this *ReportController) SaveReportContent() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	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 models.SaveReportContent
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportId := req.ReportId
+	noChangeFlag := req.NoChange
+
+	if reportId <= 0 {
+		resp := new(models.SaveReportContentResp)
+		resp.ReportId = reportId
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "保存成功"
+		br.Data = resp
+		return
+	}
+
+	// 获取报告详情
+	reportInfo, _ := models.GetReportByReportId(req.ReportId)
+	if reportInfo != nil && reportInfo.State == 2 {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 标记更新中
+	{
+		markStatus, err := services.UpdateReportEditMark(req.ReportId, 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = err.Error()
+			return
+		}
+		if markStatus.Status == 1 {
+			br.Msg = markStatus.Msg
+			return
+		}
+	}
+
+	// 内容有过修改的话,那么逻辑处理
+	if noChangeFlag != 1 {
+		content := req.Content
+		if content == "" {
+			content = this.GetString("Content")
+		}
+		if content != "" {
+			e := utils.ContentXssCheck(req.Content)
+			if e != nil {
+				br.Msg = "存在非法标签"
+				br.ErrMsg = "存在非法标签, Err: " + e.Error()
+				return
+			}
+			contentClean, e := services.FilterReportContentBr(req.Content)
+			if e != nil {
+				br.Msg = "内容去除前后空格失败"
+				br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+				return
+			}
+			content = contentClean
+
+			contentSub, err := services.GetReportContentSub(content)
+			if err != nil {
+				go alarm_msg.SendAlarmMsg("解析 ContentSub 失败,Err:"+err.Error(), 3)
+				//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
+			}
+			reportInfo.Content = html.EscapeString(content)
+			reportInfo.ContentSub = html.EscapeString(contentSub)
+			reportInfo.ContentStruct = html.EscapeString(req.ContentStruct)
+			reportInfo.HeadImg = req.HeadImg
+			reportInfo.EndImg = req.EndImg
+			reportInfo.CanvasColor = req.CanvasColor
+			reportInfo.HeadResourceId = req.HeadResourceId
+			reportInfo.EndResourceId = req.EndResourceId
+			reportInfo.ModifyTime = time.Now()
+			reportInfo.ContentModifyTime = time.Now()
+			updateCols := []string{"Content", "ContentSub", "ContentStruct", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId", "ModifyTime", "ContentModifyTime"}
+			err = reportInfo.UpdateReport(updateCols)
+			if err != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = "保存失败,Err:" + err.Error()
+				return
+			}
+			go models.AddReportSaveLog(reportId, this.SysUser.AdminId, reportInfo.Content, reportInfo.ContentSub, reportInfo.ContentStruct, reportInfo.CanvasColor, this.SysUser.AdminName, reportInfo.HeadResourceId, reportInfo.EndResourceId)
+		}
+	}
+
+	resp := new(models.SaveReportContentResp)
+	resp.ReportId = reportId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// AuthorizedListReport
+// @Title 获取有权限的报告列表接口
+// @Description 获取有权限的报告列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   Keyword   query   string  true       "搜索关键词"
+// @Param   ClassifyIdFirst   query   int  true       "一级分类id"
+// @Param   ClassifyIdSecond   query   int  true       "二级分类id"
+// @Param   ClassifyIdThird   query   int  true       "三级分类id"
+// @Success 200 {object} models.ReportListResp
+// @router /list/authorized [get]
+func (this *ReportController) AuthorizedListReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyword := this.GetString("Keyword")
+	classifyIdFirst, _ := this.GetInt("ClassifyIdFirst", 0)
+	classifyIdSecond, _ := this.GetInt("ClassifyIdSecond", 0)
+	classifyIdThird, _ := this.GetInt("ClassifyIdThird", 0)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+	var list []*models.ReportList
+
+	// 没有输入信息,那就不展示
+	if keyword == `` && classifyIdFirst <= 0 {
+		page := paging.GetPaging(currentIndex, pageSize, 0)
+		resp := new(models.ReportListResp)
+		resp.Paging = page
+		resp.List = list
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	// 当前用户有权限的报告id列表
+	grantReportIdList := make([]int, 0)
+	{
+		obj := report.ReportGrant{}
+		grantList, err := obj.GetGrantListByAdminId(this.SysUser.AdminId)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取授权报告id失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range grantList {
+			grantReportIdList = append(grantReportIdList, v.ReportId)
+		}
+	}
+
+	var condition string
+	var pars []interface{}
+
+	if classifyIdFirst > 0 {
+		condition += ` AND a.classify_id_first = ? `
+		pars = append(pars, classifyIdFirst)
+	}
+	if classifyIdSecond > 0 {
+		condition += ` AND a.classify_id_second = ? `
+		pars = append(pars, classifyIdSecond)
+	}
+	if classifyIdThird > 0 {
+		condition += ` AND a.classify_id_third = ? `
+		pars = append(pars, classifyIdSecond)
+	}
+
+	if keyword != `` {
+		condition += ` AND a.title LIKE ? `
+		pars = utils.GetLikeKeywordPars(pars, keyword, 1)
+	}
+
+	var err error
+	var total int
+
+	orCondition := `AND ( (a.is_public_publish = ? AND a.state in (2,6)) or a.admin_id = ? `
+	pars = append(pars, 1, this.SysUser.AdminId)
+
+	// 当前用户有权限的报告id列表
+	num := len(grantReportIdList)
+	if num > 0 {
+		orCondition += ` OR a.id in (` + utils.GetOrmInReplace(num) + `)`
+		pars = append(pars, grantReportIdList)
+	}
+	orCondition += ` ) `
+
+	condition += orCondition
+
+	total, err = models.GetReportListCountByAuthorized(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	list, err = models.GetReportListByAuthorized(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	//for _, item := range list {
+	//	if item.HasChapter == 1 {
+	//		item.CanEdit = true
+	//	} else {
+	//		markStatus, err := services.UpdateReportEditMark(item.Id, this.SysUser.AdminId, 2, this.SysUser.RealName, this.Lang)
+	//		if err != nil {
+	//			br.Msg = "查询标记状态失败"
+	//			br.ErrMsg = "查询标记状态失败,Err:" + err.Error()
+	//			return
+	//		}
+	//		if markStatus.Status == 0 {
+	//			item.CanEdit = true
+	//		} else {
+	//			item.Editor = markStatus.Editor
+	//		}
+	//	}
+	//}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(models.ReportListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// BaseDetail
+// @Title 获取报告基础信息详情接口
+// @Description 获取报告基础信息详情接口
+// @Param	request	body models.ReportDetailReq true "type json string"
+// @Success 200 {object} models.Report
+// @router /detail/base [get]
+func (this *ReportController) BaseDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	/*var req models.ReportDetailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}*/
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取参数失败!"
+		br.ErrMsg = "获取参数失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	reportInfo, err := models.GetReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	// 基础信息就不获取章节信息了
+	chapterList := make([]*models.ReportChapter, 0)
+
+	reportInfo.Content = html.UnescapeString(reportInfo.Content)
+	reportInfo.ContentSub = html.UnescapeString(reportInfo.ContentSub)
+
+	grandAdminList := make([]models.ReportDetailViewAdmin, 0)
+	permissionList := make([]models.ReportDetailViewPermission, 0)
+
+	// 处理报告授权用户列表
+	{
+		obj := report.ReportGrant{}
+		grantList, tmpErr := obj.GetGrantListById(reportId)
+		if tmpErr != nil {
+			br.Msg = "获取章节id授权用户列表失败"
+			br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+			return
+		}
+
+		if len(grantList) > 0 {
+			grandAdminIdList := make([]int, 0)
+			for _, v := range grantList {
+				grandAdminIdList = append(grandAdminIdList, v.AdminId)
+			}
+			adminList, tmpErr := system.GetAdminListByIdList(grandAdminIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id授权用户列表失败"
+				br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+				return
+			}
+			for _, v := range adminList {
+				grandAdminList = append(grandAdminList, models.ReportDetailViewAdmin{
+					AdminId:   v.AdminId,
+					AdminName: v.RealName,
+				})
+			}
+		}
+
+	}
+
+	// 处理章节id关联品种id列表
+	{
+		minClassifyId := reportInfo.ClassifyIdThird
+		if minClassifyId <= 0 {
+			minClassifyId = reportInfo.ClassifyIdSecond
+		}
+		if minClassifyId <= 0 {
+			minClassifyId = reportInfo.ClassifyIdFirst
+		}
+		if minClassifyId <= 0 {
+			br.Msg = "分类异常"
+			br.ErrMsg = "分类异常"
+			return
+		}
+
+		// 获取分类关联的品种id
+		classifyPermissionList, tmpErr := models.GetPermission(minClassifyId)
+		if tmpErr != nil {
+			br.Msg = "获取分类信息失败"
+			br.ErrMsg = "获取失败,Err:" + tmpErr.Error()
+			return
+		}
+
+		if len(classifyPermissionList) > 0 {
+			permissionIdList := make([]int, 0)
+			for _, v := range classifyPermissionList {
+				permissionIdList = append(permissionIdList, v.ChartPermissionId)
+			}
+			adminList, tmpErr := models.GetChartPermissionByIdList(permissionIdList)
+			if tmpErr != nil {
+				br.Msg = "获取章节id授权用户列表失败"
+				br.ErrMsg = "获取章节id授权用户列表失败, Err: " + tmpErr.Error()
+				return
+			}
+			for _, v := range adminList {
+				permissionList = append(permissionList, models.ReportDetailViewPermission{
+					PermissionId:   v.ChartPermissionId,
+					PermissionName: v.PermissionName,
+				})
+			}
+		}
+
+	}
+
+	resp := &models.ReportDetailView{
+		ReportDetail:   reportInfo,
+		ChapterList:    chapterList,
+		GrandAdminList: grandAdminList,
+		PermissionList: permissionList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// EditLayoutImg
+// @Title 版图设置接口
+// @Description 版图设置接口
+// @Param	request	body models.EditLayoutImgReq true "type json string"
+// @Success 200 {object} models.EditResp
+// @router /layout_img/edit [post]
+func (this *ReportController) EditLayoutImg() {
+	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 models.EditLayoutImgReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	//if req.Content == "" {
+	//	br.Msg = "报告内容不能为空"
+	//	return
+	//}
+	//更新标记key
+	markStatus, err := services.UpdateReportEditMark(int(req.ReportId), 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+	if markStatus.Status == 1 {
+		br.Msg = markStatus.Msg
+		//br.Ret = 202 //202 服务器已接受请求,但尚未处理。
+		return
+	}
+
+	reportInfo, e := models.GetReportByReportId(int(req.ReportId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	if reportInfo.State == models.ReportStatePublished || reportInfo.State == models.ReportStatePass {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+
+	// 编辑报告信息
+	err, errMsg := services.EditReportLayoutImg(reportInfo, req, sysUser)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		return
+	}
+
+	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
+	resp := new(models.EditResp)
+	resp.ReportId = req.ReportId
+	resp.ReportCode = reportCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+}
+
+// PublishReport
+// @Title 发布报告接口
+// @Description 发布报告
+// @Param	request	body models.PublishReq true "type json string"
+// @Success 200 Ret=200 发布成功
+// @router /publish [post]
+func (this *ReportController) PublishReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportIds := req.ReportIds
+	if reportIds == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+
+	// 这里实际上不会批量发布了...
+	reportArr := strings.Split(reportIds, ",")
+	tips := ""
+	for _, v := range reportArr {
+		vint, err := strconv.Atoi(v)
+		if err != nil {
+			br.Msg = "参数错误"
+			br.ErrMsg = "参数错误,Err:" + err.Error()
+			return
+		}
+
+		// 报告的图表刷新状态校验
+		refreshResult := data.CheckBatchChartRefreshResult("report", vint, 0)
+		if !refreshResult {
+			br.Msg = "图表刷新未完成,请稍后操作"
+			br.ErrMsg = "图表刷新未完成,请稍后操作"
+			br.IsSendEmail = false
+			return
+		}
+
+		// 报告发布
+		tmpTips, err, errMsg := services.PublishReport(vint, req.ReportUrl, this.SysUser)
+		if err != nil {
+			br.Msg = errMsg
+			br.ErrMsg = "报告发布失败,Err:" + err.Error()
+			return
+		}
+		tips = tmpTips
+	}
+	// 发布晨周报部分章节未发布的提示
+	if tips != "" {
+		br.Data = tips
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "发布成功"
+}
+
+// PublishCancelReport
+// @Title 取消发布报告接口
+// @Description 取消发布报告
+// @Param	request	body models.PublishCancelReq true "type json string"
+// @Success 200 Ret=200 取消发布成功
+// @router /publish/cancle [post]
+func (this *ReportController) PublishCancelReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PublishCancelReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportIds <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+	publishTimeNullFlag := true
+	reportInfo, err := models.GetReportById(req.ReportIds)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+	if reportInfo.MsgIsSend == 1 {
+		publishTimeNullFlag = false
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperateCancelPublish)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
+	err = models.PublishCancelReport(req.ReportIds, state, publishTimeNullFlag, this.SysUser.AdminId, this.SysUser.RealName)
+	if err != nil {
+		br.Msg = "取消发布失败"
+		br.ErrMsg = "取消发布失败,Err:" + err.Error()
+		return
+	}
+	// 更新ES禁用
+	{
+		go services.UpdateReportEs(req.ReportIds, 1)
+	}
+
+	//// 获取审批流设置
+	//confKey := "approval_flow"
+	//confTmp, e := company.GetConfigDetailByCode(confKey)
+	//if e != nil {
+	//	br.Msg = "获取审批流配置失败"
+	//	br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
+	//	return
+	//}
+	//if confTmp.ConfigValue == "1" || confTmp.ConfigValue == "2" || confTmp.ConfigValue == "3" {
+	//	br.Msg = "撤销成功"
+	//} else {
+	//	br.Msg = "取消发布成功"
+	//}
+
+	recordItem := &models.ReportStateRecord{
+		ReportId:   req.ReportIds,
+		ReportType: 1,
+		State:      state,
+		AdminId:    this.SysUser.AdminId,
+		AdminName:  this.SysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+	br.Ret = 200
+	br.Success = true
+}
+
+// PrePublishReport
+// @Title 设置定时发布接口
+// @Description 设置定时发布接口
+// @Param	request	body models.PrePublishReq true "type json string"
+// @Success 200 Ret=200 发布成功
+// @router /pre_publish [post]
+func (this *ReportController) PrePublishReport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req models.PrePublishReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId == 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id不可为空"
+		return
+	}
+	if req.PrePublishTime == "" {
+		br.Msg = "发布时间不能为空"
+		return
+	}
+	if req.PreMsgSend != 0 && req.PreMsgSend != 1 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "是否发送模版消息标识错误"
+		return
+	}
+	prePublishTime, err := time.ParseInLocation(utils.FormatDateTime, req.PrePublishTime, time.Local)
+	if err != nil {
+		br.Msg = "发布时间格式错误"
+		br.ErrMsg = "发布时间格式错误,Err:" + err.Error()
+		return
+	}
+	if prePublishTime.Before(time.Now()) {
+		br.Msg = "发布时间不允许选择过去时间"
+		return
+	}
+	if prePublishTime.Before(time.Now().Add(2 * time.Minute)) {
+		br.Msg = "发布时间距离当前时间太近了"
+		return
+	}
+
+	reportDetail, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+	if reportDetail == nil {
+		br.Msg = "报告不存在"
+		return
+	}
+
+	// 如果是章节类型的报告,那么需要确认所有章节已发布
+	if reportDetail.HasChapter == 1 {
+		chapterList, err := models.GetChapterListByReportId(reportId)
+		if err != nil {
+			return
+		}
+		for _, chapter := range chapterList {
+			if chapter.PublishState == 1 {
+				br.Msg = "还存在未发布的章节"
+				br.ErrMsg = "还存在未发布的章节"
+				return
+			}
+		}
+	} else {
+		if reportDetail.Content == "" {
+			br.Msg = "报告内容为空,不可设置定时发布"
+			br.ErrMsg = "报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportDetail.Id)
+			return
+		}
+	}
+
+	if reportDetail.State == 2 {
+		br.Msg = "报告已发布,不可设置定时发布"
+		return
+	}
+
+	// 校验是否开启了审批流
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeChinese, reportDetail.ClassifyIdFirst, reportDetail.ClassifyIdSecond, reportDetail.ClassifyIdThird)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
+		return
+	}
+	if opening {
+		br.Msg = "报告已开启审批流, 不可设置定时发布"
+		return
+	}
+
+	var tmpErr error
+	if tmpErr = models.SetPrePublishReportById(reportDetail.Id, req.PrePublishTime, req.PreMsgSend); tmpErr != nil {
+		br.Msg = "设置定时发布失败"
+		br.ErrMsg = "设置定时发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(reportDetail.Id)
+		return
+	}
+
+	// 生成报告pdf和长图
+	if req.ReportUrl != "" {
+		go services.Report2pdfAndJpeg(req.ReportUrl, reportDetail.Id, 1)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "定时发布成功"
+}
+
+// SubmitApprove
+// @Title 提交审批
+// @Description 提交审批接口
+// @Param	request	body models.ReportSubmitApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/submit [post]
+func (this *ReportController) SubmitApprove() {
+	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"
+		return
+	}
+	var req models.ReportSubmitApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.Report)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 如果是章节类型的报告,那么需要确认所有章节已发布
+	if reportItem.HasChapter == 1 {
+		chapterList, err := models.GetChapterListByReportId(reportId)
+		if err != nil {
+			return
+		}
+		for _, chapter := range chapterList {
+			if chapter.PublishState == 1 {
+				br.Msg = "还存在未发布的章节"
+				br.ErrMsg = "还存在未发布的章节"
+				return
+			}
+		}
+	} else {
+		if reportItem.Content == "" {
+			br.Msg = "报告内容为空,不可提交"
+			br.ErrMsg = "报告内容为空,不可提交,report_id:" + strconv.Itoa(reportItem.Id)
+			return
+		}
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, reportItem.ClassifyIdThird, models.ReportOperateSubmitApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待审批时, 仅更新状态
+	if state != models.ReportStateWaitApprove {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 提交审批
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeChinese, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, reportItem.ClassifyIdThird, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "提交审批失败, Err: " + e.Error()
+		return
+	}
+	reportItem.ApproveId = approveId
+	reportItem.State = models.ReportStateWaitApprove
+	reportItem.ModifyTime = time.Now().Local()
+	e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// CancelApprove
+// @Title 撤销审批
+// @Description 撤销审批
+// @Param	request	body models.ReportCancelApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/cancel [post]
+func (this *ReportController) CancelApprove() {
+	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"
+		return
+	}
+	var req models.ReportCancelApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.Report)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, reportItem.ClassifyIdThird, models.ReportOperateCancelApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待提交时, 仅更新状态
+	if state != models.ReportStateWaitSubmit {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+	//if reportItem.ApproveId <= 0 {
+	//	br.Msg = "报告审批不存在"
+	//	br.ErrMsg = fmt.Sprintf("报告审批不存在, ApproveId: %d", reportItem.ApproveId)
+	//	return
+	//}
+
+	// 撤销审批
+	e = services.CancelReportApprove(report_approve.FlowReportTypeChinese, reportItem.Id, reportItem.ApproveId, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "撤销审批失败, Err: " + e.Error()
+		return
+	}
+	//reportItem.ApproveId = 0
+	//reportItem.State = models.ReportStateWaitSubmit
+	//reportItem.ModifyTime = time.Now().Local()
+	//e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	//if e != nil {
+	//	br.Msg = "操作失败"
+	//	br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+	//	return
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// init
+// @Description: 修复历史报告数据
+// @author: Roc
+// @datetime 2024-06-21 09:19:05
+func init() {
+	//fixApproveRecord()
+	//fixChapterPermission()
+	//fixReportEs()
+	//fixSmartReport()
+}
+
+// 修复研报审批数据
+func fixApproveRecord() {
+	recordOb := new(report_approve.ReportApproveRecord)
+
+	recordCond := fmt.Sprintf(` AND %s = ? AND %s in (?,?)`, report_approve.ReportApproveRecordCols.NodeType, report_approve.ReportApproveRecordCols.State)
+	recordPars := make([]interface{}, 0)
+	recordPars = append(recordPars, 0, report_approve.ReportApproveStatePass, report_approve.ReportApproveStateRefuse)
+	list, e := recordOb.GetItemsByCondition(recordCond, recordPars, []string{}, "")
+	if e != nil {
+		fmt.Println("查找审批记录失败,Err:", e.Error())
+		return
+	}
+	for _, recordItem := range list {
+		//fmt.Println(recordItem)
+		recordItem.NodeState = report_approve.ReportApproveStatePass
+		recordItem.NodeApproveUserId = recordItem.ApproveUserId
+		recordItem.NodeApproveUserName = recordItem.ApproveUserName
+		recordItem.NodeApproveTime = recordItem.ApproveTime
+
+		// 如果不是或签,那么只需要修复自己就好了
+		if recordItem.ApproveType != report_approve.NodeApproveTypeAny {
+			recordCols := []string{"State", "ApproveTime", "ModifyTime", "NodeState", "NodeApproveUserId", "NodeApproveUserName", "NodeApproveTime"}
+			if e = recordItem.Update(recordCols); e != nil {
+				fmt.Println("更新审批记录状态失败,Err:", e.Error())
+			}
+			continue
+		}
+		// 或签
+		// 需要将该审批的同一个节点的记录标记为已审批
+		if e := recordItem.UpdateNodeState(recordItem.ReportApproveId, recordItem.NodeId, recordItem.NodeState, recordItem.NodeApproveUserId, recordItem.NodeApproveUserName, recordItem.NodeApproveTime); e != nil {
+			fmt.Println("更新同一节点的其他审批记录状态失败,Err:", e.Error())
+		}
+	}
+
+	fmt.Println("审批数据修复完成")
+}
+
+// fixChapterPermission
+// @Description: 修复章节关联的品种权限
+// @author: Roc
+// @datetime 2024-06-20 18:08:34
+func fixChapterPermission() {
+	allChapterTypePermissionList, err := models.GetAllChapterTypePermission()
+	if err != nil {
+		fmt.Println("获取所有章节类型ID获取章节类型权限列表失败,Err:", err.Error())
+		return
+	}
+
+	currChapterTypePermissionIdListMap := make(map[int][]int)
+
+	hasPermissionMap := make(map[string]bool)
+	for _, v := range allChapterTypePermissionList {
+		tmpChapterTypePermissionList, ok := currChapterTypePermissionIdListMap[v.ReportChapterTypeId]
+		if !ok {
+			tmpChapterTypePermissionList = make([]int, 0)
+		}
+		key := fmt.Sprint(v.ReportChapterTypeId, "-", v.ChartPermissionId)
+		if _, has := hasPermissionMap[key]; !has {
+			hasPermissionMap[key] = true
+			currChapterTypePermissionIdListMap[v.ReportChapterTypeId] = append(tmpChapterTypePermissionList, v.ChartPermissionId)
+		}
+	}
+
+	notIdList := []int{9675, 9675, 9740, 9749, 9768, 9773, 9791, 9792, 9793, 9850, 9851, 9852, 9852, 9852, 9853, 9854, 9856, 9857, 9857, 9858, 9859, 9860, 9861, 9862, 9862, 9863, 9866}
+	allReportChapterList, err := models.GetAllReportChapter()
+	if err != nil {
+		fmt.Println("获取所有章节失败,Err:", err.Error())
+		return
+	}
+
+	addList := make([]*report.ReportChapterPermissionMapping, 0)
+	for _, v := range allReportChapterList {
+		// 如果是上面的章节id,那么就过滤掉,因为已经入库了
+		if utils.InArrayByInt(notIdList, v.ReportChapterId) {
+			continue
+		}
+		permissionIdList, ok := currChapterTypePermissionIdListMap[v.TypeId]
+		if !ok {
+			continue
+		}
+
+		for _, permissionId := range permissionIdList {
+			addList = append(addList, &report.ReportChapterPermissionMapping{
+				ReportChapterPermissionMappingId: 0,
+				ReportChapterId:                  v.ReportChapterId,
+				ChartPermissionId:                permissionId,
+				CreateTime:                       v.ModifyTime,
+			})
+		}
+
+	}
+
+	obj := report.ReportChapterPermissionMapping{}
+	err = obj.MultiAdd(addList)
+	if err != nil {
+		fmt.Println("批量添加报章节的品种权限失败,Err:", err.Error())
+	}
+
+	return
+}
+
+// fixReportEs
+// @Description: 修复报告es数据
+// @author: Roc
+// @datetime 2024-06-20 18:08:34
+func fixReportEs() {
+
+	//reportInfo, err := models.GetReportByReportId(3941)
+	//if err != nil {
+	//	fmt.Println("查询信息失败,", err)
+	//	return
+	//}
+	//content := utils.TrimHtml(html.UnescapeString(reportInfo.Content))
+	//fmt.Println(content)
+	//
+	//fmt.Println("=========================")
+	//
+	//chapterInfo, err := models.GetReportChapterInfoById(9637)
+	//if err != nil {
+	//	fmt.Println("查询信息失败2,", err)
+	//	return
+	//}
+	//
+	//content = utils.TrimHtml(html.UnescapeString(chapterInfo.Content))
+	//fmt.Println(content)
+	//
+	//services.UpdateReportChapterEs(9637)
+	//return
+
+	var condition string
+	var pars []interface{}
+
+	condition += " AND state in (2,6) "
+	list, err := models.GetReportListV1(condition, pars, 0, 100000)
+	if err != nil {
+		fmt.Println("查询信息失败,", err)
+		return
+	}
+
+	num := len(list)
+	fmt.Println(num, "条待修复报告es数据")
+
+	for k, v := range list {
+		fmt.Println("剩余", num-k, "条")
+		services.UpdateReportEs(v.Id, 2)
+	}
+
+	fmt.Println("报告ES数据修复完成")
+
+	return
+}
+
+// fixSmartReport
+// @Description: 修复智能研报的数据
+// @author: Roc
+// @datetime 2024-06-27 16:54:41
+func fixSmartReport() {
+	fmt.Println("修复智能研报开始")
+	// 先判断是否已经修复过数据,如果修复过,那么就不用修复了
+	{
+		condition := ` AND report_layout=2 and old_report_id>0 `
+		list, err := models.GetReportByCondition(condition, []interface{}{}, []string{}, " order by id asc ", false, 0, 0)
+		if err != nil {
+			fmt.Println("获取已修复的报告列表失败, Err:" + err.Error())
+			return
+		}
+		if len(list) > 0 {
+			fmt.Println("智能研报已经修复过数据,不需要再次修复")
+			return
+		}
+	}
+
+	var condition string
+	var pars []interface{}
+	reportOB := new(smart_report.SmartReport)
+	list, e := reportOB.GetItemsByCondition(condition, pars, []string{}, " smart_report_id asc ")
+	if e != nil {
+		fmt.Println("获取智能报告列表失败, Err:" + e.Error())
+		return
+	}
+
+	addList := make([]*models.Report, 0)
+	for _, v := range list {
+		fmt.Println(v)
+		addList = append(addList, &models.Report{
+			//Id:                  0,
+			AddType:            1,
+			ClassifyIdFirst:    v.ClassifyIdFirst,
+			ClassifyNameFirst:  v.ClassifyNameFirst,
+			ClassifyIdSecond:   v.ClassifyIdSecond,
+			ClassifyNameSecond: v.ClassifyNameSecond,
+			Title:              v.Title,
+			Abstract:           v.Abstract,
+			Author:             v.Author,
+			Frequency:          v.Frequency,
+			CreateTime:         v.CreateTime.Format(utils.FormatDateTime),
+			ModifyTime:         v.ModifyTime,
+			State:              v.State,
+			PublishTime:        v.PublishTime,
+			Stage:              v.Stage,
+			MsgIsSend:          v.MsgIsSend,
+			//ThsMsgIsSend:        v.Tha,
+			Content:             v.Content,
+			VideoUrl:            v.VideoUrl,
+			VideoName:           v.VideoName,
+			VideoPlaySeconds:    fmt.Sprint(v.VideoPlaySeconds),
+			VideoSize:           v.VideoSize,
+			ContentSub:          v.ContentSub,
+			ReportCode:          fmt.Sprint(v.SmartReportId),
+			ReportVersion:       1,
+			HasChapter:          0,
+			ChapterType:         "",
+			OldReportId:         v.SmartReportId,
+			MsgSendTime:         v.MsgSendTime,
+			AdminId:             v.AdminId,
+			AdminRealName:       v.AdminRealName,
+			ApproveTime:         v.ApproveTime,
+			ApproveId:           v.ApproveId,
+			DetailImgUrl:        v.DetailImgUrl,
+			DetailPdfUrl:        v.DetailPdfUrl,
+			ContentStruct:       v.ContentStruct,
+			LastModifyAdminId:   v.LastModifyAdminId,
+			LastModifyAdminName: v.LastModifyAdminName,
+			ContentModifyTime:   v.ContentModifyTime,
+			Pv:                  v.Pv,
+			Uv:                  v.Uv,
+			HeadImg:             v.HeadImg,
+			EndImg:              v.EndImg,
+			CanvasColor:         v.CanvasColor,
+			NeedSplice:          v.NeedSplice,
+			HeadResourceId:      v.HeadResourceId,
+			EndResourceId:       v.EndResourceId,
+			ClassifyIdThird:     0,
+			ClassifyNameThird:   "",
+			CollaborateType:     1,
+			ReportLayout:        2,
+			IsPublicPublish:     1,
+			ReportCreateTime:    v.CreateTime,
+			InheritReportId:     0,
+		})
+	}
+
+	if len(addList) > 0 {
+		err := models.InsertMultiReport(addList)
+		if err != nil {
+			fmt.Println("新增智能研报失败")
+			return
+		}
+
+		reportMap := make(map[int]*models.Report)
+		// 找出已经修复过的新的研报
+		{
+			reportList, tmpErr := models.GetReportByCondition(` AND report_layout=2 and old_report_id>0 `, []interface{}{}, []string{}, " order by id asc ", false, 0, 0)
+			if tmpErr != nil {
+				fmt.Println("获取已修复的报告列表失败, Err:" + tmpErr.Error())
+				return
+			}
+			for _, v := range reportList {
+				v.ReportCode = utils.MD5(strconv.Itoa(v.Id))
+				v.Update([]string{"ReportCode"})
+				reportMap[v.OldReportId] = v
+			}
+		}
+
+		// 找出智能研报的审批单并处理
+		{
+
+			// 智能研报走审批单的
+			reportApproveObj := new(report_approve.ReportApprove)
+
+			approveList, tmpErr := reportApproveObj.GetItemsByCondition(` AND report_type=3 `, []interface{}{}, []string{}, " report_approve_id asc ")
+			if tmpErr != nil {
+				fmt.Println("获取已修复的报告列表失败, Err:" + tmpErr.Error())
+				return
+			}
+
+			for _, v := range approveList {
+				reportInfo, ok := reportMap[v.ReportId]
+				if ok {
+					v.ReportId = reportInfo.Id
+					v.ReportType = 1
+					err = v.Update([]string{"ReportType", "ReportId"})
+					if err != nil {
+						fmt.Println(v.ReportApproveId, "数据修复失败,其对应的报告ID是:", v.ReportId, ";新的报告ID是:", reportInfo.Id)
+					}
+				} else {
+					fmt.Println(v.ReportApproveId, "找不到对应的报告,其对应的报告ID是:", v.ReportId)
+				}
+			}
+		}
+	}
+
+	fmt.Println("修复智能研报完成")
+}

+ 9 - 1
controllers/sandbox/sandbox.go

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

+ 8 - 9
controllers/smart_report/smart_report.go

@@ -79,7 +79,7 @@ func (this *SmartReportController) Add() {
 	stageMax += 1
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, req.ClassifyIdFirst, req.ClassifyIdSecond, 0, models.ReportOperateAdd)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -247,7 +247,7 @@ func (this *SmartReportController) Edit() {
 		contentModify = true
 	}
 	cols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "Title", "Abstract", "Author",
-		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime", "HeadImg", "EndImg", "CanvasColor","HeadResourceId", "EndResourceId"}
+		"Frequency", "Content", "ContentSub", "ContentStruct", "ModifyTime", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId"}
 	item.ClassifyIdFirst = req.ClassifyIdFirst
 	item.ClassifyNameFirst = req.ClassifyNameFirst
 	item.ClassifyIdSecond = req.ClassifyIdSecond
@@ -484,7 +484,7 @@ func (this *SmartReportController) Publish() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, operate)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, 0, operate)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -641,7 +641,7 @@ func (this *SmartReportController) PrePublish() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond)
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, 0)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
@@ -851,7 +851,7 @@ func (this *SmartReportController) SaveContent() {
 		item.CanvasColor = req.CanvasColor
 		item.HeadResourceId = req.HeadResourceId
 		item.EndResourceId = req.EndResourceId
-		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "HeadImg", "EndImg", "CanvasColor","HeadResourceId", "EndResourceId"}
+		cols := []string{"Content", "ContentSub", "ContentStruct", "ContentModifyTime", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId"}
 		if e = item.Update(cols); e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "更新报告内容失败"
@@ -1404,7 +1404,7 @@ func (this *SmartReportController) SubmitApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateSubmitApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1427,7 +1427,7 @@ func (this *SmartReportController) SubmitApprove() {
 	}
 
 	// 提交审批
-	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeSmart, reportItem.SmartReportId, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeSmart, reportItem.SmartReportId, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, sysUser.AdminId, sysUser.RealName)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "提交审批失败, Err: " + e.Error()
@@ -1495,7 +1495,7 @@ func (this *SmartReportController) CancelApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateCancelApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1543,4 +1543,3 @@ func (this *SmartReportController) CancelApprove() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
-

+ 55 - 27
controllers/sys_admin.go

@@ -12,10 +12,11 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // 系统用户
@@ -737,32 +738,55 @@ func (this *SysAdminController) Edit() {
 		}
 	}
 
-	adminInfo.AdminName = req.AdminName
-	adminInfo.RealName = req.RealName
-	adminInfo.LastUpdatedTime = time.Now().Format(utils.FormatDateTime)
-	adminInfo.Mobile = req.Mobile
-	adminInfo.RoleId = req.RoleId
-	adminInfo.RoleName = roleName
-	adminInfo.Enabled = req.Enabled
-	adminInfo.Authority = authority
-	adminInfo.Position = req.Position
-	adminInfo.RoleTypeCode = roleItem.RoleTypeCode
-	adminInfo.Province = req.Province
-	adminInfo.ProvinceCode = req.ProvinceCode
-	adminInfo.City = req.City
-	adminInfo.CityCode = req.CityCode
-	adminInfo.EmployeeId = req.EmployeeId
-	adminInfo.Email = req.Email
-	adminInfo.TelAreaCode = req.TelAreaCode
-	adminInfo.IsLdap = req.IsLdap
-	cols := []string{
-		"AdminName", "RealName", "LastUpdatedTime", "Mobile", "RoleId", "RoleName", "Enabled", "Authority",
-		"Position", "RoleTypeCode", "Province", "ProvinceCode", "City", "CityCode", "EmployeeId", "Email", "TelAreaCode", "IsLdap",
-	}
-	if e := adminInfo.Update(cols); e != nil {
-		br.Msg = "编辑失败"
-		br.ErrMsg = "更新用户信息失败, Err:" + e.Error()
-		return
+	if adminInfo.RoleName == "admin" && adminInfo.RealName == "admin" {
+		adminInfo.LastUpdatedTime = time.Now().Format(utils.FormatDateTime)
+		adminInfo.Mobile = req.Mobile
+		adminInfo.Position = req.Position
+		adminInfo.Province = req.Province
+		adminInfo.ProvinceCode = req.ProvinceCode
+		adminInfo.City = req.City
+		adminInfo.CityCode = req.CityCode
+		adminInfo.EmployeeId = req.EmployeeId
+		adminInfo.Email = req.Email
+		adminInfo.TelAreaCode = req.TelAreaCode
+		adminInfo.IsLdap = req.IsLdap
+		cols := []string{
+			"LastUpdatedTime", "Mobile", "Position", "Province", "ProvinceCode",
+			"City", "CityCode", "EmployeeId", "Email", "TelAreaCode", "IsLdap",
+		}
+		if e := adminInfo.Update(cols); e != nil {
+			br.Msg = "编辑失败"
+			br.ErrMsg = "更新用户信息失败, Err:" + e.Error()
+			return
+		}
+	} else {
+		adminInfo.AdminName = req.AdminName
+		adminInfo.RealName = req.RealName
+		adminInfo.LastUpdatedTime = time.Now().Format(utils.FormatDateTime)
+		adminInfo.Mobile = req.Mobile
+		adminInfo.RoleId = req.RoleId
+		adminInfo.RoleName = roleName
+		adminInfo.Enabled = req.Enabled
+		adminInfo.Authority = authority
+		adminInfo.Position = req.Position
+		adminInfo.RoleTypeCode = roleItem.RoleTypeCode
+		adminInfo.Province = req.Province
+		adminInfo.ProvinceCode = req.ProvinceCode
+		adminInfo.City = req.City
+		adminInfo.CityCode = req.CityCode
+		adminInfo.EmployeeId = req.EmployeeId
+		adminInfo.Email = req.Email
+		adminInfo.TelAreaCode = req.TelAreaCode
+		adminInfo.IsLdap = req.IsLdap
+		cols := []string{
+			"AdminName", "RealName", "LastUpdatedTime", "Mobile", "RoleId", "RoleName", "Enabled", "Authority",
+			"Position", "RoleTypeCode", "Province", "ProvinceCode", "City", "CityCode", "EmployeeId", "Email", "TelAreaCode", "IsLdap",
+		}
+		if e := adminInfo.Update(cols); e != nil {
+			br.Msg = "编辑失败"
+			br.ErrMsg = "更新用户信息失败, Err:" + e.Error()
+			return
+		}
 	}
 
 	// 同步用户缓存
@@ -831,6 +855,10 @@ func (this *SysAdminController) EditEnabled() {
 		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error()
 		return
 	}
+	if adminItem.RoleName == "admin" && adminItem.RealName == "admin" {
+		br.Msg = "禁止对admin使用<禁用>功能"
+		return
+	}
 
 	if req.Enabled == 0 {
 		//禁用

+ 21 - 1
controllers/sys_role.go

@@ -7,10 +7,11 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 type SysRoleController struct {
@@ -113,6 +114,11 @@ func (this *SysRoleController) Edit() {
 		br.Msg = "角色不存在, 请刷新页面"
 		return
 	}
+	// 不允许编辑admin用户
+	if item.RoleName == "admin" {
+		br.Msg = "admin用户不可编辑"
+		return
+	}
 	exists, e := system.GetSysRoleByName(req.RoleName)
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		br.Msg = "操作失败"
@@ -179,6 +185,10 @@ func (this *SysRoleController) Delete() {
 		br.ErrMsg = "获取角色信息失败, Err: " + e.Error()
 		return
 	}
+	if role.RoleName == "admin" {
+		br.Msg = "admin用户不可删除"
+		return
+	}
 
 	err = system.DeleteSysRole(req.RoleId)
 	if err != nil {
@@ -515,6 +525,16 @@ func (this *SysRoleController) SysRoleMenuAuthList() {
 		}
 	}
 
+	sysRole, err := system.GetSysRoleById(roleId)
+	if err == nil {
+		if sysRole.RoleName == "admin" {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			return
+		}
+	}
+
 	order := `sort ASC, create_time DESC, menu_id DESC`
 	list, e := system.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, order)
 	if e != nil {

+ 3 - 3
controllers/sys_team.go

@@ -63,7 +63,7 @@ func (this *SysTeamController) Add() {
 			}
 
 			// 同步分组缓存
-			if utils.BusinessCode == utils.BusinessCodeRelease {
+			if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
 				var syncData system.SyncGroupData
 				syncData.Source = utils.SOURCE_ETA_FLAG
 				syncData.GroupId = int(groupId)
@@ -123,7 +123,7 @@ func (this *SysTeamController) Edit() {
 	}
 
 	// 同步分组缓存
-	if utils.BusinessCode == utils.BusinessCodeRelease {
+	if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
 		var syncData system.SyncGroupData
 		syncData.Source = utils.SOURCE_ETA_FLAG
 		syncData.GroupId = item.GroupId
@@ -172,7 +172,7 @@ func (this *SysTeamController) Delete() {
 	}
 
 	// 同步分组缓存
-	if utils.BusinessCode == utils.BusinessCodeRelease {
+	if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
 		var syncData system.SyncGroupData
 		syncData.Source = utils.SOURCE_ETA_FLAG
 		syncData.GroupId = req.TeamId

+ 776 - 421
controllers/target.go

@@ -18,9 +18,6 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/shopspring/decimal"
-	"github.com/tealeg/xlsx"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -28,6 +25,10 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/shopspring/decimal"
+	"github.com/tealeg/xlsx"
 )
 
 // TargetController 手工指标服务(鉴权)
@@ -183,6 +184,14 @@ func (this *TargetController) DataAdd() {
 		br.ErrMsg = "新增失败,Err:" + err.Error()
 		return
 	}
+	//将该指标的code加入到 “手工数据导入后刷新” 缓存
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_IMPORT_MANUAL_DATA, edbdata.TradeCode)
+		if err != nil {
+			fmt.Println("DataAdd CACHE_IMPORT_MANUAL_DATA LPush Err:" + err.Error())
+		}
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "新增成功"
@@ -314,6 +323,13 @@ func (this *TargetController) DataEdit() {
 			}
 		}
 	}
+	//将该指标的code加入到 “手工数据导入后刷新” 缓存
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_IMPORT_MANUAL_DATA, edbdata.TradeCode)
+		if err != nil {
+			fmt.Println("DataEdit CACHE_IMPORT_MANUAL_DATA LPush Err:" + err.Error())
+		}
+	}
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "编辑成功"
@@ -344,11 +360,13 @@ func (this *TargetController) BatchDataEdit() {
 	}
 
 	edbDataEditList := make([]data.EdbDataEdit, 0)
+	edbCodeMap := make(map[string]string)
 	for _, trade := range req.List {
 		if trade.TradeCode == "" {
 			br.Msg = "指标编码不可为空!"
 			return
 		}
+		edbCodeMap[trade.TradeCode] = trade.TradeCode
 
 		close := ""
 		loc := reflect.ValueOf(trade.Close).Kind().String()
@@ -379,6 +397,15 @@ func (this *TargetController) BatchDataEdit() {
 		br.ErrMsg = "批量修改失败:" + err.Error()
 		return
 	}
+	//将该指标的code加入到 “手工数据导入后刷新” 缓存
+	if utils.Re == nil {
+		for _, edbCode := range edbCodeMap {
+			err = utils.Rc.LPush(utils.CACHE_IMPORT_MANUAL_DATA, edbCode)
+			if err != nil {
+				fmt.Println("BatchDataEdit CACHE_IMPORT_MANUAL_DATA LPush Err:" + err.Error())
+			}
+		}
+	}
 	br.Ret = 200
 	br.Success = true
 	br.Data = failEdbDataList
@@ -1647,6 +1674,15 @@ func (this *TargetController) DataDelete() {
 		br.ErrMsg = "删除失败,Err:" + err.Error()
 		return
 	}
+
+	//将该指标的code加入到 “手工数据导入后刷新” 缓存
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_IMPORT_MANUAL_DATA, req.TradeCode)
+		if err != nil {
+			fmt.Println("DataDelete CACHE_IMPORT_MANUAL_DATA LPush Err:" + err.Error())
+		}
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "删除成功"
@@ -2436,13 +2472,13 @@ func (this *TargetController) TargetFrequencyList() {
 
 func sortEdbFrequency(frequencyList []string) (newFrequencyList []string) {
 	var frequencyMap1 = map[string]int{
-		"日度":  1,
-		"周度":  2,
-		"旬度":  3,
-		"月度":  4,
-		"季度":  5,
+		"日度":   1,
+		"周度":   2,
+		"旬度":   3,
+		"月度":   4,
+		"季度":   5,
 		"半年度": 6,
-		"年度":  7,
+		"年度":   7,
 	}
 
 	var frequencyMap2 = map[int]string{
@@ -2513,7 +2549,7 @@ func (this *TargetController) TargetItemList() {
 	classifyId, _ := this.GetInt("ClassifyId")
 	edbShowType, _ := this.GetInt("EdbShowType")
 
-	list, err := models.GetTargetItemList(classifyId, edbShowType, frequency, keyword, tradeCode, classifyIdStrList)
+	list, err := models.GetTargetItemListSort(classifyId, edbShowType, frequency, keyword, tradeCode, classifyIdStrList)
 	if err != nil {
 		br.ErrMsg = "获取失败,Err:" + err.Error()
 		br.Msg = "获取失败"
@@ -3362,6 +3398,514 @@ func (this *TargetController) ImportTargetFailListDownload() {
 	this.Ctx.Output.Download(savePath, "失败列表.xlsx")
 }
 
+// ExcelDataAdd
+// @Title 复制EXCEL数据保存
+// @Description 复制EXCEL数据保存
+// @Param  request	body models.ExcelStyleReq true "type json string"
+// @Success 200
+//// @router /target/excel_style/add [post]
+// func (this *TargetController) ExcelDataAdd() {
+// 	br := new(models.BaseResponse).Init()
+// 	var err error
+// 	errs := make([]string, 0)
+// 	defer func() {
+// 		if len(errs) > 0 {
+// 			go alarm_msg.SendAlarmMsg("新增复制EXCEL数据 新增或修改数据失败,Err:"+strings.Join(errs, ","), 3)
+// 		}
+// 		this.Data["json"] = br
+// 		this.ServeJSON()
+// 	}()
+
+// 	sysUser := this.SysUser
+// 	if sysUser == nil {
+// 		br.Msg = "请重新登录"
+// 		return
+// 	}
+
+// 	//频度
+// 	//frequency := this.GetString("Frequency")
+// 	//
+// 	//关键字
+// 	//keyword := this.GetString("Keyword")
+// 	//指标编码
+// 	//tradeCode := this.GetString("TradeCode")
+// 	//
+// 	//userId := sysUser.AdminId
+// 	//超管账号可以查看分类下的所有频度数据
+// 	//if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
+// 	//	userId = 0
+// 	//}
+// 	//
+// 	//list, err := models.GetExcelData()
+// 	//if err != nil {
+// 	//	br.ErrMsg = "获取失败,Err:" + err.Error()
+// 	//	br.Msg = "获取失败"
+// 	//	return
+// 	//}
+
+// 	var req data_manage.ExcelStyleReq
+// 	err = json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+// 	if err != nil {
+// 		br.Msg = "参数解析异常!"
+// 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+// 		return
+// 	}
+// 	//指标map
+// 	targetMap := make(map[string]*models.Edbinfo)
+// 	var cellDataList []map[string]interface{}
+// 	var dataList [][]map[string]interface{}
+// 	//var configList []map[string]interface{}
+// 	for _, item := range req.CellData {
+// 		v := item.(map[string]interface{})
+// 		cellDataList = append(cellDataList, v)
+// 	}
+
+// 	valueIndex := 0
+
+// 	var secNameList []string
+// 	var frequencyList []string
+// 	var unitList []string
+
+// 	var dateList []string
+// 	dateValueMap := make(map[string]map[int]string)
+// 	//取到所有数据
+// 	for i := range req.Data {
+// 		//取到一行
+// 		if i > 4 {
+// 			//先拿下日期列表
+// 			if req.Data[i] == nil || req.Data[i][0] == nil {
+// 				continue
+// 			}
+// 			//fmt.Println("i:", i)
+// 			//fmt.Println(req.Data[i])
+// 			//fmt.Println(req.Data[i][0])
+// 			dateMap := req.Data[i][0].(map[string]interface{})
+// 			_, ok := dateMap["m"]
+// 			if ok {
+// 				dateList = append(dateList, dateMap["m"].(string))
+// 				//fmt.Println("dateList:", dateList)
+// 			} else {
+// 				continue
+// 			}
+// 		}
+// 		for j, dataArr := range req.Data[i] {
+// 			//每一列
+// 			if dataArr == nil {
+// 				continue
+// 			}
+// 			dataMap := dataArr.(map[string]interface{})
+// 			if dataList == nil {
+// 				dataList = make([][]map[string]interface{}, len(req.Data))
+// 			}
+// 			if dataList[i] == nil {
+// 				dataList[i] = make([]map[string]interface{}, len(req.Data[i]))
+// 			}
+// 			dataList[i][j] = dataMap
+// 			//fmt.Println("dataMap", dataMap["m"])
+// 			v, _ := dataMap["m"]
+// 			//指标名称列表
+// 			if v == "指标名称" {
+// 				for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
+// 					if req.Data[i][nameIndex] == nil {
+// 						break
+// 					}
+// 					secNameMap := req.Data[i][nameIndex].(map[string]interface{})
+// 					secName, _ := secNameMap["m"]
+// 					if secName == nil {
+// 						continue
+// 					}
+// 					secNameList = append(secNameList, secNameMap["m"].(string))
+// 					//fmt.Println("secName:", secNameList)
+// 				}
+// 			}
+// 			//频度列表
+// 			if v == "频度" {
+// 				for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
+// 					if req.Data[i][nameIndex] == nil {
+// 						break
+// 					}
+// 					frequencyMap := req.Data[i][nameIndex].(map[string]interface{})
+// 					frequency, _ := frequencyMap["m"]
+// 					if frequency == nil {
+// 						continue
+// 					}
+// 					frequencyList = append(frequencyList, frequencyMap["m"].(string))
+// 					//fmt.Println("frequencyList:", frequencyList)
+// 				}
+// 			}
+// 			//单位列表
+// 			if v == "单位" {
+// 				for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
+// 					if req.Data[i][nameIndex] == nil {
+// 						break
+// 					}
+// 					unitMap := req.Data[i][nameIndex].(map[string]interface{})
+// 					unit, _ := unitMap["m"]
+// 					if unit == nil {
+// 						continue
+// 					}
+// 					unitList = append(unitList, unitMap["m"].(string))
+// 					//fmt.Println("unitList:", unitList)
+// 				}
+// 			}
+// 			//数据列表
+// 			//var valueList []string
+// 			valueListMap := make(map[int]string)
+// 			if i > 4 {
+// 				for index := 1; index < len(req.Data[i]); index++ {
+// 					if req.Data[i][index] == nil {
+// 						continue
+// 					}
+// 					valueMap := req.Data[i][index].(map[string]interface{})
+// 					value, _ := valueMap["m"]
+// 					if value == nil {
+// 						continue
+// 					}
+// 					valueListMap[index] = valueMap["m"].(string)
+
+// 					// 09-27 千位分隔符时用 "m" 取字符串存数据库会把逗号当小数点,现在换用 "v" 直接取数字再转为字符串,看看会不会有问题
+// 					if ct, ok := valueMap["ct"].(map[string]interface{}); ok {
+// 						fa, _ := ct["fa"]
+// 						if fa == "#,##0.000" {
+// 							value = valueMap["v"]
+// 							floatValue := valueMap["v"].(float64)
+// 							valueListMap[index] = strconv.FormatFloat(floatValue, 'f', -1, 64)
+// 						}
+// 					}
+
+// 					//valueList = append(valueList, valueMap["m"].(string))
+// 					//fmt.Println("valueList:", valueList)
+// 				}
+// 				dateMap := req.Data[i][0].(map[string]interface{})
+// 				date := dateMap["m"].(string)
+// 				dateValueMap[date] = valueListMap
+// 			}
+// 		}
+
+// 		//firstCell := req.Data[i][0]
+// 		//if firstCell == nil {
+// 		//	continue
+// 		//}
+// 		//dataMap := firstCell.(map[string]interface{})
+// 		//if dataList == nil {
+// 		//	dataList = make([][]map[string]interface{}, len(req.Data[i]))
+// 		//}
+// 		//if dataList[i] == nil {
+// 		//	dataList[i] = make([]map[string]interface{}, len(req.Data[i]))
+// 		//}
+// 		//dataList[i][0] = dataMap
+// 		//fmt.Println("dataMap", dataMap["m"])
+// 		//v, _ := dataMap["m"]
+// 		////指标名称
+// 		//if v == "指标名称" {
+// 		//	secNameMap := req.Data[i][1].(map[string]interface{})
+// 		//	secName = secNameMap["m"].(string)
+// 		//	fmt.Println("secName:", secName)
+// 		//}
+// 		////频度
+// 		//if v == "频度" {
+// 		//	frequencyMap := req.Data[i][1].(map[string]interface{})
+// 		//	frequency = frequencyMap["m"].(string)
+// 		//	fmt.Println("frequency:", frequency)
+// 		//}
+// 		////单位
+// 		//if v == "单位" {
+// 		//	unitMap := req.Data[i][1].(map[string]interface{})
+// 		//	unit = unitMap["m"].(string)
+// 		//	fmt.Println("unit:", unit)
+// 		//}
+// 		//
+// 		////日期
+// 		//dateMap := req.Data[i][1].(map[string]interface{})
+// 		//createDate = dateMap["m"].(string)
+// 		//
+// 		////值
+// 		//valueMap := req.Data[i][valueIndex].(map[string]interface{})
+// 		//closeVal = valueMap["m"].(string)
+
+// 		//新增指标
+// 		//errMsg := services.AddEdbItem(secName, unit, frequency, req.ClassifyId, sysUser)
+// 		//if errMsg == "" {
+// 		//	br.Msg = "新增指标异常!"
+// 		//	br.ErrMsg = errMsg
+// 		//	return
+// 		//}
+
+// 		valueIndex++
+// 	}
+
+// 	//var excelId int64
+// 	//保存EXCEL样式
+// 	//{
+// 	//	cellData, err := json.Marshal(cellDataList)
+// 	//	if err != nil {
+// 	//		br.Msg = "参数解析异常!"
+// 	//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+// 	//		return
+// 	//	}
+// 	//	config, err := json.Marshal(req.Config)
+// 	//	if err != nil {
+// 	//		br.Msg = "参数解析异常!"
+// 	//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+// 	//		return
+// 	//	}
+// 	//	datas, err := json.Marshal(dataList)
+// 	//	if err != nil {
+// 	//		br.Msg = "参数解析异常!"
+// 	//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+// 	//		return
+// 	//	}
+// 	//	excelItem := data_manage.ExcelStyle{
+// 	//		CellData: string(cellData),
+// 	//		Config:   string(config),
+// 	//		Data:     string(datas),
+// 	//	}
+// 	//	excelId, err = models.AddExcelData(&excelItem)
+// 	//	if err != nil {
+// 	//		br.ErrMsg = "获取失败,Err:" + err.Error()
+// 	//		br.Msg = "获取失败"
+// 	//		return
+// 	//	}
+// 	//
+// 	//	//判断映射表是否已经存在
+// 	//	count, err := data_manage.GetExcelEdbdataMappingCount(req.ClassifyId, target.TradeCode, frequency)
+// 	//	if err != nil {
+// 	//		br.ErrMsg = "GetExcelEdbdataMappingCount,Err:" + err.Error()
+// 	//		br.Msg = "新增映射表失败"
+// 	//		return
+// 	//	}
+// 	//	mappingItem := data_manage.ExcelEdbdataMapping{
+// 	//		ExcelId:    excelId,
+// 	//		ClassifyId: req.ClassifyId,
+// 	//		Frequency:  frequency,
+// 	//		TradeCode:  target.TradeCode,
+// 	//		CreateTime: time.Now(),
+// 	//	}
+// 	//	if count == 0{
+// 	//		//新增
+// 	//		err := data_manage.AddExcelEdbdataMapping(&mappingItem)
+// 	//		if err != nil {
+// 	//			br.ErrMsg = "AddExcelEdbdataMapping,Err:" + err.Error()
+// 	//			br.Msg = "新增映射表失败"
+// 	//			return
+// 	//		}
+// 	//	}
+// 	//}
+
+// 	// 所有的指标数据map
+// 	edbCodeDataMap := make(map[string]map[string]string)
+
+// 	//操作指标,新增指标及数据等
+// 	{
+// 		for i := 0; i < len(secNameList); i++ {
+// 			//fmt.Println("len(secNameList):", len(secNameList))
+// 			secName := secNameList[i]
+// 			frequency := frequencyList[i]
+
+// 			//防止填了指标名称后没填单位报错
+// 			if len(unitList) < len(secNameList) {
+// 				br.Msg = "单位填写异常!"
+// 				br.ErrMsg = "单位填写异常!"
+// 				return
+// 			}
+// 			unit := unitList[i]
+
+// 			for j, s := range dateList {
+// 				var closeVal string
+// 				//valueList, ok := dateValueMap[s]
+// 				valueMap, ok := dateValueMap[s]
+// 				//fmt.Println("valueMap:", valueMap)
+// 				if ok {
+// 					createDate := dateList[j]
+// 					//if i < len(valueList) {
+// 					//	closeVal = valueList[i]
+// 					//} else {
+// 					//	continue
+// 					//}
+// 					closeVal, ok = valueMap[i+1]
+// 					target, ok2 := targetMap[secName]
+// 					if !ok2 {
+// 						tmpTarget, err := models.GetTargetBySecName(secName)
+// 						if err != nil {
+// 							//如果是找不到该指标,那么新增指标
+// 							if err.Error() == utils.ErrNoRow() {
+// 								//if frequency == "" {
+// 								//	failItem := new(models.EdbdataImportFail)
+// 								//	failItem.SysUserId = strconv.Itoa(sysUser.AdminId)
+// 								//	failItem.ClassifyName = classifyName
+// 								//	failItem.CreateDate = createDate
+// 								//	failItem.SecName = secName
+// 								//	failItem.Close = closeVal
+// 								//	failItem.Remark = "新增指标失败,频度字段为空"
+// 								//	failItem.Frequency = frequency
+// 								//	failItem.Unit = unit
+// 								//	failDatas = append(failDatas, failItem)
+// 								//	continue
+// 								//}
+// 								//if unit == "" {
+// 								//	failItem := new(models.EdbdataImportFail)
+// 								//	failItem.SysUserId = strconv.Itoa(sysUser.AdminId)
+// 								//	failItem.ClassifyName = classifyName
+// 								//	failItem.CreateDate = createDate
+// 								//	failItem.SecName = secName
+// 								//	failItem.Close = closeVal
+// 								//	failItem.Remark = "新增指标失败,单位字段为空"
+// 								//	failItem.Frequency = frequency
+// 								//	failItem.Unit = unit
+// 								//	failDatas = append(failDatas, failItem)
+// 								//	continue
+// 								//}
+
+// 								tmpErr := data.AddEdbInfo(secName, unit, frequency, "", sysUser.Mobile, req.ClassifyId, sysUser.AdminId)
+// 								if tmpErr != nil {
+// 									//fmt.Println("AddEdbInfo err:", err.Error())
+// 									utils.FileLogData.Error("AddEdbInfo err :%s", tmpErr.Error())
+// 									continue
+// 								}
+// 								tmpTarget, tmpErr := models.GetTargetBySecName(secName)
+// 								target = tmpTarget
+// 								targetMap[secName] = target
+// 							} else {
+// 								fmt.Println("导入数据 获取指标:Err:" + err.Error())
+// 							}
+// 						} else {
+// 							target = tmpTarget
+// 							targetMap[secName] = target
+// 						}
+// 					}
+// 					// 判断指标的顺序是否发生改变
+// 					if targetMap[secName].Sort != i {
+// 						edbinfo := models.Edbinfo{TradeCode: target.TradeCode, Sort: i}
+// 						er := edbinfo.Update([]string{"sort"})
+// 						if er != nil {
+// 							fmt.Println("sort update err:", er.Error())
+// 						}
+// 					}
+
+// 					//判断指标数据是否已经存在
+// 					tmpDataMap, ok2 := edbCodeDataMap[target.TradeCode]
+// 					if !ok2 {
+// 						tmpDataMap = make(map[string]string)
+// 						targetDataList, tmpErr := models.GetTargetsDataList(target.TradeCode)
+// 						if tmpErr != nil {
+// 							err = tmpErr
+// 						}
+// 						for _, tmpData := range targetDataList {
+// 							tmpDataMap[tmpData.Dt] = tmpData.Close
+// 						}
+// 						edbCodeDataMap[target.TradeCode] = tmpDataMap
+// 					}
+
+// 					//判断数据是否已经存在
+// 					tmpVal, ok3 := tmpDataMap[createDate]
+// 					//数据已存在,进行更新操作
+// 					if ok3 {
+// 						if closeVal != "" {
+// 							// 库里面的数据
+// 							tmpValDecimal, tmpErr := decimal.NewFromString(tmpVal)
+// 							if tmpErr != nil {
+// 								fmt.Println("tmpVal Parse err:", tmpErr.Error())
+// 								err = tmpErr
+// 								continue
+// 							}
+// 							// 用户填写的数据
+// 							closeValDecimal, tmpErr := decimal.NewFromString(closeVal)
+// 							if tmpErr != nil {
+// 								fmt.Println("closeVal Parse err:", tmpErr.Error())
+// 								err = tmpErr
+// 								continue
+// 							}
+// 							if !tmpValDecimal.Equal(closeValDecimal) {
+// 								fmt.Println("更新数值")
+// 								err = models.ModifyTargetsDataByImport(target.TradeCode, createDate, closeVal)
+// 								if err != nil {
+// 									fmt.Println("ModifyTargetsDataByImport err:", err.Error())
+// 									errs = append(errs, err.Error())
+// 									//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 修改数据失败:Err:"+err.Error(), utils.EmailSendToUsers)
+// 								}
+// 							}
+// 						} else {
+// 							fmt.Println("删除数值")
+// 							err = models.DeleteTargetsDataByImport(target.TradeCode, createDate)
+// 							if err != nil {
+// 								fmt.Println("DeleteTargetsDataByImport err:", err.Error())
+// 								errs = append(errs, err.Error())
+// 								//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 修改数据失败:Err:"+err.Error(), utils.EmailSendToUsers)
+// 							}
+// 						}
+
+// 						if frequency != target.Frequency || unit != target.Unit || req.ClassifyId != target.ClassifyId {
+// 							fmt.Println("更新指标频度或单位")
+// 							err = models.ModifyEdbinfo(target.TradeCode, unit, frequency, req.ClassifyId)
+// 							if err != nil {
+// 								fmt.Println("EditEdbinfo err:", err.Error())
+// 								return
+// 							}
+// 						}
+// 					} else { //数据不存在,进行新增操作
+// 						if target.TradeCode != "" && createDate != "" && closeVal != "" {
+// 							fmt.Println("新增")
+// 							models.AddTargetsDataByImport(target.TradeCode, createDate, closeVal)
+// 							if err != nil {
+// 								fmt.Println("AddTargetsDataByImport err:", err.Error())
+// 								errs = append(errs, err.Error())
+// 								//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 新增数据失败:Err:"+err.Error(), utils.EmailSendToUsers)
+// 							}
+
+// 							tmpDataMap[createDate] = closeVal
+// 							edbCodeDataMap[target.TradeCode] = tmpDataMap
+// 						}
+// 					}
+
+// 					//保存关联表
+// 					//{
+// 					//	//判断映射表是否已经存在
+// 					//	count, err := data_manage.GetExcelEdbdataMappingCount(req.ClassifyId, target.TradeCode, frequency)
+// 					//	if err != nil {
+// 					//		br.ErrMsg = "GetExcelEdbdataMappingCount,Err:" + err.Error()
+// 					//		br.Msg = "新增映射表失败"
+// 					//		return
+// 					//	}
+// 					//	mappingItem := data_manage.ExcelEdbdataMapping{
+// 					//		ExcelId:    excelId,
+// 					//		ClassifyId: req.ClassifyId,
+// 					//		Frequency:  frequency,
+// 					//		TradeCode:  target.TradeCode,
+// 					//		CreateTime: time.Now(),
+// 					//	}
+// 					//	if count == 0{
+// 					//		//新增
+// 					//		err := data_manage.AddExcelEdbdataMapping(&mappingItem)
+// 					//		if err != nil {
+// 					//			br.ErrMsg = "AddExcelEdbdataMapping,Err:" + err.Error()
+// 					//			br.Msg = "新增映射表失败"
+// 					//			return
+// 					//		}
+// 					//	}
+// 					//}
+// 				}
+// 			}
+// 		}
+
+// 		list, err := models.GetTargetItemList(req.ClassifyId, 1, req.Frequency, "", "", []string{})
+// 		if err != nil {
+// 			br.ErrMsg = "获取失败,Err:" + err.Error()
+// 			br.Msg = "获取失败"
+// 			return
+// 		}
+// 		fmt.Println(list)
+// 	}
+
+// 	//resp := models.TargetItemListResp{
+// 	//	List: list,
+// 	//}
+// 	br.Ret = 200
+// 	br.Success = true
+// 	br.Msg = "获取成功"
+// 	//br.Data = resp
+// 	//br.Data = list
+// }
+
 // ExcelDataAdd
 // @Title 复制EXCEL数据保存
 // @Description 复制EXCEL数据保存
@@ -3386,27 +3930,6 @@ func (this *TargetController) ExcelDataAdd() {
 		return
 	}
 
-	//频度
-	//frequency := this.GetString("Frequency")
-	//
-	//关键字
-	//keyword := this.GetString("Keyword")
-	//指标编码
-	//tradeCode := this.GetString("TradeCode")
-	//
-	//userId := sysUser.AdminId
-	//超管账号可以查看分类下的所有频度数据
-	//if sysUser.RoleTypeCode == utils.ROLE_TYPE_CODE_ADMIN {
-	//	userId = 0
-	//}
-	//
-	//list, err := models.GetExcelData()
-	//if err != nil {
-	//	br.ErrMsg = "获取失败,Err:" + err.Error()
-	//	br.Msg = "获取失败"
-	//	return
-	//}
-
 	var req data_manage.ExcelStyleReq
 	err = json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -3415,23 +3938,13 @@ func (this *TargetController) ExcelDataAdd() {
 		return
 	}
 	//指标map
-	targetMap := make(map[string]*models.Edbinfo)
-	var cellDataList []map[string]interface{}
 	var dataList [][]map[string]interface{}
-	//var configList []map[string]interface{}
-	for _, item := range req.CellData {
-		v := item.(map[string]interface{})
-		cellDataList = append(cellDataList, v)
-	}
-
-	valueIndex := 0
 
 	var secNameList []string
 	var frequencyList []string
 	var unitList []string
 
-	var dateList []string
-	dateValueMap := make(map[string]map[int]string)
+	secNameToDateMap := make(map[string]map[string]string)
 	//取到所有数据
 	for i := range req.Data {
 		//取到一行
@@ -3440,418 +3953,260 @@ func (this *TargetController) ExcelDataAdd() {
 			if req.Data[i] == nil || req.Data[i][0] == nil {
 				continue
 			}
-			//fmt.Println("i:", i)
-			//fmt.Println(req.Data[i])
-			//fmt.Println(req.Data[i][0])
 			dateMap := req.Data[i][0].(map[string]interface{})
 			_, ok := dateMap["m"]
-			if ok {
-				dateList = append(dateList, dateMap["m"].(string))
-				//fmt.Println("dateList:", dateList)
-			} else {
-				continue
-			}
-		}
-		for j, dataArr := range req.Data[i] {
-			//每一列
-			if dataArr == nil {
+			date := dateMap["m"].(string)
+			if !ok {
 				continue
 			}
-			dataMap := dataArr.(map[string]interface{})
-			if dataList == nil {
-				dataList = make([][]map[string]interface{}, len(req.Data))
-			}
-			if dataList[i] == nil {
-				dataList[i] = make([]map[string]interface{}, len(req.Data[i]))
-			}
-			dataList[i][j] = dataMap
-			//fmt.Println("dataMap", dataMap["m"])
-			v, _ := dataMap["m"]
-			//指标名称列表
-			if v == "指标名称" {
-				for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
-					if req.Data[i][nameIndex] == nil {
-						break
-					}
-					secNameMap := req.Data[i][nameIndex].(map[string]interface{})
-					secName, _ := secNameMap["m"]
-					if secName == nil {
+			// 存储数据
+			for j := 1; j < len(req.Data[i]); j++ {
+				closeVal := req.Data[i][j].(map[string]interface{})
+				if item, ok := secNameToDateMap[secNameList[j-1]]; ok {
+					tmpVal, ok := closeVal["m"].(string)
+					if !ok || tmpVal == "" {
 						continue
 					}
-					secNameList = append(secNameList, secNameMap["m"].(string))
-					//fmt.Println("secName:", secNameList)
-				}
-			}
-			//频度列表
-			if v == "频度" {
-				for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
-					if req.Data[i][nameIndex] == nil {
-						break
-					}
-					frequencyMap := req.Data[i][nameIndex].(map[string]interface{})
-					frequency, _ := frequencyMap["m"]
-					if frequency == nil {
+					item[date] = tmpVal
+				} else {
+					tmpVal, ok := closeVal["m"].(string)
+					if !ok || tmpVal == "" {
 						continue
 					}
-					frequencyList = append(frequencyList, frequencyMap["m"].(string))
-					//fmt.Println("frequencyList:", frequencyList)
-				}
-			}
-			//单位列表
-			if v == "单位" {
-				for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
-					if req.Data[i][nameIndex] == nil {
-						break
-					}
-					unitMap := req.Data[i][nameIndex].(map[string]interface{})
-					unit, _ := unitMap["m"]
-					if unit == nil {
-						continue
+					secNameToDateMap[secNameList[j-1]] = map[string]string{
+						date: tmpVal,
 					}
-					unitList = append(unitList, unitMap["m"].(string))
-					//fmt.Println("unitList:", unitList)
 				}
 			}
-			//数据列表
-			//var valueList []string
-			valueListMap := make(map[int]string)
-			if i > 4 {
-				for index := 1; index < len(req.Data[i]); index++ {
-					if req.Data[i][index] == nil {
-						continue
+
+		} else {
+			for j, dataArr := range req.Data[i] {
+				//每一列
+				if dataArr == nil {
+					continue
+				}
+				dataMap := dataArr.(map[string]interface{})
+				if dataList == nil {
+					dataList = make([][]map[string]interface{}, len(req.Data))
+				}
+				if dataList[i] == nil {
+					dataList[i] = make([]map[string]interface{}, len(req.Data[i]))
+				}
+				dataList[i][j] = dataMap
+				//fmt.Println("dataMap", dataMap["m"])
+				v, _ := dataMap["m"]
+				//指标名称列表
+				if v == "指标名称" {
+					for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
+						if req.Data[i][nameIndex] == nil {
+							break
+						}
+						secNameMap := req.Data[i][nameIndex].(map[string]interface{})
+						secName, _ := secNameMap["m"]
+						if secName == nil {
+							continue
+						}
+						secNameList = append(secNameList, secNameMap["m"].(string))
+						//fmt.Println("secName:", secNameList)
 					}
-					valueMap := req.Data[i][index].(map[string]interface{})
-					value, _ := valueMap["m"]
-					if value == nil {
-						continue
+				}
+				//频度列表
+				if v == "频度" {
+					for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
+						if req.Data[i][nameIndex] == nil {
+							break
+						}
+						frequencyMap := req.Data[i][nameIndex].(map[string]interface{})
+						frequency, _ := frequencyMap["m"]
+						if frequency == nil {
+							continue
+						}
+						frequencyList = append(frequencyList, frequencyMap["m"].(string))
 					}
-					valueListMap[index] = valueMap["m"].(string)
-
-					// 09-27 千位分隔符时用 "m" 取字符串存数据库会把逗号当小数点,现在换用 "v" 直接取数字再转为字符串,看看会不会有问题
-					if ct, ok := valueMap["ct"].(map[string]interface{}); ok {
-						fa, _ := ct["fa"]
-						if fa == "#,##0.000" {
-							value = valueMap["v"]
-							floatValue := valueMap["v"].(float64)
-							valueListMap[index] = strconv.FormatFloat(floatValue, 'f', -1, 64)
+				}
+				//单位列表
+				if v == "单位" {
+					for nameIndex := 1; nameIndex < len(req.Data[i]); nameIndex++ {
+						if req.Data[i][nameIndex] == nil {
+							break
 						}
+						unitMap := req.Data[i][nameIndex].(map[string]interface{})
+						unit, _ := unitMap["m"]
+						if unit == nil {
+							continue
+						}
+						unitList = append(unitList, unitMap["m"].(string))
 					}
-
-					//valueList = append(valueList, valueMap["m"].(string))
-					//fmt.Println("valueList:", valueList)
 				}
-				dateMap := req.Data[i][0].(map[string]interface{})
-				date := dateMap["m"].(string)
-				dateValueMap[date] = valueListMap
 			}
 		}
-
-		//firstCell := req.Data[i][0]
-		//if firstCell == nil {
-		//	continue
-		//}
-		//dataMap := firstCell.(map[string]interface{})
-		//if dataList == nil {
-		//	dataList = make([][]map[string]interface{}, len(req.Data[i]))
-		//}
-		//if dataList[i] == nil {
-		//	dataList[i] = make([]map[string]interface{}, len(req.Data[i]))
-		//}
-		//dataList[i][0] = dataMap
-		//fmt.Println("dataMap", dataMap["m"])
-		//v, _ := dataMap["m"]
-		////指标名称
-		//if v == "指标名称" {
-		//	secNameMap := req.Data[i][1].(map[string]interface{})
-		//	secName = secNameMap["m"].(string)
-		//	fmt.Println("secName:", secName)
-		//}
-		////频度
-		//if v == "频度" {
-		//	frequencyMap := req.Data[i][1].(map[string]interface{})
-		//	frequency = frequencyMap["m"].(string)
-		//	fmt.Println("frequency:", frequency)
-		//}
-		////单位
-		//if v == "单位" {
-		//	unitMap := req.Data[i][1].(map[string]interface{})
-		//	unit = unitMap["m"].(string)
-		//	fmt.Println("unit:", unit)
-		//}
-		//
-		////日期
-		//dateMap := req.Data[i][1].(map[string]interface{})
-		//createDate = dateMap["m"].(string)
-		//
-		////值
-		//valueMap := req.Data[i][valueIndex].(map[string]interface{})
-		//closeVal = valueMap["m"].(string)
-
-		//新增指标
-		//errMsg := services.AddEdbItem(secName, unit, frequency, req.ClassifyId, sysUser)
-		//if errMsg == "" {
-		//	br.Msg = "新增指标异常!"
-		//	br.ErrMsg = errMsg
-		//	return
-		//}
-
-		valueIndex++
-	}
-
-	//var excelId int64
-	//保存EXCEL样式
-	//{
-	//	cellData, err := json.Marshal(cellDataList)
-	//	if err != nil {
-	//		br.Msg = "参数解析异常!"
-	//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-	//		return
-	//	}
-	//	config, err := json.Marshal(req.Config)
-	//	if err != nil {
-	//		br.Msg = "参数解析异常!"
-	//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-	//		return
-	//	}
-	//	datas, err := json.Marshal(dataList)
-	//	if err != nil {
-	//		br.Msg = "参数解析异常!"
-	//		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-	//		return
-	//	}
-	//	excelItem := data_manage.ExcelStyle{
-	//		CellData: string(cellData),
-	//		Config:   string(config),
-	//		Data:     string(datas),
-	//	}
-	//	excelId, err = models.AddExcelData(&excelItem)
-	//	if err != nil {
-	//		br.ErrMsg = "获取失败,Err:" + err.Error()
-	//		br.Msg = "获取失败"
-	//		return
-	//	}
-	//
-	//	//判断映射表是否已经存在
-	//	count, err := data_manage.GetExcelEdbdataMappingCount(req.ClassifyId, target.TradeCode, frequency)
-	//	if err != nil {
-	//		br.ErrMsg = "GetExcelEdbdataMappingCount,Err:" + err.Error()
-	//		br.Msg = "新增映射表失败"
-	//		return
-	//	}
-	//	mappingItem := data_manage.ExcelEdbdataMapping{
-	//		ExcelId:    excelId,
-	//		ClassifyId: req.ClassifyId,
-	//		Frequency:  frequency,
-	//		TradeCode:  target.TradeCode,
-	//		CreateTime: time.Now(),
-	//	}
-	//	if count == 0{
-	//		//新增
-	//		err := data_manage.AddExcelEdbdataMapping(&mappingItem)
-	//		if err != nil {
-	//			br.ErrMsg = "AddExcelEdbdataMapping,Err:" + err.Error()
-	//			br.Msg = "新增映射表失败"
-	//			return
-	//		}
-	//	}
-	//}
+	}
 
 	// 所有的指标数据map
-	edbCodeDataMap := make(map[string]map[string]string)
+	edbCodeInfoMap := make(map[string]*models.EdbInfoItem)
 
-	//操作指标,新增指标及数据等
-	{
-		for i := 0; i < len(secNameList); i++ {
-			//fmt.Println("len(secNameList):", len(secNameList))
-			secName := secNameList[i]
-			frequency := frequencyList[i]
-
-			//防止填了指标名称后没填单位报错
-			if len(unitList) < len(secNameList) {
-				br.Msg = "单位填写异常!"
-				br.ErrMsg = "单位填写异常!"
-				return
-			}
-			unit := unitList[i]
-
-			for j, s := range dateList {
-				var closeVal string
-				//valueList, ok := dateValueMap[s]
-				valueMap, ok := dateValueMap[s]
-				//fmt.Println("valueMap:", valueMap)
-				if ok {
-					createDate := dateList[j]
-					//if i < len(valueList) {
-					//	closeVal = valueList[i]
-					//} else {
-					//	continue
-					//}
-					closeVal, ok = valueMap[i+1]
-					target, ok2 := targetMap[secName]
-					if !ok2 {
-						tmpTarget, err := models.GetTargetBySecName(secName)
-						if err != nil {
-							//如果是找不到该指标,那么新增指标
-							if err.Error() == utils.ErrNoRow() {
-								//if frequency == "" {
-								//	failItem := new(models.EdbdataImportFail)
-								//	failItem.SysUserId = strconv.Itoa(sysUser.AdminId)
-								//	failItem.ClassifyName = classifyName
-								//	failItem.CreateDate = createDate
-								//	failItem.SecName = secName
-								//	failItem.Close = closeVal
-								//	failItem.Remark = "新增指标失败,频度字段为空"
-								//	failItem.Frequency = frequency
-								//	failItem.Unit = unit
-								//	failDatas = append(failDatas, failItem)
-								//	continue
-								//}
-								//if unit == "" {
-								//	failItem := new(models.EdbdataImportFail)
-								//	failItem.SysUserId = strconv.Itoa(sysUser.AdminId)
-								//	failItem.ClassifyName = classifyName
-								//	failItem.CreateDate = createDate
-								//	failItem.SecName = secName
-								//	failItem.Close = closeVal
-								//	failItem.Remark = "新增指标失败,单位字段为空"
-								//	failItem.Frequency = frequency
-								//	failItem.Unit = unit
-								//	failDatas = append(failDatas, failItem)
-								//	continue
-								//}
-
-								tmpErr := data.AddEdbInfo(secName, unit, frequency, "", sysUser.Mobile, req.ClassifyId, sysUser.AdminId)
-								if tmpErr != nil {
-									//fmt.Println("AddEdbInfo err:", err.Error())
-									utils.FileLogData.Error("AddEdbInfo err :%s", tmpErr.Error())
-									continue
-								}
-								tmpTarget, tmpErr := models.GetTargetBySecName(secName)
-								target = tmpTarget
-								targetMap[secName] = target
-							} else {
-								fmt.Println("导入数据 获取指标:Err:" + err.Error())
-							}
-						} else {
-							target = tmpTarget
-							targetMap[secName] = target
-						}
-					}
+	if len(frequencyList) == 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "保存成功"
+		return
+	}
 
-					//判断指标数据是否已经存在
-					tmpDataMap, ok2 := edbCodeDataMap[target.TradeCode]
-					if !ok2 {
-						tmpDataMap = make(map[string]string)
-						targetDataList, tmpErr := models.GetTargetsDataList(target.TradeCode)
-						if tmpErr != nil {
-							err = tmpErr
-						}
-						for _, tmpData := range targetDataList {
-							tmpDataMap[tmpData.Dt] = tmpData.Close
-						}
-						edbCodeDataMap[target.TradeCode] = tmpDataMap
-					}
+	//获取账户所拥有权限的分类id集合
+	classifyIdStrList, err := data.GetEdbClassifyListByAdminId(int64(sysUser.AdminId))
+	if err != nil {
+		br.Msg = "获取分类数据失败"
+		return
+	}
+	IsVaild := 0
+	for _, classifyId := range classifyIdStrList {
+		if strconv.Itoa(req.ClassifyId) == classifyId {
+			IsVaild += 1
+		}
+		if strconv.Itoa(req.OldClassifyId) == classifyId {
+			IsVaild += 1
+		}
+	}
+	if IsVaild != 2 {
+		br.Msg = "操作权限不足,请检查分类操作权限"
+		br.ErrMsg = "操作权限不足"
+		return
+	}
 
-					//判断数据是否已经存在
-					tmpVal, ok3 := tmpDataMap[createDate]
-					//数据已存在,进行更新操作
-					if ok3 {
-						if closeVal != "" {
-							// 库里面的数据
-							tmpValDecimal, tmpErr := decimal.NewFromString(tmpVal)
-							if tmpErr != nil {
-								fmt.Println("tmpVal Parse err:", tmpErr.Error())
-								err = tmpErr
-								continue
-							}
-							// 用户填写的数据
-							closeValDecimal, tmpErr := decimal.NewFromString(closeVal)
-							if tmpErr != nil {
-								fmt.Println("closeVal Parse err:", tmpErr.Error())
-								err = tmpErr
-								continue
-							}
-							if !tmpValDecimal.Equal(closeValDecimal) {
-								fmt.Println("更新数值")
-								err = models.ModifyTargetsDataByImport(target.TradeCode, createDate, closeVal)
-								if err != nil {
-									fmt.Println("ModifyTargetsDataByImport err:", err.Error())
-									errs = append(errs, err.Error())
-									//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 修改数据失败:Err:"+err.Error(), utils.EmailSendToUsers)
-								}
-							}
-						} else {
-							fmt.Println("删除数值")
-							err = models.DeleteTargetsDataByImport(target.TradeCode, createDate)
-							if err != nil {
-								fmt.Println("DeleteTargetsDataByImport err:", err.Error())
-								errs = append(errs, err.Error())
-								//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 修改数据失败:Err:"+err.Error(), utils.EmailSendToUsers)
-							}
-						}
+	// 获取当前类所有的有效指标
+	list, err := models.GetTargetItemListSort(req.ClassifyId, 1, req.Frequence, "", "", classifyIdStrList)
+	if err != nil {
+		br.Msg = "获取指标失败"
+		br.ErrMsg = "获取有效指标失败,系统错误,Err:" + err.Error()
+		return
+	}
 
-						if frequency != target.Frequency || unit != target.Unit || req.ClassifyId != target.ClassifyId {
-							fmt.Println("更新指标频度或单位")
-							err = models.ModifyEdbinfo(target.TradeCode, unit, frequency, req.ClassifyId)
-							if err != nil {
-								fmt.Println("EditEdbinfo err:", err.Error())
-								return
-							}
-						}
-					} else { //数据不存在,进行新增操作
-						if target.TradeCode != "" && createDate != "" && closeVal != "" {
-							fmt.Println("新增")
-							models.AddTargetsDataByImport(target.TradeCode, createDate, closeVal)
-							if err != nil {
-								fmt.Println("AddTargetsDataByImport err:", err.Error())
-								errs = append(errs, err.Error())
-								//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 新增数据失败:Err:"+err.Error(), utils.EmailSendToUsers)
-							}
+	for _, item := range list {
+		edbCodeInfoMap[item.SecName] = item
+	}
+	//历史的类别下的指标
+	oldEdbCodeInfoMap := make(map[string]*models.EdbInfoItem)
+	if req.ClassifyId != req.OldClassifyId || req.Frequence != req.OldFrequence {
+		// 获取历史类所有的有效指标
+		oldList, err := models.GetTargetItemListSort(req.OldClassifyId, 1, req.OldFrequence, "", "", classifyIdStrList)
+		if err != nil {
+			br.Msg = "获取指标失败"
+			br.ErrMsg = "获取有效指标失败,系统错误,Err:" + err.Error()
+			return
+		}
+		for _, item := range oldList {
+			edbCodeInfoMap[item.SecName] = item
+			oldEdbCodeInfoMap[item.SecName] = item
+		}
+	} else {
+		// 如果没有更换指标类别和频度,那就保持一致
+		oldEdbCodeInfoMap = edbCodeInfoMap
+	}
 
-							tmpDataMap[createDate] = closeVal
-							edbCodeDataMap[target.TradeCode] = tmpDataMap
-						}
-					}
+	// 删除历史类别下的指标及数据
+	if req.TradeCode == "" {
+		// TradeCode == "" 表示当前数据不是经过搜索的数据,可以进行删除指标操作
+		deleteTraceCodeList := make([]string, 0)
+		for k, v := range oldEdbCodeInfoMap {
+			if _, ok := secNameToDateMap[k]; !ok {
+				deleteTraceCodeList = append(deleteTraceCodeList, v.TradeCode)
+			}
+		}
+		// 删除指标
+		err = models.DeleteEdbinfoByTraceCodeList(deleteTraceCodeList)
+		if err != nil {
+			br.Msg = "excel保存失败"
+			br.ErrMsg = "指标及指标数据删除失败,Err:" + err.Error()
+			return
+		}
+	}
 
-					//保存关联表
-					//{
-					//	//判断映射表是否已经存在
-					//	count, err := data_manage.GetExcelEdbdataMappingCount(req.ClassifyId, target.TradeCode, frequency)
-					//	if err != nil {
-					//		br.ErrMsg = "GetExcelEdbdataMappingCount,Err:" + err.Error()
-					//		br.Msg = "新增映射表失败"
-					//		return
-					//	}
-					//	mappingItem := data_manage.ExcelEdbdataMapping{
-					//		ExcelId:    excelId,
-					//		ClassifyId: req.ClassifyId,
-					//		Frequency:  frequency,
-					//		TradeCode:  target.TradeCode,
-					//		CreateTime: time.Now(),
-					//	}
-					//	if count == 0{
-					//		//新增
-					//		err := data_manage.AddExcelEdbdataMapping(&mappingItem)
-					//		if err != nil {
-					//			br.ErrMsg = "AddExcelEdbdataMapping,Err:" + err.Error()
-					//			br.Msg = "新增映射表失败"
-					//			return
-					//		}
-					//	}
-					//}
+	// 获得需要新增的指标
+	addEdbInfoList := make([]*models.Edbinfo, 0)
+	sortEdbInfoList := make([]*models.Edbinfo, 0)
+	for i, v := range secNameList {
+		if item, ok := edbCodeInfoMap[v]; !ok {
+			t := new(models.Edbinfo)
+			t.SecName = v
+			t.Unit = unitList[i]
+			t.Remark = "手动"
+			t.Frequency = frequencyList[i]
+			t.ClassifyId = req.ClassifyId
+			t.UserId = sysUser.AdminId
+			t.Sort = len(list) + i
+			addEdbInfoList = append(addEdbInfoList, t)
+			edbCodeInfoMap[t.SecName] = &models.EdbInfoItem{
+				TradeCode: t.TradeCode,
+				SecName:   t.SecName,
+				Unit:      t.Unit,
+				Frequency: t.Frequency,
+				UserId:    t.UserId,
+			}
+		} else {
+			t := new(models.Edbinfo)
+			t.TradeCode = item.TradeCode
+			if req.Frequence != req.OldFrequence || req.ClassifyId != req.OldClassifyId {
+				t.Sort = len(list) + i
+			} else {
+				if req.TradeCode != "" {
+					// 如果是搜索的指标,那就保存原来的排序
+					t.Sort = item.Sort
+				} else {
+					t.Sort = i
 				}
 			}
+			t.Frequency = req.Frequence
+			t.ClassifyId = req.ClassifyId
+			sortEdbInfoList = append(sortEdbInfoList, t)
+		}
+	}
+	// 更新指标频度,分类,顺序
+	for _, v := range sortEdbInfoList {
+		err = v.Update([]string{"frequency", "classify_id", "sort"})
+		if err != nil {
+			br.Msg = "excel保存失败"
+			br.ErrMsg = "excel更新顺序失败,Err:" + err.Error()
+			return
 		}
 	}
+	// 新增指标
+	msg, err := data.MultiAddEdbInfo(addEdbInfoList, sysUser.Mobile)
+	if err != nil {
+		if msg != "" {
+			br.Msg = msg
+		} else {
+			br.Msg = "指标保存失败"
+		}
+		br.ErrMsg = "新增指标失败,系统错误,Err:" + err.Error()
+		return
+	}
+	for _, v := range addEdbInfoList {
+		edbCodeInfoMap[v.SecName].TradeCode = v.TradeCode
+	}
+	// 新增指标数据
+	addEdbDataList := make([]*models.Edbdata, 0)
+	for _, v := range secNameList {
+		// 指标数据
+		for date, closeVal := range secNameToDateMap[v] {
+			t := new(models.Edbdata)
+			// 理论上这里面不应该出现空值
+			t.TradeCode = edbCodeInfoMap[v].TradeCode
+			t.Dt = date
+			t.Close = closeVal
+			t.ModifyTime = time.Now()
+			addEdbDataList = append(addEdbDataList, t)
+		}
+	}
+	err = models.MultiAddEdbdata(addEdbDataList)
+	if err != nil {
+		br.Msg = "excel保存失败"
+		br.ErrMsg = "新增指标数据失败,系统错误,Err:" + err.Error()
+		return
+	}
 
-	//resp := models.TargetItemListResp{
-	//	List: list,
-	//}
 	br.Ret = 200
 	br.Success = true
-	br.Msg = "获取成功"
-	//br.Data = resp
-	//br.Data = list
+	br.Msg = "保存成功"
 }
 
 // ExcelDataEdit

+ 89 - 28
controllers/voice.go

@@ -7,9 +7,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/kgiannakakis/mp3duration/src/mp3duration"
-	"github.com/rdlucklib/rdluck_tools/file"
 	"github.com/rdlucklib/rdluck_tools/http"
-	"io/ioutil"
 	"os"
 	"path"
 	"strconv"
@@ -43,11 +41,11 @@ func (this *VoiceController) Upload() {
 	}
 	reportId, err := this.GetInt("ReportId")
 	if err != nil {
-		br.Msg = "获取资源信息失败"
-		br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
+		br.Msg = "报告id异常"
+		br.ErrMsg = "报告id异常,Err:" + err.Error()
 		return
 	}
-	report, err := models.GetReportItemById(reportId)
+	reportInfo, err := models.GetReportByReportId(reportId)
 	if err != nil {
 		br.Msg = "获取报告信息失败"
 		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
@@ -128,20 +126,39 @@ func (this *VoiceController) Upload() {
 			return
 		}
 	}
-	createTime := report.CreateTime.Format("0102")
-	videoName := report.Title + "(" + createTime + ")"
 
-	fileBody, err := ioutil.ReadFile(fpath)
+	fileBody, err := os.ReadFile(fpath)
 	videoSize := len(fileBody)
 	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
 	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
 
-	err = models.ModifyReportVideo(reportId, resourceUrl, videoName, sizeStr, playSeconds)
-	if err != nil {
-		br.Msg = "上传失败"
-		br.ErrMsg = "上传失败,Err:" + err.Error()
-		return
+	// 修改报告的音频信息
+	{
+		reportCreateTime, err := time.Parse(utils.FormatDateTime, reportInfo.CreateTime)
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "上传失败,Err:" + err.Error()
+			return
+		}
+
+		createTimeStr := reportCreateTime.Format("0102")
+		videoName := reportInfo.Title + "(" + createTimeStr + ")"
+
+		reportInfo.VideoUrl = resourceUrl
+		reportInfo.VideoName = videoName
+		reportInfo.VideoPlaySeconds = fmt.Sprint(playSeconds)
+		reportInfo.VideoSize = sizeStr
+		reportInfo.LastModifyAdminId = this.SysUser.AdminId
+		reportInfo.LastModifyAdminName = this.SysUser.RealName
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告的音频信息失败,Err:" + err.Error()
+			return
+		}
 	}
+
 	resp := new(models.ResourceResp)
 	resp.Id = newId
 	resp.ResourceUrl = resourceUrl
@@ -175,36 +192,80 @@ func (this *VoiceCommonController) Download() {
 		br.ErrMsg = "获取,ReportId,Err:" + err.Error()
 		return
 	}
-	report, err := models.GetReportById(reportId)
+	reportInfo, err := models.GetReportById(reportId)
 	if err != nil {
 		br.Msg = "获取信息失败"
 		br.ErrMsg = "获取信息失败,Err:" + err.Error()
 		return
 	}
-	savePath := time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".mp3"
-	fileBody, err := http.Get(report.VideoUrl)
-	if err != nil {
-		br.Msg = "获取信息失败"
-		br.ErrMsg = "获取信息失败,Err:" + err.Error()
-		return
+
+	savePath, fileName, err, errMsg := services.DownloadVoice(reportInfo)
+	// 如果生成了文件,那么就在退出的时候,删除该文件
+	if savePath != `` {
+		defer func() {
+			os.Remove(savePath)
+		}()
 	}
-	err = file.SaveFile(fileBody, savePath)
+
 	if err != nil {
-		br.Msg = "保存信息失败"
-		br.ErrMsg = "保存信息失败,Err:" + err.Error()
+		br.Msg = errMsg
+		br.ErrMsg = "下载失败,Err:" + err.Error()
 		return
 	}
-	fileName := report.VideoName + ".mp3"
-	this.Ctx.Output.Download(savePath, fileName)
-	defer func() {
-		os.Remove(savePath)
-	}()
+
+	if savePath != `` {
+		this.Ctx.Output.Download(savePath, fileName)
+	}
+
 	br.Ret = 200
 	br.Msg = "下载成功"
 	br.Success = true
+
 	return
 }
 
+//func (this *VoiceCommonController) Download() {
+//	br := new(models.BaseResponse).Init()
+//	defer func() {
+//		this.Data["json"] = br
+//		this.ServeJSON()
+//	}()
+//	reportId, err := this.GetInt("ReportId")
+//	if err != nil {
+//		br.Msg = "参数错误"
+//		br.ErrMsg = "获取,ReportId,Err:" + err.Error()
+//		return
+//	}
+//	report, err := models.GetReportById(reportId)
+//	if err != nil {
+//		br.Msg = "获取信息失败"
+//		br.ErrMsg = "获取信息失败,Err:" + err.Error()
+//		return
+//	}
+//	savePath := time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".mp3"
+//	fileBody, err := http.Get(report.VideoUrl)
+//	if err != nil {
+//		br.Msg = "获取信息失败"
+//		br.ErrMsg = "获取信息失败,Err:" + err.Error()
+//		return
+//	}
+//	err = file.SaveFile(fileBody, savePath)
+//	if err != nil {
+//		br.Msg = "保存信息失败"
+//		br.ErrMsg = "保存信息失败,Err:" + err.Error()
+//		return
+//	}
+//	fileName := report.VideoName + ".mp3"
+//	this.Ctx.Output.Download(savePath, fileName)
+//	defer func() {
+//		os.Remove(savePath)
+//	}()
+//	br.Ret = 200
+//	br.Msg = "下载成功"
+//	br.Success = true
+//	return
+//}
+
 // ReportChapterDownload
 // @Title 报告章节列表音频下载
 // @Description 音频下载接口

+ 1 - 1
go.mod

@@ -39,7 +39,7 @@ require (
 	github.com/silenceper/wechat/v2 v2.1.6
 	github.com/spf13/viper v1.7.0
 	github.com/tealeg/xlsx v1.0.5
-	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873
+	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.880
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.880
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.880
 	github.com/xuri/excelize/v2 v2.8.1

+ 2 - 3
go.sum

@@ -560,9 +560,8 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
 github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
 github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
 github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873 h1:CbeN5Fdzq3xea36+ZKPQcuRwwJk0ZYQRxcyWkyK5768=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873/go.mod h1:vzSh5OxbOCyFt+SdlEd9oCQGBb1oObkD7Xfod/UPvVk=
-github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.873/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.880 h1:keIcDofAxKjSlh1EmBkXIA7aCFkEWH9UP19NbV9KGas=
+github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.880/go.mod h1:J+pg3uRzo+jcSBSUYpEuUD4CE5OfEKsMohHLZne9Pa0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.880 h1:0Ok1pZ06/zZMCiW8Dm8wYOGJK1HCU5OXwNSyE5UVOAM=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.880/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ses v1.0.880 h1:PnzU5KS7x3LQGE0yetGLEGwJtLb6Uzsd79mbCiRh1rw=

+ 57 - 0
models/ai_summary/ai_pormpt.go

@@ -0,0 +1,57 @@
+package ai_summary
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type AiPrompt struct {
+	AiPromptId    int       `orm:"column(ai_prompt_id);pk"` // ai纪要提示词id
+	PromptContent string    // 原始内容
+	SysAdminId    int       // 创建人ID
+	SysAdminName  string    // 创建人姓名
+	Title         string    // 文档标题
+	CreateTime    time.Time // 创建时间
+	ModifyTime    time.Time // 更新时间
+	IsShare       int       // 是否分享,0:不分享,1:分享
+}
+
+type AiPromptItem struct {
+	AiPromptId    int    `orm:"column(ai_prompt_id);pk"` // ai纪要提示词id
+	PromptContent string // 原始内容
+	SysAdminId    int    // 创建人ID
+	SysAdminName  string // 创建人姓名
+	Title         string // 文档标题
+	CreateTime    string // 创建时间
+	ModifyTime    string // 更新时间
+	IsShare       int    // 是否分享,0:不分享,1:分享
+}
+
+func GetAiPromptList(sysUserId, isShare int) (items []*AiPromptItem, err error) {
+	sql := ``
+	if isShare == 1 {
+		sql = ` SELECT * FROM ai_prompt WHERE sys_admin_id=? and is_share=1 ORDER BY create_time DESC `
+	} else {
+		sql = ` SELECT * FROM ai_prompt WHERE sys_admin_id=? ORDER BY create_time DESC `
+	}
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, sysUserId).QueryRows(&items)
+	return
+}
+
+func GetAiPromptShareList() (items []*AiPromptItem, err error) {
+	sql := ``
+	sql = ` SELECT * FROM ai_prompt WHERE is_share=1 ORDER BY create_time DESC `
+
+	o := orm.NewOrm()
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+type RespGroupListItem struct {
+	GroupId   int64  `description:"目录id"`
+	GroupName string `description:"目录名称"`
+	AdminId   int    `description:"目录创建者账号ID"`
+	IsShare   int8   `description:"是否共享,0私有,1共享"`
+	PromptList   []*AiPromptItem
+}

+ 83 - 0
models/ai_summary/ai_summary.go

@@ -0,0 +1,83 @@
+package ai_summary
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type AiSummary struct {
+	AiSummaryId     int `orm:"column(ai_summary_id);pk"` // ai纪要id
+	SaDocId         int            // 文档ID
+	OriginContent   string         // 原始内容
+	ClassifyId      int            // 分类id
+	SysUserId      int            // 创建人ID
+	SysUserRealName    string         // 创建人姓名
+	Title           string         // 文档标题
+	CreateTime      time.Time         // 创建时间
+	ModifyTime      time.Time         // 更新时间
+	OpenaiFileName  string         // 文件名称
+	OpenaiFilePath  string         // 文件路径
+	Sort int        // 排序字段,越小越靠前,默认值:10
+}
+
+
+func GetAiChatTopicList(sysUserId int) (item []*AiSummary, err error) {
+	sql := ` SELECT * FROM ai_summary WHERE classify_id=? ORDER BY create_time DESC `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, sysUserId).QueryRows(&item)
+	return
+}
+
+// GetAiSummaryById 根据纪要id获取沙盘详情
+func GetAiSummaryById(aiSummaryId int) (sandboxInfo *AiSummary, err error) {
+	o := orm.NewOrm()
+	sql := `select * from ai_summary where ai_summary_id = ? `
+	err = o.Raw(sql, aiSummaryId).QueryRow(&sandboxInfo)
+	return
+}
+
+// DelAiSummaryById 根据纪要id删除纪要
+func DelAiSummaryById(aiSummaryId int) (err error) {
+	o := orm.NewOrm()
+	sql := `delete from ai_summary where ai_summary_id = ? `
+	_,err = o.Raw(sql, aiSummaryId).Exec()
+	return
+}
+
+func MoveAiSummary(aiSummaryId, classifyId int) (err error) {
+	o := orm.NewOrm()
+	sql := ` UPDATE  ai_summary
+			SET
+			  classify_id = ?
+			WHERE ai_summary_id = ?`
+	_, err = o.Raw(sql, classifyId, aiSummaryId).Exec()
+	return
+}
+
+// GetFirstAiSummaryByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstAiSummaryByClassifyId(classifyId int) (item *AiSummary, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM ai_summary WHERE classify_id=? order by sort asc,ai_summary_id asc limit 1`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+
+// Update
+func (aiSummary *AiSummary) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(aiSummary, cols...)
+	return
+}
+
+// UpdateSandboxSortByClassifyId 根据纪要id更新排序
+func UpdateAiSummarySortByClassifyId(classifyId, nowSort, prevAiSummaryId int, updateSort string) (err error) {
+	o := orm.NewOrm()
+	sql := ` update ai_summary set sort = ` + updateSort + ` WHERE classify_id=?  AND `
+	if prevAiSummaryId > 0 {
+		sql += ` (sort > ? or (ai_summary_id > ` + fmt.Sprint(prevAiSummaryId) + ` and sort = ` + fmt.Sprint(nowSort) + `))`
+	}
+	_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}

+ 249 - 0
models/ai_summary/ai_summary_classify.go

@@ -0,0 +1,249 @@
+package ai_summary
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type AiSummaryClassify struct {
+	AiSummaryClassifyId int       `orm:"column(ai_summary_classify_id);pk"` // ai纪要分类id
+	ClassifyName        string    // 分类名称
+	ParentId            int       // 父级id
+	CreateTime          time.Time // 创建时间
+	ModifyTime          time.Time // 修改时间
+	SysUserId           int       // 创建人id,使用指针表示可为空
+	SysUserRealName     string    // 创建人姓名
+	Level               int       // 层级
+	Sort                int       // 排序字段,越小越靠前,默认值:10
+	RootId              int       // 顶级ID
+	HasData             int       `description:"是否含有指标数据"`
+}
+
+func AddAiSummaryClassify(item *AiSummaryClassify) (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(item)
+	return
+}
+
+type AiSummaryClassifyItems struct {
+	AiSummaryClassifyId int
+	ClassifyName        string    `description:"分类名称"`
+	ParentId            int       `description:"父级id"`
+	HasData             int       `description:"是否含有指标数据"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+	SysUserId           int       `description:"创建人id"`
+	SysUserRealName     string    `description:"创建人姓名"`
+	Level               int       `description:"层级"`
+	Sort                int       `description:"排序字段,越小越靠前,默认值:10"`
+	AiSummaryId         int       `description:"纪要id"`
+	Children            []*AiSummaryClassifyItems
+}
+
+type AiSummaryClassifyListResp struct {
+	AllNodes []*AiSummaryClassifyItems
+}
+
+// GetAiSummaryClassifyByParentId
+func GetAiSummaryClassifyByParentId(parentId int) (items []*AiSummaryClassifyItems, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM ai_summary_classify WHERE parent_id=? order by sort asc,ai_summary_classify_id asc`
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+	return
+}
+
+// GetAiSummaryInfoByAdminId 获取所有我创建的纪要,用于分类展示
+func GetAiSummaryInfoByAdminId(adminId int) (items []*AiSummaryClassifyItems, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT ai_summary_id,ai_summary_classify_id,name AS classify_name,
+             sys_user_id,sys_user_real_name
+            FROM ai_summary where sys_user_id = ? ORDER BY sort asc,create_time ASC `
+	_, err = o.Raw(sql, adminId).QueryRows(&items)
+	return
+}
+
+// GetAiSummaryClassifyAndInfoByParentId
+func GetAiSummaryClassifyAndInfoByParentId(parentId int) (items []*AiSummaryClassifyItems, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT
+	0 AS ai_summary_id,
+	ai_summary_classify_id,
+	classify_name,
+	parent_id,
+	create_time,
+	modify_time,
+	sys_user_id,
+	sys_user_real_name AS sys_user_real_name,
+	sort,
+	level
+FROM
+	ai_summary_classify 
+WHERE
+	parent_id = ? UNION ALL
+SELECT
+	ai_summary_id,
+	classify_id as ai_summary_classify_id,
+	'' AS classify_name,
+	0 AS parent_id,
+	create_time,
+	modify_time,
+	sys_user_id,
+	sys_user_real_name,
+	sort,
+	0 AS level
+FROM
+	ai_summary 
+WHERE
+	classify_id = ? 
+ORDER BY
+	sort ASC,
+	ai_summary_classify_id ASC`
+	_, err = o.Raw(sql, parentId, parentId).QueryRows(&items)
+	return
+}
+
+type AddAiSummaryClassifyReq struct {
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级id,第一级传0"`
+	Level        int    `description:"层级,第一级传0,其余传上一级的层级"`
+}
+
+func GetAiSummaryClassifyCount(classifyName string, parentId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT COUNT(1) AS count FROM ai_summary_classify WHERE parent_id=? AND classify_name=? `
+	err = o.Raw(sql, parentId, classifyName).QueryRow(&count)
+	return
+}
+
+// GetAiSummaryClassifyMaxSort 获取ai纪要下最大的排序数
+func GetAiSummaryClassifyMaxSort(parentId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT Max(sort) AS sort FROM ai_summary_classify WHERE parent_id=? `
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+type EditAiSummaryClassifyReq struct {
+	ClassifyName        string `description:"分类名称"`
+	AiSummaryClassifyId int    `description:"分类id"`
+}
+
+func EditSandboxClassify(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE ai_summary_classify SET classify_name=?, modify_time=NOW() WHERE ai_summary_classify_id=? `
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
+type AiSummaryClassifyDeleteCheckReq struct {
+	AiSummaryClassifyId int `description:"分类id"`
+}
+
+func GetAiSummaryInfoCountByClassifyId(classifyId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT COUNT(1) AS count FROM ai_summary AS a
+				WHERE a.classify_id IN(
+				SELECT t.ai_summary_classify_id FROM 
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM ai_summary_classify WHERE parent_id IS NOT NULL) rd,
+					 (SELECT @pid := ?) pd 
+				WHERE FIND_IN_SET(parent_id, @pid) > 0 
+				  AND @pid := CONCAT(@pid, ',', ai_summary_classify_id) 
+				UNION SELECT * FROM ai_summary_classify WHERE ai_summary_classify_id = @pid 
+				)AS t
+				) `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+type DeleteAiSummaryClassifyReq struct {
+	AiSummaryClassifyId int `description:"分类id"`
+	AiSummaryId         int `description:"纪要id"`
+}
+
+func DeleteAiSummaryClassify(classifyId int) (err error) {
+	o := orm.NewOrm()
+	sql := ` DELETE FROM ai_summary_classify
+				WHERE ai_summary_classify_id IN(
+				SELECT t.ai_summary_classify_id FROM
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM ai_summary_classify WHERE parent_id IS NOT NULL) rd,
+				(SELECT @pid := ?) pd
+				WHERE FIND_IN_SET(parent_id, @pid) > 0
+				AND @pid := CONCAT(@pid, ',', ai_summary_classify_id)
+				UNION SELECT * FROM ai_summary_classify WHERE ai_summary_classify_id = @pid
+				)AS t
+				) `
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}
+
+// MoveAiSummaryClassifyReq 移动纪要分类请求参数
+type MoveAiSummaryClassifyReq struct {
+	AiSummaryClassifyId int `description:"分类id"`
+	AiSummaryId         int `description:"纪要ID"`
+	ParentClassifyId    int `description:"父级分类id 移动沙盘时为目标分类id"`
+	PrevId              int `description:"上一个兄弟节点分类id"`
+	NextId              int `description:"下一个兄弟节点分类id"`
+	PrevType            int `description:"上一个兄弟节点类型 1分类 2纪要 "`
+	NextType            int `description:"上一个兄弟节点类型 1分类 2纪要 "`
+}
+
+func GetAiSummaryClassifyById(classifyId int) (item *AiSummaryClassify, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM ai_summary_classify WHERE ai_summary_classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func GetAiSummaryClassifyCountById(classifyId int) (count int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT count(1) AS count FROM ai_summary_classify WHERE ai_summary_classify_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// Update 更新沙盘分类基础信息
+func (aiSummaryClassify *AiSummaryClassify) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(aiSummaryClassify, cols...)
+	return
+}
+
+// GetFirstAiSummaryClassifyByParentId 获取当前父级沙盘分类下的排序第一条的数据
+func GetFirstAiSummaryClassifyByParentId(parentId int) (item *AiSummaryClassify, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM ai_summary_classify WHERE parent_id=? order by sort asc,ai_summary_classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+// UpdateSandboxClassifySortByParentId 根据沙盘父类id更新排序
+func UpdateAiSummaryClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrm()
+	sql := ` update ai_summary_classify set sort = ` + updateSort + ` WHERE parent_id=? and sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( ai_summary_classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// 获取所有子级分类id
+func GetAiSummaryClassifySubcategories(classifyId int) (Ids string, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT GROUP_CONCAT(ai_summary_classify_id) AS ids
+FROM (
+SELECT @pv := ? AS ai_summary_classify_id
+UNION ALL
+SELECT sc.ai_summary_classify_id
+FROM ai_summary_classify sc
+JOIN (SELECT @pv := ?) initial
+WHERE sc.parent_id = @pv
+) subcategories; `
+	err = o.Raw(sql, classifyId, classifyId).QueryRow(&Ids)
+	return
+}

+ 67 - 35
models/business_conf.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"html"
@@ -9,44 +10,45 @@ import (
 )
 
 const (
-	BusinessConfUseXf                     = "UseXf"
-	BusinessConfXfAppid                   = "XfAppid"
-	BusinessConfXfApiKey                  = "XfApiKey"
-	BusinessConfXfApiSecret               = "XfApiSecret"
-	BusinessConfXfVcn                     = "XfVcn"
-	BusinessConfEnPptCoverImgs            = "EnPptCoverImgs"
-	BusinessConfIsReportApprove           = "IsReportApprove"
-	BusinessConfReportApproveType         = "ReportApproveType"
-	BusinessConfCompanyName               = "CompanyName"
-	BusinessConfCompanyWatermark          = "CompanyWatermark"
-	BusinessConfWatermarkChart            = "WatermarkChart"
-	BusinessConfLoginSmsTpId              = "LoginSmsTpId"
-	BusinessConfLoginSmsGjTpId            = "LoginSmsGjTpId"
-	BusinessConfSmsJhgnAppKey             = "SmsJhgnAppKey"
-	BusinessConfSmsJhgjAppKey             = "SmsJhgjAppKey"
-	BusinessConfLdapHost                  = "LdapHost"
-	BusinessConfLdapBase                  = "LdapBase"
-	BusinessConfLdapPort                  = "LdapPort"
-	BusinessConfEmailClient               = "EmailClient"
-	BusinessConfEmailServerHost           = "EmailServerHost"
-	BusinessConfEmailServerPort           = "EmailServerPort"
-	BusinessConfEmailSender               = "EmailSender"
-	BusinessConfEmailSenderUserName       = "EmailSenderUserName"
-	BusinessConfEmailSenderPassword       = "EmailSenderPassword"
-	BusinessConfSmsClient                 = "SmsClient"
-	BusinessConfNanHuaSmsAppKey           = "NanHuaSmsAppKey"
-	BusinessConfNanHuaSmsAppSecret        = "NanHuaSmsAppSecret"
-	BusinessConfNanHuaSmsApiHost          = "NanHuaSmsApiHost"
-	BusinessConfLoginSmsTplContent        = "LoginSmsTplContent"
-	BusinessConfLoginEmailTemplateSubject = "LoginEmailTemplateSubject"
-	BusinessConfLoginEmailTemplateContent = "LoginEmailTemplateContent"
-	BusinessConfLdapBindUserSuffix        = "LdapBindUserSuffix"
-	BusinessConfLdapUserFilter            = "LdapUserFilter"
-
+	BusinessConfUseXf                        = "UseXf"
+	BusinessConfXfAppid                      = "XfAppid"
+	BusinessConfXfApiKey                     = "XfApiKey"
+	BusinessConfXfApiSecret                  = "XfApiSecret"
+	BusinessConfXfVcn                        = "XfVcn"
+	BusinessConfEnPptCoverImgs               = "EnPptCoverImgs"
+	BusinessConfIsReportApprove              = "IsReportApprove"
+	BusinessConfReportApproveType            = "ReportApproveType"
+	BusinessConfCompanyName                  = "CompanyName"
+	BusinessConfCompanyWatermark             = "CompanyWatermark"
+	BusinessConfWatermarkChart               = "WatermarkChart"
+	BusinessConfLoginSmsTpId                 = "LoginSmsTpId"
+	BusinessConfLoginSmsGjTpId               = "LoginSmsGjTpId"
+	BusinessConfSmsJhgnAppKey                = "SmsJhgnAppKey"
+	BusinessConfSmsJhgjAppKey                = "SmsJhgjAppKey"
+	BusinessConfLdapHost                     = "LdapHost"
+	BusinessConfLdapBase                     = "LdapBase"
+	BusinessConfLdapPort                     = "LdapPort"
+	BusinessConfEmailClient                  = "EmailClient"
+	BusinessConfEmailServerHost              = "EmailServerHost"
+	BusinessConfEmailServerPort              = "EmailServerPort"
+	BusinessConfEmailSender                  = "EmailSender"
+	BusinessConfEmailSenderUserName          = "EmailSenderUserName"
+	BusinessConfEmailSenderPassword          = "EmailSenderPassword"
+	BusinessConfSmsClient                    = "SmsClient"
+	BusinessConfNanHuaSmsAppKey              = "NanHuaSmsAppKey"
+	BusinessConfNanHuaSmsAppSecret           = "NanHuaSmsAppSecret"
+	BusinessConfNanHuaSmsApiHost             = "NanHuaSmsApiHost"
+	BusinessConfLoginSmsTplContent           = "LoginSmsTplContent"
+	BusinessConfLoginEmailTemplateSubject    = "LoginEmailTemplateSubject"
+	BusinessConfLoginEmailTemplateContent    = "LoginEmailTemplateContent"
+	BusinessConfLdapBindUserSuffix           = "LdapBindUserSuffix"
+	BusinessConfLdapUserFilter               = "LdapUserFilter"
+	BusinessConfSmsJhgjVariable              = "SmsJhgjVariable"              // 聚合国际短信变量
 	BusinessConfTencentApiSecretId           = "TencentApiSecretId"           // 腾讯云API-密钥对
 	BusinessConfTencentApiSecretKey          = "TencentApiSecretKey"          // 腾讯云API-密钥对
 	BusinessConfTencentApiRecTaskCallbackUrl = "TencentApiRecTaskCallbackUrl" // 腾讯云API-语音识别回调地址
-	BusinessConfSmsJhgjVariable              = "SmsJhgjVariable"              // 聚合国际短信变量
+
+	BusinessConfEdbStopRefreshRule = "EdbStopRefreshRule" // 是否停止指标刷新规则
 )
 
 const (
@@ -220,3 +222,33 @@ func GetBusinessConfByKey(key string) (item *BusinessConf, err error) {
 	err = o.Raw(sql, key).QueryRow(&item)
 	return
 }
+
+type BusinessConfSingleSaveReq struct {
+	ConfKey string `description:"配置Key"`
+	ConfVal string `description:"配置值"`
+}
+
+type EdbStopRefreshRule struct {
+	IsOpen            int `description:"是否开启自动禁用1,开启,0未开启"`
+	BaseIndexStopDays int `description:"数据源间隔天数未加入指标库则停用"`
+	EdbStopDays       int `description:"指标库间隔天数未引用则停用"`
+}
+
+type BusinessConfSingleResp struct {
+	ConfVal string
+}
+
+// InitUseMongoConf
+// @Description:
+// @author: Roc
+// @datetime 2024-07-01 13:49:09
+func InitUseMongoConf() {
+	useMongo, e := GetBusinessConfByKey("UseMongo")
+	if e != nil {
+		return
+	}
+
+	if useMongo.ConfVal == `true` {
+		utils.UseMongo = true
+	}
+}

+ 29 - 0
models/chart_permission.go

@@ -215,6 +215,14 @@ func (c *ChartPermission) GetFirstChartPermissionByParentId(parentId int) (item
 	return
 }
 
+// GetChartPermissionList 获取品种权限列表
+func GetChartPermissionList() (list []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("weekly")
+	sql := `SELECT * FROM chart_permission ORDER BY product_id ASC, sort ASC`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
 // GetChartPermissionById 主键获取品种
 func GetChartPermissionById(permissionId int) (item *ChartPermission, err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -246,6 +254,7 @@ func FormatChartPermission2Simple(origin *ChartPermission) (item *SimpleChartPer
 	item.ChartPermissionId = origin.ChartPermissionId
 	item.ChartPermissionName = origin.PermissionName
 	item.Sort = origin.Sort
+
 	return
 }
 
@@ -254,5 +263,25 @@ func GetChartPermissionsByProductId() (list []*ChartPermission, err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `SELECT * FROM chart_permission WHERE product_id = ? AND enabled = 1 ORDER BY parent_id ASC, sort ASC, created_time ASC`
 	_, err = o.Raw(sql, FiccProductId).QueryRows(&list)
+
+	return
+}
+
+// GetChartPermissionByIdList
+// @Description: 根据品种ID列表获取权限列表
+// @author: Roc
+// @datetime 2024-06-07 10:32:29
+// @param permissionIdList []int
+// @return items []*ChartPermission
+// @return err error
+func GetChartPermissionByIdList(permissionIdList []int) (items []*ChartPermission, err error) {
+	num := len(permissionIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE chart_permission_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, permissionIdList).QueryRows(&items)
+
 	return
 }

+ 120 - 36
models/classify.go

@@ -41,6 +41,8 @@ type Classify struct {
 	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
 	IsMassSend        int       `description:"1:群发,0:非群发"`
 	Enabled           int       `description:"是否可用,1可用,0禁用"`
+	Level             int       `description:"层级"`
+	HasChild          int       `description:"是否有子级别,0:下面没有子分类,1:下面有子分类;默认:0"`
 }
 
 type ClassifyAddReq struct {
@@ -151,8 +153,12 @@ func EditClassify(req *EditClassifyReq) (err error) {
 	return
 }
 
-//获取父级分类
-
+// ParentClassify
+// @Description: 获取父级分类
+// @author: Roc
+// @datetime 2024-06-18 15:03:49
+// @return items []*Classify
+// @return err error
 func ParentClassify() (items []*Classify, err error) {
 	sql := `SELECT * FROM classify WHERE parent_id=0 order by id desc `
 	o := orm.NewOrmUsingDB("rddp")
@@ -169,31 +175,35 @@ func FindByIdClassify(classifyId int) (item *Classify, err error) {
 }
 
 type ClassifyList struct {
-	Id                int       `orm:"column(id);pk"`
-	ClassifyName      string    `description:"分类名称"`
-	Sort              int       `description:"排序"`
-	ParentId          int       `description:"父级分类id"`
-	CreateTime        time.Time `description:"创建时间"`
-	ModifyTime        time.Time `description:"修改时间"`
-	Abstract          string    `description:"简介"`
-	Descript          string    `description:"描述"`
-	ClassifyLabel     string    `description:"分类标签"`
-	ShowType          int       `description:"展示类型:1-列表 2-专栏"`
-	HasTeleconference int       `description:"是否有电话会:0-否 1-是"`
-	IsShow            int       `description:"是否在小程序显示:1-显示 0-隐藏"`
-	YbFiccSort        int       `description:"小程序FICC页排序"`
-	YbFiccIcon        string    `description:"小程序FICC页icon"`
-	YbFiccPcIcon      string    `description:"小程序PC端FICC页背景图"`
-	YbIconUrl         string    `description:"小程序已购页icon"`
-	YbBgUrl           string    `description:"小程序已购详情背景图"`
-	YbListImg         string    `description:"小程序研报列表封面图"`
-	YbShareBgImg      string    `description:"小程序研报详情分享背景图"`
-	YbRightBanner     string    `description:"Pc端详情页,右侧,报告合集背景图"`
-	RelateTel         int       `description:"是否在电话会中可选: 0-否; 1-是"`
-	RelateVideo       int       `description:"是否在路演视频中可选: 0-否; 1-是"`
-	Enabled           int       `description:"是否可用,1可用,0禁用"`
-	Child             []*ClassifyItem
-	ClassifyMenuList  []*ClassifyMenu
+	Id                    int       `orm:"column(id);pk"`
+	ClassifyName          string    `description:"分类名称"`
+	Sort                  int       `description:"排序"`
+	ParentId              int       `description:"父级分类id"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+	Abstract              string    `description:"简介"`
+	Descript              string    `description:"描述"`
+	ClassifyLabel         string    `description:"分类标签"`
+	ShowType              int       `description:"展示类型:1-列表 2-专栏"`
+	HasTeleconference     int       `description:"是否有电话会:0-否 1-是"`
+	IsShow                int       `description:"是否在小程序显示:1-显示 0-隐藏"`
+	YbFiccSort            int       `description:"小程序FICC页排序"`
+	YbFiccIcon            string    `description:"小程序FICC页icon"`
+	YbFiccPcIcon          string    `description:"小程序PC端FICC页背景图"`
+	YbIconUrl             string    `description:"小程序已购页icon"`
+	YbBgUrl               string    `description:"小程序已购详情背景图"`
+	YbListImg             string    `description:"小程序研报列表封面图"`
+	YbShareBgImg          string    `description:"小程序研报详情分享背景图"`
+	YbRightBanner         string    `description:"Pc端详情页,右侧,报告合集背景图"`
+	RelateTel             int       `description:"是否在电话会中可选: 0-否; 1-是"`
+	RelateVideo           int       `description:"是否在路演视频中可选: 0-否; 1-是"`
+	Enabled               int       `description:"是否可用,1可用,0禁用"`
+	Child                 []*ClassifyList
+	ClassifyMenuId        int `description:"二级分类-子目录ID"`
+	ClassifyMenuList      []*ClassifyMenu
+	ChartPermissionIdList []int `description:"绑定的权限ID"`
+	Level                 int   `description:"层级"`
+	HasChild              int   `description:"是否有子级别,0:下面没有子分类,1:下面有子分类;默认:0"`
 }
 
 type ClassifyItem struct {
@@ -201,6 +211,7 @@ type ClassifyItem struct {
 	ClassifyMenuId        int `description:"二级分类-子目录ID"`
 	ClassifyMenuList      []*ClassifyMenu
 	ChartPermissionIdList []int `description:"绑定的权限ID"`
+	Child                 []*ClassifyItem
 }
 
 type ClassifyListResp struct {
@@ -213,14 +224,9 @@ type ClassifyPermissionListResp struct {
 }
 
 // 获取分类列表
-func GetClassifyList(keyWord, companyType string, hideDayWeek, enabled int) (items []*ClassifyList, err error) {
+func GetClassifyList(keyWord string, enabled int) (items []*ClassifyList, err error) {
 	sql := ``
 	companyTypeSqlStr := ``
-	if companyType == "ficc" {
-		companyTypeSqlStr = " AND id != 40 AND parent_id != 40 "
-	} else if companyType == "权益" {
-		companyTypeSqlStr = " AND (id = 40 or parent_id = 40)  "
-	}
 	if enabled == 1 {
 		companyTypeSqlStr += ` AND enabled = 1 `
 	}
@@ -238,9 +244,6 @@ func GetClassifyList(keyWord, companyType string, hideDayWeek, enabled int) (ite
 		pars = utils.GetLikeKeywordPars(pars, keyWord, 2)
 	} else {
 		sql = `SELECT * FROM classify WHERE parent_id=0 ` + companyTypeSqlStr
-		if hideDayWeek == 1 {
-			sql += ` AND classify_name <> '晨报' AND classify_name <> '周报' `
-		}
 
 		sql += ` ORDER BY sort ASC, create_time ASC`
 	}
@@ -488,3 +491,84 @@ func (classifyInfo *Classify) SetEnabled(id, enabled int) (err error) {
 	}
 	return
 }
+
+// GetCountClassifyChildByParentId
+// @Description: 获取父级分类下子分类数量
+// @author: Roc
+// @datetime 2024-06-17 10:58:46
+// @param parentId int
+// @return total int
+// @return err error
+func GetCountClassifyChildByParentId(parentId int) (total int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT count(1) AS total FROM classify WHERE parent_id = ? `
+	err = o.Raw(sql, parentId).QueryRow(&total)
+
+	return
+}
+
+// GetClassifyListByKeyword
+// @Description: 获取分类列表
+// @author: Roc
+// @datetime 2024-06-19 09:49:33
+// @param keyWord string
+// @param enabled int
+// @return items []*ClassifyList
+// @return err error
+func GetClassifyListByKeyword(keyWord string, enabled int) (items []*ClassifyList, err error) {
+	sql := ``
+	pars := make([]interface{}, 0)
+
+	sql = `SELECT * FROM classify WHERE 1=1 `
+	if enabled == 1 {
+		sql += ` AND enabled = 1 `
+	}
+
+	if keyWord != `` {
+		sql += ` AND classify_name LIKE ? `
+		pars = utils.GetLikeKeywordPars(pars, keyWord, 1)
+	}
+	sql += ` ORDER BY sort ASC, create_time ASC`
+
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+// GetClassifyListByParentIdList
+// @Description: 获取分类列表
+// @author: Roc
+// @datetime 2024-06-19 09:49:33
+// @param keyWord string
+// @param enabled int
+// @return items []*ClassifyList
+// @return err error
+func GetClassifyListByParentIdList(parentClassifyIdList []int) (items []*ClassifyList, err error) {
+	num := len(parentClassifyIdList)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT * FROM classify WHERE id in (` + utils.GetOrmInReplace(num) + `) ORDER BY sort ASC, create_time ASC`
+
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, parentClassifyIdList).QueryRows(&items)
+	return
+}
+
+// GetClassifyListByIdList
+// @Description: 根据指标ID列表,获取分类列表
+// @author: Roc
+// @datetime 2024-06-27 15:23:57
+// @param classifyIdList []int
+// @return items []*Classify
+// @return err error
+func GetClassifyListByIdList(classifyIdList []int) (items []*Classify, err error) {
+	num := len(classifyIdList)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT * FROM classify WHERE id IN (` + utils.GetOrmInReplace(num) + `) `
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Raw(sql, classifyIdList).QueryRows(&items)
+	return
+}

+ 46 - 0
models/company/company_config.go

@@ -1,6 +1,9 @@
 package company
 
 import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 )
 
@@ -41,3 +44,46 @@ type ConfEnAuthRole struct {
 	RoleName string `description:"角色名称"`
 	SyncCrm  bool   `description:"是否同步CRM"`
 }
+
+// ConfigClassifyId
+// @Description: 后台配置的报告id
+type ConfigClassifyId struct {
+	Debug   int `json:"debug"`
+	Release int `json:"release"`
+}
+
+// GetReportClassifyIdByConfigKey
+// @Description: 获取关联的报告id
+// @author: Roc
+// @datetime 2024-06-18 14:10:27
+// @param configKey string
+// @return classifyId int
+// @return err error
+func GetReportClassifyIdByConfigKey(configKey string) (classifyId int, err error) {
+	// 别问为啥要从配置里拿=_=!
+	conf, e := GetConfigDetailByCode(configKey)
+	if e != nil {
+		err = errors.New("获取配置的id失败, Err: " + e.Error())
+		return
+	}
+	if conf.ConfigValue == "" {
+		err = errors.New("ID配置有误")
+		return
+	}
+	type TwoWeekIdConf struct {
+		Debug   []int
+		Release []int
+	}
+	classifyIdConf := new(ConfigClassifyId)
+	if e = json.Unmarshal([]byte(conf.ConfigValue), &classifyIdConf); e != nil {
+		err = errors.New("解析ID配置失败, Err: " + e.Error())
+		return
+	}
+	if utils.RunMode == "debug" {
+		classifyId = classifyIdConf.Debug
+	} else {
+		classifyId = classifyIdConf.Release
+	}
+
+	return
+}

+ 231 - 0
models/data_manage/base_from_business_data.go

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

+ 3 - 2
models/data_manage/base_from_trade_index.go

@@ -256,6 +256,7 @@ type BaseFromCoalmineFirmIndex struct {
 	IndexName                   string // 省份/企业名称
 	IndexCode                   string // 持买单量指标编码
 	DataTime                    string // 指标时间
+	DataTimeDate                string // 指标时间
 	DealValue                   string // 数据量
 	GroupName                   string // 集团名
 	Source                      string // 来源
@@ -477,7 +478,7 @@ func GetClassifyFirmByGroupName(groupName string) (items []*string, err error) {
 // 查询指标
 func GetPageFromCoalmineFirmIndexByFrequency(frequency, classify string, startSize, pageSize int) (items []*BaseFromCoalmineFirmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
-	sql := `SELECT * FROM base_from_coalmine_firm_index WHERE frequency=? AND index_code=? ORDER BY data_time DESC LIMIT ?,?  `
+	sql := `SELECT * FROM base_from_coalmine_firm_index WHERE frequency=? AND index_code=? ORDER BY data_time_date DESC LIMIT ?,?  `
 	_, err = o.Raw(sql, frequency, classify, startSize, pageSize).QueryRows(&items)
 	return
 }
@@ -698,6 +699,6 @@ type BaseFromCoalmineClassify struct {
 func GetCoalmineClassifyList() (list []*BaseFromCoalmineClassify, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := "SELECT * FROM base_from_coalmine_classify"
-	_,err = o.Raw(sql).QueryRows(&list)
+	_, err = o.Raw(sql).QueryRows(&list)
 	return
 }

+ 47 - 0
models/data_manage/chart_classify.go

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

+ 25 - 0
models/data_manage/chart_edb_mapping.go

@@ -164,6 +164,20 @@ func GetEtaEdbChartEdbMapping(chartInfoId int) (item *ChartEdbInfoMapping, err e
 	return
 }
 
+// GetEtaEdbChartEdbMappingList       商品曲线图查询对应的普通指标
+func GetEtaEdbChartEdbMappingList(chartInfoId int) (items []*ChartEdbInfoMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	aField := `a.chart_edb_mapping_id,a.chart_info_id,a.edb_info_id,a.create_time,a.modify_time,a.unique_code,a.max_data,a.min_data,a.is_order,a.is_axis,a.edb_info_type,a.lead_value,a.lead_unit,a.chart_style,a.chart_color,a.predict_chart_color,a.chart_width,a.source as mapping_source`
+
+	sql := ` SELECT ` + aField + `,b.source_name,b.source,b.sub_source,b.edb_code,b.edb_name,b.edb_name_en,b.frequency,b.unit,b.unit_en,b.start_date,b.end_date,b.modify_time,b.latest_date,b.latest_value,b.unique_code,b.edb_info_type AS edb_info_category_type,b.classify_id,b.is_join_permission
+             FROM chart_edb_mapping AS a
+			 INNER JOIN edb_info AS b ON a.edb_info_id=b.edb_info_id
+			 WHERE a.chart_info_id=? AND a.source = ?
+             ORDER BY chart_edb_mapping_id ASC `
+	_, err = o.Raw(sql, chartInfoId, utils.CHART_SOURCE_DEFAULT).QueryRows(&items)
+	return
+}
+
 // GetFutureGoodEdbChartEdbMapping       商品曲线图查询对应的商品指标
 func GetFutureGoodEdbChartEdbMapping(chartInfoId int) (item *ChartEdbInfoMapping, err error) {
 	o := orm.NewOrmUsingDB("data")
@@ -326,3 +340,14 @@ func ModifyChartEdbMapping(chartInfoId int, edbInfoList []*EdbInfo) (err error)
 
 	return
 }
+
+func GetRelationEdbInfoListMappingByCondition(condition string, pars []interface{}) (item []*ChartEdbInfoMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT a.* FROM edb_info AS a 
+	JOIN edb_info_calculate_mapping AS b on a.edb_info_id = b.edb_info_id WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = o.Raw(sql, pars).QueryRows(&item)
+	return
+}

+ 516 - 79
models/data_manage/chart_info.go

@@ -58,6 +58,7 @@ type ChartInfo struct {
 	UnitEn            string `description:"英文单位名称"`
 	IsJoinPermission  int    `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	ForumChartInfoId  int    `description:"社区的图表ID"`
+	ChartAlias        string `description:"图表别名"`
 }
 
 type ChartInfoMore struct {
@@ -261,6 +262,60 @@ type EditChartInfoReq struct {
 	MarkersAreas         string                  `description:"标识区"`
 	Unit                 string                  `description:"中文单位名称"`
 	UnitEn               string                  `description:"英文单位名称"`
+	ChartAlias           string                  `description:"图表别名"`
+}
+
+type MarkersLine struct {
+	Axis             int             `json:"axis" description:"1左轴 2右轴 3横轴"`
+	AxisName         string          `json:"axisName" description:"轴的名称,例如'左轴'"`
+	MarkerType       string          `json:"markerType" description:"标识线或标识区"`
+	MarkLineType     int             `json:"markLineType" description:"1:固定 2:指标计算"`
+	Value            string          `json:"value" description:"连线指向的数值,例如'4000'"`
+	FromValue        string          `json:"fromValue" description:"连线的起始点,可以为空"`
+	ToValue          string          `json:"toValue" description:"连线的结束点,可以为空"`
+	LineWidth        int             `json:"lineWidth" description:"连线的宽度"`
+	DashStyle        string          `json:"dashStyle" description:"连线的虚线样式,例如'ShortDashDot'"`
+	Color            string          `json:"color" description:"连线的颜色"`
+	Text             string          `json:"text" description:"连线旁边显示的文本"`
+	TextPosition     string          `json:"textPosition" description:"文本的显示位置,例如'bottom'"`
+	TextColor        string          `json:"textColor" description:"文本颜色"`
+	TextFontSize     int             `json:"textFontSize" description:"文本的字号大小"`
+	IsShow           bool            `json:"isShow" description:"是否显示连线及文本"`
+	EdbType          int             `json:"edbType" description:"指标类型 0图中第一个指标 1其他指标 前端回显用"`
+	EdbInfoId        int             `json:"edbInfoId" description:"指标id"`
+	Calculation      int             `json:"calculation" description:"计算方式 1区间均值 2区间均值加N倍标准差 3区间个数分位 4区间数值分位"`
+	CalculationValue int             `json:"calculationValue" description:"计算方式对应的值 2就是几倍标准差 3就是分位值 4就是数值值·"`
+	TimeIntervalType int             `json:"timeInterval" description:"时间区间 0跟随图表 1自定义"`
+	StartDate        MarkersLineTime `json:"startTime" description:"开始时间"`
+	EndDate          MarkersLineTime `json:"endTime" description:"结束时间"`
+}
+
+type MarkersLineTime struct {
+	TimeType int               `json:"timeType" description:"时间类型 1固定 2动态 3至今(仅结束时间)"`
+	Date     string            `json:"date" description:"日期"`
+	Conf     EdbDateChangeConf `json:"conf" description:"动态时间配置"`
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `description:"前移的期数"`
+	BaseDate    int `description:"基准日期 0系统日期 1指标最新日期"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+type EdbDateConfDateChange struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
+type EditFutureGoodChartInfoReq struct {
+	EditChartInfoReq
+	BarChartInfo FutureGoodBarChartInfoReq
 }
 
 type EditChartEnInfoReq struct {
@@ -268,6 +323,7 @@ type EditChartEnInfoReq struct {
 	ChartNameEn      string                    `description:"英文图表名称"`
 	ChartEdbInfoList []*EditChartEnInfoEdbItem `description:"指标及配置信息"`
 	ExtraConfig      string                    `description:"图表额外配置信息,json字符串"`
+	ChartAlias       string                    `description:"图表别名"`
 }
 type EditChartEnInfoEdbItem struct {
 	EdbInfoId int    `description:"指标ID"`
@@ -282,6 +338,7 @@ type EditChartInfoBaseReq struct {
 	ChartName        string                      `description:"图表名称(根据语言版本区分)"`
 	ChartEdbInfoList []*EditChartInfoEdbBaseItem `description:"指标及配置信息"`
 	ExtraConfig      string                      `description:"图表额外配置信息,json字符串"`
+	ChartAlias       string                      `description:"图表别名"`
 }
 type EditChartInfoEdbBaseItem struct {
 	EdbInfoId int    `description:"指标ID"`
@@ -322,6 +379,7 @@ type ChartInfoList struct {
 	CreateTime           time.Time
 	ModifyTime           time.Time
 	EdbInfoList          []*EdbInfoList
+	ChartAlias           string `description:"图表别名"`
 }
 
 type ChartInfoListResp struct {
@@ -395,7 +453,7 @@ type EdbDataList struct {
 // @return err error
 func GetEdbDataList(source, subSource, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getEdbDataListByMongo(source, subSource, edbInfoId, startDate, endDate)
 	}
 
@@ -518,7 +576,7 @@ func GetEdbDataLunarList(endInfoId int, startDate, endDate string) (list []*EdbD
 // @return err error
 func GetEdbDataListMinAndMax(source, subSource, edbInfoId int, startDate, endDate string) (min_data, max_data float64, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getEdbDataListMinAndMaxByMongo(source, subSource, edbInfoId, startDate, endDate)
 	}
 
@@ -700,7 +758,20 @@ type ChartInfoDetailResp struct {
 	BarChartInfo         BarChartInfoReq  `description:"柱方图的配置"`
 	CorrelationChartInfo *CorrelationInfo `description:"相关性图表信息"`
 	DataResp             interface{}      `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
+	ClassifyLevels       []string         `description:"图表分类的UniqueCode-从最顶级到当前分类(给前端回显定位用的=_=!)"`
 }
+
+type FutureGoodChartInfoDetailResp struct {
+	ChartInfo            *ChartInfoView
+	EdbInfoList          []*ChartEdbInfoMapping
+	XEdbIdValue          []int                     `description:"柱方图的x轴数据,指标id"`
+	YDataList            []YData                   `description:"柱方图的y轴数据"`
+	XDataList            []XData                   `description:"商品价格曲线的X轴数据"`
+	BarChartInfo         FutureGoodBarChartInfoReq `description:"柱方图的配置"`
+	CorrelationChartInfo *CorrelationInfo          `description:"相关性图表信息"`
+	DataResp             interface{}               `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
+}
+
 type BalanceTableChartListResp struct {
 	List []*BalanceChartInfoDetailResp
 }
@@ -721,6 +792,7 @@ type ExcelChartEdbView struct {
 type XData struct {
 	Name   string `description:"别名"`
 	NameEn string `description:"英文别名"`
+	IsHide int    `description:"是否隐藏,0不隐藏,1隐藏"`
 }
 
 // YData 柱方图的y轴数据
@@ -739,6 +811,10 @@ type YData struct {
 	M              []int           `description:"对应开始日期的间隔值" json:"-"`
 	Unit           string          `description:"中文单位名称"`
 	UnitEn         string          `description:"英文单位名称"`
+	SeriesEdb      struct {
+		SeriesId  int `description:"因子指标系列ID"`
+		EdbInfoId int `description:"指标ID"`
+	} `description:"对应的系列指标"`
 }
 
 func ModifyChartInfoAndMapping(edbInfoIdStr string, req *SaveChartInfoReq, chartType int) (err error) {
@@ -981,6 +1057,9 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 	sql += `,min_max_save = ? `
 	pars = append(pars, req.MinMaxSave)
 
+	sql += `,chart_alias = ? `
+	pars = append(pars, req.ChartAlias)
+
 	sql += `WHERE chart_info_id = ?`
 
 	pars = append(pars, req.ChartInfoId)
@@ -991,90 +1070,120 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 	}
 	chartEdbMappingIdList := make([]string, 0)
 
-	for _, v := range chartEdbInfoList {
-		// 查询该指标是否存在,如果存在的话,那么就去修改,否则新增
-		var tmpChartEdbMapping *ChartEdbMapping
-		csql := `SELECT *  FROM chart_edb_mapping WHERE chart_info_id=? AND edb_info_id=? AND source = ? `
-		err = to.Raw(csql, req.ChartInfoId, v.EdbInfoId, utils.CHART_SOURCE_DEFAULT).QueryRow(&tmpChartEdbMapping)
-		if err != nil && err.Error() != utils.ErrNoRow() {
-			fmt.Println("QueryRow Err:", err.Error())
-			return err
-		}
-		if tmpChartEdbMapping != nil {
-			chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(tmpChartEdbMapping.ChartEdbMappingId))
-			tmpChartEdbMapping.ModifyTime = time.Now()
-			tmpChartEdbMapping.MaxData = v.MaxData
-			tmpChartEdbMapping.MinData = v.MinData
-			tmpChartEdbMapping.IsOrder = v.IsOrder
-			tmpChartEdbMapping.IsAxis = v.IsAxis
-			tmpChartEdbMapping.EdbInfoType = v.EdbInfoType
-			tmpChartEdbMapping.LeadValue = v.LeadValue
-			tmpChartEdbMapping.LeadUnit = v.LeadUnit
-			tmpChartEdbMapping.ChartStyle = v.ChartStyle
-			tmpChartEdbMapping.ChartColor = v.ChartColor
-			tmpChartEdbMapping.PredictChartColor = v.PredictChartColor
-			tmpChartEdbMapping.ChartWidth = v.ChartWidth
-			tmpChartEdbMapping.EdbAliasName = v.EdbAliasName
-			tmpChartEdbMapping.IsConvert = v.IsConvert
-			tmpChartEdbMapping.ConvertType = v.ConvertType
-			tmpChartEdbMapping.ConvertValue = v.ConvertValue
-			tmpChartEdbMapping.ConvertUnit = v.ConvertUnit
-			tmpChartEdbMapping.ConvertEnUnit = v.ConvertEnUnit
-			_, err = to.Update(tmpChartEdbMapping, "ModifyTime", "MaxData", "MinData", "IsOrder", "IsAxis", "EdbInfoType", "LeadValue", "LeadUnit", "ChartStyle", "ChartColor", "PredictChartColor", "ChartWidth", "EdbAliasName",
-				"IsConvert", "ConvertType", "ConvertValue", "ConvertUnit", "ConvertEnUnit")
-			if err != nil {
-				fmt.Println("chart_edb_mapping Err:" + err.Error())
-				return err
-			}
-		} else {
-			mapItem := new(ChartEdbMapping)
-			mapItem.ChartInfoId = req.ChartInfoId
-			mapItem.EdbInfoId = v.EdbInfoId
-			mapItem.CreateTime = time.Now()
-			mapItem.ModifyTime = time.Now()
-			timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
-			mapItem.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp + "_" + strconv.Itoa(v.EdbInfoId))
-			mapItem.MaxData = v.MaxData
-			mapItem.MinData = v.MinData
-			mapItem.IsOrder = v.IsOrder
-			mapItem.IsAxis = v.IsAxis
-			mapItem.EdbInfoType = v.EdbInfoType
-			mapItem.LeadValue = v.LeadValue
-			mapItem.LeadUnit = v.LeadUnit
-			mapItem.ChartStyle = v.ChartStyle
-			mapItem.ChartColor = v.ChartColor
-			mapItem.PredictChartColor = v.PredictChartColor
-			mapItem.ChartWidth = v.ChartWidth
-			mapItem.Source = utils.CHART_SOURCE_DEFAULT
-			mapItem.EdbAliasName = v.EdbAliasName
-			mapItem.IsConvert = v.IsConvert
-			mapItem.ConvertType = v.ConvertType
-			mapItem.ConvertValue = v.ConvertValue
-			mapItem.ConvertUnit = v.ConvertUnit
-			mapItem.ConvertEnUnit = v.ConvertEnUnit
-			tmpId, err := to.Insert(mapItem)
-			if err != nil {
-				fmt.Println("AddChartEdbMapping Err:" + err.Error())
-				return err
+	// 获取已经配置的关联指标列表
+	var tmpChartEdbMappingList []*ChartEdbMapping
+	csql := `SELECT *  FROM chart_edb_mapping WHERE chart_info_id=? AND source = ? ORDER BY chart_edb_mapping_id ASC`
+	_, err = to.Raw(csql, req.ChartInfoId, utils.CHART_SOURCE_DEFAULT).QueryRows(&tmpChartEdbMappingList)
+	if err != nil {
+		fmt.Println("获取已经配置的关联指标列表 Err:", err.Error())
+		return err
+	}
+
+	// 已经配置的关联指标列表长度下标
+	tmpEdbIndex := len(tmpChartEdbMappingList) - 1
+	// 当下要配置的关联指标列表长度下标
+	reqEdbIndex := len(chartEdbInfoList) - 1
+
+	addChartEdbList := make([]*ChartEdbMapping, 0)
+	removeIdList := make([]int, 0)
+	for k, v := range chartEdbInfoList {
+		// 如果当前下标小于等于已经配置的关联指标的最大长度,那么就校验
+		if k <= tmpEdbIndex {
+			tmpChartEdbMapping := tmpChartEdbMappingList[k]
+
+			// 顺序未变
+			if tmpChartEdbMapping.EdbInfoId == v.EdbInfoId {
+				chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(tmpChartEdbMapping.ChartEdbMappingId))
+				tmpChartEdbMapping.ModifyTime = time.Now()
+				tmpChartEdbMapping.MaxData = v.MaxData
+				tmpChartEdbMapping.MinData = v.MinData
+				tmpChartEdbMapping.IsOrder = v.IsOrder
+				tmpChartEdbMapping.IsAxis = v.IsAxis
+				tmpChartEdbMapping.EdbInfoType = v.EdbInfoType
+				tmpChartEdbMapping.LeadValue = v.LeadValue
+				tmpChartEdbMapping.LeadUnit = v.LeadUnit
+				tmpChartEdbMapping.ChartStyle = v.ChartStyle
+				tmpChartEdbMapping.ChartColor = v.ChartColor
+				tmpChartEdbMapping.PredictChartColor = v.PredictChartColor
+				tmpChartEdbMapping.ChartWidth = v.ChartWidth
+				tmpChartEdbMapping.EdbAliasName = v.EdbAliasName
+				tmpChartEdbMapping.IsConvert = v.IsConvert
+				tmpChartEdbMapping.ConvertType = v.ConvertType
+				tmpChartEdbMapping.ConvertValue = v.ConvertValue
+				tmpChartEdbMapping.ConvertUnit = v.ConvertUnit
+				tmpChartEdbMapping.ConvertEnUnit = v.ConvertEnUnit
+				_, err = to.Update(tmpChartEdbMapping, "ModifyTime", "MaxData", "MinData", "IsOrder", "IsAxis", "EdbInfoType", "LeadValue", "LeadUnit", "ChartStyle", "ChartColor", "PredictChartColor", "ChartWidth", "EdbAliasName",
+					"IsConvert", "ConvertType", "ConvertValue", "ConvertUnit", "ConvertEnUnit")
+				if err != nil {
+					fmt.Println("chart_edb_mapping Err:" + err.Error())
+					return err
+				}
+				continue
+			} else {
+				// 如果是指标的顺序变了,那么需要删除该配置,进行新的配置
+				removeIdList = append(removeIdList, tmpChartEdbMapping.ChartEdbMappingId)
 			}
-			mapItem.ChartEdbMappingId = int(tmpId)
-			chartEdbMappingIdList = append(chartEdbMappingIdList, strconv.Itoa(mapItem.ChartEdbMappingId))
+		}
+
+		// 如果是指标的顺序变了 或者
+		// 如果当前下标是超过了已经配置的关联指标的最大长度,那么就新增
+		mapItem := new(ChartEdbMapping)
+		mapItem.ChartInfoId = req.ChartInfoId
+		mapItem.EdbInfoId = v.EdbInfoId
+		mapItem.CreateTime = time.Now()
+		mapItem.ModifyTime = time.Now()
+		timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+		mapItem.UniqueCode = utils.MD5(utils.CHART_PREFIX + "_" + timestamp + "_" + strconv.Itoa(v.EdbInfoId))
+		mapItem.MaxData = v.MaxData
+		mapItem.MinData = v.MinData
+		mapItem.IsOrder = v.IsOrder
+		mapItem.IsAxis = v.IsAxis
+		mapItem.EdbInfoType = v.EdbInfoType
+		mapItem.LeadValue = v.LeadValue
+		mapItem.LeadUnit = v.LeadUnit
+		mapItem.ChartStyle = v.ChartStyle
+		mapItem.ChartColor = v.ChartColor
+		mapItem.PredictChartColor = v.PredictChartColor
+		mapItem.ChartWidth = v.ChartWidth
+		mapItem.Source = utils.CHART_SOURCE_DEFAULT
+		mapItem.EdbAliasName = v.EdbAliasName
+		mapItem.IsConvert = v.IsConvert
+		mapItem.ConvertType = v.ConvertType
+		mapItem.ConvertValue = v.ConvertValue
+		mapItem.ConvertUnit = v.ConvertUnit
+		mapItem.ConvertEnUnit = v.ConvertEnUnit
+
+		addChartEdbList = append(addChartEdbList, mapItem)
+	}
+
+	// 当前保存的指标数与已有的指标数不一致,需要删除已有的指标关联配置
+	if reqEdbIndex < tmpEdbIndex {
+		for i := reqEdbIndex + 1; i <= tmpEdbIndex; i++ {
+			removeIdList = append(removeIdList, tmpChartEdbMappingList[i].ChartEdbMappingId)
 		}
 	}
-	if len(chartEdbMappingIdList) > 0 {
-		chartEdbMappingIdStr := strings.Join(chartEdbMappingIdList, ",")
-		dsql := `DELETE FROM chart_edb_mapping WHERE chart_info_id=? AND chart_edb_mapping_id NOT IN(` + chartEdbMappingIdStr + `)`
-		_, err = to.Raw(dsql, req.ChartInfoId).Exec()
+
+	// 删除已经移除了的指标关联配置
+	removeNum := len(removeIdList)
+	if removeNum > 0 {
+		dsql := `DELETE FROM chart_edb_mapping WHERE chart_info_id=? AND chart_edb_mapping_id  IN(` + utils.GetOrmInReplace(removeNum) + `)`
+		_, err = to.Raw(dsql, req.ChartInfoId, removeIdList).Exec()
 		if err != nil {
 			fmt.Println("delete err:" + err.Error())
 			return err
 		}
 	}
+
+	// 将新的指标关联配置写入
+	if len(addChartEdbList) > 0 {
+		_, err = to.InsertMulti(500, addChartEdbList)
+	}
+
 	return
 }
 
 // EditFutureGoodChartInfoAndMapping 修改商品价格曲线的 图表与指标 的关系
-func EditFutureGoodChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calendar string, dateType, disabled int, barChartConf string) (err error) {
+func EditFutureGoodChartInfoAndMapping(req *EditFutureGoodChartInfoReq, edbInfoIdStr string, calendar string, dateType, disabled int, barChartConf string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	to, err := o.Begin()
 	if err != nil {
@@ -1250,6 +1359,9 @@ func EditChartEnInfoAndEdbEnInfo(req *EditChartEnInfoReq) (err error) {
 		pars = append(pars, req.ExtraConfig)
 	}
 
+	updateField += ` chart_alias =?, `
+	pars = append(pars, req.ChartAlias)
+
 	sql := ` UPDATE  chart_info
 			SET ` + updateField + ` modify_time = NOW()
 			WHERE chart_info_id = ?`
@@ -1323,6 +1435,7 @@ func EditChartBaseInfoAndEdbEnInfo(req *EditChartInfoBaseReq, chartItem *ChartIn
 		chartItem.ExtraConfig = req.ExtraConfig
 		updateChartCols = append(updateChartCols, "ExtraConfig")
 	}
+
 	chartItem.ModifyTime = time.Now()
 	updateChartCols = append(updateChartCols, "ModifyTime")
 	_, err = to.Update(chartItem, updateChartCols...)
@@ -1405,6 +1518,12 @@ type AddChartInfoReq struct {
 	MarkersAreas         string                  `description:"标识区"`
 	Unit                 string                  `description:"中文单位名称"`
 	UnitEn               string                  `description:"英文单位名称"`
+	ChartAlias           string                  `description:"图表别名"`
+}
+
+type AddFutureGoodChartInfoReq struct {
+	AddChartInfoReq
+	BarChartInfo FutureGoodBarChartInfoReq
 }
 
 type PreviewChartInfoReq struct {
@@ -1417,23 +1536,134 @@ type PreviewChartInfoReq struct {
 	SeasonExtraConfig SeasonExtraItem  `description:"季节性图表中的配置,json数据"`
 	StartYear         int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
 	ChartSource       int
+	ExtraConfig       string `description:"图表额外配置信息,json字符串"`
 }
 
 type SeasonExtraItem struct {
-	ChartLegend []SeasonChartLegend `description:"自定义的图例名称"`
-	XStartDate  string              `description:"横坐标显示的起始日"`
-	XEndDate    string              `description:"横坐标显示的截止日"`
-	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	ChartLegend                 []SeasonChartLegend         `description:"自定义的图例名称"`
+	XStartDate                  string                      `description:"横坐标显示的起始日"`
+	XEndDate                    string                      `description:"横坐标显示的截止日"`
+	JumpYear                    int                         `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
 }
 
 type SeasonChartLegend struct {
 	Name  string
 	Value string
 }
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int    `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string `description:"生成样式"`
+	Shape         string `description:"形状"`
+	ChartColor    string `description:"图表颜色"`
+	Size          int    `description:"大小"`
+	Legend        string `description:"图例名称"`
+	NumFormat     int    `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int    `description:"是否连接 0不连接 1连接"`
+	LineColor     string `description:"线条颜色"`
+	LineWidth     int    `description:"线条宽度"`
+	LineStyle     string `description:"线条样式"`
+	IsShow        bool   `description:"是否显示"`
+	IsAdd         bool   `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string `description:"颜色"`
+	Year      int    `description:"均线取值范围"`
+	Legend    string `description:"图例名称"`
+	LineType  string `description:"线型"`
+	LineWidth int    `description:"线宽"`
+	IsShow    bool   `description:"是否显示"`
+	IsAdd     bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth int                      `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*ChartEdbInfoMapping
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
+}
+
 type AddChartInfoResp struct {
 	ChartInfoId int    `description:"图表id"`
 	UniqueCode  string `description:"图表唯一编码"`
 	ChartType   int    `description:"生成样式:1:曲线图,2:季节性图"`
+	ClassifyId  int    `description:"分类ID"`
 }
 
 type QuarterDataList []*QuarterData
@@ -1627,6 +1857,18 @@ type ChartInfoDetailFromUniqueCodeResp struct {
 	DataResp             interface{}      `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
 }
 
+type FutureChartInfoDetailFromUniqueCodeResp struct {
+	ChartInfo            *ChartInfoView
+	Status               bool `description:"true:图表存在,false:图表不存在"`
+	EdbInfoList          []*ChartEdbInfoMapping
+	XEdbIdValue          []int                     `description:"柱方图的x轴数据,指标id"`
+	YDataList            []YData                   `description:"柱方图的y轴数据"`
+	XDataList            []XData                   `description:"商品价格曲线的X轴数据"`
+	BarChartInfo         FutureGoodBarChartInfoReq `description:"柱方图的配置"`
+	CorrelationChartInfo *CorrelationInfo          `description:"相关性图表信息"`
+	DataResp             interface{}               `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
+}
+
 func GetChartInfoByUniqueCode(uniqueCode string) (item *ChartInfo, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM chart_info WHERE unique_code=? `
@@ -1731,6 +1973,7 @@ type ChartInfoView struct {
 	IsJoinPermission  int             `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 	HaveOperaAuth     bool            `description:"是否有数据权限,默认:false"`
 	ForumChartInfoId  int             `description:"社区的图表ID"`
+	ChartAlias        string          `description:"图表别名"`
 }
 
 type ChartViewButton struct {
@@ -1929,6 +2172,13 @@ type BarChartInfoReq struct {
 	UnitEn        string                   `description:"英文单位"`
 }
 
+type FutureGoodBarChartInfoReq struct {
+	EdbInfoIdList []BarChartInfoEdbItemReq `description:"指标信息"`
+	DateList      []BarChartInfoDateReq    `description:"日期配置"`
+	XDataList     []XData                  `description:"横轴配置"`
+	BaseEdbInfoId int                      `description:"日期基准指标id"`
+}
+
 // BarChartInfoEdbItemReq 柱方图预览请求数据(指标相关)
 type BarChartInfoEdbItemReq struct {
 	EdbInfoId     int     `description:"指标ID"`
@@ -1940,6 +2190,7 @@ type BarChartInfoEdbItemReq struct {
 	ConvertValue  float64 `description:"数据转换值"`
 	ConvertUnit   string  `description:"数据转换单位"`
 	ConvertEnUnit string  `description:"数据转换单位"`
+	IsHide        int     `description:"是否隐藏该项,0不隐藏,1隐藏"`
 }
 
 // BarChartInfoDateReq 柱方图预览请求数据(日期相关)
@@ -2000,6 +2251,8 @@ func EditCorrelationChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr stri
 	pars = append(pars, req.ChartClassifyId)
 	pars = append(pars, disabled)
 	pars = append(pars, barChartConf)
+	pars = append(pars, req.ExtraConfig)
+	pars = append(pars, req.SourcesFrom)
 
 	sql := ` UPDATE  chart_info
 			SET
@@ -2009,7 +2262,9 @@ func EditCorrelationChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr stri
 			  chart_classify_id = ?,
 			  modify_time = NOW(),
               disabled = ?,
-              bar_config = ?
+              bar_config = ?,
+			  extra_config = ?,
+			  sources_from = ?
 			`
 	if calendar != "" {
 		sql += `,calendar = ? `
@@ -2352,3 +2607,185 @@ type RadarYData struct {
 	Name  string    `description:"别名"`
 	Value []float64 `description:"每个指标的值"`
 }
+
+// ChartInfoSourcesFrom 图表来源
+type ChartInfoSourcesFrom struct {
+	IsShow   bool   `description:"是否展示" json:"isShow"`
+	Text     string `description:"来源文本" json:"text"`
+	Color    string `description:"来源颜色" json:"color"`
+	FontSize int    `description:"来源字号" json:"fontSize"`
+}
+
+// UpdateChartInfoSortByClassifyIdV2 根据分类id更新排序
+func UpdateChartInfoSortByClassifyIdV2(classifyId, nowSort int, prevChartInfoId int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update chart_info set sort = ` + updateSort + ` WHERE chart_classify_id = ?`
+	if prevChartInfoId > 0 {
+		sql += ` AND ( sort > ? or ( chart_info_id > ` + fmt.Sprint(prevChartInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	} else {
+		sql += ` AND ( sort > ? )`
+	}
+	_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// GetChartInfoMaxSortByClassifyId 获取分类下指标的最大的排序数
+func GetChartInfoMaxSortByClassifyId(classifyId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT Max(sort) AS sort FROM chart_info WHERE chart_classify_id = ? `
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+// GetChartInfoBySourceAndParentId 根据图表来源及父级ID获取图表
+func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT chart_info_id,chart_classify_id,chart_name AS chart_classify_name,chart_name_en AS chart_classify_name_en,
+             unique_code,sys_user_id,sys_user_real_name,date_type,start_date,end_date,chart_type,calendar,season_start_date,season_end_date,source
+            FROM chart_info WHERE source = ? AND chart_classify_id = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, source, parentId)
+	if adminId > 0 {
+		sql += ` AND sys_user_id = ?`
+		pars = append(pars, adminId)
+	}
+	sql += ` ORDER BY sort asc,chart_info_id ASC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// PreviewSeasonChartReq 预览季节性图的请求入参
+type PreviewSeasonChartReq struct {
+	ChartEdbInfoList  []*ChartSaveItem `description:"指标及配置信息"`
+	SeasonExtraConfig SeasonExtraItem  `description:"季节图额外配置信息,json字符串"`
+}
+
+// 截面组合图额外配置
+type ChartSectionExtraConf struct {
+	DateConfList        []*ChartSectionDateConfItem
+	IsHeap              int                      `description:"是否堆积(1.堆积,0不堆积)"`
+	XDataList           []XData                  `description:"横轴名称设置"`
+	UnitList            *ChartSectionCombineUnit `description:"纵轴单位设置"`
+	BaseChartSeriesName string                   `description:"基准系列名称"`
+	SortType            int                      `description:"排序类型,0默认,1升序,2降序"`
+}
+
+// 截面组合图额外配置
+type ChartSectionAllExtraConf struct {
+	DateConfList        []*ChartSectionDateConfItem
+	IsHeap              int                      `description:"是否堆积(1.堆积,0不堆积)"`
+	XDataList           []XData                  `description:"横轴名称设置"`
+	UnitList            *ChartSectionCombineUnit `description:"纵轴单位设置"`
+	BaseChartSeriesName string                   `description:"基准系列名称"`
+	SortType            int                      `description:"排序类型,0默认,1升序,2降序"`
+	SeriesList          []*ChartSectionSeriesItem
+}
+
+type ChartSectionDateConfItem struct {
+	MoveForward    int    `description:"前移的期数"`
+	EdbInfoId      int    `description:"指标ID"`
+	DateType       int    `description:"日期类型:0 指标日期,1系统日期"`
+	DateConfName   string `description:"引用日期名称"` // 引用日期名称不能重复
+	DateConfNameEn string `description:"引用日期英文名称"`
+	DateChange     []*ChartSectionDateChange
+}
+
+// 截面组合图引用日期配置
+type ChartSectionDateChange struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
+// 截面组合图系列配置
+type ChartSectionSeriesItem struct {
+	ChartSeriesId int     `description:"系列ID"`
+	SeriesName    string  `description:"系列名称"` //系列名称不可同名
+	SeriesNameEn  string  `description:"系列英文名称"`
+	ChartStyle    string  `description:"图表类型"`
+	ChartColor    string  `description:"颜色"`
+	ChartWidth    int     `description:"线条大小"`
+	IsPoint       int     `description:"是否用数据点展示(0 否,1是)"`
+	IsNumber      int     `description:"是否用数值展示(0 否,1是)"`
+	IsAxis        int     `description:"1:左轴,0:右轴"`
+	MaxData       float64 `description:"上限"`
+	MinData       float64 `description:"下限"`
+	//IsOrder         bool    `description:"true:正序,false:逆序"`
+	EdbInfoList     []*ChartSectionSeriesEdbConf
+	DataList        []float64
+	NoDataEdbIdList []int
+}
+type ChartSectionSeriesEdbConf struct {
+	ChartSeriesEdbMappingId int `description:"映射ID"`
+	ChartSeriesId           int `description:"系列ID"`
+	//ChartInfoId             int `description:"图表ID"`
+	EdbInfoId    int `description:"指标id"`
+	DateConf     *ChartSectionSeriesDateConfItem
+	EdbName      string `description:"中文别名"`
+	EdbNameEn    string `description:"英文别名"`
+	Unit         string `description:"单位"`
+	UnitEn       string `description:"英文单位"`
+	DateConfName string `description:"引用日期名称"`
+	DateConfType int    `description:"日期类型,0指标最新日期, 1引用日期"`
+}
+
+type ChartSectionCombineDataResp struct {
+	DateConfList        []*ChartSectionDateConfItem
+	IsHeap              int                      `description:"是否堆积(1.堆积,0不堆积)"`
+	XDataList           []XData                  `description:"横轴名称设置"`
+	UnitList            *ChartSectionCombineUnit `description:"纵轴单位设置"`
+	BaseChartSeriesName string                   `description:"基准系列名称"`
+	SortType            int                      `description:"排序类型,0默认,1升序,2降序"`
+	SeriesList          []*ChartSectionSeriesItem
+	LeftMin             string `description:"图表左侧最小值"`
+	LeftMax             string `description:"图表左侧最大值"`
+	RightMin            string `description:"图表右侧最小值"`
+	RightMax            string `description:"图表右侧最大值"`
+	Right2Min           string `description:"图表右侧最小值"`
+	Right2Max           string `description:"图表右侧最大值"`
+}
+
+// 系列里的指标日期配置
+type ChartSectionSeriesDateConfItem struct {
+	MoveForward int `description:"前移的期数"`
+	DateChange  []*ChartSectionDateChange
+}
+
+// PreviewSectionCombineChartReq 预览截面组合图的请求
+type PreviewSectionCombineChartReq struct {
+	ChartName       string `description:"图表名称"`
+	ChartClassifyId int    `description:"分类id"`
+	ExtraConfig     string `description:"图表额外配置信息,json字符串"`
+}
+
+type ChartSectionCombineUnit struct {
+	LeftName       string `description:"左轴单位"`
+	LeftNameEn     string `description:"左轴英文单位"`
+	RightName      string `description:"右轴单位"`
+	RightNameEn    string `description:"右轴英文单位"`
+	RightTwoName   string `description:"右2轴单位"`
+	RightTwoNameEn string `description:"右2轴英文单位"`
+}
+
+// EditChartInfoImageV2
+// @Description: 修改图表的缩略图
+// @author: Roc
+// @datetime 2024-07-09 10:52:34
+// @param chartInfoId int
+// @param imageUrl string
+// @return err error
+func EditChartInfoImageV2(chartInfoId int, imageUrl string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := ` UPDATE  chart_info SET chart_image=?, modify_time = NOW() WHERE chart_info_id = ? `
+	_, err = o.Raw(sql, imageUrl, chartInfoId).Exec()
+	if err != nil {
+		fmt.Println("EditChartInfoImageV2 Err:", err.Error())
+		return err
+	}
+
+	return
+}

+ 218 - 0
models/data_manage/chart_info_correlation.go

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

+ 251 - 0
models/data_manage/chart_series.go

@@ -0,0 +1,251 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type ChartSeries struct {
+	ChartSeriesId int       `orm:"column(chart_series_id);pk"`
+	SeriesName    string    `description:"系列名称"`
+	SeriesNameEn  string    `description:"系列英文名称"`
+	ChartInfoId   int       `description:"图表ID"`
+	ChartStyle    string    `description:"图表类型"`
+	ChartColor    string    `description:"颜色"`
+	ChartWidth    int       `description:"线条大小"`
+	IsPoint       int       `description:"是否用数据点展示(0 否,1是)"`
+	IsNumber      int       `description:"是否用数值展示(0 否,1是)"`
+	IsAxis        int       `description:"1:左轴,0:右轴"`
+	MaxData       float64   `description:"上限"`
+	MinData       float64   `description:"下限"`
+	IsOrder       bool      `description:"true:正序,false:逆序"`
+	CreateTime    time.Time `description:"创建时间"`
+	ModifyTime    time.Time `description:"修改时间"`
+}
+
+func (c *ChartSeries) TableName() string {
+	return "chart_series"
+}
+
+func GetChartSeriesByChartInfoId(chartInfoId int) (items []*ChartSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM chart_series WHERE chart_info_id = ?"
+	_, err = o.Raw(sql, chartInfoId).QueryRows(&items)
+	return
+}
+
+// EditChartSeriesAndEdbMapping
+func EditChartSeriesAndEdbMapping(extraConfigStr string, chartInfoId 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()
+		}
+	}()
+
+	// 获取已经存在的系列
+	series, err := GetChartSeriesByChartInfoId(chartInfoId)
+	if err != nil {
+		return
+	}
+	//整理成系列map
+	seriesMap := make(map[int]struct{})
+	seriesDeleteMap := make(map[int]struct{})
+	for _, v := range series {
+		seriesMap[v.ChartSeriesId] = struct{}{}
+		seriesDeleteMap[v.ChartSeriesId] = struct{}{}
+	}
+	var sectionExtraConfig ChartSectionAllExtraConf
+	err = json.Unmarshal([]byte(extraConfigStr), &sectionExtraConfig)
+	if err != nil {
+		err = fmt.Errorf("截面组合图配置异常")
+		return
+	}
+
+	// 删除所有的指标映射
+	_, err = o.Raw("DELETE FROM chart_series_edb_mapping WHERE chart_info_id = ?", chartInfoId).Exec()
+	if err != nil {
+		return
+	}
+
+	seriesList := sectionExtraConfig.SeriesList
+	for _, v := range seriesList {
+		seriesId := v.ChartSeriesId
+		tmp := &ChartSeries{
+			ChartSeriesId: v.ChartSeriesId,
+			SeriesName:    v.SeriesName,
+			SeriesNameEn:  v.SeriesNameEn,
+			ChartInfoId:   chartInfoId,
+			ChartStyle:    v.ChartStyle,
+			ChartColor:    v.ChartColor,
+			ChartWidth:    v.ChartWidth,
+			IsPoint:       v.IsPoint,
+			IsNumber:      v.IsNumber,
+			IsAxis:        v.IsAxis,
+			MaxData:       v.MaxData,
+			MinData:       v.MinData,
+			ModifyTime:    time.Now(),
+		}
+		if _, ok := seriesMap[v.ChartSeriesId]; !ok {
+			if seriesId > 0 {
+				err = fmt.Errorf("系列ID错误")
+				return
+			}
+			//新增
+			delete(seriesDeleteMap, v.ChartSeriesId)
+			tmp.CreateTime = time.Now()
+			seriesIdTmp, e := to.Insert(tmp)
+			if e != nil {
+				err = fmt.Errorf("AddChartSeries Err:" + e.Error())
+				return
+			}
+			seriesId = int(seriesIdTmp)
+		} else {
+			_, e := to.Update(tmp)
+			if e != nil {
+				err = fmt.Errorf("UpdateChartSeries Err:" + e.Error())
+				return
+			}
+		}
+
+		addSeriesEdbList := make([]*ChartSeriesEdbMapping, 0)
+		for _, edbItem := range v.EdbInfoList {
+			dateConfStrByte, e := json.Marshal(edbItem.DateConf)
+			if e != nil {
+				err = e
+				return
+			}
+			dateConfStr := string(dateConfStrByte)
+			edbTmp := &ChartSeriesEdbMapping{
+				ChartSeriesId: seriesId,    //todo 系列ID
+				ChartInfoId:   chartInfoId, //todo 表图ID
+				EdbInfoId:     edbItem.EdbInfoId,
+				//EdbAliasName:            "",
+				//EdbAliasNameEn:          "",
+				DateConfName: edbItem.DateConfName,
+				DateConf:     dateConfStr,
+				DateConfType: edbItem.DateConfType,
+				CreateTime:   time.Now(),
+				ModifyTime:   time.Now(),
+			}
+			addSeriesEdbList = append(addSeriesEdbList, edbTmp)
+		}
+		if len(addSeriesEdbList) > 0 {
+			_, err = to.InsertMulti(len(addSeriesEdbList), addSeriesEdbList)
+			if err != nil {
+				err = fmt.Errorf("AddChartSeries Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func AddChartSeriesAndEdbMapping(extraConfigStr string, chartInfoId 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()
+		}
+	}()
+
+	var sectionExtraConfig ChartSectionAllExtraConf
+	err = json.Unmarshal([]byte(extraConfigStr), &sectionExtraConfig)
+	if err != nil {
+		err = fmt.Errorf("截面组合图配置异常")
+		return
+	}
+	seriesList := sectionExtraConfig.SeriesList
+	for _, v := range seriesList {
+		tmp := &ChartSeries{
+			SeriesName:   v.SeriesName,
+			SeriesNameEn: v.SeriesNameEn,
+			ChartInfoId:  chartInfoId,
+			ChartStyle:   v.ChartStyle,
+			ChartColor:   v.ChartColor,
+			ChartWidth:   v.ChartWidth,
+			IsPoint:      v.IsPoint,
+			IsNumber:     v.IsNumber,
+			IsAxis:       v.IsAxis,
+			MaxData:      v.MaxData,
+			MinData:      v.MinData,
+			IsOrder:      false, //todo 是否排序
+			CreateTime:   time.Now(),
+			ModifyTime:   time.Now(),
+		}
+		seriesIdTmp, e := to.Insert(tmp)
+		if e != nil {
+			err = fmt.Errorf("AddChartSeries Err:" + e.Error())
+			return
+		}
+		seriesId := int(seriesIdTmp)
+		addSeriesEdbList := make([]*ChartSeriesEdbMapping, 0)
+		for _, edbItem := range v.EdbInfoList {
+			dateConfStrByte, e := json.Marshal(edbItem.DateConf)
+			if e != nil {
+				err = e
+				return
+			}
+			dateConfStr := string(dateConfStrByte)
+			edbTmp := &ChartSeriesEdbMapping{
+				ChartSeriesId: seriesId,    //todo 系列ID
+				ChartInfoId:   chartInfoId, //todo 表图ID
+				EdbInfoId:     edbItem.EdbInfoId,
+				//EdbAliasName:            "",
+				//EdbAliasNameEn:          "",
+				DateConfName: edbItem.DateConfName,
+				DateConfType: edbItem.DateConfType,
+				DateConf:     dateConfStr,
+				CreateTime:   time.Now(),
+				ModifyTime:   time.Now(),
+			}
+			addSeriesEdbList = append(addSeriesEdbList, edbTmp)
+		}
+		if len(addSeriesEdbList) > 0 {
+			_, e = to.InsertMulti(len(addSeriesEdbList), addSeriesEdbList)
+			if e != nil {
+				err = fmt.Errorf("AddChartSeries Err:" + e.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func DeleteChartSeriesAndEdbMapping(chartInfoId 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 chart_series WHERE chart_info_id=? `
+	_, err = to.Raw(sql, chartInfoId).Exec()
+	if err != nil {
+		return
+	}
+	sql = ` DELETE FROM  chart_series_edb_mapping WHERE chart_info_id=? `
+	_, err = to.Raw(sql, chartInfoId).Exec()
+	return
+}

+ 31 - 0
models/data_manage/chart_series_edb_mapping.go

@@ -0,0 +1,31 @@
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+type ChartSeriesEdbMapping struct {
+	ChartSeriesEdbMappingId int       `orm:"column(chart_series_edb_mapping_id);pk"`
+	ChartSeriesId           int       `description:"系列ID"`
+	ChartInfoId             int       `description:"图表ID"`
+	EdbInfoId               int       `description:"指标id"`
+	EdbAliasName            string    `description:"中文别名"`
+	EdbAliasNameEn          string    `description:"英文别名"`
+	DateConfName            string    `description:"引用日期配置名称"`
+	DateConfType            int       `description:"日期类型,0指标最新日期, 1引用日期"`
+	DateConf                string    `description:"日期配置名称"`
+	ModifyTime              time.Time `description:"修改时间"`
+	CreateTime              time.Time `description:"创建时间"`
+}
+
+func (c *ChartSeriesEdbMapping) TableName() string {
+	return "chart_series_edb_mapping"
+}
+
+func GetChartSeriesEdbByChartInfoId(chartInfoId int) (items []*ChartSeriesEdbMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM chart_series_edb_mapping WHERE chart_info_id = ?"
+	_, err = o.Raw(sql, chartInfoId).QueryRows(&items)
+	return
+}

+ 84 - 0
models/data_manage/chart_theme/request/theme.go

@@ -28,3 +28,87 @@ type SetDefaultThemeReq struct {
 	ChartThemeId     int `description:"主题id"`
 	ChartThemeTypeId int `description:"主题类型id"`
 }
+
+type ColorsOptions []string
+
+type LegendOptions struct {
+	VerticalAlign string `json:"verticalAlign"`
+	ItemStyle     struct {
+		Color        string `json:"color"`
+		FontSize     int    `json:"fontSize"`
+		Cursor       string `json:"cursor"`
+		FontWeight   string `json:"fontWeight"`
+		TextOverflow string `json:"textOverflow"`
+	} `json:"itemStyle"`
+}
+
+type TitleOptions struct {
+	Align string `json:"align"`
+	Style struct {
+		Color    string `json:"color"`
+		FontSize int    `json:"fontSize"`
+	} `json:"style"`
+}
+
+type MarkerOptions struct {
+	Style struct {
+		Color    string `json:"color"`
+		FontSize int    `json:"fontSize"`
+	} `json:"style"`
+}
+
+type AxisOptions struct {
+	Style struct {
+		Color    string `json:"color"`
+		FontSize int    `json:"fontSize"`
+	} `json:"style"`
+}
+
+type DrawOption struct {
+	PlotBackgroundColor string `json:"plotBackgroundColor"`
+}
+
+type LineOptions struct {
+	DashStyle string  `json:"dashStyle"`
+	LineWidth int     `json:"lineWidth"`
+	LineType  string  `json:"lineType"`
+	Radius    float64 `json:"radius"`
+}
+
+type OldChartOptions struct {
+	ColorsOptions  []string         `json:"colorsOptions"`
+	LineOptions    LineOptions      `json:"lineOptions"`
+	LegendOptions  interface{}      `json:"legendOptions"`
+	TitleOptions   interface{}      `json:"titleOptions"`
+	MarkerOptions  interface{}      `json:"markerOptions"`
+	XAxisOptions   interface{}      `json:"xAxisOptions"`
+	YAxisOptions   interface{}      `json:"yAxisOptions"`
+	DrawOption     interface{}      `json:"drawOption"`
+	LineOptionList []LineStyleOptions `json:"lineOptionList"`
+}
+
+type NewChartOptions struct {
+	OldChartOptions
+	LineOptionList []NewLineOptions `json:"lineOptionList"`
+}
+
+type NewLineOptions struct {
+	LineOptions
+	Color     string `json:"color"`
+	DataMark  string `json:"dataMark"`
+	MarkType  string `json:"markType"`
+	MarkSize  int    `json:"markSize"`
+	MarkColor string `json:"markColor"`
+}
+
+type LineStyleOptions struct {
+	DashStyle string `json:"dashStyle"`
+	Color     string `json:"color"`
+	LineWidth int    `json:"lineWidth"`
+	LineType  string `json:"lineType"`
+	Radius    int `json:"radius"`
+	DataMark  string `json:"dataMark"`
+	MarkType  string `json:"markType"`
+	MarkSize  int `json:"markSize"`
+	MarkColor string `json:"markColor"`
+}

+ 38 - 0
models/data_manage/chart_type.go

@@ -0,0 +1,38 @@
+package data_manage
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ChartType struct {
+	ChartTypeId     int    `orm:"column(chart_type_id);pk"`
+	ChartTypeName   string `description:"图表类型名称"`
+	ChartTypeNameEn string `description:"英文图表类型名称"`
+	ParentId        int    `description:"父级ID"`
+	Sort            int    `description:"排序"`
+	CreateTime      string `description:"创建时间"`
+}
+
+type ChartTypeList struct {
+	ChartTypeId     int
+	ChartTypeName   string `description:"图表类型名称"`
+	ChartTypeNameEn string `description:"英文图表类型名称"`
+	ParentId        int    `description:"父级ID"`
+	Child           []ChartType
+}
+
+type ChartTypeListResp struct {
+	List []ChartTypeList
+}
+
+func (c *ChartType) TableName() string {
+	return "chart_type"
+}
+
+// 查询所有图表类型
+func GetChartTypeList() (items []ChartType, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_type order by sort asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

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

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

+ 8 - 0
models/data_manage/edb_classify.go

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

+ 16 - 6
models/data_manage/edb_data_base.go

@@ -222,7 +222,7 @@ type EdbDataBase struct {
 
 func GetEdbDataAllByEdbCode(edbCode string, source, subSource, limit int) (items []*EdbInfoSearchData, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return GetEdbDataAllByEdbCodeByMongo(edbCode, source, subSource, limit)
 	}
 
@@ -331,13 +331,15 @@ func GetBaseIndexInfoByEdbCode(edbCode string, source int) (item *BaseIndexInfo,
 	return
 }
 
-func GetBaseIndexTableName(source int) (tableName string) {
-	edbSource := EdbSourceIdMap[source]
-	if edbSource != nil {
-		tableName = edbSource.IndexTableName
-	}
+func GetEdbDataBaseByCodeAndDate(source, subSource int, edbCode string, startDate string) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	tableName := GetEdbDataTableName(source, subSource)
+	sql := ` SELECT COUNT(1) AS count FROM %s WHERE edb_code=? AND data_time=? `
+	sql = fmt.Sprintf(sql, tableName)
+	err = o.Raw(sql, edbCode, startDate).QueryRow(&count)
 	return
 }
+
 func GetEdbDataAllByEdbCodeAndSubSource(edbCode string, source, subSource, limit int) (items []*EdbInfoSearchData, err error) {
 	var pars []interface{}
 	pars = append(pars, edbCode)
@@ -380,6 +382,14 @@ func GetEdbDataTableNameAndSubSource(source, subSource int) (tableName string) {
 	return
 }
 
+func GetBaseIndexTableName(source int) (tableName string) {
+	edbSource := EdbSourceIdMap[source]
+	if edbSource != nil {
+		tableName = edbSource.IndexTableName
+	}
+	return
+}
+
 func GetEdbDataAllByEdbCodes(edbCodes []string, limit int) (items []*EdbInfoSearchData, err error) {
 	var pars []interface{}
 	pars = append(pars, edbCodes)

+ 1 - 1
models/data_manage/edb_data_insert_config.go

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

+ 8 - 0
models/data_manage/edb_data_mysteel_chemical.go

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

+ 41 - 0
models/data_manage/edb_data_wind.go

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

+ 81 - 8
models/data_manage/edb_info.go

@@ -36,6 +36,7 @@ type EdbInfo struct {
 	UniqueCode       string `description:"指标唯一编码"`
 	CreateTime       time.Time
 	ModifyTime       time.Time
+	BaseModifyTime   time.Time
 	MinValue         float64 `description:"指标最小值"`
 	MaxValue         float64 `description:"指标最大值"`
 	CalculateFormula string  `description:"计算公式"`
@@ -52,8 +53,6 @@ type EdbInfo struct {
 	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:"本次更新,数据发生变化的最早日期"`
@@ -62,6 +61,8 @@ type EdbInfo struct {
 	SubSourceName    string  `description:"子数据来源名称"`
 	IndicatorCode    string  `description:"指标代码"`
 	StockCode        string  `description:"证券代码"`
+	EmptyType        int     `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
+	MaxEmptyType     int     `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`
 	Extra            string  `description:"指标额外配置"`
 	IsJoinPermission int     `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 }
@@ -251,7 +252,7 @@ func DeleteEdbInfoAndData(edbInfoId, source, subSource int) (err error) {
 	}
 
 	// 删除指标的明细数据
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		err = deleteAllEdbDataByMongo(to, edbInfoId, source, subSource)
 	} else {
 		err = deleteAllEdbDataByMysql(to, edbInfoId, source, subSource)
@@ -451,6 +452,11 @@ type EdbInfoListResp struct {
 	ClassifyList []*EdbClassifyIdItems
 }
 
+type WindEdbInfoList struct {
+	*EdbInfoList
+	ClassifyList []*EdbClassifyIdItems
+}
+
 func GetEdbInfoByCondition(condition string, pars []interface{}) (item *EdbInfoList, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM edb_info WHERE 1=1 `
@@ -611,7 +617,7 @@ type EdbInfoMaxAndMinInfo struct {
 // @return err error
 func GetEdbInfoMaxAndMinInfo(source, subSource int, edbCode string) (item *EdbInfoMaxAndMinInfo, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return GetEdbInfoMaxAndMinInfoByMongo(source, subSource, edbCode)
 	}
 
@@ -1136,11 +1142,11 @@ type EdbInfoView struct {
 	IsUpdate         int     `description:"当天是否已更新,1:未更新,2:已更新"`
 	LatestDate       string  `description:"数据最新日期(实际日期)"`
 	LatestValue      float64 `description:"数据最新值(实际值)"`
+	EndValue         float64 `description:"数据的最新值(预测日期的最新值)"`
 	SubSource        int     `description:"子数据来源:0:经济数据库,1:日期序列"`
 	SubSourceName    string  `description:"子数据来源名称"`
 	IndicatorCode    string  `description:"指标代码"`
 	StockCode        string  `description:"证券代码"`
-	EndValue         float64 `description:"数据的最新值(预测日期的最新值)"`
 }
 
 // 获取指标的所有计算指标,以及计算指标所依赖计算指标
@@ -1154,6 +1160,17 @@ func GetAllCalculateByEdbInfoId(edbInfoId int) (mappingList []*EdbInfoCalculateM
 	return
 }
 
+// 获取指标的所有计算指标,以及计算指标所依赖计算指标
+func GetAllCalculateByEdbInfoIds(edbInfoIds []int) (mappingList []*EdbInfoCalculateMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	msql := ` SELECT * FROM edb_info_calculate_mapping WHERE from_edb_info_id in (` + utils.GetOrmInReplace(len(edbInfoIds)) + `)  GROUP BY edb_info_id `
+	_, err = o.Raw(msql, edbInfoIds).QueryRows(&mappingList)
+	if err != nil {
+		return
+	}
+	return
+}
+
 func GetAllCalculate(edbInfoId int, edbInfoMap map[int]int) (mappingList []*EdbInfoCalculateMapping, err error) {
 	calculateList, err := GetAllCalculateByEdbInfoId(edbInfoId)
 	if err != nil && err.Error() != utils.ErrNoRow() {
@@ -1653,7 +1670,7 @@ func GetEdbBaseInfoList(condition string, pars []interface{}, orderBy string, st
 // @param indexCodeList []string
 // @param isStop int
 // @return err error
-func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int) (err error) {
+func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int, calculateEdbInfoIds []int) (err error) {
 	idNum := len(edbIdList)
 	if idNum <= 0 {
 		return
@@ -1676,7 +1693,14 @@ func ModifyEdbInfoUpdateStatus(edbIdList []int, isStop int) (err error) {
 	if err != nil {
 		return
 	}
-
+	if len(calculateEdbInfoIds) > 0 {
+		// 批量更新相关联的指标ID
+		sql = ` UPDATE edb_info SET no_update = ? WHERE edb_info_id IN (` + utils.GetOrmInReplace(len(calculateEdbInfoIds)) + `) `
+		_, err = o.Raw(sql, isStop, calculateEdbInfoIds).Exec()
+		if err != nil {
+			return
+		}
+	}
 	return
 }
 
@@ -1710,6 +1734,25 @@ func GetEdbInfoByCodeArr(codes []string, edbInfoType int) (items []*EdbInfo, err
 	return
 }
 
+type EdbInfoEditRecord struct {
+	EdbInfoId           int    `description:"指标ID"`
+	EdbName             string `description:"指标名称"`
+	Frequency           string `description:"频率"`
+	Unit                string `description:"单位"`
+	ClassifyId          int    `description:"分类id"`
+	CalculateFormula    string `description:"计算公式"`
+	OperateUserId       int    `description:"操作人id"`
+	OperateUserRealName string `description:"操作人姓名"`
+}
+
+func ModifyEdbInfoBaseTimeById(edbInfoId int, cTime time.Time) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	// 更新修改时间
+	sql := ` UPDATE edb_info SET base_modify_time = ? WHERE edb_info_id = ? `
+	_, err = o.Raw(sql, cTime, edbInfoId).Exec()
+	return
+}
+
 // GetEdbCodesBySource 根据来源获取指标编码
 func GetEdbCodesBySource(source int) (items []*EdbInfo, err error) {
 	o := orm.NewOrmUsingDB("data")
@@ -1730,7 +1773,7 @@ func GetEdbCodesBySource(source int) (items []*EdbInfo, err error) {
 // @return err error
 func GetAllEdbDataListData(edbInfoId, source, subSource int, startDataTime string) (dataList []*EdbData, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getAllDataByMongo(edbInfoId, source, subSource, startDataTime)
 	}
 
@@ -1821,3 +1864,33 @@ type ReplaceEdbInfoItem struct {
 	OldEdbInfo *EdbInfo
 	NewEdbInfo *EdbInfo
 }
+
+// GetEdbClassifyIdListBySource 获取普通指标的分类列表
+func GetEdbClassifyIdListBySource(source int) (items []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT distinct classify_id FROM edb_info WHERE source = ? and edb_info_type=0`
+	_, err = o.Raw(sql, source).QueryRows(&items)
+	return
+}
+
+// GetEdbInfoByClassifyIdAndSource 用于分类展示
+func GetEdbInfoByClassifyIdAndSource(classifyId, edbInfoType, source int) (items []*EdbClassifyItems, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT edb_info_id,classify_id,edb_name AS classify_name,edb_name_en AS classify_name_en,unique_code,source_name,source,sys_user_id,sys_user_real_name,start_date,edb_code,edb_type, sort,is_join_permission FROM edb_info WHERE classify_id = ? AND edb_info_type = ? AND source =?`
+
+	pars := []interface{}{classifyId, edbInfoType, source}
+	sql += ` order by sort asc,edb_info_id asc `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func GetEdbInfoListByCond(condition string, pars []interface{}) (list []*EdbInfoList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM edb_info WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY create_time DESC `
+	_, err = o.Raw(sql, pars).QueryRows(&list)
+	return
+}

+ 11 - 0
models/data_manage/edb_info_calculate.go

@@ -167,6 +167,7 @@ type EdbInfoBase struct {
 	UnitEn        string `description:"英文单位"`
 	ClassifyId    int    `description:"分类id"`
 	HaveOperaAuth bool   `description:"是否有数据权限,默认:false"`
+	EdbInfoType   int    `description:"指标类型: 0-普通指标; 1-预测指标"`
 }
 
 type CalculateEdbInfoItem struct {
@@ -254,6 +255,8 @@ type EdbInfoCalculateBatchEditReqByEdbLib struct {
 	Frequency     string `description:"频度"`
 	Unit          string `description:"单位"`
 	ClassifyId    int    `description:"分类id"`
+	AdminId       int    `description:"操作人id"`
+	AdminName     string `description:"操作人姓名"`
 	Formula       string `description:"N值"`
 	EdbInfoId     int    `description:"编辑指标id"`
 	FromEdbInfoId int    `description:"计算来源指标id"`
@@ -511,6 +514,7 @@ type CalculateMultiEdbSearchResp struct {
 	SearchItem []CalculateMultiEdbSearchItem `description:"查询结果"`
 	Paging     *paging.PagingItem
 }
+
 type CalculateMultiEdbSearchItem struct {
 	EdbInfoId       int    `description:"指标id"`
 	EdbName         string `description:"指标名称"`
@@ -524,4 +528,11 @@ type CalculateMultiEdbSearchItem struct {
 	EndValue        float64 `description:"数据的最新值(预测日期的最新值)"`
 	EndDate         string  `description:"终止日期"`
 	HaveOperaAuth   bool    `description:"是否有数据权限,默认:false"`
+	EdbInfoType     int     `description:"指标类型: 0-普通指标; 1-预测指标"`
+}
+
+// BaseStepCalculateReq 指标库-基础分步骤计算请求
+type BaseStepCalculateReq struct {
+	DataList   []*EdbInfoSearchData
+	Calculates []FactorEdbSeriesCalculatePars
 }

+ 11 - 0
models/data_manage/edb_info_calculate_mapping.go

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

+ 28 - 0
models/data_manage/edb_info_record.go

@@ -0,0 +1,28 @@
+package data_manage
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type EdbInfoRecord struct {
+	EdbInfoRecordId     int       `orm:"column(edb_info_record_id);pk"`
+	EdbInfoId           int       `orm:"column(edb_info_id)"`
+	OldEdbName          string    `description:"旧的指标名称"`
+	OldFrequency        string    `description:"旧的频率"`
+	OldUnit             string    `description:"旧的单位"`
+	NewEdbName          string    `description:"新的指标名称"`
+	NewFrequency        string    `description:"新的频率"`
+	NewUnit             string    `description:"新的单位"`
+	OperateUserId       int       `description:"执行人id"`
+	OperateUserRealName string    `description:"执行人名称"`
+	CreateTime          time.Time `description:"记录的生成时间"`
+	Timestamp           int64     `description:"时间戳"`
+}
+
+func AddEditEdbInfoRcord(edbRecord *EdbInfoRecord) (e error) {
+	o := orm.NewOrmUsingDB("data")
+	_, e = o.Insert(edbRecord)
+	return
+}

+ 518 - 0
models/data_manage/edb_info_relation.go

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

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

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

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

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

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

@@ -33,6 +33,7 @@ type ExcelInfo struct {
 	UpdateUserRealName string    `description:"更新人真实姓名"`
 	RelExcelInfoId     int       `description:"平衡表里静态表关联的动态表excel id"`
 	VersionName        string    `description:"静态表版本名称"`
+	SourcesFrom        string    `description:"图表来源"`
 }
 
 // Update 更新 excel表格基础信息
@@ -414,7 +415,6 @@ FROM excel_info WHERE 1=1 AND is_delete=0 `
 	_, err = o.Raw(sql, pars).QueryRows(&item)
 	return
 }
-
 func GetExcelListCountByCondition(condition string, pars []interface{}) (count int, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT COUNT(1) AS count FROM excel_info WHERE 1=1 AND is_delete=0 `

+ 12 - 0
models/data_manage/excel/request/excel_info.go

@@ -24,6 +24,7 @@ type AddExcelInfoReq struct {
 	Content         string      `description:"Excel表格内容"`
 	TableData       interface{} `description:"自定义表格的数据内容"`
 	ParentId        int         `description:"表格的父级id"`
+	SourcesFrom     string      `description:"图表来源"`
 }
 
 // EditExcelInfoReq 编辑表格请求
@@ -35,6 +36,7 @@ type EditExcelInfoReq struct {
 	ExcelClassifyId int         `description:"分类id"`
 	Content         string      `description:"Excel表格内容"`
 	TableData       interface{} `description:"自定义表格的数据内容"`
+	SourcesFrom     string      `description:"图表来源"`
 }
 
 // SetExcelInfoImageReq 设置excel表格图片请求
@@ -135,6 +137,16 @@ type CopyExcelInfoReq struct {
 	ExcelClassifyId int    `description:"分类id"`
 }
 
+// AddAndEditSandbox 添加/编辑沙盘的请求数据
+type AddAndEditSandbox struct {
+	ExcelInfoId       int    `description:"excel表格ID"`
+	Name              string `description:"沙盘名称"`
+	ChartPermissionId int    `description:"品种权限id"`
+	Content           string `description:"沙盘内容"`
+	PicUrl            string `description:"沙盘图片地址"`
+	SvgData           string `description:"沙盘svg图片数据"`
+}
+
 // MarkEditExcel 标记编辑表格的请求数据
 type MarkEditExcel struct {
 	ExcelInfoId int `description:"表格id"`

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

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

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

@@ -22,11 +22,14 @@ type ExcelListResp struct {
 
 // ExcelTableDetailResp  excel表格详情
 type ExcelTableDetailResp struct {
-	UniqueCode string `description:"表格唯一code"`
-	ExcelImage string `description:"表格截图"`
-	ExcelName  string `description:"表格名称"`
-	TableInfo  excel.TableData
-	Config     ExcelTableDetailConfigResp
+	UniqueCode    string `description:"表格唯一code"`
+	ExcelImage    string `description:"表格截图"`
+	ExcelName     string `description:"表格名称"`
+	TableInfo     excel.TableData
+	Config        ExcelTableDetailConfigResp
+	SourcesFrom   string `description:"图表来源"`
+	ExcelSource   string `description:"表格来源str"`
+	ExcelSourceEn string `description:"表格来源(英文)"`
 }
 
 // ExcelTableDetailConfigResp
@@ -44,8 +47,10 @@ type TableCellResp struct {
 }
 
 type TableDataItem struct {
-	EdbInfoId int                     `description:"指标id"`
-	Data      []request.ManualDataReq `description:"数据列表"`
+	EdbInfoId     int                     `description:"指标id"`
+	Data          []request.ManualDataReq `description:"数据列表"`
+	ExcelSource   string                  `description:"表格来源str"`
+	ExcelSourceEn string                  `description:"表格来源(英文)"`
 }
 
 // TableDetailResp  excel表格详情
@@ -82,6 +87,9 @@ type ExcelInfoDetail struct {
 	UpdateUserId       int                          `description:"更新人id"`
 	UpdateUserRealName string                       `description:"更新人真实姓名"`
 	RelExcelInfoId     int                          `description:"平衡表里静态表关联的动态表excel id"`
+	SourcesFrom        string                       `description:"图表来源"`
+	ExcelSource        string                       `description:"表格来源str"`
+	ExcelSourceEn      string                       `description:"表格来源(英文)"`
 }
 
 type BalanceChildTableResp struct {

+ 8 - 4
models/data_manage/excel_style.go

@@ -1,8 +1,9 @@
 package data_manage
 
 import (
-	"github.com/beego/beego/v2/client/orm"
 	"time"
+
+	"github.com/beego/beego/v2/client/orm"
 )
 
 type ExcelStyle struct {
@@ -30,8 +31,11 @@ type ExcelStyleReq struct {
 	CellData         []interface{}
 	Config           interface{}
 	Data             [][]interface{}
-	Frequency        string `description:"频度"`
+	TradeCode        string `description:"指标编码"`
+	Frequence        string `description:"频度"`
 	ClassifyId       int    `description:"分类id"`
+	OldClassifyId    int    `description:"历史分类id"`
+	OldFrequence     string `description:"历史频度"`
 }
 
 type CellData struct {
@@ -187,7 +191,7 @@ func GetExcelEdbdataMappingByTradeCode(classifyId int, frequency string) (excelI
 func GetExcelStyleById(excelId int) (item *ExcelStyle, err error) {
 	o := orm.NewOrmUsingDB("edb")
 	sql := `SELECT * FROM excel_style WHERE excel_id = ? `
-	err = o.Raw(sql,excelId).QueryRow(&item)
+	err = o.Raw(sql, excelId).QueryRow(&item)
 	return
 }
 
@@ -208,4 +212,4 @@ func GetExcelEdbdataMappingCount(classifyId int, tradeCode, frequency string) (c
 type Ct struct {
 	Fa string `json:"fa"`
 	T  string `json:"t"`
-}
+}

+ 351 - 0
models/data_manage/factor_edb_series.go

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

+ 190 - 0
models/data_manage/factor_edb_series_calculate_data.go

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

+ 153 - 0
models/data_manage/factor_edb_series_calculate_func.go

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

+ 211 - 0
models/data_manage/factor_edb_series_chart_mapping.go

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

+ 167 - 0
models/data_manage/factor_edb_series_mapping.go

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

+ 2 - 0
models/data_manage/future_good/request/future_good_chart.go

@@ -56,6 +56,8 @@ type ChartInfoReq struct {
 	BaseEdbInfoId           int                          `description:"基础的指标id"`
 	DateList                []ChartInfoDateReq           `description:"日期配置"`
 	ProfitNameEn            string                       `description:"利润英文名称"`
+	EdbInfoIdList           []int                        `description:"现货指标ID列表"`
+	XDataList               []data_manage.XData          `description:"横轴配置"`
 }
 
 // ChartInfoDateReq 图表的日期数据(日期相关)

+ 8 - 8
models/data_manage/my_chart.go

@@ -914,14 +914,6 @@ func GetChartInfoViewByIdList(chartInfoIdList []int) (items []*ChartInfoView, er
 	return
 }
 
-// GetMyChartClassifyByClassifyId 主键获取分类
-func GetMyChartClassifyByClassifyId(classifyId int) (item *MyChartClassify, err error) {
-	o := orm.NewOrmUsingDB("data")
-	sql := ` SELECT * FROM my_chart_classify WHERE my_chart_classify_id = ? `
-	err = o.Raw(sql, classifyId).QueryRow(&item)
-	return
-}
-
 // MyChartClassifyItem 我的图表分类信息
 type MyChartClassifyItem struct {
 	MyChartClassifyId   int    `description:"分类ID"`
@@ -971,3 +963,11 @@ func GetMyChartClassifyIdAndNum(cond string, pars []interface{}) (items []*MyCha
 	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
+
+// GetMyChartClassifyByClassifyId 主键获取分类
+func GetMyChartClassifyByClassifyId(classifyId int) (item *MyChartClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM my_chart_classify WHERE my_chart_classify_id = ? `
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}

+ 85 - 0
models/data_manage/mysteel_chemical_index.go

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

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

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

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

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

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно