Browse Source

Merge remote-tracking branch 'origin/master' into eta/2.0.0

Roc 8 months ago
parent
commit
0b7ac6cf49
100 changed files with 22053 additions and 2181 deletions
  1. 413 0
      controllers/ai/ai_pormpt.go
  2. 1607 0
      controllers/ai/ai_summary.go
  3. 188 411
      controllers/classify.go
  4. 1705 0
      controllers/data_manage/base_from_ths_hf.go
  5. 490 0
      controllers/data_manage/base_from_ths_hf_classify.go
  6. 283 1
      controllers/data_manage/chart_info.go
  7. 11 0
      controllers/data_manage/correlation/correlation_chart_classify.go
  8. 106 32
      controllers/data_manage/correlation/correlation_chart_info.go
  9. 1 1
      controllers/data_manage/data_manage_permission/data_manage_permission.go
  10. 94 6
      controllers/data_manage/edb_info.go
  11. 13 1
      controllers/data_manage/edb_info_calculate.go
  12. 274 258
      controllers/data_manage/factor_edb_series.go
  13. 379 61
      controllers/data_manage/future_good/future_good_chart_info.go
  14. 163 105
      controllers/data_manage/future_good/future_good_profit_chart_info.go
  15. 784 25
      controllers/data_manage/mysteel_chemical_data.go
  16. 78 0
      controllers/data_manage/predict_edb_info.go
  17. 7 7
      controllers/english_report/report.go
  18. 1 1
      controllers/ppt_v2.go
  19. 434 499
      controllers/report.go
  20. 77 4
      controllers/report_approve/report_approve.go
  21. 40 34
      controllers/report_approve/report_approve_flow.go
  22. 1 1
      controllers/report_author.go
  23. 1629 0
      controllers/report_chapter.go
  24. 50 63
      controllers/report_chapter_type.go
  25. 1956 0
      controllers/report_v2.go
  26. 8 9
      controllers/smart_report/smart_report.go
  27. 90 28
      controllers/voice.go
  28. 133 0
      models/ai_summary/ai_pormpt.go
  29. 176 0
      models/ai_summary/ai_summary.go
  30. 331 0
      models/ai_summary/ai_summary_classify.go
  31. 16 0
      models/business_conf.go
  32. 21 0
      models/chart_permission.go
  33. 153 68
      models/classify.go
  34. 46 0
      models/company/company_config.go
  35. 231 0
      models/data_manage/base_from_business_data.go
  36. 162 0
      models/data_manage/base_from_edb_mapping.go
  37. 509 0
      models/data_manage/base_from_ths_hf.go
  38. 297 0
      models/data_manage/base_from_ths_hf_classify.go
  39. 180 0
      models/data_manage/base_from_ths_hf_data.go
  40. 14 0
      models/data_manage/chart_edb_mapping.go
  41. 314 7
      models/data_manage/chart_info.go
  42. 75 30
      models/data_manage/chart_info_correlation.go
  43. 21 0
      models/data_manage/chart_info_future_good.go
  44. 19 19
      models/data_manage/chart_theme/request/theme.go
  45. 1 1
      models/data_manage/data_manage_permission/req_and_resp.go
  46. 66 4
      models/data_manage/edb_data_base.go
  47. 102 1
      models/data_manage/edb_data_insert_config.go
  48. 209 15
      models/data_manage/edb_info.go
  49. 17 0
      models/data_manage/factor_edb_series.go
  50. 10 0
      models/data_manage/factor_edb_series_chart_mapping.go
  51. 19 0
      models/data_manage/future_good/future_good_edb_info_data.go
  52. 5 12
      models/data_manage/future_good/request/future_good_chart.go
  53. 65 40
      models/data_manage/future_good_chart_info.go
  54. 90 1
      models/data_manage/mysteel_chemical_index.go
  55. 32 8
      models/db.go
  56. 16 0
      models/english_report.go
  57. 423 0
      models/mgo/base_from_ths_hf_data.go
  58. 510 0
      models/mgo/edb_data_ths_hf.go
  59. 25 0
      models/permission.go
  60. 3 3
      models/ppt_v2.go
  61. 426 95
      models/report.go
  62. 110 0
      models/report/report_chapter_grant.go
  63. 152 0
      models/report/report_chapter_permission_mapping.go
  64. 126 0
      models/report/report_grant.go
  65. 1 1
      models/report_approve/constant.go
  66. 11 1
      models/report_approve/report_approve.go
  67. 12 1
      models/report_approve/report_approve_flow.go
  68. 39 0
      models/report_approve/report_approve_record.go
  69. 343 43
      models/report_chapter.go
  70. 71 24
      models/report_chapter_type.go
  71. 60 22
      models/report_chapter_type_permission.go
  72. 66 0
      models/report_grant.go
  73. 453 0
      models/report_v2.go
  74. 23 0
      models/smart_report/smart_resource.go
  75. 19 0
      models/system/sys_admin.go
  76. 3 3
      models/wechat_send_msg.go
  77. 522 9
      routers/commentsRouter.go
  78. 1 0
      routers/router.go
  79. 184 0
      services/ai_summary/ai_summary.go
  80. 671 0
      services/classify.go
  81. 12 9
      services/data/base_edb_lib.go
  82. 66 0
      services/data/base_from_business.go
  83. 302 0
      services/data/base_from_ths_hf.go
  84. 526 0
      services/data/base_from_ths_hf_classify.go
  85. 1233 5
      services/data/chart_info.go
  86. 58 1
      services/data/chart_info_excel_balance.go
  87. 10 0
      services/data/chart_theme.go
  88. 347 23
      services/data/correlation/chart_info.go
  89. 13 0
      services/data/edb_classify.go
  90. 154 1
      services/data/edb_data.go
  91. 4 1
      services/data/edb_info.go
  92. 140 0
      services/data/edb_info_calculate.go
  93. 2 2
      services/data/excel/mixed_table.go
  94. 266 128
      services/data/future_good/chart_info.go
  95. 137 43
      services/data/future_good/profit_chart_info.go
  96. 161 2
      services/data/mysteel_chemical.go
  97. 2 0
      services/elastic.go
  98. 3 1
      services/eta_forum/eta_forum_hub.go
  99. 99 0
      services/file.go
  100. 52 10
      services/ppt.go

+ 413 - 0
controllers/ai/ai_pormpt.go

@@ -0,0 +1,413 @@
+package ai
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models"
+	"eta/eta_api/models/ai_summary"
+	"eta/eta_api/models/system"
+	aiSummaryService "eta/eta_api/services/ai_summary"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// 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,
+			AdminId:    systemAdmin.AdminId,
+			IsShare:    1,
+			PromptList: respGroupPptNameListItemList,
+		}
+		resp = append(resp, tmpRespGroupListItem)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询成功"
+	br.Data = resp
+	return
+}
+
+// AddPpt
+// @Title 新增提示词
+// @Description 新增提示词接口
+// @Param	request	body ppt_english.AddPptEnglishReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /prompt/add [post]
+func (this *AiController) AddPrompt() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.AiPromptAddReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.Title == "" {
+		br.Msg = "标题不能为空"
+		return
+	}
+
+	var msg string
+	//获取该层级下最大的排序数
+	maxSort, err := ai_summary.GetAiPromptMaxSort(this.SysUser.AdminId)
+
+	prompt := &ai_summary.AiPrompt{
+		CreateTime:    time.Now(),
+		ModifyTime:    time.Now(),
+		PromptContent: req.PromptContent,
+		SysAdminId:    this.SysUser.AdminId,
+		SysAdminName:  this.SysUser.RealName,
+		Title:         req.Title,
+		Sort:          maxSort + 1,
+	}
+	_, err = ai_summary.AddPropmt(prompt)
+	if err != nil {
+		br.Msg = "新增失败"
+		br.ErrMsg = "新增失败,Err:" + err.Error()
+		return
+	}
+
+	msg = "新增成功"
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = msg
+}
+
+// EditPpt
+// @Title 编辑提示词
+// @Description 编辑提示词接口
+// @Param	request	body ppt_english.AddPptEnglishReq true "type json string"
+// @Success 200 Ret=200 编辑成功
+// @router /prompt/edit [post]
+func (this *AiController) EditPrompt() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.AiPromptEditReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.Title == "" {
+		br.Msg = "标题不能为空"
+		return
+	}
+
+	promptItem := &ai_summary.AiPrompt{
+		AiPromptId:    req.AiPromptId,
+		PromptContent: req.PromptContent,
+		Title:         req.Title,
+		CreateTime:    time.Time{},
+		ModifyTime:    time.Now(),
+	}
+
+	err = promptItem.Update([]string{"ModifyTime", "PromptContent", "Title"})
+	if err != nil {
+		br.Msg = "编辑失败"
+		br.ErrMsg = "编辑失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "编辑成功"
+	br.IsAddLog = true
+}
+
+// DeletePpt
+// @Title 删除提示词
+// @Description 删除ppt接口
+// @Param	request	body ppt_english.DeletePptEnglishReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /prompt/delete [post]
+func (this *AiController) DeletePrompt() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.DeleteAipromptReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.AiPromptId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	promptInfo, err := ai_summary.GetAiPromptById(req.AiPromptId)
+	if err != nil {
+		br.Msg = "提示词不存在"
+		return
+	}
+	if promptInfo.SysAdminId != this.SysUser.AdminId {
+		br.Msg = "无权删除"
+		return
+	}
+	err = ai_summary.DelAiPromptyId(req.AiPromptId)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+	br.Msg = "删除成功"
+}
+
+// DetailPrompt
+// @Title 获取提示词详情
+// @Description 获取提示词详情接口
+// @Param   AiPromptId   query   int  true       "提示词id"
+// @Success 200 {object} ppt_english.PptEnglish
+// @router /prompt/detail [get]
+func (this *AiController) DetailPrompt() {
+	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
+	}
+	aiPromptId, _ := this.GetInt("AiPromptId")
+
+	promptInfo, err := ai_summary.GetAiPromptById(aiPromptId)
+	if err != nil {
+		br.Msg = "提示词不存在"
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = promptInfo
+}
+
+// MoveAiPrompt
+// @Title 移动提示词
+// @Description 移动提示词接口
+// @Param	request	body models.ReqMoveGroupPpt true "type json string"
+// @Success 200 Ret=200 移动成功
+// @router /prompt/move [post]
+func (this *AiController) MoveAiPrompt() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.ReqMovePrompt
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.AiPromptId <= 0 {
+		br.Msg = "请输入提示词ID"
+		return
+	}
+
+	prompt, err := ai_summary.GetAiPromptById(req.AiPromptId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "当前提示词不存在"
+			br.ErrMsg = "当前提示词不存在"
+			return
+		}
+		br.Msg = "提示词查询出错"
+		br.ErrMsg = "提示词查询出错:" + err.Error()
+		return
+	}
+	var updateStr []string
+
+	var currentSort, prevSort, nextSort int
+	currentSort = prompt.Sort
+
+	var prevPrompt *ai_summary.AiPrompt
+	var nextPrompt *ai_summary.AiPrompt
+	if req.PrevAiPromptId > 0 {
+		prevPrompt, err = ai_summary.GetAiPromptById(req.PrevAiPromptId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				err = errors.New("目录下的提示词不存在")
+				return
+			}
+			err = errors.New("目录下的提示词查询出错:" + err.Error())
+			return
+		}
+		prevSort = prevPrompt.Sort
+	}
+
+	if req.NextAiPromptId > 0 {
+		nextPrompt, err = ai_summary.GetAiPromptById(req.NextAiPromptId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				err = errors.New("目录下的提示词不存在")
+				return
+			}
+			err = errors.New("目录下的提示词查询出错:" + err.Error())
+			return
+		}
+		nextSort = nextPrompt.Sort
+	}
+
+	updateStr = append(updateStr, "sort")
+
+	//移到两个排序值中间操作
+	if prevSort >= currentSort {
+		//往下移动
+		err = ai_summary.MoveDownPromptBySort(this.SysUser.AdminId, prevSort, currentSort)
+		if err != nil {
+			err = errors.New("向下移动提示词出错:" + err.Error())
+			return
+		}
+		prompt.Sort = prevSort
+	} else if nextSort <= currentSort && nextSort != 0 {
+		//往上移动
+		err = ai_summary.MoveUpPromptBySort(this.SysUser.AdminId, nextSort, currentSort)
+		if err != nil {
+			err = errors.New("向上移动提示词出错:" + err.Error())
+			return
+		}
+		prompt.Sort = nextSort
+	}
+	//更新当前排序
+	err = prompt.Update(updateStr)
+	if err != nil {
+		err = errors.New("移动提示词出错:" + err.Error())
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}
+
+// SharePrompt
+// @Title 共享/取消共享单个提示词
+// @Description 共享/取消共享单个提示词接口
+// @Param	request	body models.ReqPptShare true "type json string"
+// @Success 200 {object} models.RespPptShare
+// @router /prompt/share [post]
+func (this *AiController) SharePrompt() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.DeleteAipromptReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.AiPromptId <= 0 {
+		br.Msg = "请输入提示词序号"
+		return
+	}
+
+	err = aiSummaryService.SharePrompt(req.AiPromptId, this.SysUser.AdminId)
+	if err != nil {
+		br.Msg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+
+	return
+}

+ 1607 - 0
controllers/ai/ai_summary.go

@@ -0,0 +1,1607 @@
+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"
+	saModel "eta/eta_api/models/semantic_analysis"
+	aiSummaryService "eta/eta_api/services/ai_summary"
+	"eta/eta_api/services/aiser"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"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
+	}
+	for k := range rootList {
+		rootList[k].UniqueCode = strconv.Itoa(rootList[k].AiSummaryClassifyId) + "_" + strconv.Itoa(rootList[k].AiSummaryId)
+	}
+
+	classifyAll, err := ai_summary.GetAiSummaryClassifyAndInfoByParentId(aiSummaryClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	for k := range classifyAll {
+		classifyAll[k].UniqueCode = strconv.Itoa(classifyAll[k].AiSummaryClassifyId) + "_" + strconv.Itoa(classifyAll[k].AiSummaryId)
+	}
+
+	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) AddAiSummaryClassify() {
+	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
+		}
+		if summaryInfo.SysUserId != sysUser.AdminId {
+			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 Ai纪要列表接口
+// @Description Ai纪要列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   AiSummaryClassifyId   query   int  true       "分类id"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /summary/list [get]
+func (this *AiController) AiSummaryList() {
+	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
+	}
+
+	aiSummaryClassifyId, _ := this.GetInt("AiSummaryClassifyId")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	keyWord := this.GetString("KeyWord")
+
+	var total int
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if aiSummaryClassifyId > 0 {
+		sandboxClassifyIds, err := ai_summary.GetAiSummaryClassify(aiSummaryClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取图表信息失败"
+			br.ErrMsg = "获取信息失败,GetChartClassify,Err:" + err.Error()
+			return
+		}
+		condition += " AND ai_summary_classify_id IN(" + sandboxClassifyIds + ") "
+		//pars = append(pars, chartClassifyId)
+	}
+	if keyWord != "" {
+		condition += ` AND  ( title LIKE '%` + keyWord + `%' )`
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	if isShowMe {
+		condition += ` AND sys_user_id = ? `
+		pars = append(pars, sysUser.AdminId)
+	}
+
+	//获取图表信息
+	list, err := ai_summary.GetAiSummaryListByCondition(condition, pars, startSize, pageSize)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取纪要信息失败"
+		br.ErrMsg = "获取纪要信息失败,Err:" + err.Error()
+		return
+	}
+
+	for i, v := range list {
+		ids, err := ai_summary.GetAiSummaryAllParentByClassifyId(v.ClassifyId)
+		if err != nil {
+			br.Msg = "获取父级信息错误!"
+			br.ErrMsg = "获取父级信息错误,Err:" + err.Error()
+			return
+		}
+		list[i].ParentIds = ids
+	}
+	resp := new(ai_summary.AiSummaryListResp)
+	if list == nil || len(list) <= 0 || (err != nil && err.Error() == utils.ErrNoRow()) {
+		items := make([]*ai_summary.AiSummaryItems, 0)
+		resp.Paging = page
+		resp.List = items
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	dataCount, err := ai_summary.GetAiSummaryListCountByCondition(condition, pars)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标数据总数失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, dataCount)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title Ai纪要详情接口
+// @Description Ai纪要详情接口
+// @Param   AiSummaryId   query   int  true       "详情id"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /summary/detail [get]
+func (this *AiController) AiSummaryDetail() {
+	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
+	}
+
+	aiSummaryId, _ := this.GetInt("AiSummaryId")
+
+	detail, err := ai_summary.GetAiSummaryItemById(aiSummaryId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Success = true
+		br.Msg = "获取沙盘信息失败"
+		br.ErrMsg = "获取沙盘信息失败,Err:" + err.Error()
+		return
+	}
+	resp := ai_summary.AiSummaryDetailResp{
+		AiSummaryItems: detail,
+	}
+
+	if detail.SaDocId >0 {
+		item := new(saModel.SaDoc)
+		e := item.GetItemById(detail.SaDocId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "文档已被删除, 请刷新页面"
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取文档信息失败, Err: " + e.Error()
+			return
+		}
+		resp.SaDocTitle = item.Title
+		resp.SaDocClassifyId = item.ClassifyId
+	}
+
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// @Title 新增ai纪要
+// @Description 新增ai纪要接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /summary/add [post]
+func (this *AiController) AddAiSummary() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.AddAiSummaryReq
+	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.IsSendEmail = false
+		return
+	}
+	if req.SummaryContent == "" {
+		br.Msg = "纪要内容为空"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "分类id错误"
+		br.IsSendEmail = false
+		return
+	}
+	//获取该层级下最大的排序数
+	maxSort, err := ai_summary.GetAiSummaryMaxSort(req.ClassifyId)
+
+	summary := &ai_summary.AiSummary{
+		SaDocId:         req.SaDocId,
+		OriginContent:   req.OriginContent,
+		SummaryContent:  req.SummaryContent,
+		ClassifyId:      req.ClassifyId,
+		SysUserId:       this.SysUser.AdminId,
+		SysUserRealName: this.SysUser.RealName,
+		Title:           req.Title,
+		CreateTime:      time.Now(),
+		ModifyTime:      time.Now(),
+		OpenaiFileName:  req.OpenaiFileName,
+		OpenaiFilePath:  req.OpenaiFilePath,
+		OriginTitle:     req.OriginTitle,
+		Sort:            maxSort + 1,
+	}
+
+	id, err := ai_summary.AddAiSummary(summary)
+	if err != nil {
+		br.Msg = "保存分类失败"
+		br.ErrMsg = "保存分类失败,Err:" + err.Error()
+		return
+	}
+	classify ,err := ai_summary.GetAiSummaryClassifyById(req.ClassifyId)
+	if err != nil {
+		br.Msg = "获取分类信息失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+
+	resp := ai_summary.AiSummaryAddResp{
+		AiSummaryId: int(id),
+		UniqueCode:  strconv.Itoa(req.ClassifyId) + "_" + strconv.Itoa(int(id)),
+		ParentId:    classify.ParentId,
+	}
+
+	br.Ret = 200
+	br.Msg = "保存成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// @Title 生成ai纪要
+// @Description 生成ai纪要接口
+// @Param	request	body data_manage.AddChartClassifyReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /summary/generate [post]
+func (this *AiController) GenerateAiSummary() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req ai_summary.GenerateAiSummaryReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.Prompt == "" {
+		br.Msg = "请输入提示词"
+		br.IsSendEmail = false
+		return
+	}
+	if req.OriginContent == "" && len(req.OpenaiFileId) == 0 && req.SaDocId <= 0 {
+		br.Msg = "参数错误"
+		br.IsSendEmail = false
+		return
+	}
+	if req.SaDocId > 0 {
+		// 获取段落
+		sectionOB := new(saModel.SaDocSection)
+		sectionCond := fmt.Sprintf(` AND %s = ?`, saModel.SaDocSectionColumns.DocId)
+		sectionPars := make([]interface{}, 0)
+		sectionPars = append(sectionPars, req.SaDocId)
+		sectionList, e := sectionOB.GetItemsByCondition(sectionCond, sectionPars, []string{}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取段落信息失败, Err: " + e.Error()
+			return
+		}
+		secIds := make([]int, 0)
+		for _, s := range sectionList {
+			secIds = append(secIds, s.SaDocSectionId)
+		}
+
+		for _, v := range sectionList {
+			req.OriginContent += v.Content + "<br>"
+		}
+	}
+
+	if utils.Re == nil {
+		key := "CACHE_CHAT_" + strconv.Itoa(this.SysUser.AdminId)
+		cacheVal, err := utils.Rc.RedisInt(key)
+		fmt.Println("RedisString:", cacheVal, "err:", err)
+		if err != nil && (err.Error() != "redigo: nil returned" && err.Error() != "redis: nil") {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		putVal := 0
+		if cacheVal <= 0 {
+			putVal = utils.AiChatLimit
+		} else {
+			putVal = cacheVal - 1
+		}
+
+		if putVal <= 0 {
+			br.Msg = "您今日" + strconv.Itoa(utils.AiChatLimit) + "次问答已达上限,请明天再来!"
+			br.ErrMsg = "您今日" + strconv.Itoa(utils.AiChatLimit) + "次问答已达上限,请明天再来!"
+			return
+		}
+		lastSecond := utils.GetTodayLastSecond()
+		utils.Rc.Put(key, putVal, lastSecond)
+	}
+
+	resp := new(aimod.ChatResp)
+
+	if req.OriginContent != "" {
+		// 直接提问的方式
+
+		//根据提问,获取信息
+		ask := req.Prompt + req.OriginContent
+
+		var answer string
+		//answerArr := []string{
+		//	"周度数据显示,成品油现货市场价格跟随原油下跌,但近期相对抗跌,裂解价差走扩。批零价差方面汽油收窄,柴油走扩",
+		//	"出口利润在原油下跌海外成品油矛盾更大的情况下汽柴油出口窗口完全关闭",
+		//	"汽油需求在经历五一假期的一段高峰后将回归平稳,总体没有明显矛盾,后期我们担心更多的还是柴油。"}
+		//获取主题下的所有信息
+		//AiChatTopicId
+
+		//获取主题下的所有信息
+		//AiChatTopicId
+		historyList, err := aimod.GetAiChatList(req.AiChatTopicId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取主题历史数据失败!"
+			br.ErrMsg = "获取主题历史数据失败,Err:" + err.Error()
+			return
+		}
+
+		historyChatList := make([]aimod.HistoryChat, 0)
+		for _, v := range historyList {
+			historyChat := new(aimod.HistoryChat)
+			historyChat.Ask = v.Ask
+			historyChat.Answer = v.Answer
+			historyChatList = append(historyChatList, *historyChat)
+		}
+
+		answer, err = aiser.ChatAutoMsg(ask, historyChatList, req.Model)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "获取数据失败,ChatAutoMsg,Err:" + err.Error()
+			return
+		}
+
+		resp.Ask = ask
+		resp.Answer = answer
+		resp.Model = req.Model
+
+		if req.AiChatTopicId <= 0 { //新增
+			topic := new(aimod.AiChatTopic)
+			topic.TopicName = ask
+			topic.SysUserId = this.SysUser.AdminId
+			topic.SysUserRealName = this.SysUser.RealName
+			topic.CreateTime = time.Now()
+			topic.ModifyTime = time.Now()
+			topicId, err := aimod.AddAiChatTopic(topic)
+			if err != nil {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "生成话题失败,Err:" + err.Error()
+				return
+			}
+			resp.AiChatTopicId = int(topicId)
+			chatItem := new(aimod.AiChat)
+			chatItem.AiChatTopicId = resp.AiChatTopicId
+			chatItem.Ask = ask
+			chatItem.AskUuid = utils.MD5(ask)
+			chatItem.Answer = answer
+			chatItem.Model = EnabledModelsForMap[req.Model]
+			chatItem.SysUserId = this.SysUser.AdminId
+			chatItem.SysUserRealName = this.SysUser.RealName
+			chatItem.CreateTime = time.Now()
+			chatItem.ModifyTime = time.Now()
+			_, err = aimod.AddAiChat(chatItem)
+			if err != nil {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+				return
+			}
+		} else {
+			resp.AiChatTopicId = req.AiChatTopicId
+			chatItem := new(aimod.AiChat)
+			chatItem.AiChatTopicId = resp.AiChatTopicId
+			chatItem.Ask = ask
+			chatItem.AskUuid = utils.MD5(ask)
+			chatItem.Answer = answer
+			chatItem.Model = EnabledModelsForMap[req.Model]
+			chatItem.SysUserId = this.SysUser.AdminId
+			chatItem.SysUserRealName = this.SysUser.RealName
+			chatItem.CreateTime = time.Now()
+			chatItem.ModifyTime = time.Now()
+			_, err = aimod.AddAiChat(chatItem)
+			if err != nil {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+				return
+			}
+		}
+
+	} else if len(req.OpenaiFileId) > 0 {
+		// 走文件上传的方式
+		//根据提问,获取信息
+
+		ask := req.Prompt
+		askUuid := utils.MD5(ask)
+
+		var assistantId, threadId string
+		if req.AiChatTopicId > 0 {
+			aiChatTopicObj := new(aimod.AiChatTopic)
+			aiChatTopicObj.AiChatTopicId = req.AiChatTopicId
+			topic, err := aiChatTopicObj.GetAiChatTopicById()
+			if err != nil {
+				if err.Error() == utils.ErrNoRow() {
+					br.Msg = "获取数据失败!"
+					br.ErrMsg = "获取数据失败,主题不存在,Err:" + err.Error()
+					return
+				}
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "获取数据失败,GetAiChatTopicById,Err:" + err.Error()
+				return
+			}
+			assistantId = topic.AssistantId
+			threadId = topic.ThreadId
+		}
+
+		var answer string
+		//获取主题下的所有信息
+		//AiChatTopicId
+		//historyList, err := aimod.GetAiChatList(req.AiChatTopicId)
+		//if err != nil && err.Error() != utils.ErrNoRow() {
+		//	br.Msg = "获取主题历史数据失败!"
+		//	br.ErrMsg = "获取主题历史数据失败,Err:" + err.Error()
+		//	return
+		//}
+
+		frList := make([]aimod.HistoryChat, 0)
+		tmpFileIdList := make([]string, 0)
+		tmpFileIdList = append(tmpFileIdList, req.OpenaiFileId...)
+		//// 历史消息
+		//for _, v := range historyList {
+		//	if v.OpenaiFileId != "" {
+		//		tmpFileIdList = append(tmpFileIdList, v.OpenaiFileId)
+		//	} else {
+		//		historyFr := new(aimod.HistoryChat)
+		//		historyFr.Ask = v.Ask
+		//		historyFr.Answer = v.Answer
+		//		historyFr.OpenaiFileId = tmpFileIdList
+		//		frList = append(frList, *historyFr)
+		//		tmpFileIdList = []string{}
+		//	}
+		//}
+		// 当前的消息
+		{
+			frItem := new(aimod.HistoryChat)
+			frItem.Ask = ask
+			frItem.Answer = ""
+			frItem.OpenaiFileId = tmpFileIdList
+			frList = append(frList, *frItem)
+		}
+
+		fileRetrieveResp, err := aiser.FileRetrieve(assistantId, threadId, req.Model, frList, req.OpenaiFileId)
+		if err != nil {
+			br.Msg = "获取数据失败!"
+			br.ErrMsg = "获取数据失败,FileRetrieve,Err:" + err.Error()
+			return
+		}
+
+		if fileRetrieveResp != nil {
+			if fileRetrieveResp.Ret == 200 {
+				assistantId = fileRetrieveResp.Data.AssistantId
+				threadId = fileRetrieveResp.Data.ThreadId
+				answer = fileRetrieveResp.Data.Answer
+			} else {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = fileRetrieveResp.Msg
+				return
+			}
+		}
+
+		if req.AiChatTopicId <= 0 { //新增
+			topic := new(aimod.AiChatTopic)
+			topic.TopicName = ask
+			topic.SysUserId = this.SysUser.AdminId
+			topic.SysUserRealName = this.SysUser.RealName
+			topic.CreateTime = time.Now()
+			topic.ModifyTime = time.Now()
+			topic.AssistantId = assistantId
+			topic.ThreadId = threadId
+			topicId, err := aimod.AddAiChatTopic(topic)
+			if err != nil {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "生成话题失败,Err:" + err.Error()
+				return
+			}
+			chatItem := new(aimod.AiChat)
+			chatItem.AiChatTopicId = int(topicId)
+			chatItem.Ask = ask
+			chatItem.AskUuid = utils.MD5(ask)
+			chatItem.Answer = answer
+			chatItem.Model = EnabledModelsForMap[req.Model]
+			chatItem.SysUserId = this.SysUser.AdminId
+			chatItem.SysUserRealName = this.SysUser.RealName
+			chatItem.CreateTime = time.Now()
+			chatItem.ModifyTime = time.Now()
+			_, err = aimod.AddAiChat(chatItem)
+			if err != nil {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+				return
+			}
+			req.AiChatTopicId = int(topicId)
+		} else {
+			chatItem := new(aimod.AiChat)
+			chatItem.AiChatTopicId = req.AiChatTopicId
+			chatItem.Ask = ask
+			chatItem.AskUuid = askUuid
+			chatItem.Answer = answer
+			chatItem.Model = EnabledModelsForMap[req.Model]
+			chatItem.SysUserId = this.SysUser.AdminId
+			chatItem.SysUserRealName = this.SysUser.RealName
+			chatItem.CreateTime = time.Now()
+			chatItem.ModifyTime = time.Now()
+			_, err = aimod.AddAiChat(chatItem)
+			if err != nil {
+				br.Msg = "获取数据失败!"
+				br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+				return
+			}
+			//更新	assistantId,threadId
+			aiChatTopicObj := new(aimod.AiChatTopic)
+
+			updateParams := make(map[string]interface{})
+			updateParams["assistant_id"] = assistantId
+			updateParams["thread_id"] = threadId
+			updateParams["modify_time"] = time.Now()
+
+			whereParam := make(map[string]interface{})
+			whereParam["ai_chat_topic_id"] = req.AiChatTopicId
+
+			err = aiChatTopicObj.Update(updateParams, whereParam)
+			if err != nil {
+				br.Msg = "获取失败!"
+				br.ErrMsg = "修改助手标识失败,Err:" + err.Error()
+				return
+			}
+		}
+		resp.Model = aimod.ModelViewMap[req.Model]
+		resp.AiChatTopicId = req.AiChatTopicId
+		resp.Ask = ask
+		resp.Answer = answer
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+//// @Title 文件上传
+//// @Description 文件上传接口
+//// @Param   File   query   file  true       "文件"
+//// @Param   AiChatTopicId   query   int  true       "主题id"
+//// @Success 200 {object} models.ResourceResp
+//// @router /file/upload [post]
+//func (this *AiFileController) FileUpload2() {
+//	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
+//	}
+//	model := this.GetString("Model")
+//
+//	aiChatTopicId, _ := this.GetInt("AiChatTopicId")
+//
+//	uploadFileName := h.Filename //上传的文件名
+//	ext := path.Ext(h.Filename)
+//	dateDir := time.Now().Format("20060102")
+//	uploadDir := utils.STATIC_DIR + "ai/" + 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 := ``
+//	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 = 1
+//	item.CreateTime = time.Now()
+//	newId, err := models.AddResource(item)
+//	if err != nil {
+//		br.Msg = "资源上传失败"
+//		br.ErrMsg = "资源上传失败,Err:" + err.Error()
+//		return
+//	}
+//	//调用AI接口,上传文件
+//	resp := models.ResourceResp{
+//		Id:           newId,
+//		ResourceUrl:  resourceUrl,
+//		ResourceName: uploadFileName,
+//	}
+//	uploadResult, err := aiser.OpenAiFileUpload(resourceUrl, uploadFileName, model)
+//	if err != nil {
+//		br.Msg = "文件上传失败"
+//		br.ErrMsg = "文件上传失败,Err:" + err.Error()
+//		return
+//	}
+//
+//	var assistantId, threadId string
+//	if aiChatTopicId > 0 {
+//		aiChatTopicObj := new(aimod.AiChatTopic)
+//		aiChatTopicObj.AiChatTopicId = aiChatTopicId
+//		topic, err := aiChatTopicObj.GetAiChatTopicById()
+//		if err != nil {
+//			if err.Error() == utils.ErrNoRow() {
+//				br.Msg = "获取数据失败!"
+//				br.ErrMsg = "获取数据失败,主题不存在,Err:" + err.Error()
+//				return
+//			}
+//			br.Msg = "获取数据失败!"
+//			br.ErrMsg = "获取数据失败,GetAiChatTopicById,Err:" + err.Error()
+//			return
+//		}
+//		assistantId = topic.AssistantId
+//		threadId = topic.ThreadId
+//	}
+//
+//	if aiChatTopicId <= 0 { //新增
+//		topic := new(aimod.AiChatTopic)
+//		var filenameWithSuffix string
+//		filenameWithSuffix = path.Base(uploadFileName)
+//		var fileSuffix string
+//		fileSuffix = path.Ext(filenameWithSuffix)
+//		var filenameOnly string
+//		filenameOnly = strings.TrimSuffix(filenameWithSuffix, fileSuffix)
+//		topic.TopicName = filenameOnly
+//		topic.SysUserId = this.SysUser.AdminId
+//		topic.SysUserRealName = this.SysUser.RealName
+//		topic.CreateTime = time.Now()
+//		topic.ModifyTime = time.Now()
+//		topic.AssistantId = assistantId
+//		topic.ThreadId = threadId
+//		topicId, err := aimod.AddAiChatTopic(topic)
+//		if err != nil {
+//			br.Msg = "获取数据失败!"
+//			br.ErrMsg = "生成话题失败,Err:" + err.Error()
+//			return
+//		}
+//		aiChatTopicId = int(topicId)
+//		chatItem := new(aimod.AiChat)
+//		chatItem.AiChatTopicId = aiChatTopicId
+//		chatItem.Ask = uploadFileName
+//		chatItem.AskUuid = utils.MD5(uploadFileName)
+//		chatItem.Model = EnabledModelsForMap[model]
+//		chatItem.SysUserId = this.SysUser.AdminId
+//		chatItem.SysUserRealName = this.SysUser.RealName
+//		if uploadResult != nil && uploadResult.Data != nil {
+//			chatItem.OpenaiFileId = uploadResult.Data.ID
+//			chatItem.OpenaiFileName = uploadFileName
+//		}
+//		chatItem.OpenaiFilePath = resourceUrl
+//		chatItem.CreateTime = time.Now()
+//		chatItem.ModifyTime = time.Now()
+//		_, err = aimod.AddAiChat(chatItem)
+//		if err != nil {
+//			br.Msg = "获取数据失败!"
+//			br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+//			return
+//		}
+//	} else {
+//		chatItem := new(aimod.AiChat)
+//		chatItem.AiChatTopicId = aiChatTopicId
+//		chatItem.Ask = uploadFileName
+//		chatItem.AskUuid = utils.MD5(fileName)
+//		chatItem.Model = EnabledModelsForMap[model]
+//		chatItem.SysUserId = this.SysUser.AdminId
+//		chatItem.SysUserRealName = this.SysUser.RealName
+//		if uploadResult != nil && uploadResult.Data != nil {
+//			chatItem.OpenaiFileId = uploadResult.Data.ID
+//			chatItem.OpenaiFileName = uploadFileName
+//		}
+//		chatItem.OpenaiFilePath = resourceUrl
+//		chatItem.CreateTime = time.Now()
+//		chatItem.ModifyTime = time.Now()
+//		_, err = aimod.AddAiChat(chatItem)
+//		if err != nil {
+//			br.Msg = "获取数据失败!"
+//			br.ErrMsg = "生成话题记录失败,Err:" + err.Error()
+//			return
+//		}
+//	}
+//
+//	if uploadResult != nil && uploadResult.Data != nil && uploadResult.Data.ID != "" {
+//		uploadObj := new(aimod.FileUploadRecord)
+//		uploadObj.AdminId = this.SysUser.AdminId
+//		uploadObj.FileUrl = resourceUrl
+//		uploadObj.FileName = uploadFileName
+//		uploadObj.OpenaiFileId = uploadResult.Data.ID
+//		uploadObj.OpenaiFileName = uploadResult.Data.FileName
+//		uploadObj.OpenaiObject = uploadResult.Data.Object
+//		uploadObj.OpenaiStatus = uploadResult.Data.Status
+//		uploadObj.OpenaiPurpose = uploadResult.Data.Purpose
+//		uploadObj.OpenaiStatusDetails = uploadResult.Data.StatusDetails
+//		uploadObj.OpenaiCreatedAt = uploadResult.Data.CreatedAt
+//		uploadObj.CreateTime = time.Now()
+//		uploadObj.ModifyTime = time.Now()
+//		_, err = uploadObj.AddFileUploadRecord()
+//		if err != nil {
+//			br.Msg = "上传失败"
+//			br.ErrMsg = "上传失败,Err:" + err.Error()
+//			return
+//		}
+//		resp.OpenaiFileId = uploadObj.OpenaiFileId
+//	}
+//	resp.AiChatTopicId = aiChatTopicId
+//	br.Msg = "上传成功"
+//	br.Ret = 200
+//	br.Success = true
+//	br.Data = resp
+//	return
+//}
+
+
+// AiSummaryClassifyList
+// @Title 获取所有纪要分类接口-不包含沙盘
+// @Description 获取所有纪要分类接口-不包含沙盘
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /summary/classifyList [get]
+func (this *AiController) AiSummaryClassifyList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := new(ai_summary.AiSummaryClassifyListResp)
+
+	rootList, err := ai_summary.GetAiSummaryClassifyByParentId(0)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	classifyAll, err := ai_summary.GetAiSummaryClassifyAll()
+	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.AiSummaryClassifyItemsMakeTree(this.SysUser, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+
+	resp.AllNodes = nodeAll
+	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:" + tmpErr.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
+}

+ 1705 - 0
controllers/data_manage/base_from_ths_hf.go

@@ -0,0 +1,1705 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/mgo"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/shopspring/decimal"
+	"go.mongodb.org/mongo-driver/bson"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// BaseFromThsHfController 同花顺高频数据
+type BaseFromThsHfController struct {
+	controllers.BaseAuthController
+}
+
+// Search
+// @Title 新增指标-查询指标信息接口
+// @Description 新增指标-查询指标信息接口
+// @Param	request	body data_manage.ThsHfSearchEdbReq true "type json string"
+// @Success 200 {object} data_manage.ThsHfSearchEdbResp
+// @router /ths_hf/index/search [get]
+func (this *BaseFromThsHfController) Search() {
+	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 params data_manage.ThsHfSearchEdbReq
+	if e := this.ParseForm(&params); e != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, err: %v", e)
+		return
+	}
+	params.StockCode = strings.TrimSpace(params.StockCode)
+	if params.StockCode == "" {
+		br.Msg = "请输入证券代码"
+		return
+	}
+	stockCodes := strings.Split(params.StockCode, ",")
+	if len(stockCodes) == 0 {
+		br.Msg = "请输入证券代码"
+		return
+	}
+	if len(stockCodes) > 10 {
+		br.Msg = "最多输入10个证券代码"
+		return
+	}
+	params.EdbCode = strings.TrimSpace(params.EdbCode)
+	if params.EdbCode == "" {
+		br.Msg = "请输入指标代码"
+		return
+	}
+	edbCodes := strings.Split(params.EdbCode, ",")
+	if len(edbCodes) == 0 {
+		br.Msg = "请输入指标代码"
+		return
+	}
+	if len(edbCodes) > 20 {
+		br.Msg = "最多选择/输入20个指标代码"
+		return
+	}
+	if params.StartTime == "" {
+		br.Msg = "请选择起始时间"
+		return
+	}
+	_, e := time.ParseInLocation(utils.FormatDateTime, params.StartTime, time.Local)
+	if e != nil {
+		br.Msg = "起始时间格式有误"
+		br.ErrMsg = fmt.Sprintf("起始时间格式有误, %v", e)
+		return
+	}
+	// 结束时间选填, 不填则为当前时间
+	if params.EndTime != "" {
+		_, e := time.ParseInLocation(utils.FormatDateTime, params.EndTime, time.Local)
+		if e != nil {
+			br.Msg = "截止时间格式有误"
+			br.ErrMsg = fmt.Sprintf("截止时间格式有误, %v", e)
+			return
+		}
+	}
+	if params.EndTime == "" {
+		params.EndTime = time.Now().Local().Format(utils.FormatDateTime)
+	}
+	if !utils.InArrayByInt(data_manage.ThsHfPeriodArr, params.Interval) {
+		br.Msg = "时间周期有误"
+		br.ErrMsg = fmt.Sprintf("时间周期有误, Interval: %d", params.Interval)
+		return
+	}
+	if params.CPS != "" && !utils.InArrayByStr(data_manage.ThsHfCPSArr, params.CPS) {
+		br.Msg = "复权方式有误"
+		br.ErrMsg = fmt.Sprintf("复权方式有误, CPS: %s", params.CPS)
+		return
+	}
+	if params.BaseDate != "" {
+		_, e = time.ParseInLocation(utils.FormatDate, params.BaseDate, time.Local)
+		if e != nil {
+			br.Msg = "复权基点格式有误"
+			br.ErrMsg = fmt.Sprintf("复权基点格式有误, %v", e)
+			return
+		}
+	}
+	if params.Fill != "" && !utils.InArrayByStr(data_manage.ThsHfFillArr, params.Fill) {
+		br.Msg = "非交易间隔处理有误"
+		br.ErrMsg = fmt.Sprintf("非交易间隔处理有误, Fill: %s", params.Fill)
+		return
+	}
+
+	// 校验已入库的指标
+	checkResp, indexExists, e := data.CheckExistThsHfEdb(stockCodes, edbCodes)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("校验已存在的高频指标失败, %v", e)
+		return
+	}
+	if len(checkResp.ExistIndex) > 0 && checkResp.ExistAll {
+		br.Msg = "指标均已存在"
+		return
+	}
+
+	resp := make([]*data_manage.ThsHfSearchEdbResp, 0)
+
+	// 请求API获取数据
+	indexes, e := data.GetEdbDataThsHf(params)
+	if e != nil {
+		br.Msg = "未搜索到指标"
+		br.ErrMsg = fmt.Sprintf("获取高频指标失败, %v", e)
+		return
+	}
+	if len(indexes) == 0 {
+		br.Msg = "未搜索到指标"
+		return
+	}
+	for _, v := range indexes {
+		// 忽略掉校验出来的已入库指标
+		k := fmt.Sprintf("%s-%s", v.StockCode, v.EdbCode)
+		if indexExists[k] {
+			continue
+		}
+
+		// 默认指标名称
+		suffix := data_manage.ThsHfEdbCodeCn[v.EdbCode]
+		if suffix == "" {
+			suffix = v.EdbCode
+		}
+		indexName := fmt.Sprintf("%s%dm%s", v.StockCode, params.Interval, suffix)
+
+		item := new(data_manage.ThsHfSearchEdbResp)
+		item.StockCode = v.StockCode
+		item.EdbCode = v.EdbCode
+		item.IndexName = indexName
+		item.Frequency = params.Interval
+
+		// 搜索只展示100条数据
+		var limit int
+		sort.Slice(v.IndexData, func(i, j int) bool {
+			return v.IndexData[i].DataTime.After(v.IndexData[j].DataTime)
+		})
+		for _, d := range v.IndexData {
+			if limit > 100 {
+				break
+			}
+			limit += 1
+			strVal := decimal.NewFromFloat(d.Value).Round(4).String()
+			item.IndexData = append(item.IndexData, data_manage.ThsHfSearchEdbData{
+				DataTime: d.DataTime.Format(utils.FormatDateTime),
+				Value:    strVal,
+			})
+		}
+		resp = append(resp, item)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// ExistCheck
+// @Title 新增指标-指标存在校验
+// @Description 新增指标-指标存在校验
+// @Param	request	body data_manage.ThsHfSearchEdbReq true "type json string"
+// @Success 200 {object} data_manage.ThsHfExistCheckResp
+// @router /ths_hf/index/exist_check [get]
+func (this *BaseFromThsHfController) ExistCheck() {
+	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 params data_manage.ThsHfSearchEdbReq
+	if e := this.ParseForm(&params); e != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, err: %v", e)
+		return
+	}
+	params.StockCode = strings.TrimSpace(params.StockCode)
+	if params.StockCode == "" {
+		br.Msg = "请输入证券代码"
+		return
+	}
+	stockCodes := strings.Split(params.StockCode, ",")
+	if len(stockCodes) == 0 {
+		br.Msg = "请输入证券代码"
+		return
+	}
+	params.EdbCode = strings.TrimSpace(params.EdbCode)
+	if params.EdbCode == "" {
+		br.Msg = "请输入指标代码"
+		return
+	}
+	edbCodes := strings.Split(params.EdbCode, ",")
+	if len(edbCodes) == 0 {
+		br.Msg = "请输入指标代码"
+		return
+	}
+
+	// 校验已存在的高频指标
+	resp, _, e := data.CheckExistThsHfEdb(stockCodes, edbCodes)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("校验已存在的高频指标失败, %v", e)
+		return
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// List
+// @Title 列表
+// @Description 列表
+// @Param	request	body data_manage.ThsHfIndexListForm true "type json string"
+// @Success 200 {object} data_manage.ThsHfIndexPageListResp
+// @router /ths_hf/index/list [get]
+func (this *BaseFromThsHfController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var params data_manage.ThsHfIndexListForm
+	if e := this.ParseForm(&params); e != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, err: %v", e)
+		return
+	}
+	if params.SortField > 0 && !utils.InArrayByInt([]int{1, 2, 3, 4}, params.SortField) {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SortField: %d", params.SortField)
+		return
+	}
+	if params.SortType > 0 && !utils.InArrayByInt([]int{1, 2}, params.SortType) {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, SortType: %d", params.SortType)
+		return
+	}
+	resp := new(data_manage.ThsHfIndexPageListResp)
+	resp.List = make([]*data_manage.BaseFromThsHfIndexItem, 0)
+
+	// 查询所有分类-分类查询/分类完整路径用
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	//classifies := make([]*data_manage.BaseFromThsHfClassify, 0)
+	classifyIdItem := make(map[int]*data_manage.BaseFromThsHfClassify)
+	{
+		list, e := classifyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{classifyOb.Cols().PrimaryId, classifyOb.Cols().ClassifyName, classifyOb.Cols().ClassifyNameEn, classifyOb.Cols().LevelPath}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取分类列表失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			classifyIdItem[v.BaseFromThsHfClassifyId] = v
+		}
+		//classifies = list
+	}
+
+	// 筛选项
+	var (
+		cond      string
+		pars      []interface{}
+		listOrder string
+	)
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	{
+		// 分类
+		if params.ClassifyId != "" {
+			classifyIdArr := strings.Split(params.ClassifyId, ",")
+			classifyIds := make([]int, 0)
+			for _, v := range classifyIdArr {
+				t, _ := strconv.Atoi(v)
+				if t > 0 {
+					classifyIds = append(classifyIds, t)
+				}
+			}
+			if len(classifyIds) == 0 {
+				page := paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+				resp.Paging = page
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
+			}
+			cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().BaseFromThsHfClassifyId, utils.GetOrmInReplace(len(classifyIds)))
+			pars = append(pars, classifyIds)
+
+			//// 不包含子分类
+			//if len(classifyIds) > 0 && !params.IncludeChild {
+			//	cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().BaseFromThsHfClassifyId, utils.GetOrmInReplace(len(classifyIds)))
+			//	pars = append(pars, classifyIds)
+			//}
+			//
+			//// 包含子分类
+			//if len(classifyIds) > 0 && params.IncludeChild {
+			//	queryClassifyIds := make([]int, 0)
+			//	queryClassifyExist := make(map[int]bool)
+			//
+			//	for _, v := range classifyIds {
+			//		// 遍历所有分类从LevelPath中找含有查询分类ID的...=_=!
+			//		for _, cv := range classifies {
+			//			if queryClassifyExist[cv.BaseFromThsHfClassifyId] {
+			//				continue
+			//			}
+			//			if cv.LevelPath == "" {
+			//				continue
+			//			}
+			//			strArr := strings.Split(cv.LevelPath, ",")
+			//			if len(strArr) == 0 {
+			//				continue
+			//			}
+			//			for _, sv := range strArr {
+			//				tv, _ := strconv.Atoi(sv)
+			//				if tv == v {
+			//					queryClassifyIds = append(queryClassifyIds, cv.BaseFromThsHfClassifyId)
+			//					queryClassifyExist[cv.BaseFromThsHfClassifyId] = true
+			//					break
+			//				}
+			//			}
+			//		}
+			//	}
+			//
+			//	if len(queryClassifyIds) == 0 {
+			//		page := paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+			//		resp.Paging = page
+			//		br.Ret = 200
+			//		br.Success = true
+			//		br.Msg = "获取成功"
+			//		return
+			//	}
+			//	cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().BaseFromThsHfClassifyId, utils.GetOrmInReplace(len(queryClassifyIds)))
+			//	pars = append(pars, queryClassifyIds)
+			//}
+		}
+
+		if params.Frequency != "" {
+			frequencyArr := strings.Split(params.Frequency, ",")
+			if len(frequencyArr) > 0 {
+				cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().Frequency, utils.GetOrmInReplace(len(frequencyArr)))
+				pars = append(pars, frequencyArr)
+			}
+		}
+		if params.SysAdminId != "" {
+			adminIdArr := strings.Split(params.SysAdminId, ",")
+			adminIds := make([]int, 0)
+			for _, v := range adminIdArr {
+				t, _ := strconv.Atoi(v)
+				if t > 0 {
+					adminIds = append(adminIds, t)
+				}
+			}
+			if len(adminIds) > 0 {
+				cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().SysUserId, utils.GetOrmInReplace(len(adminIds)))
+				pars = append(pars, adminIds)
+			}
+		}
+		params.Keywords = strings.TrimSpace(params.Keywords)
+		if params.Keywords != "" {
+			cond += fmt.Sprintf(" AND (%s LIKE ? OR %s LIKE ?)", indexOb.Cols().IndexCode, indexOb.Cols().IndexName)
+			kw := fmt.Sprint("%", params.Keywords, "%")
+			pars = append(pars, kw, kw)
+		}
+
+		// 排序
+		if params.SortField > 0 && params.SortType > 0 {
+			fieldMap := map[int]string{1: indexOb.Cols().StartDate, 2: indexOb.Cols().EndDate, 3: indexOb.Cols().ModifyTime, 4: indexOb.Cols().LatestValue}
+			typeMap := map[int]string{1: "ASC", 2: "DESC"}
+			listOrder = fmt.Sprintf("%s %s", fieldMap[params.SortField], typeMap[params.SortType])
+		}
+	}
+
+	// 列表总计
+	total, e := indexOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标总数失败, %v", e)
+		return
+	}
+	if total <= 0 {
+		page := paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+		resp.Paging = page
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	// 分页查询
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+	items, e := indexOb.GetPageItemsByCondition(cond, pars, []string{}, listOrder, startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标列表失败, %v", e)
+		return
+	}
+	for _, v := range items {
+		t := v.Format2Item()
+		// 分类完整路径
+		classify := classifyIdItem[v.BaseFromThsHfClassifyId]
+		levelArr := make([]string, 0)
+		if classify != nil && classify.LevelPath != "" {
+			arr := strings.Split(classify.LevelPath, ",")
+			for _, a := range arr {
+				i, _ := strconv.Atoi(a)
+				if classifyIdItem[i] != nil {
+					if this.Lang == utils.EnLangVersion {
+						levelArr = append(levelArr, classifyIdItem[i].ClassifyNameEn)
+					} else {
+						levelArr = append(levelArr, classifyIdItem[i].ClassifyName)
+					}
+				}
+			}
+		}
+		t.ClassifyPath = strings.Join(levelArr, "/")
+		resp.List = append(resp.List, t)
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	resp.Paging = page
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增指标
+// @Description 新增指标
+// @Param	request	body data_manage.ThsHfAddEdbReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/add [post]
+func (this *BaseFromThsHfController) 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 params data_manage.ThsHfAddEdbReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if params.StartTime == "" {
+		br.Msg = "请选择起始时间"
+		return
+	}
+	_, e := time.ParseInLocation(utils.FormatDateTime, params.StartTime, time.Local)
+	if e != nil {
+		br.Msg = "起始时间格式有误"
+		br.ErrMsg = fmt.Sprintf("起始时间格式有误, %v", e)
+		return
+	}
+	if params.EndTime != "" {
+		_, e := time.ParseInLocation(utils.FormatDateTime, params.EndTime, time.Local)
+		if e != nil {
+			br.Msg = "截止时间格式有误"
+			br.ErrMsg = fmt.Sprintf("截止时间格式有误, %v", e)
+			return
+		}
+	}
+	if !utils.InArrayByInt(data_manage.ThsHfPeriodArr, params.Interval) {
+		br.Msg = "时间周期有误"
+		br.ErrMsg = fmt.Sprintf("时间周期有误, Interval: %d", params.Interval)
+		return
+	}
+	if params.CPS != "" && !utils.InArrayByStr(data_manage.ThsHfCPSArr, params.CPS) {
+		br.Msg = "复权方式有误"
+		br.ErrMsg = fmt.Sprintf("复权方式有误, CPS: %s", params.CPS)
+		return
+	}
+	if params.BaseDate != "" {
+		_, e = time.ParseInLocation(utils.FormatDate, params.BaseDate, time.Local)
+		if e != nil {
+			br.Msg = "复权基点格式有误"
+			br.ErrMsg = fmt.Sprintf("复权基点格式有误, %v", e)
+			return
+		}
+	}
+	if params.Fill != "" && !utils.InArrayByStr(data_manage.ThsHfFillArr, params.Fill) {
+		br.Msg = "非交易间隔处理有误"
+		br.ErrMsg = fmt.Sprintf("非交易间隔处理有误, Fill: %s", params.Fill)
+		return
+	}
+	if len(params.IndexList) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	formExist := make(map[string]bool)
+	indexNames := make([]string, 0)
+	classifyIds := make([]int, 0)
+	for _, v := range params.IndexList {
+		if v.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+		if !utils.InArrayByInt(classifyIds, v.ClassifyId) {
+			classifyIds = append(classifyIds, v.ClassifyId)
+		}
+		if v.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		v.IndexName = strings.TrimSpace(v.IndexName)
+		if v.IndexName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		if formExist[v.IndexName] {
+			br.Msg = "指标名称重复, 请重新输入"
+			return
+		}
+		formExist[v.IndexName] = true
+		indexNames = append(indexNames, v.IndexName)
+	}
+
+	// 校验指标分类
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	{
+		cond := fmt.Sprintf(` AND %s IN (%s)`, classifyOb.Cols().PrimaryId, utils.GetOrmInReplace(len(classifyIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, classifyIds)
+		list, e := classifyOb.GetItemsByCondition(cond, pars, []string{classifyOb.Cols().PrimaryId, classifyOb.Cols().ClassifyName}, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("查询分类失败, %v", e)
+			return
+		}
+		classifyMap := make(map[int]*data_manage.BaseFromThsHfClassify)
+		for _, v := range list {
+			classifyMap[v.BaseFromThsHfClassifyId] = v
+		}
+		for _, v := range params.IndexList {
+			t := classifyMap[v.ClassifyId]
+			if t == nil {
+				br.Msg = fmt.Sprintf("%s分类不存在, 请重新选择或刷新页面", t.ClassifyName)
+				return
+			}
+		}
+	}
+
+	// 校验指标名称
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	{
+		cond := fmt.Sprintf(` AND %s IN (%s)`, indexOb.Cols().IndexName, utils.GetOrmInReplace(len(indexNames)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, indexNames)
+		list, e := indexOb.GetItemsByCondition(cond, pars, []string{indexOb.Cols().IndexName}, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("查询指标名称失败, %v", e)
+			return
+		}
+		if len(list) > 0 {
+			tips := "以下指标名称重复, 请重新输入: \n"
+			for _, v := range list {
+				tips += fmt.Sprintf("%s\n", v.IndexName)
+			}
+			br.Msg = tips
+			return
+		}
+	}
+
+	// 新增指标
+	for _, v := range params.IndexList {
+		var req data_manage.ThsHfBaseAddReq
+		req.StartTime = params.StartTime
+		req.EndTime = params.EndTime
+		req.Interval = params.Interval
+		req.Fill = params.Fill
+		req.CPS = params.CPS
+		req.BaseDate = params.BaseDate
+		req.SysAdminId = sysUser.AdminId
+		req.SysAdminName = sysUser.RealName
+		req.ClassifyId = v.ClassifyId
+		req.Unit = v.Unit
+		req.IndexName = v.IndexName
+		req.Frequency = v.Frequency
+		req.StockCode = v.StockCode
+		req.EdbCode = v.EdbCode
+		_, e = data.BaseAddThsHf(req)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("BaseAddThsHf err: %v", e))
+			continue
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Edit
+// @Title 编辑指标
+// @Description 编辑指标
+// @Param	request	body data_manage.ThsHfIndexEditReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/edit [post]
+func (this *BaseFromThsHfController) 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 params data_manage.ThsHfIndexEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if params.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", params.IndexId)
+		return
+	}
+	params.IndexName = strings.TrimSpace(params.IndexName)
+	if params.IndexName == "" {
+		br.Msg = "请输入指标名称"
+		return
+	}
+	if params.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	params.Unit = strings.TrimSpace(params.Unit)
+	if params.Unit == "" {
+		br.Msg = "请输入单位"
+		return
+	}
+
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	item, e := indexOb.GetItemById(params.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "指标不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	_, e = classifyOb.GetItemById(params.ClassifyId)
+	if e != nil {
+		br.Msg = "分类有误"
+		br.ErrMsg = fmt.Sprintf("分类信息有误, %v", e)
+		return
+	}
+
+	// 重名验证
+	{
+		cond := fmt.Sprintf(" AND %s = ? AND %s <> ?", indexOb.Cols().IndexName, indexOb.Cols().PrimaryId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, params.IndexName, params.IndexId)
+		count, e := indexOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取重名指标失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "指标名称已存在, 请重新输入"
+			return
+		}
+	}
+
+	item.IndexName = params.IndexName
+	item.BaseFromThsHfClassifyId = params.ClassifyId
+	item.Unit = params.Unit
+	item.ModifyTime = time.Now().Local()
+	updateCols := []string{item.Cols().IndexName, item.Cols().BaseFromThsHfClassifyId, item.Cols().Unit, item.Cols().ModifyTime}
+	if e = item.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新指标信息失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 详情
+// @Description 详情
+// @Param   IndexId  query  int  true  "指标ID"
+// @Param   DataDate  query  string  false  "数据日期"
+// @Success 200 {object} data_manage.ThsHfSearchEdbResp
+// @router /ths_hf/index/detail [get]
+func (this *BaseFromThsHfController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	indexId, _ := this.GetInt("IndexId")
+	if indexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", indexId)
+		return
+	}
+	dataDate := this.GetString("DataDate")
+	if dataDate != "" {
+		_, e := time.Parse(utils.FormatDate, dataDate)
+		if e != nil {
+			br.Msg = "数据日期格式有误"
+			br.ErrMsg = fmt.Sprintf("数据日期格式有误, DataDate: %s", dataDate)
+			return
+		}
+	}
+
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	item, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "指标不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	indexItem := item.Format2Item()
+	if item.EndDate.IsZero() {
+		item.EndDate = time.Now()
+	}
+	// 默认取有数据的最新日期
+	if dataDate == "" {
+		dataDate = item.EndDate.Format(utils.FormatDate)
+	}
+	startTime := fmt.Sprintf("%s 00:00:00", dataDate)
+	endTime := fmt.Sprintf("%s 23:59:59", dataDate)
+
+	// 获取数据
+	dataList, e := data.GetThsHfBaseIndexData(indexItem.IndexCode, startTime, endTime)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标数据失败, %v", e)
+		return
+	}
+
+	type DetailResp struct {
+		Index    *data_manage.BaseFromThsHfIndexItem
+		DataDate string `description:"数据日期"`
+		DataList []*data_manage.BaseFromThsHfDataItem
+	}
+	resp := new(DetailResp)
+	resp.Index = indexItem
+	resp.DataDate = dataDate
+	resp.DataList = dataList
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Refresh
+// @Title 刷新指标
+// @Description 刷新指标
+// @Param	request	body data_manage.ThsHfIndexOptReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/refresh [post]
+func (this *BaseFromThsHfController) Refresh() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var params data_manage.ThsHfIndexOptReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if params.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", params.IndexId)
+		return
+	}
+
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	_, e := indexOb.GetItemById(params.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "指标不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+
+	// 指标刷新逻辑
+	indexIds := []int{params.IndexId}
+	_, e = data.RefreshBaseThsHfIndex(indexIds, 1)
+	if e != nil {
+		br.Msg = "刷新失败"
+		br.ErrMsg = fmt.Sprintf("刷新指标失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Remove
+// @Title 删除指标
+// @Description 删除指标
+// @Param	request	body data_manage.ThsHfIndexOptReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/remove [post]
+func (this *BaseFromThsHfController) Remove() {
+	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 params data_manage.ThsHfIndexOptReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if params.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", params.IndexId)
+		return
+	}
+
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	item, e := indexOb.GetItemById(params.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+
+	// 删除校验, 指标库存在不可删除
+	edbMappingOb := new(data_manage.BaseFromEdbMapping)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", edbMappingOb.Cols().BaseIndexCode)
+		pars := make([]interface{}, 0)
+		pars = append(pars, item.IndexCode)
+		count, e := edbMappingOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取源指标关联失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "指标已被引用, 不允许删除"
+			return
+		}
+	}
+
+	if e = item.Remove(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("删除指标失败, %v", e)
+		return
+	}
+
+	if utils.UseMongo {
+		mogDataObj := mgo.BaseFromThsHfData{}
+		if e = mogDataObj.RemoveMany(bson.M{"index_code": item.IndexCode}); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("删除指标数据失败-Mgo, %v", e)
+			return
+		}
+	} else {
+		dataOb := new(data_manage.BaseFromThsHfData)
+		cond := fmt.Sprintf(" %s = ?", dataOb.Cols().BaseFromThsHfIndexId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, params.IndexId)
+		if e = dataOb.RemoveByCondition(cond, pars); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("删除指标数据失败, %v", e)
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ListChoice
+// @Title 列表选择
+// @Description 列表选择
+// @Param	request	body data_manage.ThsHfIndexMultiOptReq true "type json string"
+// @Success 200 string "获取成功"
+// @router /ths_hf/index/list_choice [get]
+func (this *BaseFromThsHfController) ListChoice() {
+	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 params data_manage.ThsHfIndexListChoiceReq
+	if e := this.ParseForm(&params); e != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, err: %v", e)
+		return
+	}
+	var listIds []int
+	if params.ListIds != "" {
+		strArr := strings.Split(params.ListIds, ",")
+		for _, v := range strArr {
+			t, _ := strconv.Atoi(v)
+			if t > 0 {
+				listIds = append(listIds, t)
+			}
+		}
+	}
+
+	var (
+		cond string
+		pars []interface{}
+	)
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	resp := make([]*data_manage.ThsHfIndexListChoiceItem, 0)
+
+	// 非列表全选
+	if !params.SelectAll {
+		if len(listIds) > 0 {
+			cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().PrimaryId, utils.GetOrmInReplace(len(listIds)))
+			pars = append(pars, listIds)
+		}
+	}
+
+	// 列表全选, 根据条件筛选列表并过滤params.ListIds中选择的ID
+	if params.SelectAll {
+		// 查询所有分类-分类查询用
+		classifyOb := new(data_manage.BaseFromThsHfClassify)
+		classifies := make([]*data_manage.BaseFromThsHfClassify, 0)
+		classifyIdItem := make(map[int]*data_manage.BaseFromThsHfClassify)
+		{
+			list, e := classifyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{classifyOb.Cols().PrimaryId, classifyOb.Cols().ClassifyName, classifyOb.Cols().ClassifyNameEn, classifyOb.Cols().LevelPath}, "")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取分类列表失败, %v", e)
+				return
+			}
+			for _, v := range list {
+				classifyIdItem[v.BaseFromThsHfClassifyId] = v
+			}
+			classifies = list
+		}
+
+		// 筛选项
+		if params.ClassifyId != "" {
+			classifyIdArr := strings.Split(params.ClassifyId, ",")
+			classifyIds := make([]int, 0)
+			for _, v := range classifyIdArr {
+				t, _ := strconv.Atoi(v)
+				if t > 0 {
+					classifyIds = append(classifyIds, t)
+				}
+			}
+
+			// 不包含子分类
+			if len(classifyIds) > 0 && !params.IncludeChild {
+				cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().BaseFromThsHfClassifyId, utils.GetOrmInReplace(len(classifyIds)))
+				pars = append(pars, classifyIds)
+			}
+
+			// 包含子分类
+			if len(classifyIds) > 0 && params.IncludeChild {
+				queryClassifyIds := make([]int, 0)
+				queryClassifyExist := make(map[int]bool)
+
+				for _, v := range classifyIds {
+					// 遍历所有分类从LevelPath中找含有查询分类ID的...=_=!
+					for _, cv := range classifies {
+						if queryClassifyExist[cv.BaseFromThsHfClassifyId] {
+							continue
+						}
+						if cv.LevelPath == "" {
+							continue
+						}
+						strArr := strings.Split(cv.LevelPath, ",")
+						if len(strArr) == 0 {
+							continue
+						}
+						for _, sv := range strArr {
+							tv, _ := strconv.Atoi(sv)
+							if tv == v {
+								queryClassifyIds = append(queryClassifyIds, cv.BaseFromThsHfClassifyId)
+								queryClassifyExist[cv.BaseFromThsHfClassifyId] = true
+								break
+							}
+						}
+					}
+				}
+
+				if len(queryClassifyIds) == 0 {
+					br.Data = resp
+					br.Ret = 200
+					br.Success = true
+					br.Msg = "获取成功"
+					return
+				}
+				cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().BaseFromThsHfClassifyId, utils.GetOrmInReplace(len(queryClassifyIds)))
+				pars = append(pars, queryClassifyIds)
+			}
+		}
+
+		if params.Frequency != "" {
+			frequencyArr := strings.Split(params.Frequency, ",")
+			if len(frequencyArr) > 0 {
+				cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().Frequency, utils.GetOrmInReplace(len(frequencyArr)))
+				pars = append(pars, frequencyArr)
+			}
+		}
+		if params.SysAdminId != "" {
+			adminIdArr := strings.Split(params.SysAdminId, ",")
+			adminIds := make([]int, 0)
+			for _, v := range adminIdArr {
+				t, _ := strconv.Atoi(v)
+				if t > 0 {
+					adminIds = append(adminIds, t)
+				}
+			}
+			if len(adminIds) > 0 {
+				cond += fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().SysUserId, utils.GetOrmInReplace(len(adminIds)))
+				pars = append(pars, adminIds)
+			}
+		}
+		params.Keywords = strings.TrimSpace(params.Keywords)
+		if params.Keywords != "" {
+			cond += fmt.Sprintf(" AND (%s LIKE ? OR %s LIKE ?)", indexOb.Cols().IndexCode, indexOb.Cols().IndexName)
+			kw := fmt.Sprint("%", params.Keywords, "%")
+			pars = append(pars, kw, kw)
+		}
+
+		// 过滤掉选择的指标
+		if len(listIds) > 0 {
+			cond += fmt.Sprintf(" AND %s NOT IN (%s)", indexOb.Cols().PrimaryId, utils.GetOrmInReplace(len(listIds)))
+			pars = append(pars, listIds)
+		}
+	}
+
+	fields := []string{indexOb.Cols().PrimaryId, indexOb.Cols().IndexCode, indexOb.Cols().IndexName}
+	list, e := indexOb.GetItemsByCondition(cond, pars, fields, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	if len(list) > 500 {
+		br.Msg = "选择指标超过500个, 请重新选择"
+		return
+	}
+	for _, v := range list {
+		resp = append(resp, &data_manage.ThsHfIndexListChoiceItem{
+			IndexId:   v.BaseFromThsHfIndexId,
+			IndexCode: v.IndexCode,
+			IndexName: v.IndexName,
+		})
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// MultiOpt
+// @Title 批量操作-移动分类/删除/刷新
+// @Description 批量操作-移动分类/删除/刷新
+// @Param	request	body data_manage.ThsHfIndexMultiOptReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/multi_opt [post]
+func (this *BaseFromThsHfController) MultiOpt() {
+	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 params data_manage.ThsHfIndexMultiOptReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if !utils.InArrayByInt([]int{1, 2, 3}, params.OptType) {
+		br.Msg = "请选择操作类型"
+		return
+	}
+	if len(params.IndexIds) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if params.OptType == 1 && params.MoveClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	if params.OptType == 3 && !utils.InArrayByInt([]int{1, 2}, params.RefreshType) {
+		br.Msg = "请选择刷新方式"
+		return
+	}
+	resp := new(data_manage.ThsHfIndexMultiOptResp)
+
+	// 指标名称
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	indexMap := make(map[int]*data_manage.BaseFromThsHfIndex)
+	{
+		cond := fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().PrimaryId, utils.GetOrmInReplace(len(params.IndexIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, params.IndexIds)
+		list, e := indexOb.GetItemsByCondition(cond, pars, []string{indexOb.Cols().PrimaryId, indexOb.Cols().IndexCode, indexOb.Cols().IndexName}, "")
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = fmt.Sprintf("获取源指标列表失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			indexMap[v.BaseFromThsHfIndexId] = v
+		}
+	}
+
+	// 批量移动
+	if params.OptType == 1 {
+		if e := indexOb.UpdateClassifyMulti(params.IndexIds, params.MoveClassifyId); e != nil {
+			br.Msg = "移动失败"
+			br.ErrMsg = fmt.Sprintf("批量移动分类失败, %v", e)
+			return
+		}
+	}
+
+	// 批量删除
+	if params.OptType == 2 {
+		// 被引用的指标需要提示出来, 未被引用的直接删除
+		usedIndexIds := make([]int, 0)
+		{
+			edbMappingOb := new(data_manage.BaseFromEdbMapping)
+			cond := fmt.Sprintf(" AND %s IN (%s)", edbMappingOb.Cols().BaseFromIndexId, utils.GetOrmInReplace(len(params.IndexIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, params.IndexIds)
+			list, e := edbMappingOb.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = fmt.Sprintf("获取源指标关联失败, %v", e)
+				return
+			}
+			for _, v := range list {
+				if !utils.InArrayByInt(usedIndexIds, v.BaseFromIndexId) {
+					usedIndexIds = append(usedIndexIds, v.BaseFromIndexId)
+				}
+			}
+		}
+
+		// 可删除的指标
+		removeIndexIds := params.IndexIds
+		if len(usedIndexIds) > 0 {
+			removeIndexIds = utils.MinusInt(params.IndexIds, usedIndexIds)
+
+			// 标记不允许删除的
+			for _, v := range usedIndexIds {
+				t := indexMap[v]
+				if t == nil {
+					continue
+				}
+				resp.Fail = append(resp.Fail, &data_manage.ThsHfIndexBaseInfo{
+					IndexId:   v,
+					IndexCode: t.IndexCode,
+					IndexName: t.IndexName,
+				})
+			}
+		}
+
+		if len(removeIndexIds) > 0 {
+			if e := indexOb.MultiRemove(removeIndexIds); e != nil {
+				br.Msg = "删除失败"
+				br.ErrMsg = fmt.Sprintf("批量删除指标失败, %v", e)
+				return
+			}
+
+			if utils.UseMongo {
+				mogDataObj := mgo.BaseFromThsHfData{}
+				for _, v := range removeIndexIds {
+					if e := mogDataObj.RemoveMany(bson.M{"base_from_ths_hf_index_id": v}); e != nil {
+						br.Msg = "操作失败"
+						br.ErrMsg = fmt.Sprintf("批量删除指标数据失败-Mgo, %v", e)
+						return
+					}
+				}
+			} else {
+				dataOb := new(data_manage.BaseFromThsHfData)
+				cond := fmt.Sprintf(" %s IN (%s)", dataOb.Cols().BaseFromThsHfIndexId, utils.GetOrmInReplace(len(removeIndexIds)))
+				pars := make([]interface{}, 0)
+				pars = append(pars, removeIndexIds)
+				if e := dataOb.RemoveByCondition(cond, pars); e != nil {
+					br.Msg = "删除失败"
+					br.ErrMsg = fmt.Sprintf("批量删除指标数据失败, %v", e)
+					return
+				}
+			}
+
+			// 标记删除成功的
+			for _, v := range removeIndexIds {
+				t := indexMap[v]
+				if t == nil {
+					continue
+				}
+				resp.Success = append(resp.Success, &data_manage.ThsHfIndexBaseInfo{
+					IndexId:   v,
+					IndexCode: t.IndexCode,
+					IndexName: t.IndexName,
+				})
+			}
+		}
+	}
+
+	// 批量刷新
+	if params.OptType == 3 {
+		isAsync, e := data.RefreshBaseThsHfIndex(params.IndexIds, params.RefreshType)
+		if e != nil {
+			br.Msg = "刷新失败"
+			br.ErrMsg = fmt.Sprintf("批量刷新指标失败, %v", e)
+			return
+		}
+		if isAsync {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功, 刷新指标较多, 请10分钟后查看"
+			return
+		}
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Save2EdbPre
+// @Title 批量新增指标库-前置
+// @Description 批量新增指标库-前置
+// @Param	request	body data_manage.ThsHfIndexMultiSave2EdbReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/save2edb_pre [post]
+func (this *BaseFromThsHfController) Save2EdbPre() {
+	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 params data_manage.ThsHfIndexMultiSave2EdbPreReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if len(params.IndexIds) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	convertRule := params.ConvertRule
+	if convertRule.ConvertType != 1 && convertRule.ConvertType != 2 {
+		br.Msg = "请选择数据转换方式"
+		return
+	}
+
+	// 生成的指标名称后缀
+	var (
+		suffixName  string
+		startTimeCn string
+		endTimeCn   string
+		calculateCn string
+	)
+	dayMap := map[int]string{1: "当日", 2: "前日"}
+	calculateMap := map[int]string{1: "均值", 2: "最大值", 3: "最小值"}
+	if convertRule.ConvertType == 1 {
+		if convertRule.ConvertFixed.FixedDay != 1 && convertRule.ConvertFixed.FixedDay != 2 {
+			br.Msg = "请选择指定时间"
+			return
+		}
+		if convertRule.ConvertFixed.FixedTime == "" {
+			br.Msg = "请选择指定时间"
+			return
+		}
+		timePrefix := time.Now().Local().Format(utils.FormatDate)
+		st := fmt.Sprintf("%s %s", timePrefix, convertRule.ConvertFixed.FixedTime)
+		startTime, e := time.Parse(utils.FormatDateTime, st)
+		if e != nil {
+			br.Msg = "指定时间格式有误"
+			return
+		}
+		startTimeCn = fmt.Sprintf("%s%s", dayMap[convertRule.ConvertFixed.FixedDay], startTime.Format("15点04"))
+	}
+	if convertRule.ConvertType == 2 {
+		if convertRule.ConvertArea.StartDay != 1 && convertRule.ConvertArea.StartDay != 2 {
+			br.Msg = "请选择起始时间"
+			return
+		}
+		if convertRule.ConvertArea.StartTime == "" {
+			br.Msg = "请选择起始时间"
+			return
+		}
+		var startTimePre string
+		if convertRule.ConvertArea.StartDay == 1 {
+			startTimePre = time.Now().Local().Format(utils.FormatDate)
+		}
+		if convertRule.ConvertArea.StartDay == 2 {
+			startTimePre = time.Now().Local().AddDate(0, 0, -1).Format(utils.FormatDate)
+		}
+		st := fmt.Sprintf("%s %s", startTimePre, convertRule.ConvertArea.StartTime)
+		startTime, e := time.Parse(utils.FormatDateTime, st)
+		if e != nil {
+			br.Msg = "起始时间格式有误"
+			return
+		}
+		startTimeCn = fmt.Sprintf("%s%s", dayMap[convertRule.ConvertArea.StartDay], startTime.Format("15点04"))
+
+		if convertRule.ConvertArea.EndDay != 1 && convertRule.ConvertArea.EndDay != 2 {
+			br.Msg = "请选择截止时间"
+			return
+		}
+		if convertRule.ConvertArea.EndTime == "" {
+			br.Msg = "请选择截止时间"
+			return
+		}
+		var endTimePre string
+		if convertRule.ConvertArea.EndDay == 1 {
+			endTimePre = time.Now().Local().Format(utils.FormatDate)
+		}
+		if convertRule.ConvertArea.EndDay == 2 {
+			endTimePre = time.Now().Local().AddDate(0, 0, -1).Format(utils.FormatDate)
+		}
+		ed := fmt.Sprintf("%s %s", endTimePre, convertRule.ConvertArea.EndTime)
+		endTime, e := time.Parse(utils.FormatDateTime, ed)
+		if e != nil {
+			br.Msg = "截止时间格式有误"
+			return
+		}
+		endTimeCn = fmt.Sprintf("至%s%s", dayMap[convertRule.ConvertArea.EndDay], endTime.Format("15点04"))
+		if startTime.After(endTime) {
+			br.Msg = "起始日期不可早于截止日期"
+			return
+		}
+		calculateCn = calculateMap[convertRule.ConvertArea.CalculateType]
+	}
+	suffixName = fmt.Sprint(startTimeCn, endTimeCn, calculateCn)
+
+	// 查询指标
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	cond := fmt.Sprintf(" AND %s IN (%s)", indexOb.Cols().PrimaryId, utils.GetOrmInReplace(len(params.IndexIds)))
+	pars := make([]interface{}, 0)
+	pars = append(pars, params.IndexIds)
+	list, e := indexOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+
+	resp := make([]*data_manage.ThsHfIndexMultiSave2EdbPreItem, 0)
+	for _, v := range list {
+		t := new(data_manage.ThsHfIndexMultiSave2EdbPreItem)
+		t.IndexId = v.BaseFromThsHfIndexId
+		t.IndexCode = v.IndexCode
+		t.IndexName = v.IndexName
+		edbCn := data_manage.ThsHfEdbCodeCn[v.Indicator]
+		if edbCn == "" {
+			edbCn = v.Indicator
+		}
+		t.NewIndexName = fmt.Sprint(v.StockCode, v.Frequency, edbCn, suffixName)
+		t.StockCode = v.StockCode
+		t.EdbCode = v.Indicator
+		t.Unit = v.Unit
+		t.Frequency = v.Frequency
+		t.NewFrequency = "日度"
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Save2Edb
+// @Title 批量新增指标库
+// @Description 批量新增指标库
+// @Param	request	body data_manage.ThsHfIndexMultiSave2EdbReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/index/save2edb [post]
+func (this *BaseFromThsHfController) Save2Edb() {
+	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 params data_manage.ThsHfIndexMultiSave2EdbReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &params); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if len(params.NewIndexes) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	convertRule := params.ConvertRule
+	if convertRule.ConvertType != 1 && convertRule.ConvertType != 2 {
+		br.Msg = "请选择数据转换方式"
+		return
+	}
+	if convertRule.ConvertType == 1 {
+		if convertRule.ConvertFixed.FixedDay != 1 && convertRule.ConvertFixed.FixedDay != 2 {
+			br.Msg = "请选择指定时间"
+			return
+		}
+		if convertRule.ConvertFixed.FixedTime == "" {
+			br.Msg = "请选择指定时间"
+			return
+		}
+		timePrefix := time.Now().Local().Format(utils.FormatDate)
+		st := fmt.Sprintf("%s %s", timePrefix, convertRule.ConvertFixed.FixedTime)
+		_, e := time.Parse(utils.FormatDateTime, st)
+		if e != nil {
+			br.Msg = "指定时间格式有误"
+			return
+		}
+	}
+	if convertRule.ConvertType == 2 {
+		if convertRule.ConvertArea.StartDay != 1 && convertRule.ConvertArea.StartDay != 2 {
+			br.Msg = "请选择起始时间"
+			return
+		}
+		if convertRule.ConvertArea.StartTime == "" {
+			br.Msg = "请选择起始时间"
+			return
+		}
+		var startTimePre string
+		if convertRule.ConvertArea.StartDay == 1 {
+			startTimePre = time.Now().Local().Format(utils.FormatDate)
+		}
+		if convertRule.ConvertArea.StartDay == 2 {
+			startTimePre = time.Now().Local().AddDate(0, 0, -1).Format(utils.FormatDate)
+		}
+		st := fmt.Sprintf("%s %s", startTimePre, convertRule.ConvertArea.StartTime)
+		startTime, e := time.Parse(utils.FormatDateTime, st)
+		if e != nil {
+			br.Msg = "起始时间格式有误"
+			return
+		}
+
+		if convertRule.ConvertArea.EndDay != 1 && convertRule.ConvertArea.EndDay != 2 {
+			br.Msg = "请选择截止时间"
+			return
+		}
+		if convertRule.ConvertArea.EndTime == "" {
+			br.Msg = "请选择截止时间"
+			return
+		}
+		var endTimePre string
+		if convertRule.ConvertArea.EndDay == 1 {
+			endTimePre = time.Now().Local().Format(utils.FormatDate)
+		}
+		if convertRule.ConvertArea.EndDay == 2 {
+			endTimePre = time.Now().Local().AddDate(0, 0, -1).Format(utils.FormatDate)
+		}
+		ed := fmt.Sprintf("%s %s", endTimePre, convertRule.ConvertArea.EndTime)
+		endTime, e := time.Parse(utils.FormatDateTime, ed)
+		if e != nil {
+			br.Msg = "截止时间格式有误"
+			return
+		}
+		if startTime.After(endTime) {
+			br.Msg = "起始日期不可早于截止日期"
+			return
+		}
+	}
+	resp := new(data_manage.ThsHfIndexMultiSave2EdbResp)
+
+	// 判断指标名称是否重复
+	edbNameExist := make(map[string]bool)
+	{
+		items, e := data_manage.GetEdbInfoFieldList(``, make([]interface{}, 0), []string{"edb_name"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取指标名称列表失败, %v", e)
+			return
+		}
+		for _, v := range items {
+			edbNameExist[v.EdbName] = true
+		}
+	}
+	for _, v := range params.NewIndexes {
+		if edbNameExist[v.NewIndexName] {
+			resp.Exist = append(resp.Exist, v)
+		}
+	}
+	if len(resp.Exist) > 0 {
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "含重复指标名称"
+		return
+	}
+
+	// 请求指标库新增
+	for _, v := range params.NewIndexes {
+		var r data_manage.ThsHfIndexMultiSave2EdbLibReq
+		r.ConvertRule = convertRule
+		v.SysAdminId = sysUser.AdminId
+		v.SysAdminName = sysUser.RealName
+		r.NewIndex = v
+		b, e := json.Marshal(r)
+		if e != nil {
+			v.Tips = "新增失败"
+			v.ErrMsg = e.Error()
+			resp.Fail = append(resp.Fail, v)
+			continue
+		}
+
+		res, e := data.AddBaseEdbInfo(string(b), utils.DATA_SOURCE_THS, utils.DATA_SUB_SOURCE_HIGH_FREQUENCY, this.Lang)
+		if e != nil {
+			v.Tips = "新增失败"
+			v.ErrMsg = fmt.Sprintf("AddBaseEdbInfo, err: %v", e)
+			resp.Fail = append(resp.Fail, v)
+			continue
+		}
+		if res.Ret != 200 {
+			v.Tips = res.Msg
+			v.ErrMsg = fmt.Sprintf("AddBaseEdbInfo, Ret: %d, ErrMsg: %s", res.Ret, res.ErrMsg)
+			resp.Fail = append(resp.Fail, v)
+			continue
+		}
+		v.Tips = "新增成功"
+		resp.Success = append(resp.Success, v)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 490 - 0
controllers/data_manage/base_from_ths_hf_classify.go

@@ -0,0 +1,490 @@
+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"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ClassifyList
+// @Title 分类列表-含指标
+// @Description 分类列表-含指标
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} data_manage.ThsHfSearchEdbResp
+// @router /ths_hf/classify/list [get]
+func (this *BaseFromThsHfController) ClassifyList() {
+	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
+	}
+	parentId, _ := this.GetInt("ParentId")
+
+	resp := make([]*data_manage.BaseFromThsHfClassifyListItem, 0)
+	// 查询分类
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := classifyOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", classifyOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp = append(resp, &data_manage.BaseFromThsHfClassifyListItem{
+				ClassifyId:     v.BaseFromThsHfClassifyId,
+				ClassifyName:   v.ClassifyName,
+				ClassifyNameEn: v.ClassifyNameEn,
+				ParentId:       v.ParentId,
+				Level:          v.Level,
+				Sort:           v.Sort,
+				UniqueCode:     v.UniqueCode,
+			})
+		}
+	}
+
+	// 查询指标
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", indexOb.Cols().BaseFromThsHfClassifyId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := indexOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", indexOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取分类下指标失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp = append(resp, &data_manage.BaseFromThsHfClassifyListItem{
+				ItemType:   1,
+				IndexId:    v.BaseFromThsHfIndexId,
+				IndexCode:  v.IndexCode,
+				IndexName:  v.IndexName,
+				ParentId:   parentId,
+				Sort:       v.Sort,
+				UniqueCode: v.IndexCode,
+			})
+		}
+	}
+	sort.Slice(resp, func(i, j int) bool {
+		return resp[i].Sort < resp[j].Sort
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// ClassifyTree
+// @Title 分类树
+// @Description 分类树
+// @Success 200 {object} data_manage.ThsHfSearchEdbResp
+// @router /ths_hf/classify/tree [get]
+func (this *BaseFromThsHfController) ClassifyTree() {
+	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
+	}
+
+	// 获取所有分类
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	list, e := classifyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, fmt.Sprintf("%s ASC, %s ASC", classifyOb.Cols().ParentId, classifyOb.Cols().Sort))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取分类列表失败, %v", e)
+		return
+	}
+	items := make([]*data_manage.BaseFromThsHfClassifyItem, 0)
+	for _, v := range list {
+		items = append(items, v.Format2Item())
+	}
+	tree := data.GetThsHfClassifyTreeRecursive(items, 0)
+
+	br.Data = tree
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// ClassifyAdd
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body data_manage.ThsHfClassifyAddReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/classify/add [post]
+func (this *BaseFromThsHfController) ClassifyAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.ThsHfClassifyAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "请选择上级分类"
+		return
+	}
+	if req.Level > 6 {
+		br.Msg = "目前只支持6级目录"
+		return
+	}
+
+	// 校验分类名称
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		if this.Lang == utils.EnLangVersion {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyNameEn)
+		} else {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyName)
+		}
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ParentId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+
+	// 层级路径
+	var levelPath string
+	var rootId int
+	if req.ParentId > 0 {
+		parent, e := classifyOb.GetItemById(req.ParentId)
+		if e != nil {
+			br.Msg = "上级分类有误"
+			br.ErrMsg = fmt.Sprintf("获取上级分类失败, %v", e)
+			return
+		}
+		levelPath = parent.LevelPath
+		rootId = parent.RootId
+	}
+
+	sortMax, e := classifyOb.GetSortMax(req.ParentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类最大排序失败, %v", e)
+		return
+	}
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	classifyOb.ParentId = req.ParentId
+	classifyOb.ClassifyName = req.ClassifyName
+	classifyOb.ClassifyNameEn = req.ClassifyName
+	classifyOb.Level = req.Level + 1
+	classifyOb.Sort = sortMax + 1
+	classifyOb.SysUserId = sysUser.AdminId
+	classifyOb.SysUserRealName = sysUser.RealName
+	classifyOb.UniqueCode = utils.MD5(classifyOb.TableName() + "_" + timestamp)
+	classifyOb.CreateTime = time.Now().Local()
+	classifyOb.ModifyTime = time.Now().Local()
+	if e = classifyOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("新增分类失败, %v", e)
+		return
+	}
+	if req.ParentId > 0 {
+		// 用英文逗号拼接方便查询
+		classifyOb.LevelPath = fmt.Sprintf("%s,%d", levelPath, classifyOb.BaseFromThsHfClassifyId)
+		classifyOb.RootId = rootId
+	} else {
+		classifyOb.LevelPath = fmt.Sprint(classifyOb.BaseFromThsHfClassifyId)
+		classifyOb.RootId = classifyOb.BaseFromThsHfClassifyId
+	}
+	if e = classifyOb.Update([]string{classifyOb.Cols().LevelPath, classifyOb.Cols().RootId}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Data = classifyOb.BaseFromThsHfClassifyId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ClassifyEdit
+// @Title 编辑分类
+// @Description 编辑分类
+// @Param	request	body data_manage.ThsHfClassifyEditReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/classify/edit [post]
+func (this *BaseFromThsHfController) ClassifyEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.ThsHfClassifyEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	classifyItem, e := classifyOb.GetItemById(req.ClassifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+
+	// 校验分类名称
+	{
+		cond := fmt.Sprintf(" AND %s <> ?", classifyOb.Cols().PrimaryId)
+		if this.Lang == utils.EnLangVersion {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyNameEn)
+		} else {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyName)
+		}
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+	classifyItem.ClassifyName = req.ClassifyName
+	classifyItem.ClassifyNameEn = req.ClassifyName
+	classifyItem.ModifyTime = time.Now().Local()
+	updateCols := []string{classifyOb.Cols().ClassifyName, classifyOb.Cols().ClassifyNameEn, classifyOb.Cols().ModifyTime}
+	if e = classifyItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ClassifyRemove
+// @Title 删除分类
+// @Description 删除分类
+// @Param	request	body data_manage.ThsHfClassifyRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/classify/remove [post]
+func (this *BaseFromThsHfController) ClassifyRemove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.ThsHfClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	classifyOb := new(data_manage.BaseFromThsHfClassify)
+	classifyItem, e := classifyOb.GetItemById(req.ClassifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+
+	// 查询子分类
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类下含有子分类, 不允许删除"
+			return
+		}
+	}
+
+	// 查询指标
+	indexOb := new(data_manage.BaseFromThsHfIndex)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", indexOb.Cols().BaseFromThsHfClassifyId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId)
+		count, e := indexOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类下指标数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类下含有指标, 不允许删除"
+			return
+		}
+	}
+
+	if e := classifyItem.Remove(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("删除分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ClassifyMove
+// @Title 移动分类
+// @Description 移动分类
+// @Param	request	body data_manage.BaseFromThsHfClassifyMoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /ths_hf/classify/move [post]
+func (this *BaseFromThsHfController) ClassifyMove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.BaseFromThsHfClassifyMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId <= 0 && req.ItemId <= 0 {
+		br.Msg = "请选择分类或指标"
+		return
+	}
+
+	err, errMsg := data.ThsHfMoveClassify(req, sysUser)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 283 - 1
controllers/data_manage/chart_info.go

@@ -1423,6 +1423,66 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
 	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 
+	// 标识线计算
+	if req.MarkersLines != "" {
+		markerLines := make([]data_manage.MarkersLine, 0)
+		err = json.Unmarshal([]byte(req.MarkersLines), &markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+		for i := range markerLines {
+			if markerLines[i].EdbType == 0 && markerLines[i].TimeIntervalType == 0 {
+				// 图上第一个指标且时间区间跟随图表
+				if edbList[0].IsAxis == 1 {
+					value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
+					if err != nil {
+						br.Msg = "标识线配置异常"
+						br.ErrMsg = "标识线配置异常" + err.Error()
+						return
+					}
+					markerLines[i].Value = value
+				} else {
+					// 其他的都走指标计算
+					edbInfo, err := data_manage.GetEdbInfoById(markerLines[i].EdbInfoId)
+					if err != nil {
+						br.Msg = "指标计算标识线获取指标信息异常"
+						br.ErrMsg = "指标计算标识线获取指标信息异常" + err.Error()
+						return
+					}
+					// 判断时间区间不为跟随图表的情况
+					if markerLines[i].TimeIntervalType != 0 {
+						startDate = markerLines[i].StartDate.Date
+						endDate = markerLines[i].EndDate.Date
+					}
+					dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, startDate, endDate)
+					if err != nil {
+						br.Msg = "指标计算标识线获取指标数据异常"
+						br.ErrMsg = "指标计算标识线获取指标数据异常" + err.Error()
+						return
+					}
+					value, err := data.MarkerLineCalculate(markerLines[i], dataList, chartInfo)
+					if err != nil {
+						br.Msg = "标识线配置异常"
+						br.ErrMsg = "标识线配置异常" + err.Error()
+						return
+					}
+					markerLines[i].Value = value
+				}
+			}
+		}
+
+		markerLineStr, err := json.Marshal(markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+
+		chartInfo.MarkersLines = string(markerLineStr)
+	}
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -3322,6 +3382,61 @@ func (this *ChartInfoController) PreviewBarChartInfo() {
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 标识线计算
+	if req.MarkersLines != "" {
+		markerLines := make([]data_manage.MarkersLine, 0)
+		err = json.Unmarshal([]byte(req.MarkersLines), &markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+		for i := range markerLines {
+			if markerLines[i].EdbType == 0 && markerLines[i].TimeIntervalType == 0 {
+				// 图上第一个指标且时间区间跟随图表
+				if edbList[0].IsAxis == 1 {
+					value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
+					if err != nil {
+						br.Msg = "标识线配置异常"
+						br.ErrMsg = "标识线配置异常" + err.Error()
+						return
+					}
+					markerLines[i].Value = value
+				} else {
+					// 其他的都走指标计算
+					edbInfo, err := data_manage.GetEdbInfoById(markerLines[i].EdbInfoId)
+					if err != nil {
+						br.Msg = "指标计算标识线获取指标信息异常"
+						br.ErrMsg = "指标计算标识线获取指标信息异常" + err.Error()
+						return
+					}
+					dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, "", "")
+					if err != nil {
+						br.Msg = "指标计算标识线获取指标数据异常"
+						br.ErrMsg = "指标计算标识线获取指标数据异常" + err.Error()
+						return
+					}
+					value, err := data.MarkerLineCalculate(markerLines[i], dataList, chartInfo)
+					if err != nil {
+						br.Msg = "标识线配置异常"
+						br.ErrMsg = "标识线配置异常" + err.Error()
+						return
+					}
+					markerLines[i].Value = value
+				}
+			}
+		}
+
+		markerLineStr, err := json.Marshal(markerLines)
+		if err != nil {
+			br.Msg = "标识线配置异常"
+			br.ErrMsg = "标识线配置异常" + err.Error()
+			return
+		}
+
+		chartInfo.MarkersLines = string(markerLineStr)
+	}
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -3886,6 +4001,7 @@ func (this *ChartInfoController) ChartInfoConvertDetail() {
 
 	edbInfoId := this.GetString("EdbInfoId")
 	chartType, _ := this.GetInt("ChartType")
+	isAxis, _ := this.GetInt("IsAxis")
 
 	calendar := this.GetString("Calendar")
 	if calendar == "" {
@@ -4029,7 +4145,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 != `` {
@@ -4243,3 +4359,169 @@ func (this *ChartInfoController) ForumDelete() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// PreviewSeasonChartInfo
+// @Title 图表-获取预览的季节性图
+// @Description 图表-获取预览的季节性图
+// @Param	ExtraConfig	body data_manage.PreviewRadarChartReq true "type json string"
+// @Success 200 {object} data_manage.ChartEdbInfoDetailResp
+// @router /chart_info/preview/season [post]
+func (this *ChartInfoController) PreviewSeasonChartInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req data_manage.PreviewSeasonChartReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	chartInfo := new(data_manage.ChartInfoView)
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	chartType := utils.CHART_TYPE_SEASON
+	if len(req.ChartEdbInfoList) <= 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	edbInfoIdArr := make([]int, 0)
+	edbInfoNameMap := make(map[int]string)
+	for _, v := range req.ChartEdbInfoList {
+		edbInfoIdArr = append(edbInfoIdArr, v.EdbInfoId)
+		edbInfoNameMap[v.EdbInfoId] = v.EdbAliasName
+	}
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdArr)
+	for _, v := range mappingList {
+		if name, ok := edbInfoNameMap[v.EdbInfoId]; ok {
+			v.EdbAliasName = name
+		}
+	}
+
+	var seasonExtraConfig string
+	seasonExtra, tErr := json.Marshal(req.SeasonExtraConfig)
+	if tErr != nil {
+		br.Msg = "季节性图表配置信息异常"
+		br.ErrMsg = "季节性图表配置信息异常,Err:" + tErr.Error()
+		return
+	}
+	seasonExtraConfig = string(seasonExtra)
+	// 获取图表中的指标数据
+	edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetChartEdbData(0, chartType, "", "", "", mappingList, "", seasonExtraConfig)
+	if err != nil {
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	warnEdbList := make([]string, 0)
+	for _, v := range edbList {
+		if v.IsNullData {
+			warnEdbList = append(warnEdbList, v.EdbName+"("+v.EdbCode+")")
+		}
+	}
+	if len(warnEdbList) > 0 {
+		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
+	}
+
+	//图表操作权限
+	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId, true)
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList, utils.CHART_SOURCE_DEFAULT, chartType)
+	chartInfo.Button = data_manage.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
+	}
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+
+// ChartInfoImgSetBySvg
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_svg [post]
+func (this *ChartInfoController) ChartInfoImgSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,ChartInfoId Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 11 - 0
controllers/data_manage/correlation/correlation_chart_classify.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services/data"
+	correlationServ "eta/eta_api/services/data/correlation"
 	"eta/eta_api/services/data/data_manage_permission"
 	"eta/eta_api/utils"
 	"fmt"
@@ -544,6 +545,15 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 				}
 			}
 		}
+
+		// 删除图表关联
+		e = correlationServ.RemoveCorrelationRelate(chartInfo.ChartInfoId)
+		if e != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = fmt.Sprintf("删除相关性图表关联失败, %v", e)
+			return
+		}
+
 		//新增操作日志
 		{
 			chartLog := new(data_manage.ChartInfoLog)
@@ -560,6 +570,7 @@ func (this *CorrelationChartClassifyController) DeleteChartClassify() {
 			go data_manage.AddChartInfoLog(chartLog)
 		}
 	}
+
 	br.Ret = 200
 	br.Msg = "删除成功"
 	br.Success = true

+ 106 - 32
controllers/data_manage/correlation/correlation_chart_info.go

@@ -1390,8 +1390,23 @@ func (this *CorrelationChartInfoController) Copy() {
 			originEdbSeriesMapping = list
 		}
 
+		seriesData := make([]*data_manage.FactorEdbSeriesCalculateData, 0)
+		{
+			dataOb := new(data_manage.FactorEdbSeriesCalculateData)
+			cond := fmt.Sprintf(" AND %s IN (%s)", dataOb.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, seriesIds)
+			list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取系列计算数据失败, %v", e)
+				return
+			}
+			seriesData = list
+		}
+
 		// 新增图表/相关性图表/图表指标关联/指标系列图表关联
-		chartInfoId, seriesIdMap, e := data_manage.CreateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, originChartMappings, true, originSeries, originEdbSeriesMapping)
+		chartInfoId, seriesIdMap, e := data_manage.CreateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, originChartMappings, true, originSeries, originEdbSeriesMapping, seriesData)
 		if e != nil {
 			br.Msg = "保存失败"
 			br.ErrMsg = fmt.Sprintf("新增多因子相关性图表失败, Err: %v", e)
@@ -2354,25 +2369,43 @@ func (this *CorrelationChartInfoController) MultiFactorAdd() {
 		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
+	// TODO:图表关联-生成相关性矩阵
+	var calculatePars data_manage.CalculateCorrelationMatrixPars
+	calculatePars.BaseEdbInfoId = req.BaseEdbInfoId
+	calculatePars.SeriesIds = req.FactorCorrelation.SeriesIds
+	var correlationConf data_manage.CorrelationConfig
+	correlationConf.LeadUnit = req.FactorCorrelation.LeadUnit
+	correlationConf.LeadValue = req.FactorCorrelation.LeadValue
+	correlationConf.CalculateUnit = req.FactorCorrelation.CalculateUnit
+	correlationConf.CalculateValue = req.FactorCorrelation.CalculateValue
+	calculatePars.Correlation = correlationConf
+	_, chartMappings, e := correlationServ.CalculateCorrelationMatrix(calculatePars)
+	if e != nil {
+		br.Msg = "计算矩阵失败"
+		br.ErrMsg = fmt.Sprintf("计算相关性矩阵失败, %v", e)
+		return
 	}
+
+	// 指标系列-图表关联
+	//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 // 先重置一下
+		//v.EdbUsed = 0 // 先重置一下
 		if edbUsed[k] {
 			v.EdbUsed = 1
 		}
@@ -2382,6 +2415,7 @@ func (this *CorrelationChartInfoController) MultiFactorAdd() {
 	// 另存为
 	originSeries := make([]*data_manage.FactorEdbSeries, 0)
 	originEdbSeriesMapping := make([]*data_manage.FactorEdbSeriesMapping, 0)
+	seriesData := make([]*data_manage.FactorEdbSeriesCalculateData, 0)
 	if req.SaveAs {
 		// 注意此处要复制一份系列及系列关联指标, 不可共用被复制图表的系列
 		{
@@ -2410,10 +2444,23 @@ func (this *CorrelationChartInfoController) MultiFactorAdd() {
 			}
 			originEdbSeriesMapping = list
 		}
+		{
+			dataOb := new(data_manage.FactorEdbSeriesCalculateData)
+			cond := fmt.Sprintf(" AND %s IN (%s)", dataOb.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+			pars := make([]interface{}, 0)
+			pars = append(pars, seriesIds)
+			list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, "")
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("获取系列计算数据失败, %v", e)
+				return
+			}
+			seriesData = list
+		}
 	}
 
 	// 新增图表/相关性图表/图表指标关联/指标系列图表关联
-	chartInfoId, seriesIdMap, e := data_manage.CreateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, chartMappings, req.SaveAs, originSeries, originEdbSeriesMapping)
+	chartInfoId, seriesIdMap, e := data_manage.CreateMultiFactorCorrelationChartAndEdb(chartInfo, edbMappings, chartCorrelate, chartMappings, req.SaveAs, originSeries, originEdbSeriesMapping, seriesData)
 	if e != nil {
 		br.Msg = "保存失败"
 		br.ErrMsg = fmt.Sprintf("新增多因子相关性图表失败, Err: %v", e)
@@ -2710,34 +2757,61 @@ func (this *CorrelationChartInfoController) MultiFactorEdit() {
 		edbMappings = append(edbMappings, m)
 	}
 
-	seriesIds := req.FactorCorrelation.SeriesIds
+	//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
+
+	var calculatePars data_manage.CalculateCorrelationMatrixPars
+	calculatePars.BaseEdbInfoId = req.BaseEdbInfoId
+	calculatePars.SeriesIds = req.FactorCorrelation.SeriesIds
+	var correlationConf data_manage.CorrelationConfig
+	correlationConf.LeadUnit = req.FactorCorrelation.LeadUnit
+	correlationConf.LeadValue = req.FactorCorrelation.LeadValue
+	correlationConf.CalculateUnit = req.FactorCorrelation.CalculateUnit
+	correlationConf.CalculateValue = req.FactorCorrelation.CalculateValue
+	calculatePars.Correlation = correlationConf
+	_, chartMappings, e := correlationServ.CalculateCorrelationMatrix(calculatePars)
+	if e != nil {
+		br.Msg = "计算矩阵失败"
+		br.ErrMsg = fmt.Sprintf("计算相关性矩阵失败, %v", e)
+		return
 	}
+
+	// 指标系列-图表关联
+	//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
+	//}
+	//existChartMappingIds := make([]int, 0) // 之前加的需要删除掉
+	//updateChartMappings := make([]*data_manage.FactorEdbSeriesChartMapping, 0)
 	for _, v := range chartMappings {
+		//if v.ChartInfoId == req.ChartInfoId {
+		//	existChartMappingIds = append(existChartMappingIds, v.FactorEdbSeriesChartMappingId)
+		//	continue
+		//}
+
 		k := fmt.Sprintf("%d-%d", v.FactorEdbSeriesId, v.EdbInfoId)
 		v.EdbUsed = 0
 		if edbUsed[k] {
 			v.EdbUsed = 1
 		}
+		v.ChartInfoId = chartInfo.ChartInfoId
+		v.Source = chartSource
 		v.ModifyTime = time.Now().Local()
+		//updateChartMappings = append(updateChartMappings, v)
 	}
 
 	// 更新图表/相关性图表/图表指标关联/指标系列图表关联

+ 1 - 1
controllers/data_manage/data_manage_permission/data_manage_permission.go

@@ -63,7 +63,7 @@ func (c *DataMangePermissionController) SetEdbChartPermission() {
 		return
 	}
 
-	err, errMsg := data_manage_permission.SetEdbChartPermission(req.Source, req.SubSource, req.UserId, req.UserList, req.IsSelectAll, req.DataIdList, req.NoDataIdList, req.Keyword, req.ClassifyId, sysUser.AdminId)
+	err, errMsg := data_manage_permission.SetEdbChartPermission(req.Source, req.SubSource, req.UserId, req.UserList, req.IsSelectAll, req.DataIdList, req.NoDataIdList, req.Keyword, req.Classify, sysUser.AdminId)
 	if err != nil {
 		//br.Success = true
 		br.Msg = "设置失败"

+ 94 - 6
controllers/data_manage/edb_info.go

@@ -10,8 +10,8 @@ import (
 	request2 "eta/eta_api/models/data_manage/excel/request"
 	"eta/eta_api/models/data_manage/request"
 	"eta/eta_api/models/data_manage/response"
-	"eta/eta_api/models/mgo"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
 	"eta/eta_api/services/data/data_manage_permission"
@@ -22,7 +22,6 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
-	"go.mongodb.org/mongo-driver/bson"
 	"strconv"
 	"strings"
 	"sync"
@@ -59,6 +58,9 @@ func (this *EdbInfoController) EdbInfoSearch() {
 	stockCode := this.GetString("StockCode")
 	frequency := this.GetString("Frequency")
 
+	extraPars := this.GetString("ExtraPars")
+	extraPars = strings.TrimSpace(extraPars)
+
 	if source <= 0 {
 		br.Msg = "无效的数据来源"
 		return
@@ -221,7 +223,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					br.Msg = "请输入指标代码"
 					return
 				}
-				respItem, err := data.AddEdbDataThsDs(source, stockCode, edbCode)
+				respItem, err := data.AddEdbDataThsDs(source, stockCode, edbCode, extraPars)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1734,8 +1736,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()
@@ -1744,7 +1745,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,
 				})
 			}
@@ -5789,6 +5790,17 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 		edbInfoItem.StockCode = v.StockCode
 		edbInfoItem.TerminalCode = terminalCode
 		edbInfoItem.ServerUrl = serverUrl
+		var extra data_manage.EdbInfoExtra
+		if v.ApiExtraPars != "" {
+			extra.ApiExtraPars = v.ApiExtraPars
+			b, e := json.Marshal(extra)
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = fmt.Sprintf("额外参数JSON格式化失败, %v", e)
+				return
+			}
+			edbInfoItem.Extra = string(b)
+		}
 
 		// 指标入库
 		edbInfo, err, errMsg, isSendEmail := data.EdbInfoWsdAdd(edbInfoItem)
@@ -6418,3 +6430,79 @@ func (this *EdbInfoController) SmmEdbInfoBatchAdd() {
 	br.Data = resp
 	br.IsAddLog = true
 }
+
+// ChartImageSetBySvg
+// @Title 设置指标的图表图片
+// @Description 设置指标的图表图片接口
+// @Param	request	body data_manage.SetChartInfoImageReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /edb_info/image/set_by_svg [post]
+func (this *EdbInfoController) ChartImageSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	edbInfoId, _ := this.GetInt("EdbInfoId", 0)
+	if edbInfoId <= 0 {
+		br.Msg = "指标参数错误"
+		br.ErrMsg = "指标参数错误,EdbInfoId Is Empty"
+		return
+	}
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if edbInfoId <= 0 || resourceUrl == "" {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "保存成功"
+		return
+	}
+
+	edbInfo, e := data_manage.GetEdbInfoById(edbInfoId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "找不到该指标"
+			return
+		}
+		br.Msg = "保存失败"
+		br.ErrMsg = "获取指标失败, Err:" + e.Error()
+		return
+	}
+	edbInfo.ChartImage = resourceUrl
+	if e = edbInfo.Update([]string{"ChartImage"}); e != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "更新指标缩略图失败, Err:" + e.Error()
+		return
+	}
+
+	// 修改es数据
+	go data.AddOrEditEdbInfoToEs(edbInfoId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}

+ 13 - 1
controllers/data_manage/edb_info_calculate.go

@@ -1468,7 +1468,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)
@@ -2005,6 +2005,18 @@ func (this *ChartInfoController) CalculateMapping() {
 		br.ErrMsg = "获取失败,Err:" + e.Error()
 		return
 	}
+
+	// 英文来源,显示英文名称
+	if this.Lang == utils.EnLangVersion {
+		fromEdbInfo, e := data_manage.GetEdbInfoById(item.FromEdbInfoId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + e.Error()
+			return
+		}
+		item.FromEdbName = fromEdbInfo.EdbNameEn
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "保存成功"

+ 274 - 258
controllers/data_manage/factor_edb_series.go

@@ -10,10 +10,8 @@ import (
 	correlationServ "eta/eta_api/services/data/correlation"
 	"eta/eta_api/utils"
 	"fmt"
-	"sort"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 )
 
@@ -338,6 +336,7 @@ func (this *FactorEdbSeriesController) Edit() {
 		br.ErrMsg = "获取因子指标系列失败, Err: " + e.Error()
 		return
 	}
+	originCalculateState := seriesItem.CalculateState
 
 	edbArr, e := data_manage.GetEdbInfoByIdList(req.EdbInfoIds)
 	if e != nil {
@@ -379,6 +378,7 @@ func (this *FactorEdbSeriesController) Edit() {
 	}
 
 	// 更新系列信息和指标关联
+	seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculateNone
 	if calculateLen > 0 {
 		seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculating
 		seriesItem.CalculateStep = calculatesJson
@@ -406,15 +406,19 @@ func (this *FactorEdbSeriesController) Edit() {
 		br.ErrMsg = "计算因子指标失败, Err: " + e.Error()
 		return
 	}
+	if seriesItem.CalculateState == data_manage.FactorEdbSeriesCalculating {
+		seriesItem.CalculateState = data_manage.FactorEdbSeriesCalculated
+	}
 
-	// 更新系列计算状态
-	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
+	// 若原状态不一致, 更新状态
+	if originCalculateState != seriesItem.CalculateState {
+		cols := []string{seriesItem.Cols().CalculateState, seriesItem.Cols().ModifyTime}
+		seriesItem.ModifyTime = time.Now().Local()
+		if e = seriesItem.Update(cols); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新因子指标系列计算状态失败, Err: " + e.Error()
+			return
+		}
 	}
 
 	br.Data = calculateResp
@@ -556,258 +560,270 @@ func (this *FactorEdbSeriesController) CorrelationMatrix() {
 		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)
+	// 矩阵计算
+	var calculatePars data_manage.CalculateCorrelationMatrixPars
+	calculatePars.BaseEdbInfoId = req.BaseEdbInfoId
+	calculatePars.SeriesIds = req.SeriesIds
+	calculatePars.Correlation = req.Correlation
+	resp, _, e := correlationServ.CalculateCorrelationMatrix(calculatePars)
 	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取因子指标失败, Err: " + e.Error()
+		br.Msg = "计算失败"
+		br.ErrMsg = fmt.Sprintf("计算相关性矩阵失败, %v", e)
 		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
-	}
+	// 获取标的指标信息及数据
+	//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

+ 379 - 61
controllers/data_manage/future_good/future_good_chart_info.go

@@ -324,7 +324,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 = "参数解析异常!"
@@ -510,7 +510,12 @@ func (this *FutureGoodChartInfoController) ChartInfoAdd() {
 		return
 	}
 	mapList := make([]*data_manage.ChartEdbMapping, 0)
+	hasAddMap := make(map[int]struct{})
 	for _, v := range barChartConfReq.EdbInfoIdList {
+		if _, ok := hasAddMap[v.EdbInfoId]; ok {
+			continue
+		}
+		hasAddMap[v.EdbInfoId] = struct{}{}
 		mapItem := new(data_manage.ChartEdbMapping)
 		mapItem.ChartInfoId = int(newId)
 		mapItem.EdbInfoId = v.EdbInfoId
@@ -609,7 +614,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 = "参数解析异常!"
@@ -1295,8 +1300,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()
@@ -1304,12 +1309,16 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 	}
 	futureGoodEdbInfoMapping, err = data_manage.GetFutureGoodEdbChartEdbMapping(chartInfoId)
 	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "期货指标不存在"
+			return
+		}
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
 		return
 	}
 
-	if edbInfoMapping == nil {
+	if len(edbInfoMappingList) == 0 {
 		br.Msg = "请选择ETA指标"
 		br.ErrMsg = "请选择ETA指标"
 		br.IsSendEmail = false
@@ -1323,16 +1332,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 = "商品价格曲线图未配置"
@@ -1345,10 +1356,24 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 		return
 	}
 
-	barChartInfoDateList = barConfig.DateList
+	baseEdbInfoId := barConfig.BaseEdbInfoId
+	var baseEdbInfoMapping *data_manage.ChartEdbInfoMapping
+
+	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()
@@ -1416,7 +1441,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
@@ -1498,8 +1523,8 @@ 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
@@ -1633,7 +1658,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()
@@ -1647,8 +1672,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 = "商品价格曲线图未配置"
@@ -1662,10 +1686,25 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		return
 	}
 
-	barChartInfoDateList = barConfig.DateList
+	baseEdbInfoId := barConfig.BaseEdbInfoId
+	var baseEdbInfoMapping *data_manage.ChartEdbInfoMapping
+
+	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()
@@ -1677,7 +1716,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		errMsg = "商品价格曲线图表异常"
 		return
 	}
-	baseEdbInfo := edbList[0] //现货指标
+	baseEdbInfo := baseEdbInfoMapping //现货指标
 
 	for _, v := range edbList {
 		if v.IsNullData {
@@ -2079,16 +2118,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 {
@@ -2099,8 +2135,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 = "商品价格曲线图未配置"
@@ -2114,8 +2149,6 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 			return
 		}
 
-		barChartInfoDateList = barConfig.DateList
-
 		startDate := chartInfo.StartDate
 		endDate := chartInfo.EndDate
 
@@ -2129,8 +2162,23 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 			}
 		}
 
+		baseEdbInfoId := barConfig.BaseEdbInfoId
+		var baseEdbInfoMapping *data_manage.ChartEdbInfoMapping
+		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()
@@ -2142,7 +2190,7 @@ func (this *FutureGoodChartInfoController) BaseChartInfoDetailFromUniqueCode() {
 			br.ErrMsg = "商品价格曲线图表异常"
 			return
 		}
-		baseEdbInfo := edbList[0] //现货指标
+		baseEdbInfo := baseEdbInfoMapping //现货指标
 
 		for _, v := range edbList {
 			if v.IsNullData {
@@ -2188,10 +2236,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
@@ -2901,7 +2951,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 = "参数解析异常!"
@@ -2925,21 +2975,43 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 		br.Msg = "请选择日期"
 		return
 	}
+	if req.BaseEdbInfoId == 0 {
+		br.Msg = "请选择日期基准指标"
+		return
+	}
 	chartInfo := new(data_manage.ChartInfoView)
 
-	if len(req.EdbInfoIdList) != 2 {
-		br.Msg = "指标数量异常"
+	var futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping
+	edbInfoMappingList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	edbIds := make([]int, 0)
+	goodEdbIds := make([]int, 0)
+	for _, v := range req.EdbInfoIdList {
+		if v.Source == utils.CHART_SOURCE_DEFAULT {
+			edbIds = append(edbIds, v.EdbInfoId)
+		} else if v.Source == utils.CHART_SOURCE_FUTURE_GOOD {
+			goodEdbIds = append(goodEdbIds, v.EdbInfoId)
+		}
+	}
+	edbInfoMappingListTmp, err := data_manage.GetChartEdbMappingListByEdbInfoIdList(edbIds)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
 		return
 	}
-	var edbInfoMapping, futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping
+	edbInfoMappingListMap := make(map[int]*data_manage.ChartEdbInfoMapping)
+	for _, v := range edbInfoMappingListTmp {
+		edbInfoMappingListMap[v.EdbInfoId] = v
+	}
+
 	for _, v := range req.EdbInfoIdList {
 		if v.Source == utils.CHART_SOURCE_DEFAULT {
-			edbInfoMapping, err = data_manage.GetChartEdbMappingByEdbInfoId(v.EdbInfoId)
-			if err != nil {
+			edbInfoMapping, ok := edbInfoMappingListMap[v.EdbInfoId]
+			if !ok {
 				br.Msg = "获取失败"
-				br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+				br.ErrMsg = "获取图表,指标信息失败 "
 				return
 			}
+			edbInfoMappingList = append(edbInfoMappingList, edbInfoMapping)
 		} else if v.Source == utils.CHART_SOURCE_FUTURE_GOOD {
 			futureGoodEdbInfoMapping, err = data_manage.GetChartEdbMappingByFutureGoodEdbInfoId(v.EdbInfoId)
 			if err != nil {
@@ -2955,7 +3027,7 @@ func (this *FutureGoodChartInfoController) PreviewBarChartInfo() {
 		}
 	}
 
-	if edbInfoMapping == nil {
+	if len(edbInfoMappingList) == 0 {
 		br.Msg = "请选择ETA指标"
 		br.ErrMsg = "请选择ETA指标"
 		br.IsSendEmail = false
@@ -2968,8 +3040,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()
@@ -2998,7 +3079,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
@@ -3031,7 +3112,7 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 		return
 	}
 
-	var req request.EditChartInfoBaseReq
+	var req data_manage.EditFutureGoodChartInfoBaseReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
 	if err != nil {
 		br.Msg = "参数解析异常!"
@@ -3071,19 +3152,22 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 	//	return
 	//}
 
-	edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartItem.ChartInfoId)
+	edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(req.ChartInfoId)
 	if err != nil {
-		br.Msg = "修改失败"
-		br.ErrMsg = "获取图表现货价格指标信息失败,指标信息失败,Err:" + err.Error()
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
 		return
 	}
-
+	edbIds := make([]int, 0)
+	for _, edbInfoMapping := range edbInfoMappingList {
+		edbIds = append(edbIds, edbInfoMapping.EdbInfoId)
+	}
 	//校验指标信息是否存在
-	edbInfo, err := data_manage.GetEdbInfoById(edbInfoMapping.EdbInfoId)
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(edbIds)
 	if err != nil {
 		if err.Error() == utils.ErrNoRow() {
 			br.Msg = "图表不存在!"
-			br.ErrMsg = "图表指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoMapping.EdbInfoId)
+			br.ErrMsg = "图表指标不存在,ChartInfoId:" + strconv.Itoa(req.ChartInfoId)
 			return
 		} else {
 			br.Msg = "获取图表信息失败!"
@@ -3091,10 +3175,53 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 			return
 		}
 	}
-	if edbInfo == nil {
-		br.Msg = "指标不存在!"
-		br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoMapping.EdbInfoId)
-		return
+	edbInfoListMap := make(map[int]*data_manage.EdbInfo)
+	for _, v := range edbInfoList {
+		edbInfoListMap[v.EdbInfoId] = v
+	}
+	var edbCondition string
+	var edbPars []interface{}
+	for _, v := range req.ChartEdbInfoList {
+		_, ok := edbInfoListMap[v.EdbInfoId]
+		if !ok {
+			br.Msg = "指标不存在!"
+			br.ErrMsg = "指标不存在,ChartInfoId:" + strconv.Itoa(v.EdbInfoId)
+			return
+		}
+		//校验指标名称是否已存在
+		//判断指标名称是否重复
+		if v.EdbName != "" {
+			edbCondition = ""
+			edbPars = make([]interface{}, 0)
+
+			edbCondition += " AND edb_info_id<>? "
+			edbPars = append(edbPars, v.EdbInfoId)
+
+			switch this.Lang {
+			case utils.EnLangVersion:
+				edbCondition += " AND edb_name_en =? "
+			default:
+				edbCondition += " AND edb_name =? "
+			}
+
+			edbPars = append(edbPars, v.EdbName)
+
+			edbExist, e := data_manage.GetEdbInfoByCondition(edbCondition, edbPars)
+			if e != nil {
+				if e.Error() != utils.ErrNoRow() {
+					br.Msg = "判断英文指标名称是否存在失败"
+					br.ErrMsg = "判断英文指标名称是否存在失败,Err:" + e.Error()
+					return
+				}
+			}
+
+			if e == nil && edbExist.EdbInfoId > 0 {
+				br.Msg = edbExist.EdbName + ":" + v.EdbName + "指标名称已存在"
+				br.ErrMsg = "指标名称已存在,请重新填写"
+				br.IsSendEmail = false
+				return
+			}
+		}
 	}
 
 	if req.ChartName != "" {
@@ -3128,7 +3255,59 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 
 	switch chartItem.Source {
 	case utils.CHART_SOURCE_FUTURE_GOOD:
-		err = data_manage.EditBaseFutureGoodChartInfoAndEdbEnInfo(chartItem, req.ChartName, edbInfo.EdbInfoId, req.EdbName, req.Unit, this.Lang)
+		if len(req.XDataList) > 0 {
+			// 处理横轴名称
+			// 商品价格曲线图的一些配置
+			var barConfig data_manage.FutureGoodBarChartInfoReq
+			if chartItem.BarConfig == `` {
+				br.Msg = "商品价格曲线图未配置"
+				br.ErrMsg = "商品价格曲线图未配置"
+				return
+			}
+			err = json.Unmarshal([]byte(chartItem.BarConfig), &barConfig)
+			if err != nil {
+				br.Msg = "商品价格曲线图配置异常"
+				br.ErrMsg = "商品价格曲线图配置异常"
+				return
+			}
+			xDataList := barConfig.XDataList
+			length := len(xDataList)
+			switch this.Lang {
+			case utils.EnLangVersion:
+				for k, v := range req.XDataList {
+					v = strings.TrimPrefix(v, " ")
+					if v != `` {
+						if length-1 >= k {
+							xDataList[k].NameEn = v
+						} else {
+							tmp := data_manage.XData{Name: v, NameEn: v}
+							xDataList = append(xDataList, tmp)
+						}
+					}
+				}
+			default:
+				for k, v := range req.XDataList {
+					v = strings.TrimPrefix(v, " ")
+					if v != `` {
+						if length-1 >= k {
+							xDataList[k].Name = v
+						} else {
+							tmp := data_manage.XData{Name: v, NameEn: v}
+							xDataList = append(xDataList, tmp)
+						}
+					}
+				}
+			}
+			barConfig.XDataList = xDataList
+			barConfigByte, e := json.Marshal(barConfig)
+			if e != nil {
+				br.Msg = "商品价格曲线图配置异常"
+				br.ErrMsg = "商品价格曲线图配置异常 Err:" + e.Error()
+				return
+			}
+			chartItem.BarConfig = string(barConfigByte)
+		}
+		err = data_manage.EditBaseFutureGoodChartInfoAndEdbEnInfo(chartItem, &req, this.Lang)
 		if req.FutureGoodName != `` {
 			futureGoodEdbInfoMapping, err := data_manage.GetFutureGoodEdbChartEdbMapping(chartItem.ChartInfoId)
 			if err != nil {
@@ -3140,7 +3319,7 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 			if err != nil {
 				if err.Error() == utils.ErrNoRow() {
 					br.Msg = "图表不存在!"
-					br.ErrMsg = "图表指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoMapping.EdbInfoId)
+					br.ErrMsg = "图表指标不存在,futureGoodEdbInfo:" + strconv.Itoa(futureGoodEdbInfo.FutureGoodEdbInfoId)
 					return
 				} else {
 					br.Msg = "获取图表信息失败!"
@@ -3150,7 +3329,7 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 			}
 			if futureGoodEdbInfo == nil {
 				br.Msg = "期货商品指标不存在!"
-				br.ErrMsg = "期货商品指标不存在,ChartInfoId:" + strconv.Itoa(edbInfoMapping.EdbInfoId)
+				br.ErrMsg = "期货商品指标不存在,futureGoodEdbInfo:" + strconv.Itoa(futureGoodEdbInfo.FutureGoodEdbInfoId)
 				return
 			}
 
@@ -3179,7 +3358,85 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 			}
 		}
 	case utils.CHART_SOURCE_FUTURE_GOOD_PROFIT:
-		err = data_manage.EditBaseFutureGoodProfitChartInfoAndEdbEnInfo(chartItem, req.ChartName, edbInfo.EdbInfoId, req.EdbName, req.Unit, req.ProfitName, this.Lang)
+		if len(req.XDataList) > 0 {
+			// 处理横轴名称
+			// 商品价格曲线图的一些配置
+			var barConfig request.ChartInfoReq
+			if chartItem.ExtraConfig == `` {
+				br.Msg = "商品价格曲线图未配置"
+				br.ErrMsg = "商品价格曲线图未配置"
+				return
+			}
+			err = json.Unmarshal([]byte(chartItem.ExtraConfig), &barConfig)
+			if err != nil {
+				br.Msg = "商品价格曲线图配置异常"
+				br.ErrMsg = "商品价格曲线图配置异常"
+				return
+			}
+			xDataList := barConfig.XDataList
+			length := len(xDataList)
+			switch this.Lang {
+			case utils.EnLangVersion:
+				for k, v := range req.XDataList {
+					v = strings.TrimPrefix(v, " ")
+					if v != `` {
+						if length-1 >= k {
+							xDataList[k].NameEn = v
+						} else {
+							tmp := data_manage.XData{Name: v, NameEn: v}
+							xDataList = append(xDataList, tmp)
+						}
+					}
+				}
+			default:
+				for k, v := range req.XDataList {
+					v = strings.TrimPrefix(v, " ")
+					if v != `` {
+						if length-1 >= k {
+							xDataList[k].Name = v
+						} else {
+							tmp := data_manage.XData{Name: v, NameEn: v}
+							xDataList = append(xDataList, tmp)
+						}
+					}
+				}
+			}
+			barConfig.XDataList = xDataList
+			barConfigByte, e := json.Marshal(barConfig)
+			if e != nil {
+				br.Msg = "商品价格曲线图配置异常"
+				br.ErrMsg = "商品价格曲线图配置异常 Err:" + e.Error()
+				return
+			}
+			chartItem.ExtraConfig = string(barConfigByte)
+
+			xDataByte, e := json.Marshal(xDataList)
+			if e != nil {
+				br.Msg = "商品价格曲线图配置异常"
+				br.ErrMsg = "商品价格曲线图配置异常 Err:" + e.Error()
+				return
+			}
+			//同步修改利润表扩展表
+			chartInfoFutureGoodProfit := new(future_good.ChartInfoFutureGoodProfit)
+			if e = chartInfoFutureGoodProfit.GetItemById(chartItem.ChartInfoId); e != nil {
+				br.Msg = "商品利润曲线图不存在"
+				br.ErrMsg = "商品利润曲线图查询出错 Err:" + e.Error()
+				return
+			}
+			chartInfoFutureGoodProfit.XValue = string(xDataByte)
+			// 更改扩展图表信息
+			// 查找商品利润图表的扩展信息
+			chartInfoFutureGoodProfit.ModifyTime = time.Now()
+			updateStr := []string{"ModifyTime", "XValue"}
+			err = chartInfoFutureGoodProfit.Update(updateStr)
+			if err != nil {
+				br.Msg = "商品利润曲线图更新失败"
+				br.ErrMsg = "商品利润曲线图更新失败 Err:" + e.Error()
+				return
+			}
+
+		}
+		err = data_manage.EditBaseFutureGoodProfitChartInfoAndEdbEnInfo(chartItem, &req, this.Lang)
 	default:
 		br.Msg = "错误的图表类型"
 		br.ErrMsg = "错误的图表类型"
@@ -3198,7 +3455,9 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 	go data.EsAddOrEditMyChartInfoByChartInfoId(chartItem.ChartInfoId)
 
 	//指标 修改es信息
-	go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+	for _, v := range edbIds {
+		go data.AddOrEditEdbInfoToEs(v)
+	}
 
 	//新增操作日志
 	{
@@ -3227,3 +3486,62 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 	br.Msg = "编辑成功"
 	br.IsAddLog = true
 }
+
+// ChartInfoImgSetBySvg
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_svg [post]
+func (this *FutureGoodChartInfoController) ChartInfoImgSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,ChartInfoId Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 163 - 105
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,41 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoAdd() {
 	extraConf := string(extraConfByte)
 
 	var edbInfoIdArr []int
+	baseEdbInfo := new(data_manage.EdbInfo)
 	// ETA指标
-	edbInfo, err := data_manage.GetEdbInfoById(extraReq.BaseEdbInfoId)
+	edbInfoListTmp, 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
+	}
+	//按照请求顺序排序
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	for _, v := range edbInfoListTmp {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+	edbInfoList := make([]*data_manage.EdbInfo, 0)
+	for _, v := range extraReq.EdbInfoIdList {
+		edbInfoList = append(edbInfoList, edbInfoMap[v])
+	}
+
+	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 +246,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 +292,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()
@@ -447,6 +461,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 = "商品利润曲线图信息异常"
@@ -456,34 +473,41 @@ func (this *FutureGoodChartInfoController) ProfitChartInfoEdit() {
 	extraConf := string(extraConfByte)
 
 	var edbInfoIdArr []int
+	baseEdbInfo := new(data_manage.EdbInfo)
 	// ETA指标
-	edbInfo, err := data_manage.GetEdbInfoById(extraReq.BaseEdbInfoId)
+	edbInfoListTmp, 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
+	}
+	//按照请求顺序排序
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	for _, v := range edbInfoListTmp {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+	edbInfoList := make([]*data_manage.EdbInfo, 0)
+	for _, v := range extraReq.EdbInfoIdList {
+		edbInfoList = append(edbInfoList, edbInfoMap[v])
+	}
+	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)
@@ -551,26 +575,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,
@@ -595,7 +620,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()
@@ -923,9 +948,29 @@ func (this *FutureGoodChartInfoController) PreviewProfitChartInfo() {
 		br.IsSendEmail = false
 		return
 	}
-
+	if len(req.EdbInfoIdList) == 0 {
+		br.Msg = "请选择ETA指标"
+		br.ErrMsg = "请选择ETA指标"
+		br.IsSendEmail = false
+		return
+	}
+	//查询基本指标信息
+	edbInfoListTmp, err := data_manage.GetEdbInfoByIdList(req.EdbInfoIdList)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	edbInfoMap := make(map[int]*data_manage.EdbInfo)
+	for _, v := range edbInfoListTmp {
+		edbInfoMap[v.EdbInfoId] = v
+	}
+	edbInfoList := make([]*data_manage.EdbInfo, 0)
+	for _, v := range req.EdbInfoIdList {
+		edbInfoList = append(edbInfoList, edbInfoMap[v])
+	}
 	// 获取图表中的指标数据
-	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()
@@ -933,25 +978,27 @@ func (this *FutureGoodChartInfoController) PreviewProfitChartInfo() {
 	}
 
 	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
-	edbList = append(edbList, &data_manage.ChartEdbInfoMapping{
-		EdbInfoId:      baseEdbInfo.EdbInfoId,
-		SourceName:     baseEdbInfo.SourceName,
-		Source:         baseEdbInfo.Source,
-		EdbCode:        baseEdbInfo.EdbCode,
-		EdbName:        baseEdbInfo.EdbName,
-		EdbAliasName:   baseEdbInfo.EdbName,
-		EdbNameEn:      baseEdbInfo.EdbNameEn,
-		EdbAliasNameEn: baseEdbInfo.EdbNameEn,
-		EdbType:        baseEdbInfo.EdbType,
-		Frequency:      baseEdbInfo.Frequency,
-		FrequencyEn:    data.GetFrequencyEn(baseEdbInfo.Frequency),
-		Unit:           baseEdbInfo.Unit,
-		UnitEn:         baseEdbInfo.UnitEn,
-		StartDate:      baseEdbInfo.StartDate,
-		EndDate:        baseEdbInfo.EndDate,
-		ModifyTime:     baseEdbInfo.ModifyTime.Format(utils.FormatDateTime),
-		MappingSource:  1,
-	})
+	for _, v := range edbInfoList {
+		edbList = append(edbList, &data_manage.ChartEdbInfoMapping{
+			EdbInfoId:      v.EdbInfoId,
+			SourceName:     v.SourceName,
+			Source:         v.Source,
+			EdbCode:        v.EdbCode,
+			EdbName:        v.EdbName,
+			EdbAliasName:   v.EdbName,
+			EdbNameEn:      v.EdbNameEn,
+			EdbAliasNameEn: v.EdbNameEn,
+			EdbType:        v.EdbType,
+			Frequency:      v.Frequency,
+			FrequencyEn:    data.GetFrequencyEn(v.Frequency),
+			Unit:           v.Unit,
+			UnitEn:         v.UnitEn,
+			StartDate:      v.StartDate,
+			EndDate:        v.EndDate,
+			ModifyTime:     v.ModifyTime.Format(utils.FormatDateTime),
+			MappingSource:  1,
+		})
+	}
 
 	warnEdbList := make([]string, 0)
 	for _, v := range edbList {
@@ -1003,15 +1050,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 = "获取失败"
@@ -1247,15 +1297,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 = "获取失败"
@@ -1295,7 +1354,6 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 	}
 
 	warnEdbList := make([]string, 0)
-	baseEdbInfo := edbList[0] //现货指标
 
 	for _, v := range edbList {
 		if v.IsNullData {

+ 784 - 25
controllers/data_manage/mysteel_chemical_data.go

@@ -6,17 +6,22 @@ import (
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/data_manage/request"
 	"eta/eta_api/models/data_manage/response"
+	"eta/eta_api/models/system"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
+	"eta/eta_api/services/data_stat"
+	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"github.com/tealeg/xlsx"
 	"os"
 	"path/filepath"
 	"sort"
+	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
 )
 
 // MysteelChemicalClassify
@@ -588,6 +593,17 @@ func (this *EdbInfoController) MysteelChemicalData() {
 		if dataList == nil {
 			dataList = make([]*data_manage.MysteelChemicalData, 0)
 		}
+		edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, v.IndexCode)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标库统计数据失败,Err:" + err.Error()
+			return
+		}
+		if edbInfo == nil {
+			list[k].EdbInfoId = 0
+		} else {
+			list[k].EdbInfoId = edbInfo.EdbInfoId
+		}
 		list[k].DataList = dataList
 		list[k].Paging = page
 	}
@@ -844,38 +860,117 @@ func (this *EdbClassifyController) AddMysteelChemical() {
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
+
 	resp := new(response.AddMysteelChemicalIndexInfoResp)
-	for i, v := range req.List {
-		if v.IndexCode == "" {
-			br.Msg = "请输入指标ID"
-			br.IsSendEmail = false
+	conf, err := models.GetBusinessConf()
+	if err != nil {
+		br.Msg = "添加指标失败"
+		br.ErrMsg = "获取业务配置失败,Err:" + err.Error()
+		return
+	}
+	// 判断钢联化工的数据刷新方式
+	if conf["MySteelDataMethod"] == "api" {
+		if len(req.List) > 150 {
+			br.Msg = "添加指标失败,指标数量不能超过150条"
 			return
 		}
-		if v.BaseFromMysteelChemicalClassifyId <= 0 {
-			br.Msg = "请选择分类"
-			br.IsSendEmail = false
+		addMySteelList := make([]*data_manage.BaseFromMysteelChemicalIndex, 0)
+		for _, v := range req.List {
+			if v.IndexCode == "" {
+				br.Msg = "请输入指标ID"
+				br.IsSendEmail = false
+				return
+			}
+			if v.BaseFromMysteelChemicalClassifyId <= 0 {
+				br.Msg = "请选择分类"
+				br.IsSendEmail = false
+				return
+			}
+
+			var updateTime1, updateTime2 string
+			if v.UpdateTime != `` {
+				updateTimeList := strings.Split(v.UpdateTime, ",")
+				updateTime1 = updateTimeList[0]
+				if len(updateTimeList) > 1 {
+					updateTime2 = updateTimeList[1]
+				}
+			}
+			v.IndexCode = utils.TrimStr(v.IndexCode)
+			v.IndexCode = strings.Replace(v.IndexCode, "\t", "", -1)
+
+			addMySteelList = append(addMySteelList, &data_manage.BaseFromMysteelChemicalIndex{
+				BaseFromMysteelChemicalClassifyId: v.BaseFromMysteelChemicalClassifyId,
+				IndexCode:                         strings.ToUpper(v.IndexCode),
+				IndexName:                         "",
+				Unit:                              "",
+				Frequency:                         "",
+				StartDate:                         "",
+				EndDate:                           "",
+				Describe:                          "",
+				Source:                            "api",
+				TerminalCode:                      "",
+				UpdateWeek:                        v.UpdateWeek,
+				UpdateTime:                        updateTime1,
+				UpdateTime2:                       updateTime2,
+				SysUserId:                         this.SysUser.AdminId,
+				SysUserRealName:                   this.SysUser.RealName,
+				ModifyTime:                        time.Now(),
+				CreateTime:                        time.Now(),
+			})
+		}
+		mySteelChemicalIndexInfos, err, errMsg := data.BatchAddMysteelChemicalIndex(addMySteelList, this.Lang)
+		if err != nil {
+			br.Msg = errMsg
+			br.ErrMsg = errMsg + ";Err:" + err.Error()
 			return
 		}
 
-		v.IndexCode = utils.TrimStr(v.IndexCode)
-		v.IndexCode = strings.Replace(v.IndexCode, "\t", "", -1)
-
-		//添加指标
-		mysteelChemicalIndexInfo, err, errMsg := data.AddMysteelChemicalIndex(v.BaseFromMysteelChemicalClassifyId, strings.ToUpper(v.IndexCode), v.UpdateWeek, v.UpdateTime, this.SysUser.AdminId, this.SysUser.RealName, this.Lang)
-		if errMsg != `` {
-			br.Msg = errMsg
-			br.ErrMsg = errMsg
+		if len(mySteelChemicalIndexInfos) > 0 {
+			resp.BaseFromMysteelChemicalIndexId = mySteelChemicalIndexInfos[0].BaseFromMysteelChemicalIndexId
+			resp.IndexCode = mySteelChemicalIndexInfos[0].IndexCode
+			resp.UniqueCode = fmt.Sprint(mySteelChemicalIndexInfos[0].BaseFromMysteelChemicalClassifyId, "_", mySteelChemicalIndexInfos[0].BaseFromMysteelChemicalIndexId)
+		}
+		for _, v := range mySteelChemicalIndexInfos {
+			err = utils.Rc.LPush(utils.CACHE_MYSTEEL_CHEMICAL_ADD_DATA, v.IndexCode)
 			if err != nil {
-				br.ErrMsg = errMsg + ";Err:" + err.Error()
-			} else {
-				br.IsSendEmail = false
+				br.Msg = "添加指标失败" + v.IndexCode
+				br.ErrMsg = "添加指标缓存失败" + v.IndexCode + ";Err:" + err.Error()
+				return
 			}
-			return
 		}
-		if i == 0 {
-			resp.BaseFromMysteelChemicalIndexId = mysteelChemicalIndexInfo.BaseFromMysteelChemicalIndexId
-			resp.IndexCode = mysteelChemicalIndexInfo.IndexCode
-			resp.UniqueCode = fmt.Sprint(mysteelChemicalIndexInfo.BaseFromMysteelChemicalClassifyId, "_", mysteelChemicalIndexInfo.BaseFromMysteelChemicalIndexId)
+	} else {
+		for i, v := range req.List {
+			if v.IndexCode == "" {
+				br.Msg = "请输入指标ID"
+				br.IsSendEmail = false
+				return
+			}
+			if v.BaseFromMysteelChemicalClassifyId <= 0 {
+				br.Msg = "请选择分类"
+				br.IsSendEmail = false
+				return
+			}
+
+			v.IndexCode = utils.TrimStr(v.IndexCode)
+			v.IndexCode = strings.Replace(v.IndexCode, "\t", "", -1)
+
+			//添加指标
+			mysteelChemicalIndexInfo, err, errMsg := data.AddMysteelChemicalIndex(v.BaseFromMysteelChemicalClassifyId, strings.ToUpper(v.IndexCode), v.UpdateWeek, v.UpdateTime, this.SysUser.AdminId, this.SysUser.RealName, this.Lang)
+			if errMsg != `` {
+				br.Msg = errMsg
+				br.ErrMsg = errMsg
+				if err != nil {
+					br.ErrMsg = errMsg + ";Err:" + err.Error()
+				} else {
+					br.IsSendEmail = false
+				}
+				return
+			}
+			if i == 0 {
+				resp.BaseFromMysteelChemicalIndexId = mysteelChemicalIndexInfo.BaseFromMysteelChemicalIndexId
+				resp.IndexCode = mysteelChemicalIndexInfo.IndexCode
+				resp.UniqueCode = fmt.Sprint(mysteelChemicalIndexInfo.BaseFromMysteelChemicalClassifyId, "_", mysteelChemicalIndexInfo.BaseFromMysteelChemicalIndexId)
+			}
 		}
 	}
 
@@ -1173,3 +1268,667 @@ func (this *EdbInfoController) MysteelChemicalRefresh() {
 	br.Msg = "刷新成功"
 	br.Data = item
 }
+
+// NameCheck
+// @Title 重名校验
+// @Description 批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /mysteel_chemical/name_check [post]
+func (c *EdbInfoController) NameCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req []*data_manage.NameCheckEdbInfoReq
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	codeMax := 30
+	codeLen := len(req)
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+
+	type NameCheckResult struct {
+		EdbCode string
+		EdbName string
+		Exist   bool
+	}
+	indexNames := make([]string, 0)
+	resp := make([]*NameCheckResult, 0)
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		indexNames = append(indexNames, v.EdbName)
+		resp = append(resp, &NameCheckResult{
+			EdbCode: v.EdbCode,
+			EdbName: v.EdbName,
+		})
+		dataItems, err := data_manage.GetEdbDataAllByEdbCode(v.EdbCode, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, 0, utils.EDB_DATA_LIMIT)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
+			return
+		}
+		if len(dataItems) <= 0 {
+			respItem, err := data.AddEdbData(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, v.EdbCode, v.Frequency)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				return
+			}
+			if respItem.Ret != 200 {
+				br.Msg = "未搜索到该指标"
+				br.ErrMsg = respItem.ErrMsg + ";EdbCode:" + v.EdbCode
+				return
+			}
+		}
+	}
+
+	// 重名校验
+	edbList, e := data_manage.GetEdbInfoByNameArr(indexNames, utils.EDB_INFO_TYPE)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取重名指标失败, Err: " + e.Error()
+		return
+	}
+	nameExists := make(map[string]bool)
+	for _, v := range edbList {
+		nameExists[v.EdbName] = true
+	}
+	if len(nameExists) > 0 {
+		for _, v := range resp {
+			v.Exist = nameExists[v.EdbName]
+		}
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// BatchAdd
+// @Title 批量新增
+// @Description 批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /mysteel_chemical/batch_add [post]
+func (this *EdbInfoController) BatchAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_BATCH_ADD_MYSTEEL_CHEMICAL_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req []*data_manage.AddEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if len(req) > 30 {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		v.Frequency = strings.TrimSpace(v.Frequency)
+		if v.Frequency == "" {
+			br.Msg = "请选择频度"
+			return
+		}
+		v.Unit = strings.TrimSpace(v.Unit)
+		if v.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		if v.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+	}
+
+	// 限定同一时间最多批量新增30个指标
+	for _, v := range req {
+		var r data.MysteelChemicalIndexSource2EdbReq
+		r.EdbCode = v.EdbCode
+		r.EdbName = v.EdbName
+		r.Frequency = v.Frequency
+		r.Unit = v.Unit
+		r.ClassifyId = v.ClassifyId
+		r.AdminId = sysUser.AdminId
+		r.AdminRealName = sysUser.RealName
+
+		edbInfo, e, errMsg, skip := data.MysteelChemicalIndexSource2Edb(r, this.Lang)
+		if e != nil {
+			br.Msg = "操作失败"
+			if errMsg != "" {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = e.Error()
+			return
+		}
+		if skip {
+			continue
+		}
+
+		// 试用平台更新用户累计新增指标数
+		if utils.BusinessCode == utils.BusinessCodeSandbox {
+			go func() {
+				adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+				if e != nil {
+					tips := fmt.Sprintf("试用平台更新用户累计新增指标数-获取用户失败, Err: " + e.Error())
+					utils.FileLog.Info(tips)
+					return
+				}
+				if adminItem.DepartmentName != "ETA试用客户" {
+					return
+				}
+				var ur etaTrialService.EtaTrialUserReq
+				ur.Mobile = adminItem.Mobile
+				_, _ = etaTrialService.UpdateUserIndexNum(ur)
+			}()
+		}
+
+		// 新增操作日志
+		{
+			edbLog := new(data_manage.EdbInfoLog)
+			edbLog.EdbInfoId = edbInfo.EdbInfoId
+			edbLog.SourceName = edbInfo.SourceName
+			edbLog.Source = edbInfo.Source
+			edbLog.EdbCode = edbInfo.EdbCode
+			edbLog.EdbName = edbInfo.EdbName
+			edbLog.ClassifyId = edbInfo.ClassifyId
+			edbLog.SysUserId = sysUser.AdminId
+			edbLog.SysUserRealName = sysUser.RealName
+			edbLog.CreateTime = time.Now()
+			edbLog.Content = string(this.Ctx.Input.RequestBody)
+			edbLog.Status = "新增指标"
+			edbLog.Method = this.Ctx.Input.URI()
+			go data_manage.AddEdbInfoLog(edbLog)
+		}
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// EdbInfoAdd
+// @Title 新增指标接口
+// @Description 新增指标接口
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /mysteel_chemical/edb_info/add [post]
+func (this *EdbInfoController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req data_manage.AddEdbInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	req.EdbName = strings.Trim(req.EdbName, " ")
+	req.EdbCode = strings.Trim(req.EdbCode, " ")
+
+	if req.EdbCode == "" {
+		br.Msg = "指标ID不能为空"
+		return
+	}
+
+	if req.EdbName == "" {
+		br.Msg = "指标名称不能为空"
+		return
+	}
+
+	if req.Frequency == "" {
+		br.Msg = "频率不能为空"
+		return
+	}
+
+	if req.Unit == "" {
+		br.Msg = "单位不能为空"
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	tmpInfo, err := data_manage.GetBaseFromMysteelChemicalIndexByCode(req.EdbCode)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	}
+	var source int
+	if tmpInfo != nil {
+		source = utils.DATA_SOURCE_MYSTEEL_CHEMICAL
+	} else {
+		br.Msg = "指标不存在"
+		return
+	}
+
+	// 指标入库
+	edbInfo, err, errMsg, isSendEmail := data.EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, req.StartDate, req.EndDate, sysUser.AdminId, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 试用平台更新用户累计新增指标数
+	adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + err.Error()
+		return
+	}
+	if utils.BusinessCode == utils.BusinessCodeSandbox && adminItem.DepartmentName == "ETA试用客户" {
+		go func() {
+			var r etaTrialService.EtaTrialUserReq
+			r.Mobile = adminItem.Mobile
+			_, _ = etaTrialService.UpdateUserIndexNum(r)
+		}()
+	}
+
+	//新增操作日志
+	{
+		// 添加钢联指标更新日志
+		if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+			go data_stat.AddEdbInfoUpdateLog(edbInfo.EdbInfoId, 1, "", sysUser, 2)
+		}
+
+		edbLog := new(data_manage.EdbInfoLog)
+		edbLog.EdbInfoId = edbInfo.EdbInfoId
+		edbLog.SourceName = edbInfo.SourceName
+		edbLog.Source = edbInfo.Source
+		edbLog.EdbCode = edbInfo.EdbCode
+		edbLog.EdbName = edbInfo.EdbName
+		edbLog.ClassifyId = edbInfo.ClassifyId
+		edbLog.SysUserId = sysUser.AdminId
+		edbLog.SysUserRealName = sysUser.RealName
+		edbLog.CreateTime = time.Now()
+		edbLog.Content = string(this.Ctx.Input.RequestBody)
+		edbLog.Status = "新增指标"
+		edbLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddEdbInfoLog(edbLog)
+	}
+
+	// 更新es
+	go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+
+	resp := new(data_manage.AddEdbInfoResp)
+	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.UniqueCode = edbInfo.UniqueCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// AddCheck
+// @Title 新增指标检测接口
+// @Description 新增指标检测接口
+// @Param   EdbCode   query   string  true      "指标编码/指标代码"
+// @Success Ret=200 保存成功
+// @router /mysteel_chemical/edb_info/add_check [get]
+// func (this *EdbInfoController) AddCheck() {
+// 	br := new(models.BaseResponse).Init()
+// 	defer func() {
+// 		this.Data["json"] = br
+// 		this.ServeJSON()
+// 	}()
+
+// 	sysUser := this.SysUser
+// 	if sysUser == nil {
+// 		br.Msg = "请登录"
+// 		br.ErrMsg = "请登录,SysUser Is Empty"
+// 		br.Ret = 408
+// 		return
+// 	}
+// var req []*data_manage.AddCheckEdbInfoReq
+// 	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+// 		br.Msg = "参数解析异常!"
+// 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+// 		return
+// 	}
+
+// 	source := utils.DATA_SOURCE_MYSTEEL_CHEMICAL
+// 	indexNameMap := make(map[string]string)
+// 	indexNames := make([]string, 0)
+// 	for _, v := range req {
+// 		v.EdbCode = strings.Trim(v.EdbCode, "\t")
+// 		v.EdbCode = strings.Trim(v.EdbCode, " ")
+// 		v.EdbCode = strings.Replace(v.EdbCode, "\t", "", -1)
+// 		if v.EdbCode == "" {
+// 			br.Msg = "请输入指标ID"
+// 			return
+// 		}
+// 		v.EdbName = strings.Trim(v.EdbName, "\t")
+// 		v.EdbName = strings.Trim(v.EdbName, " ")
+// 		v.EdbName = strings.Replace(v.EdbName, "\t", "", -1)
+// 		if v.EdbName == "" {
+// 			br.Msg = "请输入指标名称"
+// 			return
+// 		}
+// 		indexNames = append(indexNames, v.EdbName)
+
+// 		item, err := data_manage.GetEdbInfoByEdbCode(source, edbCode)
+// 		if err != nil && err.Error() != utils.ErrNoRow() {
+// 			br.Msg = "获取失败"
+// 			br.ErrMsg = "获取失败,Err:" + err.Error()
+// 			return
+// 		}
+
+// 		resp := new(data_manage.EdbInfoMySteelChemicalCheckResp)
+// 		if item != nil && item.EdbInfoId > 0 {
+// 			resp.Status = 1
+// 			// 查询该指标是否有权限
+// 			obj := data_manage.EdbInfoNoPermissionAdmin{}
+// 			conf, err := obj.GetByEdbInfoIdAndAdminId(this.SysUser.AdminId, item.EdbInfoId)
+// 			if err != nil && err.Error() != utils.ErrNoRow() {
+// 				br.Msg = "获取失败"
+// 				br.ErrMsg = "获取当前账号的不可见指标配置失败,err:" + err.Error()
+// 				return
+// 			}
+// 			if conf != nil {
+// 				resp.Status = 3
+// 			}
+// 		} else {
+// 			resp.Status = 2
+// 			dataItems, err := data_manage.GetEdbDataAllByEdbCode(edbCode, source, 0, utils.EDB_DATA_LIMIT)
+// 			if err != nil && err.Error() != utils.ErrNoRow() {
+// 				br.Msg = "获取失败"
+// 				br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
+// 				return
+// 			}
+
+// 			if len(dataItems) <= 0 {
+// 				respItem, err := data.AddEdbData(source, edbCode, frequency)
+// 				if err != nil {
+// 					br.Msg = "获取失败"
+// 					br.ErrMsg = "获取失败,Err:" + err.Error()
+// 					return
+// 				}
+// 				if respItem.Ret != 200 {
+// 					br.Msg = "未搜索到该指标"
+// 					br.ErrMsg = respItem.ErrMsg + ";EdbCode:" + edbCode
+// 					return
+// 				}
+// 			}
+// 		}
+// 	}
+
+// 	br.Ret = 200
+// 	br.Success = true
+// 	br.Msg = "保存成功"
+// 	br.Data = resp
+// 	br.IsAddLog = true
+// }
+
+// AddCheck
+// @Title 新增校验
+// @Description 新增校验
+// @Param	request	body request.BusinessDataBatchAddCheckReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /mysteel_chemical/edb_info/add_check [post]
+func (c *EdbInfoController) AddCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req *data_manage.MysteelChemicalDataBatchAddCheckReq
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	codeMax := 30
+	codeLen := len(req.IndexCodes)
+
+	// 获取指标库已有指标
+	existsEdb, e := data_manage.GetEdbCodesBySource(utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取自有数据已添加的指标失败, Err: " + e.Error()
+		return
+	}
+	existMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range existsEdb {
+		existMap[v.EdbCode] = v
+	}
+
+	obj := data_manage.BaseFromMysteelChemicalIndex{}
+	if codeLen == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+
+	// 查询选中的指标
+	cond := fmt.Sprintf(` AND %s IN (%s)`, data_manage.BaseFromMysteelChemicalIndexCols.IndexCode, utils.GetOrmInReplace(codeLen))
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.IndexCodes)
+	list, e := obj.GeItemsByCondition(cond, pars, 0)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取自有数据原始指标列表失败, Err: " + e.Error()
+		return
+	}
+
+	if len(list) > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+
+	resp := make([]*data_manage.BaseFromMysteelChemicalIndexItem, 0)
+	for _, v := range list {
+		if v.EdbInfoId > 0 {
+			v.EdbExist = 1
+		}
+		resp = append(resp, v)
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// MysteelChemicalSearch
+// @Title 钢联化工指标查询
+// @Description 钢联化工指标查询
+// @Param   BaseFromMysteelChemicalClassifyIds   query   string  true       "分类id"
+// @Param   Keyword   query   string  true       "名称关键词"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /mysteel_chemical/batch_search [get]
+func (this *EdbInfoController) MysteelChemicalBatchSearch() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	classifyIdStr := this.GetString("BaseFromMysteelChemicalClassifyIds")
+
+	var condition string
+	var pars []interface{}
+	classifyIds := strings.Split(classifyIdStr, ",")
+	if len(classifyIds) > 0 && classifyIds[0] != `` {
+		condition += " AND base_from_mysteel_chemical_classify_id IN (" + utils.GetOrmInReplace(len(classifyIds)) + " ) "
+		pars = append(pars, classifyIds)
+	}
+	keyword := this.GetString("Keyword")
+	if keyword != `` {
+		condition += " AND (index_name like ? OR index_code like ?) "
+		pars = utils.GetLikeKeywordPars(pars, keyword, 2)
+	}
+
+	if classifyIdStr == `` && keyword == `` {
+		var list = make([]*data_manage.MysteelChemicalList, 0)
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = list
+		return
+	}
+
+	list, err := data_manage.GetMysteelChemicalIndex(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	classifyIdInts := make([]int, 0)
+	for _, v := range list {
+		v.UniqueCode = fmt.Sprint(v.BaseFromMysteelChemicalClassifyId, "_", v.Id)
+		if v.BaseFromMysteelChemicalClassifyId > 0 {
+			classifyIdInts = append(classifyIdInts, v.BaseFromMysteelChemicalClassifyId)
+		}
+	}
+	if len(classifyIdInts) > 0 {
+		// 查询父级分类信息
+		classifyList, e := data_manage.GetBaseFromMysteelChemicalClassifyByIds(classifyIdInts)
+		if e != nil {
+			br.Msg = "获取目录信息失败"
+			br.ErrMsg = "获取失获取目录信息失败,Err:" + e.Error()
+			return
+		}
+		classifyListMap := make(map[int]int, 0)
+		for _, v := range classifyList {
+			classifyListMap[v.BaseFromMysteelChemicalClassifyId] = v.ParentId
+		}
+		for _, v := range list {
+			v.UniqueCode = fmt.Sprint(v.BaseFromMysteelChemicalClassifyId, "_", v.Id)
+			if p, ok := classifyListMap[v.BaseFromMysteelChemicalClassifyId]; ok {
+				v.ParentClassifyId = p
+			}
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}

+ 78 - 0
controllers/data_manage/predict_edb_info.go

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

+ 7 - 7
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()
@@ -676,7 +676,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 +779,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 +838,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 +1301,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 +1324,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 +1392,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()

+ 1 - 1
controllers/ppt_v2.go

@@ -813,7 +813,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()

File diff suppressed because it is too large
+ 434 - 499
controllers/report.go


+ 77 - 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
 	}
 
@@ -311,14 +317,71 @@ func (this *ReportApproveController) List() {
 		ormList = list
 	}
 
+	// 审批通过的报告显示下载按钮
+	reportImg := make(map[string]string)
+	reportPdf := make(map[string]string)
+	{
+		var reportIds, enReportIds []int
+		for _, v := range ormList {
+			if v.State != report_approve.ReportApproveStatePass {
+				continue
+			}
+			if v.ReportType == report_approve.FlowReportTypeChinese {
+				reportIds = append(reportIds, v.ReportId)
+			}
+			if v.ReportType == report_approve.FlowReportTypeEnglish {
+				enReportIds = append(enReportIds, v.ReportId)
+			}
+		}
+		if len(reportIds) > 0 {
+			reports, e := models.GetReportFieldsByIds(reportIds, []string{"id", "detail_img_url", "detail_pdf_url"})
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("GetReportFieldsByIds, %v", e)
+				return
+			}
+			for _, r := range reports {
+				k := fmt.Sprintf("%d-%d", report_approve.FlowReportTypeChinese, r.Id)
+				reportImg[k] = r.DetailImgUrl
+				reportPdf[k] = r.DetailPdfUrl
+			}
+		}
+		if len(enReportIds) > 0 {
+			enReports, e := models.GetEnglishReportFieldsByIds(enReportIds, []string{"id", "detail_img_url", "detail_pdf_url"})
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("GetEnglishReportFieldsByIds, %v", e)
+				return
+			}
+			for _, r := range enReports {
+				k := fmt.Sprintf("%d-%d", report_approve.FlowReportTypeEnglish, r.Id)
+				reportImg[k] = r.DetailImgUrl
+				reportPdf[k] = r.DetailPdfUrl
+			}
+		}
+	}
+
 	// 格式化列表
 	for _, v := range ormList {
 		t := report_approve.FormatReportApproveOrm2Item(v)
 		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
 		}
+
+		k := fmt.Sprintf("%d-%d", t.ReportType, t.ReportId)
+		t.DetailImgUrl = reportImg[k]
+		t.DetailPdfUrl = reportPdf[k]
+
 		respList = append(respList, t)
 	}
 
@@ -502,9 +565,18 @@ func (this *ReportApproveController) Detail() {
 	detail.Report.ReportType = approveItem.ReportType
 	detail.Report.ReportId = approveItem.ReportId
 	detail.Report.ReportTitle = approveItem.ReportTitle
+	detail.Report.ReportLayout = 1
 
 	// 报告分类路由
 	if approveItem.ReportType == report_approve.FlowReportTypeChinese {
+		reportInfo, e := models.GetReportLayoutByReportId(approveItem.ReportId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取报告失败, Err: " + e.Error()
+			return
+		}
+		detail.Report.ReportLayout = reportInfo.ReportLayout
+
 		detail.Report.ReportCode = utils.MD5(strconv.Itoa(approveItem.ReportId))
 		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], cnClassifyIdName[approveItem.ClassifyFirstId], cnClassifyIdName[approveItem.ClassifySecondId])
 	}
@@ -513,7 +585,8 @@ 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.ReportLayout = 2
+		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 +995,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()

+ 40 - 34
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)
 	}
@@ -193,7 +206,7 @@ func (this *ReportApproveFlowController) Add() {
 		br.ErrMsg = fmt.Sprintf("审批流报告类型有误, ReportType: %d", req.ReportType)
 		return
 	}
-	if req.ClassifyFirstId <= 0 || req.ClassifySecondId <= 0 {
+	if req.ClassifyFirstId <= 0 && req.ClassifySecondId <= 0 && req.ClassifyThirdId <= 0 {
 		br.Msg = "请选择报告分类"
 		return
 	}
@@ -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
@@ -330,7 +345,7 @@ func (this *ReportApproveFlowController) Edit() {
 		br.ErrMsg = fmt.Sprintf("审批流报告类型有误, ReportType: %d", req.ReportType)
 		return
 	}
-	if req.ClassifyFirstId <= 0 || req.ClassifySecondId <= 0 {
+	if req.ClassifyFirstId <= 0 && req.ClassifySecondId <= 0 && req.ClassifyThirdId <= 0 {
 		br.Msg = "请选择报告分类"
 		return
 	}
@@ -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, "")
 		}

+ 1629 - 0
controllers/report_chapter.go

@@ -0,0 +1,1629 @@
+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"
+	"strings"
+	"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
+	}
+
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == `` {
+		br.Msg = "章节名称不能为空"
+		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
+	}
+
+	// 判断名称是否重复
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND report_id = ? AND title=? "
+		pars = append(pars, req.ReportId, req.Title)
+		count, err := models.GetCountReportChapterByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "新增失败"
+			br.ErrMsg = "判断章节名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "章节名称不允许重复"
+			br.ErrMsg = "章节名称不允许重复"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	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
+	}
+
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == `` {
+		br.Msg = "章节名称不能为空"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 判断名称是否重复
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND report_id = ? AND title=? AND report_chapter_id != ? "
+		pars = append(pars, reportChapterInfo.ReportId, req.Title, reportChapterId)
+		count, err := models.GetCountReportChapterByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "新增失败"
+			br.ErrMsg = "判断章节名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "章节名称不允许重复"
+			br.ErrMsg = "章节名称不允许重复"
+			br.IsSendEmail = false
+			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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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
+		}
+	}
+
+	if req.Title != "" {
+		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
+	reportChapterInfo.ModifyTime = time.Now()
+	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", "ModifyTime")
+
+	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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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
+		reportInfo.VoiceGenerateType = 1
+		reportChapterInfo.ModifyTime = time.Now()
+		err = reportChapterInfo.UpdateChapter([]string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "VideoKind", "LastModifyAdminId", "LastModifyAdminName", "VoiceGenerateType", "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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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
+		}
+	}()
+
+	// 生成音频
+	go services.UpdateChaptersVideo([]int{chapterInfo.ReportChapterId})
+
+	// 更新章节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
+}
+
+// EditChapterTitle
+// @Title 编辑章节标题
+// @Description 编辑章节标题
+// @Param	request	body models.EditReportChapterReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /chapter/title/edit [post]
+func (this *ReportController) EditChapterTitle() {
+	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
+	}
+
+	req.Title = strings.TrimSpace(req.Title)
+	if req.Title == `` {
+		br.Msg = "章节名称不能为空"
+		return
+	}
+
+	// 获取章节详情
+	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
+	if err != nil {
+		br.Msg = "报告章节信息有误"
+		br.ErrMsg = "报告章节信息有误, Err: " + err.Error()
+		return
+	}
+
+	// 判断名称是否重复
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND report_id = ? AND title=? AND report_chapter_id != ? "
+		pars = append(pars, reportChapterInfo.ReportId, req.Title, reportChapterId)
+		count, err := models.GetCountReportChapterByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "新增失败"
+			br.ErrMsg = "判断章节名称是否存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "章节名称不允许重复"
+			br.ErrMsg = "章节名称不允许重复"
+			br.IsSendEmail = false
+			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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		return
+	}
+	// 报告的最后编辑人
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	reportChapterInfo.Title = req.Title
+	reportChapterInfo.LastModifyAdminId = sysUser.AdminId
+	reportChapterInfo.LastModifyAdminName = sysUser.RealName
+	reportChapterInfo.ModifyTime = time.Now()
+
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "Title", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+
+	// 更新报告的最近编辑人信息
+	if err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}); err != nil {
+		br.Msg = "保存失败"
+		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+		return
+	}
+	// 更新章节
+	if err = reportChapterInfo.UpdateChapter(updateCols); 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
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}

+ 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()

+ 1956 - 0
controllers/report_v2.go

@@ -0,0 +1,1956 @@
+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, classifyIdThird)
+	}
+
+	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([]int, 0)
+		syncReportIdArr := make([]string, 0)      // 同步过来的报告IDs
+		oldAndNewReportIdMap := make(map[int]int) // 旧报告和新报告的id对应关系
+		for i := 0; i < listLen; i++ {
+			reportIdArr = append(reportIdArr, 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]
+		}
+
+		// 多人协作的协作报告,需要判断是否可编辑
+		{
+			grantObj := report.ReportGrant{}
+			grantList, err := grantObj.GetGrantListByIdList(reportIdArr)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取报告授权失败,Err:" + err.Error()
+				return
+			}
+
+			//grantMap := make(map[报告id]map[用户id]bool)
+			grantMap := make(map[int]map[int]bool)
+			for _, v := range grantList {
+				grantUserMap, ok := grantMap[v.ReportId]
+				if !ok {
+					grantUserMap = make(map[int]bool)
+				}
+				grantUserMap[v.AdminId] = true
+				grantMap[v.ReportId] = grantUserMap
+			}
+
+			for i, item := range list {
+				if item.AdminId == this.SysUser.AdminId {
+					list[i].HasAuth = true
+					continue
+				}
+
+				// 查找授权
+				var hasAuth bool
+				grantUserMap, ok := grantMap[item.Id]
+
+				// 如果报告根本没有授权用户,说明没有授权当前用户
+				if !ok {
+					continue
+				}
+				_, ok = grantUserMap[this.SysUser.AdminId]
+				list[i].HasAuth = hasAuth
+				// 如果报告关联用户找到,说明有授权当前用户
+				if ok {
+					list[i].HasAuth = true
+				}
+			}
+
+		}
+	}
+
+	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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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 = "该报告已发布,不允许编辑"
+		br.IsSendEmail = false
+		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和长图
+	{
+		reportPdfUrl := services.GetGeneralPdfUrl(reportDetail.ReportCode, reportDetail.ReportLayout)
+		go services.Report2pdfAndJpeg(reportPdfUrl, 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
+	}
+
+	// 报告权限处理
+	{
+		go services.HandleReportPermission(reportItem)
+	}
+
+	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 = recordItem.State
+		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}
+	notIdList := []int{}
+	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("修复智能研报完成")
+}

+ 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 = "操作成功"
 }
-

+ 90 - 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,40 @@ 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.VoiceGenerateType = 1
+		reportInfo.ModifyTime = time.Now()
+		err = reportInfo.UpdateReport([]string{"VideoUrl", "VideoName", "VideoPlaySeconds", "VideoSize", "LastModifyAdminId", "LastModifyAdminName", "VoiceGenerateType", "ModifyTime"})
+		if err != nil {
+			br.Msg = "上传失败"
+			br.ErrMsg = "修改报告的音频信息失败,Err:" + err.Error()
+			return
+		}
 	}
+
 	resp := new(models.ResourceResp)
 	resp.Id = newId
 	resp.ResourceUrl = resourceUrl
@@ -175,36 +193,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 音频下载接口

+ 133 - 0
models/ai_summary/ai_pormpt.go

@@ -0,0 +1,133 @@
+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:分享
+	Sort          int       // 排序
+}
+
+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:分享
+	Sort          int    // 排序
+}
+
+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 sort  `
+	} else {
+		sql = ` SELECT * FROM ai_prompt WHERE sys_admin_id=? ORDER BY sort  `
+	}
+	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
+}
+
+type AiPromptAddReq struct {
+	PromptContent string // 原始内容
+	Title         string // 文档标题
+}
+
+func AddPropmt(item *AiPrompt) (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(item)
+	return
+}
+
+type AiPromptEditReq struct {
+	PromptContent string // 原始内容
+	Title         string // 文档标题
+	AiPromptId    int    // ai纪要提示词id
+}
+
+// Update
+func (aiPrompt *AiPrompt) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(aiPrompt, cols...)
+	return
+}
+
+// DelAiSummaryById 根据纪要id删除纪要
+func DelAiPromptyId(aiPromptId int) (err error) {
+	o := orm.NewOrm()
+	sql := `delete from ai_prompt where ai_prompt_id = ? `
+	_, err = o.Raw(sql, aiPromptId).Exec()
+	return
+}
+
+func GetAiPromptById(promptId int) (item *AiPrompt, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM ai_prompt WHERE 1=1 AND ai_prompt_id=? `
+	err = o.Raw(sql, promptId).QueryRow(&item)
+	return
+}
+
+type DeleteAipromptReq struct {
+	AiPromptId int // ai纪要提示词id
+}
+
+type ReqMovePrompt struct {
+	AiPromptId     int `description:"当前被移动的目录里的ppt绑定序号"`
+	PrevAiPromptId int `description:"上一个目录里的ppt绑定序号,ppt最终在目录中置顶,则传0值"`
+	NextAiPromptId int `description:"下一个目录里的ppt绑定序号,ppt最终在目录末尾,则传0值"`
+}
+
+// GetAiPromptMaxSort 获取ai提示词下最大的排序数
+func GetAiPromptMaxSort(adminId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT Max(sort) AS sort FROM ai_prompt WHERE sys_admin_id=? `
+	err = o.Raw(sql, adminId).QueryRow(&sort)
+	return
+}
+
+
+// MoveDownPromptBySort 往下移动提示词
+func MoveDownPromptBySort(sysAdminId, prevSort, currentSort int) (err error) {
+	o := orm.NewOrm()
+	sql := `update ai_prompt set sort = sort - 1 where sys_admin_id = ? and sort <= ? and sort> ? `
+	_, err = o.Raw(sql, sysAdminId, prevSort, currentSort).Exec()
+	return
+}
+
+// MoveUpPromptBySort 往上移动提示词
+func MoveUpPromptBySort(sysAdminId, nextSort, currentSort int) (err error) {
+	o := orm.NewOrm()
+	sql := `update ai_prompt set sort = sort + 1 where sys_admin_id = ? and sort >= ? and sort< ?`
+	_, err = o.Raw(sql, sysAdminId, nextSort, currentSort).Exec()
+	return
+}

+ 176 - 0
models/ai_summary/ai_summary.go

@@ -0,0 +1,176 @@
+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    // 原始内容
+	SummaryContent  string    // 纪要内容
+	ClassifyId      int       // 分类id
+	SysUserId       int       // 创建人ID
+	SysUserRealName string    // 创建人姓名
+	Title           string    // 文档标题
+	CreateTime      time.Time // 创建时间
+	ModifyTime      time.Time // 更新时间
+	OpenaiFileName  string    // 文件名称
+	OpenaiFilePath  string    // 文件路径
+	OriginTitle     string    // 原文标题
+	Sort            int       // 排序字段,越小越靠前,默认值:10
+}
+
+type AiSummaryItems struct {
+	AiSummaryId     int    `orm:"column(ai_summary_id);pk"` // ai纪要id
+	SaDocId         int    // 文档ID
+	OriginContent   string // 原始内容
+	OriginTitle     string // 原文标题
+	SummaryContent  string // 纪要内容
+	ClassifyId      int    // 分类id
+	SysUserId       int    // 创建人ID
+	SysUserRealName string // 创建人姓名
+	Title           string // 文档标题
+	CreateTime      string // 创建时间
+	ModifyTime      string // 更新时间
+	OpenaiFileName  string // 文件名称
+	OpenaiFilePath  string // 文件路径
+	Sort            int    // 排序字段,越小越靠前,默认值:10
+	ParentIds       string
+}
+
+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
+}
+
+// GetAiSummaryById 根据纪要id获取详情
+func GetAiSummaryItemById(aiSummaryId int) (sandboxInfo *AiSummaryItems, 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
+}
+
+func GetAiSummaryListByCondition(condition string, pars []interface{}, startSize, pageSize int) (item []*AiSummaryItems, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM ai_summary WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " ORDER BY create_time DESC LIMIT ?,? "
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&item)
+	return
+}
+
+func GetAiSummaryListCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT COUNT(1) AS count FROM ai_summary WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func AddAiSummary(item *AiSummary) (lastId int64, err error) {
+	o := orm.NewOrm()
+	lastId, err = o.Insert(item)
+	return
+}
+
+type AddAiSummaryReq struct {
+	SaDocId        int    // 文档ID
+	OriginContent  string // 原始内容
+	SummaryContent string // 纪要内容
+	ClassifyId     int    // 分类id
+	Title          string // 文档标题
+	OpenaiFileName string // 文件名称
+	OpenaiFilePath string // 文件路径
+	OriginTitle    string // 原文标题
+}
+
+type GenerateAiSummaryReq struct {
+	AiChatTopicId int      `description:"主题id"`
+	OriginContent string   // 原始内容
+	OpenaiFileId  []string `description:"openai返回的文件id"`
+	Model         string   `description:"模型名称"`
+	Prompt        string   `description:"提示词"`
+	SaDocId       int      // 文档ID
+}
+
+// GetAiSummaryMaxSort 获取ai纪要下最大的排序数
+func GetAiSummaryMaxSort(classifyId int) (sort int, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT Max(sort) AS sort FROM ai_summary WHERE parent_id=? `
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+type AiSummaryAddResp struct {
+	AiSummaryId int
+	UniqueCode  string // 唯一编码
+	ParentId    int
+}
+
+type AiSummaryDetailResp struct {
+	*AiSummaryItems
+	SaDocTitle      string `description:"文档库标题"`
+	SaDocClassifyId int    `description:"文档库分类id"`
+}

+ 331 - 0
models/ai_summary/ai_summary_classify.go

@@ -0,0 +1,331 @@
+package ai_summary
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"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"`
+	UniqueCode          string    `description:"唯一编码"`
+	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
+	a.*,
+	b.ai_summary_classify_id,
+	b.classify_name 
+FROM
+	ai_summary AS a
+	JOIN ai_summary_classify AS b ON a.classify_id = b.ai_summary_classify_id 
+WHERE
+	a.sys_user_id = ? 
+ORDER BY
+	a.sort ASC,
+	a.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,
+	title 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
+}
+
+func GetAiSummaryClassify(aiSummaryClassify int) (aiSummaryClassifyIds string, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT GROUP_CONCAT(t.ai_summary_classify_id) AS ai_summary_classify_id FROM (
+			SELECT a.ai_summary_classify_id FROM ai_summary_classify AS a 
+			WHERE a.ai_summary_classify_id=?
+			UNION ALL
+			SELECT a.ai_summary_classify_id FROM ai_summary_classify AS a 
+			WHERE a.parent_id=? UNION ALL
+	SELECT
+		ai_summary_classify_id 
+	FROM
+		ai_summary_classify 
+WHERE
+	parent_id IN ( SELECT ai_summary_classify_id FROM ai_summary_classify WHERE parent_id = ? )
+			)AS t`
+	err = o.Raw(sql, aiSummaryClassify, aiSummaryClassify, aiSummaryClassify).QueryRow(&aiSummaryClassifyIds)
+	return
+}
+
+func GetAiSummaryAllParentByClassifyId(aiSummaryClassifyId int) (ids string, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT
+	GROUP_CONCAT(DISTINCT m.ai_summary_classify_id  ORDER BY m.level) AS ids 
+FROM
+	(
+	SELECT
+		@id AS _id,(
+		SELECT
+			@id := parent_id 
+		FROM
+			ai_summary_classify 
+		WHERE
+			ai_summary_classify_id = _id 
+		) 
+	FROM
+		(
+		SELECT
+			@id :=(
+			SELECT
+				parent_id 
+			FROM
+				ai_summary_classify 
+			WHERE
+				ai_summary_classify_id = ? 
+			)) vm,
+		ai_summary_classify m 
+	WHERE
+		@id IS NOT NULL 
+	) vm
+	INNER JOIN ai_summary_classify m 
+WHERE
+	ai_summary_classify_id = vm._id `
+	err = o.Raw(sql, aiSummaryClassifyId).QueryRow(&ids)
+	return
+}
+
+type AiSummaryListResp struct {
+	Paging *paging.PagingItem
+	List   []*AiSummaryItems
+}
+
+// GetAiSummaryClassifyAll
+func GetAiSummaryClassifyAll() (items []*AiSummaryClassifyItems, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT * FROM ai_summary_classify WHERE parent_id<>0 order by sort asc,ai_summary_classify_id asc`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+

+ 16 - 0
models/business_conf.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"html"
@@ -237,3 +238,18 @@ type EdbStopRefreshRule struct {
 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
+	}
+}

+ 21 - 0
models/chart_permission.go

@@ -246,6 +246,7 @@ func FormatChartPermission2Simple(origin *ChartPermission) (item *SimpleChartPer
 	item.ChartPermissionId = origin.ChartPermissionId
 	item.ChartPermissionName = origin.PermissionName
 	item.Sort = origin.Sort
+
 	return
 }
 
@@ -254,5 +255,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
 }

+ 153 - 68
models/classify.go

@@ -9,38 +9,41 @@ import (
 )
 
 type Classify struct {
-	Id                int       `orm:"column(id);pk"`
-	ClassifyName      string    `description:"分类名称"`
-	Sort              int       `json:"-"`
-	ParentId          int       `description:"父级分类id"`
-	CreateTime        time.Time `description:"创建时间"`
-	ModifyTime        time.Time `description:"修改时间"`
-	Abstract          string    `description:"栏目简介"`
-	Descript          string    `description:"分享描述"`
-	ReportAuthor      string    `description:"栏目作者"`
-	AuthorDescript    string    `description:"作者简介"`
-	ColumnImgUrl      string    `description:"栏目配图"`
-	HeadImgUrl        string    `description:"头部banner"`
-	AvatarImgUrl      string    `description:"头像"`
-	ReportImgUrl      string    `description:"报告配图"`
-	HomeImgUrl        string    `description:"首页配图"`
-	ClassifyLabel     string    `description:"分类标签"`
-	ShowType          int       `description:"展示类型:1-列表 2-专栏"`
-	HasTeleconference int       `description:"是否有电话会:0-否 1-是"`
-	VipTitle          string    `description:"研究员头衔"`
-	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-是"`
-	IsMassSend        int       `description:"1:群发,0:非群发"`
-	Enabled           int       `description:"是否可用,1可用,0禁用"`
+	Id                   int       `orm:"column(id);pk"`
+	ClassifyName         string    `description:"分类名称"`
+	Sort                 int       `json:"-"`
+	ParentId             int       `description:"父级分类id"`
+	CreateTime           time.Time `description:"创建时间"`
+	ModifyTime           time.Time `description:"修改时间"`
+	Abstract             string    `description:"栏目简介"`
+	Descript             string    `description:"分享描述"`
+	ReportAuthor         string    `description:"栏目作者"`
+	AuthorDescript       string    `description:"作者简介"`
+	ColumnImgUrl         string    `description:"栏目配图"`
+	HeadImgUrl           string    `description:"头部banner"`
+	AvatarImgUrl         string    `description:"头像"`
+	ReportImgUrl         string    `description:"报告配图"`
+	HomeImgUrl           string    `description:"首页配图"`
+	ClassifyLabel        string    `description:"分类标签"`
+	ShowType             int       `description:"展示类型:1-列表 2-专栏"`
+	HasTeleconference    int       `description:"是否有电话会:0-否 1-是"`
+	VipTitle             string    `description:"研究员头衔"`
+	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-是"`
+	IsMassSend           int       `description:"1:群发,0:非群发"`
+	Enabled              int       `description:"是否可用,1可用,0禁用"`
+	Level                int       `description:"层级"`
+	HasChild             int       `description:"是否有子级别,0:下面没有子分类,1:下面有子分类;默认:0"`
+	ReportDetailShowType int       `description:"报告详情的展示类型:1-拼接;2:目录"`
 }
 
 type ClassifyAddReq struct {
@@ -151,8 +154,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 +176,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 +212,7 @@ type ClassifyItem struct {
 	ClassifyMenuId        int `description:"二级分类-子目录ID"`
 	ClassifyMenuList      []*ClassifyMenu
 	ChartPermissionIdList []int `description:"绑定的权限ID"`
+	Child                 []*ClassifyItem
 }
 
 type ClassifyListResp struct {
@@ -213,14 +225,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 +245,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 +492,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
+}

+ 162 - 0
models/data_manage/base_from_edb_mapping.go

@@ -0,0 +1,162 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromEdbMapping 同花顺高频数据
+type BaseFromEdbMapping struct {
+	Id              int       `orm:"column(id);pk"`
+	BaseFromIndexId int       `description:"源指标ID"`
+	BaseIndexCode   string    `description:"源指标编码"`
+	EdbInfoId       int       `description:"指标ID"`
+	EdbCode         string    `description:"指标编码"`
+	Source          int       `description:"指标来源:1-同花顺..."`
+	SubSource       int       `description:"子数据来源:0-经济数据库;1-日期序列;2-高频数据"`
+	ConvertRule     string    `description:"转换规则"`
+	CreateTime      time.Time `description:"创建时间"`
+	ModifyTime      time.Time `description:"修改时间"`
+}
+
+func (m *BaseFromEdbMapping) TableName() string {
+	return "base_from_edb_mapping"
+}
+
+type BaseFromEdbMappingCols struct {
+	PrimaryId       string
+	BaseFromIndexId string
+	BaseIndexCode   string
+	EdbInfoId       string
+	EdbCode         string
+	Source          string
+	SubSource       string
+	ConvertRule     string
+	CreateTime      string
+	ModifyTime      string
+}
+
+func (m *BaseFromEdbMapping) Cols() BaseFromEdbMappingCols {
+	return BaseFromEdbMappingCols{
+		PrimaryId:       "id",
+		BaseFromIndexId: "base_from_index_id",
+		BaseIndexCode:   "base_index_code",
+		EdbInfoId:       "edb_info_id",
+		EdbCode:         "edb_code",
+		Source:          "source",
+		SubSource:       "sub_source",
+		ConvertRule:     "convert_rule",
+		CreateTime:      "create_time",
+		ModifyTime:      "modify_time",
+	}
+}
+
+func (m *BaseFromEdbMapping) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *BaseFromEdbMapping) CreateMulti(items []*BaseFromEdbMapping) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *BaseFromEdbMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *BaseFromEdbMapping) 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.Id).Exec()
+	return
+}
+
+func (m *BaseFromEdbMapping) 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 *BaseFromEdbMapping) 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 *BaseFromEdbMapping) GetItemById(id int) (item *BaseFromEdbMapping, 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 *BaseFromEdbMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromEdbMapping, 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 *BaseFromEdbMapping) 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 *BaseFromEdbMapping) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromEdbMapping, 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 *BaseFromEdbMapping) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromEdbMapping, 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
+}

+ 509 - 0
models/data_manage/base_from_ths_hf.go

@@ -0,0 +1,509 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// TODO:有时间还是放在配置里面吧...
+var (
+	ThsHfPeriodArr = []int{1, 3, 5, 10, 15, 30, 60}                                                                                                                                // 时间周期
+	ThsHfCPSArr    = []string{"no", "forward1", "forward2", "forward3", "forward4", "backward1", "backward2", "backward3", "backward4"}                                            // 复权方式
+	ThsHfFillArr   = []string{"Original", "Previous", "Blank"}                                                                                                                     // 非交易间隔处理
+	ThsHfEdbCodeCn = map[string]string{"open": "开盘价", "close": "收盘价", "high": "最高价", "low": "最低价", "avgprice": "均价", "volume": "成交价", "amt": "成交额", "pct_chg": "涨跌幅", "oi": "持仓量"} // 可选指标代码对应中文
+)
+
+// BaseFromThsHfIndex 同花顺高频数据
+type BaseFromThsHfIndex struct {
+	BaseFromThsHfIndexId    int       `orm:"column(base_from_ths_hf_index_id);pk"`
+	BaseFromThsHfClassifyId int       `description:"分类ID"`
+	IndexCode               string    `description:"指标编码"`
+	IndexName               string    `description:"指标名称"`
+	Unit                    string    `description:"单位"`
+	Source                  string    `description:"数据来源"`
+	Frequency               string    `description:"频度"`
+	StartDate               time.Time `description:"开始日期(至时分秒)"`
+	EndDate                 time.Time `description:"结束日期(至时分秒)"`
+	Describe                string    `description:"指标描述"`
+	Sort                    int       `description:"排序"`
+	IsStop                  int       `description:"是否停更:0-否;1-停更"`
+	TerminalCode            string    `description:"所属终端编码"`
+	StockCode               string    `description:"证券代码"`
+	Indicator               string    `description:"同花顺指标代码"`
+	ApiPars                 string    `description:"API请求参数"`
+	LatestValue             float64   `description:"最新值"`
+	SysUserId               int       `description:"创建人ID"`
+	SysUserRealName         string    `description:"创建人姓名"`
+	CreateTime              time.Time `description:"创建时间"`
+	ModifyTime              time.Time `description:"修改时间"`
+}
+
+func (m *BaseFromThsHfIndex) TableName() string {
+	return "base_from_ths_hf_index"
+}
+
+type BaseFromThsHfIndexCols struct {
+	PrimaryId               string
+	BaseFromThsHfClassifyId string
+	IndexCode               string
+	IndexName               string
+	Unit                    string
+	Source                  string
+	Frequency               string
+	StartDate               string
+	EndDate                 string
+	Describe                string
+	Sort                    string
+	IsStop                  string
+	TerminalCode            string
+	StockCode               string
+	Indicator               string
+	ApiPars                 string
+	LatestValue             string
+	SysUserId               string
+	SysUserRealName         string
+	CreateTime              string
+	ModifyTime              string
+}
+
+func (m *BaseFromThsHfIndex) Cols() BaseFromThsHfIndexCols {
+	return BaseFromThsHfIndexCols{
+		PrimaryId:               "base_from_ths_hf_index_id",
+		BaseFromThsHfClassifyId: "base_from_ths_hf_classify_id",
+		IndexCode:               "index_code",
+		IndexName:               "index_name",
+		Unit:                    "unit",
+		Source:                  "source",
+		Frequency:               "frequency",
+		StartDate:               "start_date",
+		EndDate:                 "end_date",
+		Describe:                "describe",
+		Sort:                    "sort",
+		IsStop:                  "is_stop",
+		TerminalCode:            "terminal_code",
+		StockCode:               "stock_code",
+		Indicator:               "indicator",
+		ApiPars:                 "api_pars",
+		LatestValue:             "latest_value",
+		SysUserId:               "sys_user_id",
+		SysUserRealName:         "sys_user_real_name",
+		CreateTime:              "create_time",
+		ModifyTime:              "modify_time",
+	}
+}
+
+func (m *BaseFromThsHfIndex) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.BaseFromThsHfIndexId = int(id)
+	return
+}
+
+func (m *BaseFromThsHfIndex) CreateMulti(items []*BaseFromThsHfIndex) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *BaseFromThsHfIndex) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *BaseFromThsHfIndex) 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.BaseFromThsHfIndexId).Exec()
+	return
+}
+
+func (m *BaseFromThsHfIndex) 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 *BaseFromThsHfIndex) 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 *BaseFromThsHfIndex) GetItemById(id int) (item *BaseFromThsHfIndex, 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 *BaseFromThsHfIndex) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromThsHfIndex, 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 *BaseFromThsHfIndex) 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 *BaseFromThsHfIndex) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromThsHfIndex, 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 *BaseFromThsHfIndex) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromThsHfIndex, 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
+}
+
+// BaseFromThsHfIndexItem 同花顺高频数据信息
+type BaseFromThsHfIndexItem struct {
+	IndexId      int     `description:"同花顺高频数据ID"`
+	ClassifyId   int     `description:"分类ID"`
+	IndexCode    string  `description:"指标编码"`
+	IndexName    string  `description:"指标名称"`
+	Unit         string  `description:"单位"`
+	Source       string  `description:"数据来源"`
+	Frequency    string  `description:"频度"`
+	StartDate    string  `description:"开始日期(至时分秒)"`
+	EndDate      string  `description:"结束日期(至时分秒)"`
+	Describe     string  `description:"指标描述"`
+	Sort         int     `description:"排序"`
+	StockCode    string  `description:"证券代码"`
+	Indicator    string  `description:"同花顺指标代码"`
+	LatestValue  float64 `description:"最新值"`
+	ClassifyPath string  `description:"完整分类路径"`
+	CreateTime   string  `description:"创建时间"`
+	ModifyTime   string  `description:"修改时间"`
+}
+
+func (m *BaseFromThsHfIndex) Format2Item() (item *BaseFromThsHfIndexItem) {
+	item = new(BaseFromThsHfIndexItem)
+	item.ClassifyId = m.BaseFromThsHfClassifyId
+	item.IndexId = m.BaseFromThsHfIndexId
+	item.IndexCode = m.IndexCode
+	item.IndexName = m.IndexName
+	item.Unit = m.Unit
+	item.Source = m.Source
+	item.Frequency = m.Frequency
+	item.StartDate = utils.TimeTransferString(utils.FormatDateTime, m.StartDate)
+	item.EndDate = utils.TimeTransferString(utils.FormatDateTime, m.EndDate)
+	item.Describe = m.Describe
+	item.Sort = m.Sort
+	item.StockCode = m.StockCode
+	item.Indicator = m.Indicator
+	item.LatestValue = m.LatestValue
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+func (m *BaseFromThsHfIndex) UpdateClassifyMulti(ids []int, classifyId int) (err error) {
+	if len(ids) == 0 || classifyId <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = NOW() WHERE %s IN (%s)`, m.TableName(), m.Cols().BaseFromThsHfClassifyId, m.Cols().ModifyTime, m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, classifyId, ids).Exec()
+	return
+}
+
+// ThsHfSearchEdbReq 搜索指标请求体
+type ThsHfSearchEdbReq struct {
+	StockCode string `form:"StockCode" description:"证券代码" `
+	EdbCode   string `form:"EdbCode" description:"指标代码"`
+	StartTime string `form:"StartTime" description:"每日数据开始时间"`
+	EndTime   string `form:"EndTime" description:"每日数据结束时间"`
+	Interval  int    `form:"Interval" description:"时间周期"`
+	Fill      string `form:"Fill" description:"非交易间隔处理"`
+	CPS       string `form:"CPS" description:"复权方式"`
+	BaseDate  string `form:"BaseDate" description:"复权基点"`
+}
+
+// ThsHfExistCheckResp 指标存在校验响应
+type ThsHfExistCheckResp struct {
+	ExistAll   bool                   `description:"指标是否全部存在: true-是; false-否"`
+	ExistIndex []ThsHfExistCheckIndex `description:"已存在的指标信息"`
+}
+
+// ThsHfExistCheckIndex 指标存在校验-指标信息
+type ThsHfExistCheckIndex struct {
+	IndexId   int    `description:"指标ID"`
+	IndexCode string `description:"指标编码"`
+	IndexName string `description:"指标名称"`
+}
+
+// ThsHfSearchEdbResp 响应体
+type ThsHfSearchEdbResp struct {
+	StockCode string               `description:"证券代码" `
+	EdbCode   string               `description:"指标代码"`
+	IndexName string               `description:"指标名称"`
+	Frequency int                  `description:"频度: 1-60"`
+	IndexData []ThsHfSearchEdbData `description:"指标数据"`
+}
+
+type ThsHfSearchEdbData struct {
+	DataTime string
+	Value    string
+}
+
+// ThsHfIndexWithData 同花顺高频指标
+type ThsHfIndexWithData struct {
+	StockCode string            `description:"证券代码"`
+	EdbCode   string            `description:"指标代码"`
+	IndexData []*ThsHfIndexData `description:"指标数据"`
+}
+
+// ThsHfIndexData 同花顺高频指标数据
+type ThsHfIndexData struct {
+	DataTime time.Time `description:"数据时间(2006-01-02 15:04)"`
+	Value    float64   `description:"数据值"`
+}
+
+// ThsHfIndexDataLibResp 同花顺高频指标-指标库响应
+type ThsHfIndexDataLibResp struct {
+	Ret     int
+	Msg     string
+	ErrMsg  string
+	ErrCode string
+	Data    []*ThsHfIndexWithData
+	Success bool `description:"true 执行成功,false 执行失败"`
+}
+
+// ThsHfAddEdbReq 新增指标请求体
+type ThsHfAddEdbReq struct {
+	StartTime string                   `description:"每日数据开始时间"`
+	EndTime   string                   `description:"每日数据结束时间"`
+	Interval  int                      `description:"时间周期"`
+	Fill      string                   `description:"非交易间隔处理"`
+	CPS       string                   `description:"复权方式"`
+	BaseDate  string                   `description:"复权基点"`
+	IndexList []*ThsHfBaseAddIndexItem `description:"指标信息"`
+}
+
+type ThsHfBaseAddIndexItem struct {
+	ClassifyId int    `description:"分类ID"`
+	Unit       string `description:"单位"`
+	IndexName  string `description:"指标名称"`
+	Frequency  string `description:"频度"`
+	StockCode  string `description:"证券代码"`
+	EdbCode    string `description:"指标代码"`
+}
+
+type ThsHfBaseAddReq struct {
+	StartTime             string `description:"每日数据开始时间"`
+	EndTime               string `description:"每日数据结束时间"`
+	Interval              int    `description:"时间周期"`
+	Fill                  string `description:"非交易间隔处理"`
+	CPS                   string `description:"复权方式"`
+	BaseDate              string `description:"复权基点"`
+	SysAdminId            int    `description:"创建人ID"`
+	SysAdminName          string `description:"创建人姓名"`
+	ThsHfBaseAddIndexItem `description:"指标信息"`
+}
+
+// ThsHfIndexEditReq 编辑指标请求
+type ThsHfIndexEditReq struct {
+	IndexId    int    `description:"指标ID"`
+	IndexName  string `description:"指标名称"`
+	ClassifyId int    `description:"分类ID"`
+	Unit       string `description:"单位"`
+}
+
+// ThsHfIndexOptReq 指标操作请求
+type ThsHfIndexOptReq struct {
+	IndexId int `description:"指标ID"`
+}
+
+// ThsHfIndexListChoiceReq 指标列表选择请求
+type ThsHfIndexListChoiceReq struct {
+	ClassifyId   string `form:"ClassifyId" description:"分类ID(多选)"`
+	IncludeChild bool   `form:"IncludeChild" description:"是否包含子分类"`
+	Frequency    string `form:"Frequency" description:"频度(多选)"`
+	SysAdminId   string `form:"SysAdminId" description:"创建人ID(多选)"`
+	Keywords     string `form:"Keywords" description:"关键词: 指标ID/指标名称"`
+	ListIds      string `form:"ListIds" description:"列表选择项/排除项(全选为true时为排除项)"`
+	SelectAll    bool   `form:"SelectAll" description:"是否全选: true/false"`
+}
+
+// ThsHfIndexListChoiceItem 指标列表选择响应
+type ThsHfIndexListChoiceItem struct {
+	IndexId   int    `description:"指标ID"`
+	IndexCode string `description:"指标编码"`
+	IndexName string `description:"指标名称"`
+}
+
+// ThsHfIndexMultiOptReq 指标批量操作请求
+type ThsHfIndexMultiOptReq struct {
+	IndexIds       []int `description:"指标IDs"`
+	OptType        int   `description:"操作类型: 1-移动分类; 2-删除; 3-刷新"`
+	MoveClassifyId int   `description:"移动至分类ID"`
+	RefreshType    int   `description:"刷新类型: 1-最近6小时; 2-全部刷新"`
+}
+
+// ThsHfIndexListForm 指标列表表单
+type ThsHfIndexListForm struct {
+	PageSize     int    `form:"PageSize" description:"每页数据量"`
+	CurrentIndex int    `form:"CurrentIndex" description:"页码"`
+	SortField    int    `form:"SortField" description:"排序字段: 1-指标开始时间; 2-指标最新时间; 3-更新时间; 4-最新值"`
+	SortType     int    `form:"SortType" description:"排序类型: 1-升序; 2-降序"`
+	ClassifyId   string `form:"ClassifyId" description:"分类ID(多选)"`
+	IncludeChild bool   `form:"IncludeChild" description:"是否包含子分类"`
+	Frequency    string `form:"Frequency" description:"频度(多选)"`
+	SysAdminId   string `form:"SysAdminId" description:"创建人ID(多选)"`
+	Keywords     string `form:"Keywords" description:"关键词: 指标ID/指标名称"`
+}
+
+type ThsHfIndexPageListResp struct {
+	List   []*BaseFromThsHfIndexItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+func GetThsHfIndexById(indexId int) (item *BaseFromThsHfIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_ths_hf_index WHERE base_from_ths_hf_index_id = ?`
+	err = o.Raw(sql, indexId).QueryRow(&item)
+	return
+}
+
+// UpdateThsHfIndexSortByClassifyId 根据分类id更新排序
+func UpdateThsHfIndexSortByClassifyId(classifyId, nowSort int, prevEdbInfoId int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update base_from_ths_hf_index set sort = ` + updateSort + ` WHERE base_from_ths_hf_classify_id=?`
+	if prevEdbInfoId > 0 {
+		sql += ` AND ( sort > ? or ( base_from_ths_hf_index_id > ` + fmt.Sprint(prevEdbInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	} else {
+		sql += ` AND ( sort > ? )`
+	}
+	_, err = o.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// GetThsHfIndexMaxSortByClassifyId 获取分类下指标的最大的排序数
+func GetThsHfIndexMaxSortByClassifyId(classifyId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT Max(sort) AS sort FROM base_from_ths_hf_index WHERE base_from_ths_hf_classify_id = ?`
+	err = o.Raw(sql, classifyId).QueryRow(&sort)
+	return
+}
+
+// GetFirstThsHfIndexByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstThsHfIndexByClassifyId(classifyId int) (item *BaseFromThsHfIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_ths_hf_index WHERE base_from_ths_hf_classify_id = ? order by sort asc,base_from_ths_hf_index_id asc limit 1`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+type ThsHfIndexConvert2EdbRule struct {
+	ConvertType  int `description:"转换类型: 1-指定时间值; 2-区间计算值"`
+	ConvertFixed struct {
+		FixedDay  int    `description:"指定时间值日期: 1-当日; 2-前一日"`
+		FixedTime string `description:"指定时间值时点(HH:mm:ss)"`
+	} `description:"指定时间值"`
+	ConvertArea struct {
+		StartDay      int    `description:"起始时间日期: 1-当日; 2-前一日"`
+		StartTime     string `description:"起始时间时点(HH:mm:ss)"`
+		EndDay        int    `description:"截止时间日期: 1-当日; 2-前一日"`
+		EndTime       string `description:"截止时间时点(HH:mm:ss)"`
+		CalculateType int    `description:"计算类型: 1-区间均值; 2-最大值; 3-最小值"`
+	} `description:"区间计算值"`
+}
+
+// ThsHfIndexMultiSave2EdbPreReq 批量添加指标库请求
+type ThsHfIndexMultiSave2EdbPreReq struct {
+	ConvertRule ThsHfIndexConvert2EdbRule
+	IndexIds    []int `description:"指标IDs"`
+}
+
+type ThsHfIndexMultiSave2EdbReq struct {
+	ConvertRule ThsHfIndexConvert2EdbRule
+	NewIndexes  []*ThsHfIndexMultiSave2EdbPreItem `description:"新增指标"`
+}
+
+type ThsHfIndexMultiSave2EdbLibReq struct {
+	ConvertRule ThsHfIndexConvert2EdbRule
+	NewIndex    *ThsHfIndexMultiSave2EdbPreItem `description:"新增指标"`
+}
+
+// ThsHfIndexMultiSave2EdbPreItem 批量新增指标库信息
+type ThsHfIndexMultiSave2EdbPreItem struct {
+	IndexId      int    `description:"指标ID"`
+	IndexCode    string `description:"指标编码"`
+	IndexName    string `description:"原指标名称"`
+	NewIndexName string `description:"新指标名称"`
+	StockCode    string `description:"证券代码"`
+	EdbCode      string `description:"指标代码"`
+	Unit         string `description:"单位"`
+	Frequency    string `description:"原频度"`
+	NewFrequency string `description:"新频度(固定日度)"`
+	ClassifyId   int    `description:"指标库分类ID"`
+	SysAdminId   int    `description:"创建人ID"`
+	SysAdminName string `description:"创建人姓名"`
+	Tips         string `description:"提示信息"`
+	ErrMsg       string `description:"错误信息"`
+}
+
+type ThsHfIndexMultiSave2EdbResp struct {
+	Exist   []*ThsHfIndexMultiSave2EdbPreItem `description:"已存在的指标"`
+	Success []*ThsHfIndexMultiSave2EdbPreItem `description:"添加成功的指标"`
+	Fail    []*ThsHfIndexMultiSave2EdbPreItem `description:"添加失败的指标"`
+}
+
+type ThsHfIndexMultiOptResp struct {
+	Success []*ThsHfIndexBaseInfo `description:"操作成功的指标"`
+	Fail    []*ThsHfIndexBaseInfo `description:"操作失败的指标"`
+}
+
+type ThsHfIndexBaseInfo struct {
+	IndexId   int    `description:"指标ID"`
+	IndexCode string `description:"指标编码"`
+	IndexName string `description:"指标名称"`
+}

+ 297 - 0
models/data_manage/base_from_ths_hf_classify.go

@@ -0,0 +1,297 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromThsHfClassify 同花顺高频数据-分类
+type BaseFromThsHfClassify struct {
+	BaseFromThsHfClassifyId int       `orm:"column(base_from_ths_hf_classify_id);pk"`
+	ClassifyName            string    `description:"分类名称"`
+	ClassifyNameEn          string    `description:"英文分类名称"`
+	ParentId                int       `description:"父级ID"`
+	SysUserId               int       `description:"创建人ID"`
+	SysUserRealName         string    `description:"创建人姓名"`
+	Level                   int       `description:"层级"`
+	Sort                    int       `description:"排序"`
+	RootId                  int       `description:"顶级分类ID"`
+	LevelPath               string    `description:"层级路径"`
+	UniqueCode              string    `description:"唯一编码"`
+	CreateTime              time.Time `description:"创建时间"`
+	ModifyTime              time.Time `description:"修改时间"`
+}
+
+func (m *BaseFromThsHfClassify) TableName() string {
+	return "base_from_ths_hf_classify"
+}
+
+type BaseFromThsHfClassifyCols struct {
+	PrimaryId       string
+	ClassifyName    string
+	ClassifyNameEn  string
+	ParentId        string
+	SysUserId       string
+	SysUserRealName string
+	Level           string
+	Sort            string
+	RootId          string
+	LevelPath       string
+	UniqueCode      string
+	CreateTime      string
+	ModifyTime      string
+}
+
+func (m *BaseFromThsHfClassify) Cols() BaseFromThsHfClassifyCols {
+	return BaseFromThsHfClassifyCols{
+		PrimaryId:       "base_from_ths_hf_classify_id",
+		ClassifyName:    "classify_name",
+		ClassifyNameEn:  "classify_name_en",
+		ParentId:        "parent_id",
+		SysUserId:       "sys_user_id",
+		SysUserRealName: "sys_user_real_name",
+		Level:           "level",
+		Sort:            "sort",
+		RootId:          "root_id",
+		LevelPath:       "level_path",
+		UniqueCode:      "unique_code",
+		CreateTime:      "create_time",
+		ModifyTime:      "modify_time",
+	}
+}
+
+func (m *BaseFromThsHfClassify) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.BaseFromThsHfClassifyId = int(id)
+	return
+}
+
+func (m *BaseFromThsHfClassify) CreateMulti(items []*BaseFromThsHfClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *BaseFromThsHfClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *BaseFromThsHfClassify) 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.BaseFromThsHfClassifyId).Exec()
+	return
+}
+
+func (m *BaseFromThsHfClassify) 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 *BaseFromThsHfClassify) 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 *BaseFromThsHfClassify) GetItemById(id int) (item *BaseFromThsHfClassify, 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 *BaseFromThsHfClassify) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromThsHfClassify, 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 *BaseFromThsHfClassify) 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 *BaseFromThsHfClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromThsHfClassify, 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 *BaseFromThsHfClassify) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromThsHfClassify, 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
+}
+
+// BaseFromThsHfClassifyItem 同花顺高频数据信息
+type BaseFromThsHfClassifyItem struct {
+	ClassifyId     int                          `description:"分类ID"`
+	ClassifyName   string                       `description:"分类名称"`
+	ClassifyNameEn string                       `description:"英文分类名称"`
+	ParentId       int                          `description:"父级ID"`
+	Level          int                          `description:"层级"`
+	Sort           int                          `description:"排序"`
+	LevelPath      string                       `description:"层级路径"`
+	UniqueCode     string                       `description:"唯一编码"`
+	Children       []*BaseFromThsHfClassifyItem `description:"子分类"`
+}
+
+func (m *BaseFromThsHfClassify) Format2Item() (item *BaseFromThsHfClassifyItem) {
+	item = new(BaseFromThsHfClassifyItem)
+	item.ClassifyId = m.BaseFromThsHfClassifyId
+	item.ClassifyName = m.ClassifyName
+	item.ClassifyNameEn = m.ClassifyNameEn
+	item.ParentId = m.ParentId
+	item.Level = m.Level
+	item.Sort = m.Sort
+	item.LevelPath = m.LevelPath
+	item.UniqueCode = m.UniqueCode
+	item.Children = make([]*BaseFromThsHfClassifyItem, 0)
+	return
+}
+
+type ThsHfClassifyAddReq struct {
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级ID, 第一级传0"`
+	Level        int    `description:"层级, 第一级传0, 其余传上一级的层级"`
+}
+
+type ThsHfClassifyEditReq struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+}
+
+func (m *BaseFromThsHfClassify) GetSortMax(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT Max(%s) FROM %s WHERE %s = ?`, m.Cols().Sort, m.TableName(), m.Cols().ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+type ThsHfClassifyRemoveReq struct {
+	ClassifyId int `description:"分类ID"`
+}
+
+type BaseFromThsHfClassifyListItem struct {
+	ItemType       int                              `description:"类型: 0-分类; 1-指标"`
+	ClassifyId     int                              `description:"分类ID"`
+	ClassifyName   string                           `description:"分类名称"`
+	ClassifyNameEn string                           `description:"英文分类名称"`
+	IndexId        int                              `description:"指标ID"`
+	IndexCode      string                           `description:"指标编码"`
+	IndexName      string                           `description:"指标名称"`
+	ParentId       int                              `description:"父级ID"`
+	Level          int                              `description:"层级"`
+	Sort           int                              `description:"排序"`
+	UniqueCode     string                           `description:"唯一编码, 指标的话用indexCode"`
+	Children       []*BaseFromThsHfClassifyListItem `description:"子分类"`
+}
+
+type BaseFromThsHfClassifyMoveReq struct {
+	ClassifyId       int `description:"分类ID"`
+	ParentClassifyId int `description:"父级分类ID"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类ID"`
+	NextClassifyId   int `description:"下一个兄弟节点分类ID"`
+	ItemId           int `description:"指标ID, 如果指标ID有值,则移动对象为指标,否则认为移动对象为分类"`
+	PrevItemId       int `description:"上一个指标ID"`
+	NextItemId       int `description:"下一个指标ID"`
+}
+
+func GetThsHfClassifyById(classifyId int) (item *BaseFromThsHfClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_ths_hf_classify WHERE base_from_ths_hf_classify_id = ?`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func GetThsHfClassifyByRootIdLevel(rootId int, orderStr string) (items []*BaseFromThsHfClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_ths_hf_classify WHERE root_id = ? `
+	if orderStr != "" {
+		sql += orderStr
+	} else {
+		sql += ` order by level desc, sort asc, base_from_ths_hf_classify_id asc`
+	}
+	_, err = o.Raw(sql, rootId).QueryRows(&items)
+	return
+}
+
+// UpdateThsHfClassifySortByParentId 根据父类id更新排序
+func UpdateThsHfClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update base_from_ths_hf_classify set sort = ` + updateSort + ` WHERE parent_id = ? AND sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( base_from_ths_hf_classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstThsHfClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func GetFirstThsHfClassifyByParentId(parentId int) (item *BaseFromThsHfClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_ths_hf_classify WHERE parent_id = ? order by sort asc,base_from_ths_hf_classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+func UpdateThsHfClassifyChildByParentClassifyId(classifyIds []int, rootId int, levelStep int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	var pars []interface{}
+	pars = append(pars, rootId, levelStep)
+	pars = append(pars, classifyIds)
+	// 更新相关联的二级分类的parentId,和classify_name_second
+	sql := `update base_from_ths_hf_classify SET root_id = ?, level = level+? where base_from_ths_hf_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	_, err = o.Raw(sql, pars).Exec()
+	if err != nil {
+		return
+	}
+	return
+}

+ 180 - 0
models/data_manage/base_from_ths_hf_data.go

@@ -0,0 +1,180 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromThsHfData 同花顺高频数据-指标数据
+type BaseFromThsHfData struct {
+	BaseFromThsHfDataId  int       `orm:"column(base_from_ths_hf_data_id);pk"`
+	BaseFromThsHfIndexId int       `description:"指标ID"`
+	IndexCode            string    `description:"指标编码"`
+	DataTime             time.Time `description:"数据日期(至时分秒)"`
+	Value                float64   `description:"数据值"`
+	UniqueCode           string    `description:"唯一编码"`
+	CreateTime           time.Time `description:"创建时间"`
+	ModifyTime           time.Time `description:"修改时间"`
+	DataTimestamp        int64     `description:"数据日期时间戳"`
+}
+
+func (m *BaseFromThsHfData) TableName() string {
+	return "base_from_ths_hf_data"
+}
+
+type BaseFromThsHfDataCols struct {
+	PrimaryId            string
+	BaseFromThsHfIndexId string
+	IndexCode            string
+	DataTime             string
+	Value                string
+	UniqueCode           string
+	CreateTime           string
+	ModifyTime           string
+	DataTimestamp        string
+}
+
+func (m *BaseFromThsHfData) Cols() BaseFromThsHfDataCols {
+	return BaseFromThsHfDataCols{
+		PrimaryId:            "base_from_ths_hf_data_id",
+		BaseFromThsHfIndexId: "base_from_ths_hf_index_id",
+		IndexCode:            "index_code",
+		DataTime:             "data_time",
+		Value:                "value",
+		UniqueCode:           "unique_code",
+		CreateTime:           "create_time",
+		ModifyTime:           "modify_time",
+		DataTimestamp:        "data_timestamp",
+	}
+}
+
+func (m *BaseFromThsHfData) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.BaseFromThsHfDataId = int(id)
+	return
+}
+
+func (m *BaseFromThsHfData) CreateMulti(items []*BaseFromThsHfData) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *BaseFromThsHfData) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *BaseFromThsHfData) 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.BaseFromThsHfDataId).Exec()
+	return
+}
+
+func (m *BaseFromThsHfData) 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 *BaseFromThsHfData) 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 *BaseFromThsHfData) GetItemById(id int) (item *BaseFromThsHfData, 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 *BaseFromThsHfData) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromThsHfData, 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 *BaseFromThsHfData) 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 *BaseFromThsHfData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromThsHfData, 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 *BaseFromThsHfData) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromThsHfData, 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
+}
+
+// BaseFromThsHfDataItem 同花顺高频数据信息
+type BaseFromThsHfDataItem struct {
+	DataId     int     `description:"数据ID"`
+	IndexId    int     `description:"指标ID"`
+	IndexCode  string  `description:"指标编码"`
+	DataTime   string  `description:"数据日期(至时分秒)"`
+	Value      float64 `description:"数据值"`
+	UniqueCode string  `description:"唯一编码"`
+}
+
+func (m *BaseFromThsHfData) Format2Item() (item *BaseFromThsHfDataItem) {
+	item = new(BaseFromThsHfDataItem)
+	item.DataId = m.BaseFromThsHfDataId
+	item.IndexId = m.BaseFromThsHfIndexId
+	item.IndexCode = m.IndexCode
+	item.DataTime = utils.TimeTransferString(utils.FormatDateTime, m.DataTime)
+	item.Value = m.Value
+	item.UniqueCode = m.UniqueCode
+	return
+}

+ 14 - 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")

+ 314 - 7
models/data_manage/chart_info.go

@@ -263,6 +263,59 @@ type EditChartInfoReq struct {
 	UnitEn               string                  `description:"英文单位名称"`
 }
 
+type EditFutureGoodChartInfoReq struct {
+	EditChartInfoReq
+	BarChartInfo FutureGoodBarChartInfoReq
+}
+
+type MarkersLine struct {
+	Axis             int             `json:"axis" description:"1左轴 2右轴 3横轴"`
+	AxisName         string          `json:"axisName" description:"轴的名称,例如'左轴'"`
+	MarkerType       string          `json:"markerType" description:"标识线或标识区"`
+	MarkLineType     int             `json:"markLineType" description:"1:固定 2:指标计算"`
+	Value            string          `json:"value" description:"连线指向的数值,例如'4000'"`
+	FromValue        string          `json:"fromValue" description:"连线的起始点,可以为空"`
+	ToValue          string          `json:"toValue" description:"连线的结束点,可以为空"`
+	LineWidth        float64         `json:"lineWidth" description:"连线的宽度"`
+	DashStyle        string          `json:"dashStyle" description:"连线的虚线样式,例如'ShortDashDot'"`
+	Color            string          `json:"color" description:"连线的颜色"`
+	Text             string          `json:"text" description:"连线旁边显示的文本"`
+	TextPosition     string          `json:"textPosition" description:"文本的显示位置,例如'bottom'"`
+	TextColor        string          `json:"textColor" description:"文本颜色"`
+	TextFontSize     int             `json:"textFontSize" description:"文本的字号大小"`
+	IsShow           bool            `json:"isShow" description:"是否显示连线及文本"`
+	EdbType          int             `json:"edbType" description:"指标类型 0图中第一个指标 1其他指标 前端回显用"`
+	EdbInfoId        int             `json:"edbInfoId" description:"指标id"`
+	Calculation      int             `json:"calculation" description:"计算方式 1区间均值 2区间均值加N倍标准差 3区间个数分位 4区间数值分位"`
+	CalculationValue float64         `json:"calculationValue" description:"计算方式对应的值 2就是几倍标准差 3就是分位值 4就是数值值·"`
+	TimeIntervalType int             `json:"timeInterval" description:"时间区间 0跟随图表 1自定义"`
+	StartDate        MarkersLineTime `json:"startTime" description:"开始时间"`
+	EndDate          MarkersLineTime `json:"endTime" description:"结束时间"`
+}
+
+type MarkersLineTime struct {
+	TimeType int               `json:"timeType" description:"时间类型 1固定 2动态 3至今(仅结束时间)"`
+	Date     string            `json:"date" description:"日期"`
+	Conf     EdbDateChangeConf `json:"conf" description:"动态时间配置"`
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `json:"moveForward" description:"前移的期数"`
+	BaseDate    int `json:"baseDate" description:"基准日期 0系统日期 1指标最新日期"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+type EdbDateConfDateChange struct {
+	Year         int    `json:"year" description:"前移的期数"`
+	Month        int    `json:"month" description:"前移的期数"`
+	Day          int    `json:"day" description:"前移的期数"`
+	Frequency    string `json:"moveForward" description:"频度变换"`
+	FrequencyDay string `json:"frequencyDay" description:"频度的固定日期"`
+	ChangeType   int    `json:"changeType" description:"日期变换类型1日期位移,2指定频率"`
+}
+
 type EditChartEnInfoReq struct {
 	ChartInfoId      int                       `description:"图表ID"`
 	ChartNameEn      string                    `description:"英文图表名称"`
@@ -395,9 +448,12 @@ 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)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return getThsHfEdbDataListByMongo(source, subSource, edbInfoId, startDate, endDate)
+	}
 
 	return getEdbDataListByMysql(source, subSource, edbInfoId, startDate, endDate)
 
@@ -518,9 +574,12 @@ 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)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return getThsHfEdbDataListMinAndMaxByMongo(source, subSource, edbInfoId, startDate, endDate)
+	}
 
 	return getEdbDataListMinAndMaxByMysql(source, subSource, edbInfoId, startDate, endDate)
 }
@@ -703,6 +762,17 @@ type ChartInfoDetailResp struct {
 	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
 }
@@ -723,6 +793,7 @@ type ExcelChartEdbView struct {
 type XData struct {
 	Name   string `description:"别名"`
 	NameEn string `description:"英文别名"`
+	IsHide int    `description:"是否隐藏,0不隐藏,1隐藏"`
 }
 
 // YData 柱方图的y轴数据
@@ -1080,7 +1151,7 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 }
 
 // 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 {
@@ -1159,7 +1230,12 @@ func EditFutureGoodChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr strin
 		return err
 	}
 	chartEdbMappingIdList := make([]string, 0)
+	hasAddMap := make(map[int]struct{})
 	for _, v := range req.BarChartInfo.EdbInfoIdList {
+		if _, ok := hasAddMap[v.EdbInfoId]; ok {
+			continue
+		}
+		hasAddMap[v.EdbInfoId] = struct{}{}
 		// 查询该指标是否存在,如果存在的话,那么就去修改,否则新增
 		var tmpChartEdbMapping *ChartEdbMapping
 		csql := `SELECT *  FROM chart_edb_mapping WHERE chart_info_id=? AND edb_info_id=? AND source = ? `
@@ -1413,6 +1489,11 @@ type AddChartInfoReq struct {
 	UnitEn               string                  `description:"英文单位名称"`
 }
 
+type AddFutureGoodChartInfoReq struct {
+	AddChartInfoReq
+	BarChartInfo FutureGoodBarChartInfoReq
+}
+
 type PreviewChartInfoReq struct {
 	ChartEdbInfoList  []*ChartSaveItem `description:"指标及配置信息"`
 	DateType          int              `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间,6:起始日期至今 20:最近N年"`
@@ -1423,19 +1504,129 @@ type PreviewChartInfoReq struct {
 	SeasonExtraConfig SeasonExtraItem  `description:"季节性图表中的配置,json数据"`
 	StartYear         int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
 	ChartSource       int
+	MarkersLines      string `description:"标识线"`
 }
 
 type SeasonExtraItem struct {
-	ChartLegend []SeasonChartLegend `description:"自定义的图例名称"`
-	XStartDate  string              `description:"横坐标显示的起始日"`
-	XEndDate    string              `description:"横坐标显示的截止日"`
-	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	ChartLegend                 []SeasonChartLegend         `description:"自定义的图例名称"`
+	XStartDate                  string                      `description:"横坐标显示的起始日"`
+	XEndDate                    string                      `description:"横坐标显示的截止日"`
+	JumpYear                    int                         `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
 }
 
 type SeasonChartLegend struct {
 	Name  string
 	Value string
 }
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int     `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string  `description:"生成样式"`
+	Shape         string  `description:"形状"`
+	ChartColor    string  `description:"图表颜色"`
+	Size          float64 `description:"大小"`
+	Legend        string  `description:"图例名称"`
+	NumFormat     int     `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int     `description:"是否连接 0不连接 1连接"`
+	LineColor     string  `description:"线条颜色"`
+	LineWidth     float64 `description:"线条宽度"`
+	LineStyle     string  `description:"线条样式"`
+	IsShow        bool    `description:"是否显示"`
+	IsAdd         bool    `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string  `description:"颜色"`
+	Year      int     `description:"均线取值范围"`
+	Legend    string  `description:"图例名称"`
+	LineType  string  `description:"线型"`
+	LineWidth float64 `description:"线宽"`
+	IsShow    bool    `description:"是否显示"`
+	IsAdd     bool    `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth float64                  `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*ChartEdbInfoMapping
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
+}
+
 type AddChartInfoResp struct {
 	ChartInfoId int    `description:"图表id"`
 	UniqueCode  string `description:"图表唯一编码"`
@@ -1634,6 +1825,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=? `
@@ -1934,6 +2137,14 @@ type BarChartInfoReq struct {
 	YEdbList      []BarChartInfoEdbItemReq `description:"Y轴选择的指标列表"`
 	Unit          string                   `description:"中文单位"`
 	UnitEn        string                   `description:"英文单位"`
+	MarkersLines  string                   `description:"标识线"`
+}
+
+type FutureGoodBarChartInfoReq struct {
+	EdbInfoIdList []BarChartInfoEdbItemReq `description:"指标信息"`
+	DateList      []BarChartInfoDateReq    `description:"日期配置"`
+	XDataList     []XData                  `description:"横轴配置"`
+	BaseEdbInfoId int                      `description:"日期基准指标id"`
 }
 
 // BarChartInfoEdbItemReq 柱方图预览请求数据(指标相关)
@@ -1947,6 +2158,7 @@ type BarChartInfoEdbItemReq struct {
 	ConvertValue  float64 `description:"数据转换值"`
 	ConvertUnit   string  `description:"数据转换单位"`
 	ConvertEnUnit string  `description:"数据转换单位"`
+	IsHide        int     `description:"是否隐藏该项,0不隐藏,1隐藏"`
 }
 
 // BarChartInfoDateReq 柱方图预览请求数据(日期相关)
@@ -2409,3 +2621,98 @@ func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*Ch
 	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
+
+// PreviewSeasonChartReq 预览季节性图的请求入参
+type PreviewSeasonChartReq struct {
+	ChartEdbInfoList  []*ChartSaveItem `description:"指标及配置信息"`
+	SeasonExtraConfig SeasonExtraItem  `description:"季节图额外配置信息,json字符串"`
+}
+
+// EditChartInfoImageV2
+// @Description: 修改图表的缩略图
+// @author: Roc
+// @datetime 2024-07-09 10:52:34
+// @param chartInfoId int
+// @param imageUrl string
+// @return err error
+func EditChartInfoImageV2(chartInfoId int, imageUrl string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := ` UPDATE  chart_info SET chart_image=?, modify_time = NOW() WHERE chart_info_id = ? `
+	_, err = o.Raw(sql, imageUrl, chartInfoId).Exec()
+	if err != nil {
+		fmt.Println("EditChartInfoImageV2 Err:", err.Error())
+		return err
+	}
+
+	return
+}
+
+func getThsHfEdbDataListByMongo(source, subSource, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
+	list = make([]*EdbDataList, 0)
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_info_id": edbInfoId,
+	}
+
+	// 数据日期
+	dateCondition, err := mgo.BuildDateCondition(startDate, endDate)
+	if err != nil {
+		return
+	}
+	if len(dateCondition) > 0 {
+		queryConditions["data_time"] = dateCondition
+	}
+
+	// 获取列表数据
+	tmpDataList, tmpErr := mogDataObj.GetAllDataList(queryConditions, []string{"data_time"})
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	for k, v := range tmpDataList {
+		list = append(list, &EdbDataList{
+			EdbDataId:     k + 1,
+			EdbInfoId:     v.EdbInfoId,
+			DataTime:      v.DataTime.Format(utils.FormatDate),
+			DataTimestamp: v.DataTimestamp,
+			Value:         v.Value,
+		})
+	}
+	return
+}
+
+func getThsHfEdbDataListMinAndMaxByMongo(source, subSource, edbInfoId int, startDate, endDate string) (minData, maxData float64, err error) {
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_info_id": edbInfoId,
+	}
+	// 日期
+	dateCondition, err := mgo.BuildDateCondition(startDate, endDate)
+	if err != nil {
+		return
+	}
+	if len(dateCondition) > 0 {
+		queryConditions["data_time"] = dateCondition
+	}
+
+	pipeline := []bson.M{
+		{"$match": queryConditions},
+		{"$group": bson.M{
+			"_id":       nil,
+			"min_value": bson.M{"$min": "$value"},
+			"max_value": bson.M{"$max": "$value"},
+		}},
+		{"$project": bson.M{"_id": 0}}, // 可选,如果不需要_id字段
+	}
+	result, err := mogDataObj.GetEdbInfoMaxAndMinInfo(pipeline)
+	if err != nil {
+		return
+	}
+	minData = result.MinValue
+	maxData = result.MaxValue
+	return
+}

+ 75 - 30
models/data_manage/chart_info_correlation.go

@@ -168,7 +168,7 @@ type CorrelationChartInfoExtraConfig struct {
 }
 
 // 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) {
+func CreateMultiFactorCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingList []*ChartEdbMapping, correlationInfo *ChartInfoCorrelation, chartMappings []*FactorEdbSeriesChartMapping, saveAs bool, copySeries []*FactorEdbSeries, copySeriesEdb []*FactorEdbSeriesMapping, copySeriesData []*FactorEdbSeriesCalculateData) (chartInfoId int, seriesIdMap map[int]int, err error) {
 	seriesIdMap = make(map[int]int) // 此处做一个原ID与新ID的映射, 另存为才用的到
 	o := orm.NewOrmUsingDB("data")
 	tx, e := o.Begin()
@@ -216,31 +216,44 @@ func CreateMultiFactorCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingLis
 	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.FactorEdbSeriesChartMappingId = 0
 				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
-				}
+				//v.FactorEdbSeriesId = seriesIdMap[v.FactorEdbSeriesId]
 			}
+			_, e = tx.InsertMulti(200, chartMappings)
+			if e != nil {
+				err = fmt.Errorf("insert series chart mappings err: %v", e)
+				return
+			}
+
+			//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
 	}
 
 	// 另存为
+	originSeriesIds := make([]int, 0)
 	for _, v := range copySeries {
 		id := v.FactorEdbSeriesId
+		originSeriesIds = append(originSeriesIds, id)
 		v.FactorEdbSeriesId = 0
 		newId, e := tx.Insert(v)
 		if e != nil {
@@ -270,6 +283,22 @@ func CreateMultiFactorCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingLis
 			return
 		}
 	}
+
+	// 系列指标计算
+	if len(copySeriesData) > 0 {
+		newCalculateData := make([]*FactorEdbSeriesCalculateData, 0)
+		for _, v := range copySeriesData {
+			t := v
+			t.FactorEdbSeriesCalculateDataId = 0
+			t.FactorEdbSeriesId = seriesIdMap[t.FactorEdbSeriesId]
+			newCalculateData = append(newCalculateData, t)
+		}
+		_, e = tx.InsertMulti(200, newCalculateData)
+		if e != nil {
+			err = fmt.Errorf("copy series calculate data err: %v", e)
+			return
+		}
+	}
 	return
 }
 
@@ -321,25 +350,41 @@ func UpdateMultiFactorCorrelationChartAndEdb(chartInfo *ChartInfo, edbMappingLis
 		return
 	}
 
+	// 删除原关联
+	chartMappingOb := new(FactorEdbSeriesChartMapping)
+	//if len(existsChartMappingIds) > 0 {
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, chartMappingOb.TableName(), chartMappingOb.Cols().ChartInfoId)
+	_, e = tx.Raw(sql, chartInfo.ChartInfoId).Exec()
+	if e != nil {
+		err = fmt.Errorf("clear chart mapping 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()
+		_, e = tx.InsertMulti(200, chartMappings)
 		if e != nil {
-			err = fmt.Errorf("sql prepare err: %v", e)
+			err = fmt.Errorf("insert series chart mappings 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
-			}
-		}
+
+		//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 {
+		//	_, 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
 }

+ 21 - 0
models/data_manage/chart_info_future_good.go

@@ -0,0 +1,21 @@
+package data_manage
+
+// FutureGoodProfitChartInfoReq 图表预览请求数据
+type FutureGoodProfitChartInfoReq struct {
+	FutureGoodEdbInfoIdList []EdbInfoFromTag                   `description:"指标信息"`
+	CalculateFormula        string                             `description:"计算公式"`
+	BaseEdbInfoId           int                                `description:"基础的指标id"`
+	DateList                []FutureGoodProfitChartInfoDateReq `description:"日期配置"`
+	ProfitNameEn            string                             `description:"利润英文名称"`
+	EdbInfoIdList           []int                              `description:"现货指标ID列表"`
+	XDataList               []XData                            `description:"横轴配置"`
+}
+
+// FutureGoodProfitChartInfoDateReq 图表的日期数据(日期相关)
+type FutureGoodProfitChartInfoDateReq struct {
+	Type  int    `description:"配置类型"`
+	Date  string `description:"固定日期"`
+	Value int    `description:"N天的值"`
+	Color string `description:"颜色"`
+	Name  string `description:"别名"`
+}

+ 19 - 19
models/data_manage/chart_theme/request/theme.go

@@ -70,20 +70,20 @@ type DrawOption struct {
 
 type LineOptions struct {
 	DashStyle string  `json:"dashStyle"`
-	LineWidth int     `json:"lineWidth"`
+	LineWidth float64 `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"`
+	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"`
 }
 
@@ -102,13 +102,13 @@ type NewLineOptions struct {
 }
 
 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"`
-}
+	DashStyle string  `json:"dashStyle"`
+	Color     string  `json:"color"`
+	LineWidth float64 `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"`
+}

+ 1 - 1
models/data_manage/data_manage_permission/req_and_resp.go

@@ -40,7 +40,7 @@ type SetEdbChartPermissionReq struct {
 	NoDataIdList []string `description:"指标/图表/表格唯一id列表"`
 	UserList     []int    `description:"赋权用户id列表,如果为空,说明要给这些指标移除权限管控"`
 	IsSelectAll  bool     `description:"是否选择所有指标"`
-	ClassifyId   string   `description:"分类id,支持多选,用英文,隔开"`
+	Classify     string   `description:"分类id,支持多选,用英文,隔开"`
 	Keyword      string   `description:"关键字"`
 }
 

+ 66 - 4
models/data_manage/edb_data_base.go

@@ -14,10 +14,12 @@ import (
 func GetEdbDataTableName(source, subSource int) (tableName string) {
 	switch source {
 	case utils.DATA_SOURCE_THS:
-		tableName = "edb_data_ths"
-		if subSource == utils.DATA_SUB_SOURCE_DATE {
+		switch subSource {
+		case utils.DATA_SUB_SOURCE_DATE:
 			tableName = "edb_data_ths_ds"
-		} else {
+		case utils.DATA_SUB_SOURCE_HIGH_FREQUENCY:
+			tableName = "edb_data_ths_hf"
+		default:
 			tableName = "edb_data_ths"
 		}
 	case utils.DATA_SOURCE_WIND:
@@ -222,9 +224,12 @@ 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)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return GetThsHfEdbDataAllByEdbCodeByMongo(edbCode, source, subSource, limit)
+	}
 
 	return GetEdbDataAllByEdbCodeByMysql(edbCode, source, subSource, limit)
 }
@@ -403,3 +408,60 @@ func GetAddSql(edbInfoId, edbCode, dataTime, timestampStr string, value string)
 	addSql += "),"
 	return
 }
+
+func GetThsHfEdbDataAllByEdbCodeByMongo(edbCode string, source, subSource, limit int) (list []*EdbInfoSearchData, err error) {
+	list = make([]*EdbInfoSearchData, 0)
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_code": edbCode,
+	}
+
+	var tmpDataList []*mgo.EdbDataThsHf
+	// 获取列表数据
+	if limit > 0 {
+		tmpDataList, err = mogDataObj.GetLimitDataList(queryConditions, int64(limit), []string{"-data_time"})
+	} else {
+		tmpDataList, err = mogDataObj.GetLimitDataList(queryConditions, int64(limit), []string{"-data_time"})
+	}
+	if err != nil {
+		return
+	}
+	for _, v := range tmpDataList {
+		list = append(list, &EdbInfoSearchData{
+			DataTime: v.DataTime.Format(utils.FormatDate),
+			Value:    v.Value,
+		})
+	}
+	return
+}
+
+func GetThsHfEdbDataBaseMongoByEdbInfoId(edbInfoId int, source, subSource int) (list []*EdbDataBase, err error) {
+	list = make([]*EdbDataBase, 0)
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_info_id": edbInfoId,
+	}
+	// 获取列表数据
+	tmpDataList, tmpErr := mogDataObj.GetAllDataList(queryConditions, []string{"data_time"})
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	for k, v := range tmpDataList {
+
+		list = append(list, &EdbDataBase{
+			EdbDataId:     k + 1,
+			EdbInfoId:     v.EdbInfoId,
+			EdbCode:       v.EdbCode,
+			DataTime:      v.DataTime.Format(utils.FormatDate),
+			DataTimestamp: v.DataTimestamp,
+			Value:         strconv.FormatFloat(v.Value, 'f', -1, 64),
+		})
+	}
+
+	return
+}

+ 102 - 1
models/data_manage/edb_data_insert_config.go

@@ -133,8 +133,10 @@ 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 if edbInfo.Source == utils.DATA_SOURCE_THS && edbInfo.SubSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		dateStr, err = updateThsHfInsertConfigValueByMongo(to, edbInfo, oldConfigDate, date, value)
 	} else {
 		dateStr, err = updateInsertConfigValueByMysql(to, edbInfo, oldConfigDate, date, value)
 	}
@@ -336,3 +338,102 @@ func updateInsertConfigValueByMongo(to orm.TxOrmer, edbInfo *EdbInfo, oldConfigD
 
 	return
 }
+
+func updateThsHfInsertConfigValueByMongo(to orm.TxOrmer, edbInfo *EdbInfo, oldConfigDate, newDate time.Time, value string) (dateStr string, err error) {
+	tableName := GetEdbDataTableName(edbInfo.Source, edbInfo.SubSource)
+	if tableName == `` {
+		err = errors.New("找不到该指标的数据表")
+		return
+	}
+
+	dateStr = newDate.Format(utils.FormatDate)
+	timestamp := newDate.UnixNano() / 1e6
+	var floatValue float64
+	if value != "" {
+		floatValue, err = strconv.ParseFloat(value, 64)
+		if err != nil {
+			fmt.Println("转换失败:", err.Error())
+			return
+		}
+	}
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	coll := mogDataObj.GetCollection()
+
+	var edbDateData *mgo.EdbDataThsHf
+	if !oldConfigDate.IsZero() {
+		// 构建查询条件
+		queryConditions := bson.M{
+			"edb_info_id": edbInfo.EdbInfoId,
+			"data_time":   oldConfigDate,
+		}
+
+		edbDateData, err = mogDataObj.GetItem(queryConditions)
+		//if tmpErr != nil && tmpErr == mongo.ErrNoDocuments {
+		//	err = tmpErr
+		//	return
+		//}
+		if err != nil && err != mongo.ErrNoDocuments {
+			return
+		}
+		err = nil
+
+	}
+
+	// 如果是没有历史数据,那么就需要增加数据
+	if edbDateData == nil {
+		addDataItem := mgo.EdbDataThsHf{
+			//ID:            primitive.ObjectID{},
+			EdbInfoId:     edbInfo.EdbInfoId,
+			EdbCode:       edbInfo.EdbCode,
+			DataTime:      newDate,
+			Value:         floatValue,
+			CreateTime:    time.Now(),
+			ModifyTime:    time.Now(),
+			DataTimestamp: timestamp,
+		}
+		err = mogDataObj.InsertDataByColl(coll, addDataItem)
+		if err != nil {
+			fmt.Println("mogDataObj.BatchInsertData() Err:" + err.Error())
+			return
+		}
+		return
+	}
+
+	// 数据清空
+	if value == "" {
+		queryConditions := bson.M{
+			"edb_info_id": edbInfo.EdbInfoId,
+		}
+
+		// 获取最新的两条数据
+		tmpDataList, tmpErr := mogDataObj.GetLimitDataList(queryConditions, 2, []string{"-data_time"})
+		if tmpErr != nil {
+			fmt.Println("mogDataObj.GetLimitDataList() Err:" + tmpErr.Error())
+			return
+		}
+		// 如果并没有两条数据,那么就返回
+		if len(tmpDataList) < 2 {
+			return
+		}
+
+		// 实际应该是倒数第二条数据的日期
+		dateStr = tmpDataList[1].DataTime.Format(utils.FormatDate)
+
+		// 删除插入的数据
+		err = mogDataObj.RemoveManyByColl(coll, bson.M{"_id": tmpDataList[0].ID})
+
+		return
+	}
+
+	// 更新配置的数据
+	updateData := bson.M{"$set": bson.M{
+		"value":          floatValue,
+		"modify_time":    time.Now(),
+		"data_time":      newDate,
+		"data_timestamp": timestamp,
+	}}
+	err = mogDataObj.UpdateDataByColl(coll, bson.M{"_id": edbDateData.ID}, updateData)
+
+	return
+}

+ 209 - 15
models/data_manage/edb_info.go

@@ -7,12 +7,13 @@ import (
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/beego/beego/v2/client/orm"
-	"github.com/rdlucklib/rdluck_tools/paging"
-	"go.mongodb.org/mongo-driver/bson"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"go.mongodb.org/mongo-driver/bson"
 )
 
 type EdbInfo struct {
@@ -156,6 +157,10 @@ type EdbInfoSearchResp struct {
 	StockSearchList []*EdbInfoSearch
 }
 
+type EdbInfoMySteelChemicalCheckResp struct {
+	Status int `description:"1:数据已存在于弘则数据库,2:新数据,3:数据已存在于弘则数据库,但是当前账号无权限"`
+}
+
 func GetEdbInfoByEdbCode(source int, edbCode string) (item *EdbInfo, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM edb_info WHERE source=? AND edb_code=? `
@@ -196,6 +201,20 @@ func GetEdbInfoByIdList(edbInfoIdList []int) (items []*EdbInfo, err error) {
 	return
 }
 
+// type MysteelChemicalDataListReq struct {
+// 	Frequency    string `form:"Frequency" description:"频度"`
+// 	Source       int    `form:"Source" description:"来源id"`
+// 	Keywords     string `form:"Keywords" description:"指标ID/指标名称"`
+// 	SortField    int    `form:"SortField" description:"排序字段: 0-默认; 1-开始时间; 2-最新时间; 3-更新时间"`
+// 	SortRule     int    `form:"SortRule" description:"排序方式: 0-默认; 1-正序; 2-倒序"`
+// }
+
+// MysteelChemicalDataBatchAddCheckReq 钢联化工指标批量添加校验
+type MysteelChemicalDataBatchAddCheckReq struct {
+	// MysteelChemicalDataListReq
+	IndexCodes []string `form:"IndexCodes" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
+}
+
 type AddEdbInfoReq struct {
 	Source     int    `description:"来源id"`
 	EdbCode    string `description:"指标编码"`
@@ -207,6 +226,12 @@ type AddEdbInfoReq struct {
 	EndDate    string `description:"终止日期"`
 }
 
+type NameCheckEdbInfoReq struct {
+	EdbCode   string `description:"指标编码"`
+	EdbName   string `description:"指标名称"`
+	Frequency string `description:"频率"`
+}
+
 func DeleteEdbInfoAndData(edbInfoId, source, subSource int) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	to, err := o.Begin()
@@ -229,8 +254,11 @@ 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 if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		mogDataObj := mgo.EdbDataThsHf{}
+		err = mogDataObj.RemoveMany(bson.M{"edb_info_id": edbInfoId})
 	} else {
 		err = deleteAllEdbDataByMysql(to, edbInfoId, source, subSource)
 	}
@@ -594,9 +622,12 @@ 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)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return GetThsHfEdbInfoMaxAndMinInfoByMongo(source, subSource, edbCode)
+	}
 
 	// 默认走mysql
 	return GetEdbInfoMaxAndMinInfoByMysql(source, subSource, edbCode)
@@ -899,9 +930,63 @@ func ReplaceChartEdb(oldEdbInfo, newEdbInfo *EdbInfo) (relationEdbInfoIdList []i
 					chartInfo.EdbInfoIds = strings.Join(edbInfoIds, ",")
 					updateStr = append(updateStr, "EdbInfoIds")
 				}
-				if chartInfo.ExtraConfig != "" {
+				if chartInfo.ExtraConfig != "" || chartInfo.BarConfig != "" {
 					//判断是否是拟合方程或者散点图截面图
-					if chartInfo.Source == utils.CHART_SOURCE_LINE_EQUATION {
+					if chartInfo.Source == utils.CHART_SOURCE_FUTURE_GOOD {
+						// 商品价格曲线图的一些配置
+						var barConfig FutureGoodBarChartInfoReq
+						err = json.Unmarshal([]byte(chartInfo.BarConfig), &barConfig)
+						if err != nil {
+							errmsg = "商品价格曲线图配置异常 json.Unmarshal:Err:" + err.Error()
+							return
+						}
+						if barConfig.BaseEdbInfoId == oldEdbInfo.EdbInfoId {
+							barConfig.BaseEdbInfoId = newEdbInfo.EdbInfoId
+						}
+						for k, item := range barConfig.EdbInfoIdList {
+							if item.EdbInfoId == oldEdbInfo.EdbInfoId && item.Source == 1 {
+								barConfig.EdbInfoIdList[k].EdbInfoId = newEdbInfo.EdbInfoId
+							}
+						}
+						// 更新图表配置信息
+						// 重新序列化
+						configJson, e := json.Marshal(barConfig)
+						if e != nil {
+							err = e
+							errmsg = "图表配置信息序列化失败:json.Marshal Err: " + e.Error()
+							return
+						}
+						configStr := string(configJson)
+						chartInfo.BarConfig = configStr
+						updateStr = append(updateStr, "BarConfig")
+					} else if chartInfo.Source == utils.CHART_SOURCE_FUTURE_GOOD_PROFIT {
+						// 商品利润曲线图的一些配置
+						var extraConf FutureGoodProfitChartInfoReq
+						err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConf)
+						if err != nil {
+							errmsg = "商品利润曲线图配置异常 json.Unmarshal:Err:" + err.Error()
+							return
+						}
+						if extraConf.BaseEdbInfoId == oldEdbInfo.EdbInfoId {
+							extraConf.BaseEdbInfoId = newEdbInfo.EdbInfoId
+						}
+						for k, v := range extraConf.EdbInfoIdList {
+							if v == oldEdbInfo.EdbInfoId {
+								extraConf.EdbInfoIdList[k] = newEdbInfo.EdbInfoId
+							}
+						}
+						// 更新图表配置信息
+						// 重新序列化
+						configJson, e := json.Marshal(extraConf)
+						if e != nil {
+							err = e
+							errmsg = "图表配置信息序列化失败:json.Marshal Err: " + e.Error()
+							return
+						}
+						configStr := string(configJson)
+						chartInfo.ExtraConfig = configStr
+						updateStr = append(updateStr, "ExtraConfig")
+					} else if chartInfo.Source == utils.CHART_SOURCE_LINE_EQUATION {
 						//解析配置内容
 						var lineChartInfoConfig request.LineChartInfoReq
 						err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &lineChartInfoConfig)
@@ -1540,13 +1625,14 @@ type BatchAddEdbInfoReq struct {
 }
 
 type BatchAddEdbInfo struct {
-	Source     int    `description:"来源id"`
-	EdbName    string `description:"指标名称"`
-	Frequency  string `description:"频率"`
-	Unit       string `description:"单位"`
-	ClassifyId int    `description:"分类id"`
-	StockCode  string `description:"证券代码"`
-	EdbCode    string `description:"指标编码"`
+	Source       int    `description:"来源id"`
+	EdbName      string `description:"指标名称"`
+	Frequency    string `description:"频率"`
+	Unit         string `description:"单位"`
+	ClassifyId   int    `description:"分类id"`
+	StockCode    string `description:"证券代码"`
+	EdbCode      string `description:"指标编码"`
+	ApiExtraPars string `description:"API额外参数"`
 }
 
 func GetEdbInfoWsdMaxAndMinInfo(source, subSource int, edbCode string) (item *EdbInfoMaxAndMinInfo, err error) {
@@ -1700,6 +1786,17 @@ func GetEdbInfoByNameArr(names []string, edbInfoType int) (items []*EdbInfo, err
 	return
 }
 
+// GetEdbInfoByNameArr 根据指标编码获取指标
+func GetEdbInfoByCodeArr(codes []string, edbInfoType int) (items []*EdbInfo, err error) {
+	if len(codes) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT edb_info_id, edb_code, edb_name FROM edb_info WHERE edb_code IN (%s) AND edb_info_type = ? `, utils.GetOrmInReplace(len(codes)))
+	_, err = o.Raw(sql, codes, edbInfoType).QueryRows(&items)
+	return
+}
+
 // GetEdbCodesBySource 根据来源获取指标编码
 func GetEdbCodesBySource(source int) (items []*EdbInfo, err error) {
 	o := orm.NewOrmUsingDB("data")
@@ -1720,9 +1817,12 @@ 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)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return getThsHfAllDataByMongo(edbInfoId, source, subSource, startDataTime)
+	}
 
 	// 默认走mysql
 	return getAllDataByMysql(edbInfoId, source, subSource, startDataTime)
@@ -1831,6 +1931,22 @@ type ReplaceEdbInfoItem struct {
 	NewEdbInfo *EdbInfo
 }
 
+func GetEdbInfoFieldList(cond string, pars []interface{}, fields []string) (items []*EdbInfo, err error) {
+	field := " * "
+	if len(fields) > 0 {
+		field = strings.Join(fields, ",")
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT %s FROM edb_info WHERE 1=1 %s `, field, cond)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// EdbInfoExtra 指标额外数据-extra字段
+type EdbInfoExtra struct {
+	ApiExtraPars string `description:"API-额外参数(如同花顺日期序列)"`
+}
+
 func GetEdbInfoListByCond(condition string, pars []interface{}) (list []*EdbInfoList, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT * FROM edb_info WHERE 1=1 `
@@ -1841,3 +1957,81 @@ func GetEdbInfoListByCond(condition string, pars []interface{}) (list []*EdbInfo
 	_, err = o.Raw(sql, pars).QueryRows(&list)
 	return
 }
+
+func GetThsHfEdbInfoMaxAndMinInfoByMongo(source, subSource int, edbCode string) (item *EdbInfoMaxAndMinInfo, err error) {
+	mogDataObj := new(mgo.EdbDataThsHf)
+	pipeline := []bson.M{
+		{"$match": bson.M{"edb_code": edbCode}},
+		{"$group": bson.M{
+			"_id":       nil,
+			"min_date":  bson.M{"$min": "$data_time"},
+			"max_date":  bson.M{"$max": "$data_time"},
+			"min_value": bson.M{"$min": "$value"},
+			"max_value": bson.M{"$max": "$value"},
+		}},
+		{"$project": bson.M{"_id": 0}}, // 可选,如果不需要_id字段
+	}
+	result, err := mogDataObj.GetEdbInfoMaxAndMinInfo(pipeline)
+	if err != nil {
+		fmt.Println("EdbDataThsHf getEdbDataThsHfList Err:" + err.Error())
+		return
+	}
+
+	if !result.MaxDate.IsZero() {
+		whereQuery := bson.M{"edb_code": edbCode, "data_time": result.MaxDate}
+		selectParam := bson.D{{"value", 1}, {"_id", 0}}
+		latestValue, tmpErr := mogDataObj.GetLatestValue(whereQuery, selectParam)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		result.LatestValue = latestValue.Value
+		result.EndValue = latestValue.Value
+	}
+
+	item = &EdbInfoMaxAndMinInfo{
+		MinDate:     result.MinDate.Format(utils.FormatDate),
+		MaxDate:     result.MaxDate.Format(utils.FormatDate),
+		MinValue:    result.MinValue,
+		MaxValue:    result.MaxValue,
+		LatestValue: result.LatestValue,
+	}
+
+	return
+}
+
+func getThsHfAllDataByMongo(edbInfoId, source, subSource int, startDataTime string) (dataList []*EdbData, err error) {
+	dataList = make([]*EdbData, 0)
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_info_id": edbInfoId,
+	}
+
+	// 开始日期
+	dateCondition, err := mgo.BuildDateCondition(startDataTime, "")
+	if err != nil {
+		return
+	}
+	if len(dateCondition) > 0 {
+		queryConditions["data_time"] = dateCondition
+	}
+
+	// 获取列表数据
+	tmpDataList, tmpErr := mogDataObj.GetAllDataList(queryConditions, []string{"-data_time"})
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	for k, v := range tmpDataList {
+		dataList = append(dataList, &EdbData{
+			EdbDataId: k + 1,
+			EdbInfoId: v.EdbInfoId,
+			DataTime:  v.DataTime.Format(utils.FormatDate),
+			Value:     v.Value,
+		})
+	}
+
+	return
+}

+ 17 - 0
models/data_manage/factor_edb_series.go

@@ -94,6 +94,16 @@ func (m *FactorEdbSeries) MultiRemove(ids []int) (err error) {
 	return
 }
 
+func (m *FactorEdbSeries) 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 *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)
@@ -349,3 +359,10 @@ func (a FactorEdbSeriesCorrelationMatrixOrder) Less(i, j int) bool {
 	// 负数按绝对值的降序排序(即数值的升序)
 	return a[i].XData > a[j].XData
 }
+
+// CalculateCorrelationMatrixPars 计算相关性矩阵参数
+type CalculateCorrelationMatrixPars struct {
+	BaseEdbInfoId int               `description:"标的指标ID"`
+	SeriesIds     []int             `description:"系列IDs"`
+	Correlation   CorrelationConfig `description:"相关性配置"`
+}

+ 10 - 0
models/data_manage/factor_edb_series_chart_mapping.go

@@ -103,6 +103,16 @@ func (m *FactorEdbSeriesChartMapping) MultiRemove(ids []int) (err error) {
 	return
 }
 
+func (m *FactorEdbSeriesChartMapping) 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 *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)

+ 19 - 0
models/data_manage/future_good/future_good_edb_info_data.go

@@ -1,6 +1,7 @@
 package future_good
 
 import (
+	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
@@ -101,3 +102,21 @@ func GetFutureGoodEdbDataListByDate(futureGoodEdbInfoId int, startDate, endDate
 	_, err = o.Raw(sql, futureGoodEdbInfoId, pars).QueryRows(&list)
 	return
 }
+
+func GetFutureGoodEdbDataListByIdsAndDate(futureGoodEdbInfoIds []int, startDate, endDate string) (list []*FutureGoodEdbData, err error) {
+	var pars []interface{}
+	sql := `SELECT * FROM future_good_edb_data WHERE 1=1 AND future_good_edb_info_id in (` + utils.GetOrmInReplace(len(futureGoodEdbInfoIds)) + `)  `
+	if startDate != "" {
+		sql += ` AND data_time>=? `
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		sql += ` AND data_time<=? `
+		pars = append(pars, endDate)
+	}
+
+	sql += ` ORDER BY data_time ASC `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, futureGoodEdbInfoIds, pars).QueryRows(&list)
+	return
+}

+ 5 - 12
models/data_manage/future_good/request/future_good_chart.go

@@ -1,6 +1,8 @@
 package request
 
-import "eta/eta_api/models/data_manage"
+import (
+	"eta/eta_api/models/data_manage"
+)
 
 type EditChartEnInfoReq struct {
 	ChartInfoId      int    `description:"图表ID"`
@@ -56,6 +58,8 @@ type ChartInfoReq struct {
 	BaseEdbInfoId           int                          `description:"基础的指标id"`
 	DateList                []ChartInfoDateReq           `description:"日期配置"`
 	ProfitNameEn            string                       `description:"利润英文名称"`
+	EdbInfoIdList           []int                        `description:"现货指标ID列表"`
+	XDataList               []data_manage.XData          `description:"横轴配置"`
 }
 
 // ChartInfoDateReq 图表的日期数据(日期相关)
@@ -110,14 +114,3 @@ type SectionScatterEdbItemReq struct {
 	YDateValue int    `description:"Y轴的日期N天的值"`
 	IsShow     bool   `description:"是否展示"`
 }
-
-// EditChartInfoBaseReq
-// @Description: 修改商品图表的基础信息
-type EditChartInfoBaseReq struct {
-	ChartInfoId    int    `description:"图表ID"`
-	ChartName      string `description:"图表名称(根据当前语言版本不同而不同)"`
-	EdbName        string `description:"指标名称(根据当前语言版本不同而不同)"`
-	Unit           string `description:"指标单位(根据当前语言版本不同而不同)"`
-	ProfitName     string `description:"利润名称(根据当前语言版本不同而不同)"`
-	FutureGoodName string `description:"期货合约名称(根据当前语言版本不同而不同)"`
-}

+ 65 - 40
models/data_manage/future_good_chart_info.go

@@ -56,6 +56,17 @@ func EditFutureGoodChartEnInfoAndEdbEnInfo(chartInfoId int, chartNameEn string,
 	return
 }
 
+type EditFutureGoodChartInfoBaseReq struct {
+	ChartInfoId int    `description:"图表ID"`
+	ChartName   string `description:"图表名称(根据当前语言版本不同而不同)"`
+	/*	EdbName        string `description:"指标名称(根据当前语言版本不同而不同)"`
+		Unit           string `description:"指标单位(根据当前语言版本不同而不同)"`*/
+	ProfitName       string                      `description:"利润名称(根据当前语言版本不同而不同)"`
+	FutureGoodName   string                      `description:"期货合约名称(根据当前语言版本不同而不同)"`
+	ChartEdbInfoList []*EditChartInfoEdbBaseItem `description:"指标及配置信息"`
+	XDataList        []string                    `description:"商品价格曲线的X轴数据"`
+}
+
 // EditBaseFutureGoodChartInfoAndEdbEnInfo
 // @Description:  编辑期货商品基础的图表信息及指标信息
 // @author: Roc
@@ -67,7 +78,7 @@ func EditFutureGoodChartEnInfoAndEdbEnInfo(chartInfoId int, chartNameEn string,
 // @param edbUnit string
 // @param lang string
 // @return err error
-func EditBaseFutureGoodChartInfoAndEdbEnInfo(chartInfo *ChartInfo, chartName string, edbInfoId int, edbName, edbUnit, lang string) (err error) {
+func EditBaseFutureGoodChartInfoAndEdbEnInfo(chartInfo *ChartInfo, req *EditFutureGoodChartInfoBaseReq, lang string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	to, err := o.Begin()
 	if err != nil {
@@ -85,63 +96,54 @@ func EditBaseFutureGoodChartInfoAndEdbEnInfo(chartInfo *ChartInfo, chartName str
 	updateChartCols := make([]string, 0)
 	switch lang {
 	case utils.EnLangVersion:
-		chartInfo.ChartNameEn = chartName
+		chartInfo.ChartNameEn = req.ChartName
 		updateChartCols = append(updateChartCols, "ChartNameEn")
 	default:
-		chartInfo.ChartName = chartName
+		chartInfo.ChartName = req.ChartName
 		updateChartCols = append(updateChartCols, "ChartName")
 	}
 	chartInfo.ModifyTime = time.Now()
-	updateChartCols = append(updateChartCols, "ModifyTime")
+	updateChartCols = append(updateChartCols, "ModifyTime", "BarConfig")
 	_, err = to.Update(chartInfo, updateChartCols...)
 	if err != nil {
 		fmt.Println("UPDATE  chart_info Err:", err.Error())
 		return err
 	}
-
-	var count int
-	csql := `SELECT COUNT(1) AS count FROM chart_edb_mapping WHERE chart_info_id=? AND edb_info_id=? AND source = 1 `
-	err = to.Raw(csql, chartInfo.ChartInfoId, edbInfoId).QueryRow(&count)
-	if err != nil {
-		fmt.Println("QueryRow Err:", err.Error())
-		return err
-	}
-	if count > 0 {
+	for _, v := range req.ChartEdbInfoList {
 		msql := ` UPDATE edb_info SET modify_time = NOW()  `
 		pars := make([]interface{}, 0)
 		switch lang {
 		case utils.EnLangVersion:
-			if edbName != `` {
+			if v.EdbName != `` {
 				msql += ` ,edb_name_en = ? `
-				pars = append(pars, edbName)
+				pars = append(pars, v.EdbName)
 			}
 
-			if edbUnit != `` {
+			if v.Unit != `` {
 				msql += ` ,unit_en = ? `
-				pars = append(pars, edbUnit)
+				pars = append(pars, v.Unit)
 			}
 
 		default:
 
-			if edbName != `` {
+			if v.EdbName != `` {
 				msql += ` ,edb_name = ? `
-				pars = append(pars, edbName)
+				pars = append(pars, v.EdbName)
 			}
 
-			if edbUnit != `` {
+			if v.Unit != `` {
 				msql += ` ,unit = ? `
-				pars = append(pars, edbUnit)
+				pars = append(pars, v.Unit)
 			}
 		}
 		msql += ` WHERE edb_info_id = ? `
-		pars = append(pars, edbInfoId)
+		pars = append(pars, v.EdbInfoId)
 		_, err = to.Raw(msql, pars...).Exec()
 		if err != nil {
 			fmt.Println("edb_info Err:" + err.Error())
 			return err
 		}
 	}
-
 	return
 }
 
@@ -205,7 +207,7 @@ func EditFutureGoodProfitChartEnInfoAndEdbEnInfo(chartInfoId int, chartNameEn st
 // @param profitName string
 // @param lang string
 // @return err error
-func EditBaseFutureGoodProfitChartInfoAndEdbEnInfo(chartInfo *ChartInfo, chartName string, edbInfoId int, edbName, edbUnit, profitName, lang string) (err error) {
+func EditBaseFutureGoodProfitChartInfoAndEdbEnInfo(chartInfo *ChartInfo, req *EditFutureGoodChartInfoBaseReq, lang string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	to, err := o.Begin()
 	if err != nil {
@@ -223,32 +225,55 @@ func EditBaseFutureGoodProfitChartInfoAndEdbEnInfo(chartInfo *ChartInfo, chartNa
 	updateChartCols := make([]string, 0)
 	switch lang {
 	case utils.EnLangVersion:
-		chartInfo.ChartNameEn = chartName
+		chartInfo.ChartNameEn = req.ChartName
 		updateChartCols = append(updateChartCols, "ChartNameEn")
 	default:
-		chartInfo.ChartName = chartName
+		chartInfo.ChartName = req.ChartName
 		updateChartCols = append(updateChartCols, "ChartName")
 	}
 	chartInfo.ModifyTime = time.Now()
-	updateChartCols = append(updateChartCols, "ModifyTime")
+	updateChartCols = append(updateChartCols, "ModifyTime", "ExtraConfig")
 	_, err = to.Update(chartInfo, updateChartCols...)
 	if err != nil {
 		fmt.Println("UPDATE  chart_info Err:", err.Error())
 		return err
 	}
-
-	// 更改指标英文信息
 	var sql string
-	switch lang {
-	case utils.EnLangVersion:
-		sql = ` UPDATE  edb_info SET edb_name_en = ?,unit_en = ?,modify_time = NOW() WHERE edb_info_id = ? `
-	default:
-		sql = ` UPDATE  edb_info SET edb_name = ?,unit = ?,modify_time = NOW() WHERE edb_info_id = ? `
-	}
-	_, err = to.Raw(sql, edbName, edbUnit, edbInfoId).Exec()
-	if err != nil {
-		fmt.Println("edb_info Err:" + err.Error())
-		return
+	// 更改指标英文信息
+	for _, v := range req.ChartEdbInfoList {
+		msql := ` UPDATE edb_info SET modify_time = NOW()  `
+		pars := make([]interface{}, 0)
+		switch lang {
+		case utils.EnLangVersion:
+			if v.EdbName != `` {
+				msql += ` ,edb_name_en = ? `
+				pars = append(pars, v.EdbName)
+			}
+
+			if v.Unit != `` {
+				msql += ` ,unit_en = ? `
+				pars = append(pars, v.Unit)
+			}
+
+		default:
+
+			if v.EdbName != `` {
+				msql += ` ,edb_name = ? `
+				pars = append(pars, v.EdbName)
+			}
+
+			if v.Unit != `` {
+				msql += ` ,unit = ? `
+				pars = append(pars, v.Unit)
+			}
+		}
+		msql += ` WHERE edb_info_id = ? `
+		pars = append(pars, v.EdbInfoId)
+		_, err = to.Raw(msql, pars...).Exec()
+		if err != nil {
+			fmt.Println("edb_info Err:" + err.Error())
+			return err
+		}
 	}
 
 	// 更改指标英文信息
@@ -259,7 +284,7 @@ func EditBaseFutureGoodProfitChartInfoAndEdbEnInfo(chartInfo *ChartInfo, chartNa
 	default:
 		sql = ` UPDATE  chart_info_future_good_profit SET profit_name = ?,modify_time = NOW() WHERE chart_info_id = ? `
 	}
-	_, err = to.Raw(sql, profitName, chartInfo.ChartInfoId).Exec()
+	_, err = to.Raw(sql, req.ProfitName, chartInfo.ChartInfoId).Exec()
 	if err != nil {
 		fmt.Println("chart_info_future_good_profit Err:" + err.Error())
 		return

+ 90 - 1
models/data_manage/mysteel_chemical_index.go

@@ -3,9 +3,11 @@ package data_manage
 import (
 	"eta/eta_api/utils"
 	"fmt"
+	"strings"
+	"time"
+
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
-	"time"
 )
 
 // BaseFromMysteelChemicalIndex 钢联化工指标表
@@ -34,12 +36,75 @@ type BaseFromMysteelChemicalIndex struct {
 	EndValue                          float64   `description:"指标的最新值"`
 }
 
+type BaseFromMysteelChemicalIndexItem struct {
+	BaseFromMysteelChemicalIndexId int64  `orm:"column(base_from_mysteel_chemical_index_id);pk"`
+	IndexCode                      string `description:"指标编码"`
+	IndexName                      string `description:"指标名称"`
+	Unit                           string `description:"单位"`
+	Frequency                      string `description:"频度"`
+	Source                         int    `description:"数据来源"`
+	StartDate                      string `description:"开始日期"`
+	EndDate                        string `description:"结束日期"`
+	CreateTime                     string `description:"创建时间"`
+	ModifyTime                     string `description:"修改时间"`
+	EdbInfoId                      int    `description:"eta指标库的id"`
+	EdbUniqueCode                  string `description:"指标库唯一编码"`
+	EdbClassifyId                  int    `description:"指标库分类ID"`
+	IsStop                         int    `description:"是否停更:1:停更,0:未停更"`
+	EdbExist                       int    `description:"指标库是否已添加:0-否;1-是"`
+}
+
+var BaseFromMysteelChemicalIndexCols = struct {
+	BaseFromMysteelChemicalIndexId string
+	IndexCode                      string
+	IndexName                      string
+	Unit                           string
+	Frequency                      string
+	Source                         string
+	StartDate                      string
+	EndDate                        string
+	CreateTime                     string
+	ModifyTime                     string
+}{
+	BaseFromMysteelChemicalIndexId: "base_from_mysteel_chemical_index_id",
+	IndexCode:                      "index_code",
+	IndexName:                      "index_name",
+	Unit:                           "unit",
+	Frequency:                      "frequency",
+	Source:                         "source",
+	StartDate:                      "start_date",
+	EndDate:                        "end_date",
+	CreateTime:                     "create_time",
+	ModifyTime:                     "modify_time",
+}
+
 // Update 更新钢联化工指标基础信息
 func (item *BaseFromMysteelChemicalIndex) Update(cols []string) (err error) {
 	o := orm.NewOrmUsingDB("data")
 	_, err = o.Update(item, cols...)
 	return
 }
+func (m *BaseFromMysteelChemicalIndex) GeItemsByCondition(condition string, pars []interface{}, limitSize int) (items []*BaseFromMysteelChemicalIndexItem, err error) {
+	if pars == nil {
+		pars = make([]interface{}, 0)
+	}
+
+	sql := `select a.*,b.edb_info_id,b.unique_code as edb_unique_code,b.classify_id as edb_classify_id FROM base_from_mysteel_chemical_index AS a LEFT JOIN edb_info b on a.index_code=b.edb_code AND b.source=? WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` order by a.base_from_mysteel_chemical_index_id desc `
+
+	if limitSize > 0 {
+		sql += " limit ? "
+		pars = append(pars, limitSize)
+	}
+
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, pars).QueryRows(&items)
+
+	return
+}
 
 // AddBaseFromMysteelChemicalIndex 添加钢联化工指标
 func AddBaseFromMysteelChemicalIndex(item *BaseFromMysteelChemicalIndex) (lastId int64, err error) {
@@ -53,6 +118,13 @@ func AddBaseFromMysteelChemicalIndex(item *BaseFromMysteelChemicalIndex) (lastId
 	return
 }
 
+// AddBaseFromMysteelChemicalIndex 添加钢联化工指标
+func BatchAddBaseFromMysteelChemicalIndex(items []*BaseFromMysteelChemicalIndex) (lastId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
 // BaseFromMysteelChemicalData 钢联化工指标数据表
 type BaseFromMysteelChemicalData struct {
 	BaseFromMysteelChemicalDataId  int       `orm:"column(base_from_mysteel_chemical_data_id);pk"`
@@ -115,6 +187,7 @@ type MysteelChemicalList struct {
 	UnitName                          string             `orm:"column(unit)"`
 	UniqueCode                        string             `description:"唯一编码"`
 	FrequencyName                     string             `orm:"column(frequency)"`
+	EdbInfoId                         int                `description:"指标库的id"`
 	UpdateTime                        string             `orm:"column(modify_time)"`
 	IsStop                            int                `description:"是否停更:1:停更,0:未停更"`
 	Paging                            *paging.PagingItem `description:"分页数据"`
@@ -184,6 +257,22 @@ func GetBaseFromMysteelChemicalIndexByCode(indexCode string) (item *BaseFromMyst
 	return
 }
 
+// GetBaseFromMysteelChemicalIndexByCodeList 根据指标code获取指标信息
+func GetBaseFromMysteelChemicalIndexByCodeList(indexCodeList []string) (items []*BaseFromMysteelChemicalIndex, err error) {
+	if len(indexCodeList) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_mysteel_chemical_index WHERE index_code IN (%s) `
+	holder := make([]string, 0)
+	for range indexCodeList {
+		holder = append(holder, "?")
+	}
+	sql = fmt.Sprintf(sql, strings.Join(holder, ","))
+	_, err = o.Raw(sql, indexCodeList).QueryRows(&items)
+	return
+}
+
 // GetBaseFromMysteelChemicalIndexByClassifyIdAndName 根据分类id和指标名名获取指标信息
 func GetBaseFromMysteelChemicalIndexByClassifyIdAndName(classifyId int, chartName string) (item *BaseFromMysteelChemicalIndex, err error) {
 	o := orm.NewOrmUsingDB("data")

+ 32 - 8
models/db.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_api/models/ai_summary"
 	"eta/eta_api/models/aimod"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/data_manage"
@@ -15,6 +16,7 @@ import (
 	"eta/eta_api/models/eta_trial"
 	"eta/eta_api/models/fe_calendar"
 	"eta/eta_api/models/ppt_english"
+	"eta/eta_api/models/report"
 	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/semantic_analysis"
@@ -202,7 +204,7 @@ func init() {
 	initFactorEdbSeries()
 
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
-	data_manage.InitEdbSourceVar()
+	afterInitTable()
 }
 
 // initSystem 系统表 数据表
@@ -253,12 +255,15 @@ func initReport() {
 		new(ChartPermission),                     // 权限表
 		new(YbPcSuncode),
 		new(YbSuncodePars),
-		new(ReportAuthor),                  //报告作者
-		new(ClassifyMenu),                  // 报告分类-子目录表
-		new(ClassifyMenuRelation),          // 报告分类-子目录关联表
-		new(ChartPermissionChapterMapping), // 权限mapping表
-		new(ReportChapterType),             // 报告章节类型表
-		new(ReportStateRecord),             // 研报状态修改记录表
+		new(ReportAuthor),                          //报告作者
+		new(ClassifyMenu),                          // 报告分类-子目录表
+		new(ClassifyMenuRelation),                  // 报告分类-子目录关联表
+		new(ChartPermissionChapterMapping),         // 权限mapping表
+		new(ReportChapterType),                     // 报告章节类型表
+		new(ReportStateRecord),                     // 研报状态修改记录表
+		new(report.ReportGrant),                    // 报告授权用户表
+		new(report.ReportChapterGrant),             // 报告章节授权用户表
+		new(report.ReportChapterPermissionMapping), // 报告章节的权限关系表
 	)
 }
 
@@ -339,7 +344,11 @@ func initEdbData() {
 		new(data_manage.EdbDataInsertConfig),      // 指标数据插入配置表
 		new(data_manage.EdbInfoNoPermissionAdmin), //指标不可见用户配置表
 		new(data_manage.EdbTerminal),              //指标终端
-		new(data_manage.EdbInfoRelation),          //指标关系表
+		new(data_manage.BaseFromThsHfIndex),
+		new(data_manage.BaseFromThsHfData),
+		new(data_manage.BaseFromThsHfClassify),
+		new(data_manage.BaseFromEdbMapping),
+		new(data_manage.EdbInfoRelation), //指标关系表
 	)
 }
 
@@ -526,6 +535,9 @@ func initAi() {
 		new(aimod.AiChatTopic),
 		new(aimod.AiChat),
 		new(aimod.FileUploadRecord),
+		new(ai_summary.AiSummaryClassify),
+		new(ai_summary.AiSummary),
+		new(ai_summary.AiPrompt),
 	)
 }
 
@@ -613,3 +625,15 @@ func initFactorEdbSeries() {
 		new(data_manage.FactorEdbSeriesCalculateData), // 因子指标系列-指标关联
 	)
 }
+
+// afterInitTable
+// @Description: 初始化表结构的的后置操作
+// @author: Roc
+// @datetime 2024-07-01 13:31:09
+func afterInitTable() {
+	// 初始化指标来源配置
+	data_manage.InitEdbSourceVar()
+
+	// 初始化是否启用mongo配置
+	InitUseMongoConf()
+}

+ 16 - 0
models/english_report.go

@@ -96,6 +96,8 @@ type AddEnglishReportReq struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdThird    int    `description:"三级分类id"`
+	ClassifyNameThird  string `description:"三级分类名称"`
 	Title              string `description:"标题"`
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
@@ -998,3 +1000,17 @@ func UpdatePdfUrlEnglishReportById(reportId int) (err error) {
 	_, err = o.Raw(sql, reportId).Exec()
 	return
 }
+
+func GetEnglishReportFieldsByIds(ids []int, fields []string) (items []*EnglishReport, err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	field := " * "
+	if len(fields) > 0 {
+		field = fmt.Sprintf(" %s ", strings.Join(fields, ","))
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM english_report WHERE id IN (%s)`, field, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).QueryRows(&items)
+	return
+}

+ 423 - 0
models/mgo/base_from_ths_hf_data.go

@@ -0,0 +1,423 @@
+package mgo
+
+import (
+	"context"
+	"errors"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/qiniu/qmgo"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"time"
+)
+
+// BaseFromThsHfData
+// @Description: 同花顺高频集合
+type BaseFromThsHfData struct {
+	ID                   primitive.ObjectID `json:"_id" bson:"_id,omitempty"`                                   // 文档id
+	BaseFromThsHfDataId  int64              `json:"base_from_ths_hf_data_id" bson:"base_from_ths_hf_data_id"`   // 指标数据ID
+	BaseFromThsHfIndexId int64              `json:"base_from_ths_hf_index_id" bson:"base_from_ths_hf_index_id"` // 指标ID
+	IndexCode            string             `json:"index_code" bson:"index_code"`                               // 指标编码
+	DataTime             time.Time          `json:"data_time" bson:"data_time"`                                 // 数据日期
+	Value                float64            `json:"value" bson:"value"`                                         // 数据值
+	UniqueCode           string             `json:"unique_code" bson:"unique_code"`                             // 唯一编码
+	CreateTime           time.Time          `json:"create_time" bson:"create_time"`                             // 创建时间
+	ModifyTime           time.Time          `json:"modify_time" bson:"modify_time"`                             // 修改时间
+	DataTimestamp        int64              `json:"data_timestamp" bson:"data_timestamp"`                       // 数据日期时间戳
+}
+
+// CollectionName
+// @Description:  获取集合名称
+func (m *BaseFromThsHfData) CollectionName() string {
+	return "base_from_ths_hf_data"
+}
+
+// DataBaseName
+// @Description: 获取数据库名称
+func (m *BaseFromThsHfData) DataBaseName() string {
+	return utils.MgoDataDbName
+}
+
+// GetCollection
+// @Description: 获取mongodb集合的句柄
+func (m *BaseFromThsHfData) GetCollection() *qmgo.Collection {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	return db.Collection(m.CollectionName())
+}
+
+// GetAllDataList
+// @Description: 根据条件获取所有数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:42:19
+// @param sort []string
+// @param whereParams interface{}
+// @return result []BaseFromThsHfData
+// @return err error
+func (m *BaseFromThsHfData) GetAllDataList(whereParams interface{}, sort []string) (result []*BaseFromThsHfData, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+	return
+}
+
+// GetLimitDataList
+// @Description: 根据条件获取指定数量数据列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-06 17:08:32
+// @param whereParams interface{}
+// @param size int64
+// @return result []*BaseFromThsHfData
+// @return err error
+func (m *BaseFromThsHfData) GetLimitDataList(whereParams interface{}, size int64, sort []string) (result []*BaseFromThsHfData, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).Limit(size).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	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 []*BaseFromThsHfData
+// @return err error
+func (m *BaseFromThsHfData) GetPageDataList(whereParams interface{}, startSize, size int64, sort []string) (result []*BaseFromThsHfData, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).Skip(startSize).Limit(size).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	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 *BaseFromThsHfData) GetCountDataList(whereParams interface{}) (count int64, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	count, err = coll.Find(ctx, whereParams).Count()
+
+	return
+}
+
+// InsertDataByColl
+// @Description: 写入单条数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param addData interface{}
+// @return err error
+func (m *BaseFromThsHfData) InsertDataByColl(coll *qmgo.Collection, addData interface{}) (err error) {
+	ctx := context.TODO()
+	_, err = coll.InsertOne(ctx, addData)
+	if err != nil {
+		fmt.Println("InsertDataByColl:Err:" + err.Error())
+		return
+	}
+
+	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 *BaseFromThsHfData) BatchInsertData(bulk int, dataList []interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.BatchInsertDataByColl(coll, bulk, dataList)
+}
+
+// BatchInsertDataByColl
+// @Description: 批量写入数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param coll *qmgo.Collection
+// @param bulk int 每次请求保存的数据量
+// @param dataList []interface{}
+// @return err error
+func (m *BaseFromThsHfData) BatchInsertDataByColl(coll *qmgo.Collection, bulk int, dataList []interface{}) (err error) {
+	ctx := context.TODO()
+	dataNum := len(dataList)
+	if dataNum <= 0 {
+		return
+	}
+
+	// 不设置每次保存切片数量大小,或者实际数据量小于设置的切片数量大小,那么就直接保存吧
+	if bulk <= 0 || dataNum <= bulk {
+		_, err = coll.InsertMany(ctx, dataList)
+		if err != nil {
+			fmt.Println("BatchInsertData:Err:" + err.Error())
+			return
+		}
+		return
+	}
+
+	// 分批保存
+	i := 0
+	tmpAddDataList := make([]interface{}, 0)
+	for _, v := range dataList {
+		tmpAddDataList = append(tmpAddDataList, v)
+		i++
+		if i >= bulk {
+			_, err = coll.InsertMany(ctx, tmpAddDataList)
+			if err != nil {
+				fmt.Println("BatchInsertData:Err:" + err.Error())
+				return
+			}
+			i = 0
+			tmpAddDataList = make([]interface{}, 0)
+		}
+	}
+
+	if len(tmpAddDataList) > 0 {
+		_, err = coll.InsertMany(ctx, tmpAddDataList)
+		if err != nil {
+			fmt.Println("BatchInsertData:Err:" + err.Error())
+			return
+		}
+	}
+
+	return
+}
+
+// UpdateDataByColl
+// @Description: 单条数据修改
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 15:01:51
+// @param whereParams interface{}
+// @param updateParams interface{}
+// @return err error
+func (m *BaseFromThsHfData) UpdateDataByColl(coll *qmgo.Collection, whereParams, updateParams interface{}) (err error) {
+	ctx := context.TODO()
+	err = coll.UpdateOne(ctx, whereParams, updateParams)
+	if err != nil {
+		fmt.Println("UpdateDataByColl:Err:" + err.Error())
+		return
+	}
+	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 *BaseFromThsHfData) UpdateData(whereParams, updateParams interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	err = coll.UpdateOne(ctx, whereParams, updateParams)
+	if err != nil {
+		fmt.Println("UpdateData:Err:" + err.Error())
+		return
+	}
+	return
+}
+
+// HandleData
+// @Description: 事务处理数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 10:40:20
+// @param addDataList []BaseAddFromBusinessData
+// @param updateDataList []BaseFromThsHfData
+// @return result interface{}
+// @return err error
+func (m *BaseFromThsHfData) HandleData(addDataList, updateDataList []BaseFromThsHfData) (result interface{}, err error) {
+
+	ctx := context.TODO()
+
+	callback := func(sessCtx context.Context) (interface{}, error) {
+		// 重要:确保事务中的每一个操作,都使用传入的sessCtx参数
+
+		db := utils.MgoDataCli.Database(m.DataBaseName())
+		coll := db.Collection(m.CollectionName())
+
+		// 插入数据
+		if len(addDataList) > 0 {
+			_, err = coll.InsertMany(sessCtx, addDataList)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// 修改
+
+		if len(updateDataList) > 0 {
+			for _, v := range updateDataList {
+				err = coll.UpdateOne(ctx, bson.M{"_id": v.ID}, bson.M{"$set": bson.M{"value": v.Value, "modify_time": v.ModifyTime}})
+				if err != nil {
+					fmt.Println("BatchInsertData:Err:" + err.Error())
+					return nil, err
+				}
+			}
+		}
+
+		return nil, nil
+	}
+	result, err = utils.MgoDataCli.DoTransaction(ctx, callback)
+
+	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 *BaseFromThsHfData) GetEdbInfoMaxAndMinInfo(whereParams interface{}) (result EdbInfoMaxAndMinInfo, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Aggregate(ctx, whereParams).One(&result)
+	if err != nil {
+		return
+	}
+	result.MinDate = result.MinDate.In(time.Local)
+	result.MaxDate = result.MaxDate.In(time.Local)
+	result.LatestDate = result.LatestDate.In(time.Local)
+
+	return
+}
+
+// GetLatestValue
+// @Description: 获取当前指标的最新数据记录
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 17:16:15
+// @param whereParams interface{}
+// @param selectParam interface{}
+// @return latestValue LatestValue
+// @return err error
+func (m *BaseFromThsHfData) GetLatestValue(whereParams, selectParam interface{}) (latestValue LatestValue, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+
+	//var result interface{}
+	//err = coll.Find(ctx, whereParams).Select(selectParam).One(&result)
+	err = coll.Find(ctx, whereParams).Select(selectParam).One(&latestValue)
+	return
+}
+
+func (m *BaseFromThsHfData) RemoveMany(whereParams interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	return m.RemoveManyByColl(coll, whereParams)
+}
+
+func (m *BaseFromThsHfData) RemoveManyByColl(coll *qmgo.Collection, whereParams interface{}) (err error) {
+	ctx := context.TODO()
+	_, err = coll.RemoveAll(ctx, whereParams)
+	if err != nil {
+		fmt.Println("RemoveManyByColl:Err:" + err.Error())
+		return
+	}
+	return
+}

+ 510 - 0
models/mgo/edb_data_ths_hf.go

@@ -0,0 +1,510 @@
+package mgo
+
+import (
+	"context"
+	"errors"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/qiniu/qmgo"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"time"
+)
+
+// EdbDataThsHf
+// @Description: 同花顺高频集合(指标库)
+type EdbDataThsHf struct {
+	ID            primitive.ObjectID `json:"_id" bson:"_id,omitempty" `            // 文档id
+	EdbInfoId     int                `json:"edb_info_id" bson:"edb_info_id"`       // 指标ID
+	EdbCode       string             `json:"edb_code" bson:"edb_code"`             // 指标编码
+	DataTime      time.Time          `json:"data_time" bson:"data_time"`           // 数据日期
+	Value         float64            `json:"value" bson:"value"`                   // 数据值
+	CreateTime    time.Time          `json:"create_time" bson:"create_time"`       // 创建时间
+	ModifyTime    time.Time          `json:"modify_time" bson:"modify_time"`       // 修改时间
+	DataTimestamp int64              `json:"data_timestamp" bson:"data_timestamp"` // 数据日期时间戳
+}
+
+// CollectionName
+// @Description:  获取集合名称
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:36
+// @return string
+func (m *EdbDataThsHf) CollectionName() string {
+	return "edb_data_ths_hf"
+}
+
+// DataBaseName
+// @Description: 获取数据库名称
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:33
+// @return string
+func (m *EdbDataThsHf) DataBaseName() string {
+	return utils.MgoDataDbName
+}
+
+// GetCollection
+// @Description: 获取mongodb集合的句柄
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:33
+// @return string
+func (m *EdbDataThsHf) GetCollection() *qmgo.Collection {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	return db.Collection(m.CollectionName())
+}
+
+// GetItem
+// @Description: 根据条件获取单条数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-09 10:00:49
+// @param whereParams interface{}
+// @return item *EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetItem(whereParams interface{}) (item *EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.GetItemByColl(coll, whereParams)
+}
+
+// GetItemByColl
+// @Description: 根据条件获取单条数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-09 13:22:06
+// @param coll *qmgo.Collection
+// @param whereParams interface{}
+// @return item *EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetItemByColl(coll *qmgo.Collection, whereParams interface{}) (item *EdbDataThsHf, err error) {
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).One(&item)
+	if err != nil {
+		return
+	}
+
+	item.DataTime = item.DataTime.In(time.Local)
+	item.CreateTime = item.CreateTime.In(time.Local)
+	item.ModifyTime = item.ModifyTime.In(time.Local)
+
+	return
+}
+
+// GetAllDataList
+// @Description: 根据条件获取所有数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:42:19
+// @param whereParams interface{}
+// @param sort []string
+// @return result []EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetAllDataList(whereParams interface{}, sort []string) (result []*EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	return
+}
+
+// GetLimitDataList
+// @Description: 根据条件获取指定数量数据列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-06 17:08:32
+// @param whereParams interface{}
+// @param size int64
+// @param sort []string
+// @return result []*BaseFromBusinessData
+// @return err error
+func (m *EdbDataThsHf) GetLimitDataList(whereParams interface{}, size int64, sort []string) (result []*EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).Limit(size).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	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 []*EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetPageDataList(whereParams interface{}, startSize, size int64, sort []string) (result []*EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).Skip(startSize).Limit(size).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	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 *EdbDataThsHf) GetCountDataList(whereParams interface{}) (count int64, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	count, err = coll.Find(ctx, whereParams).Count()
+
+	return
+}
+
+// InsertDataByColl
+// @Description: 写入单条数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param addData interface{}
+// @return err error
+func (m *EdbDataThsHf) InsertDataByColl(coll *qmgo.Collection, addData interface{}) (err error) {
+	ctx := context.TODO()
+	_, err = coll.InsertOne(ctx, addData)
+	if err != nil {
+		fmt.Println("InsertDataByColl:Err:" + err.Error())
+		return
+	}
+
+	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 *EdbDataThsHf) BatchInsertData(bulk int, dataList []interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.BatchInsertDataByColl(coll, bulk, dataList)
+}
+
+// BatchInsertDataByColl
+// @Description: 批量写入数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param coll *qmgo.Collection
+// @param bulk int 每次请求保存的数据量
+// @param dataList []interface{}
+// @return err error
+func (m *EdbDataThsHf) BatchInsertDataByColl(coll *qmgo.Collection, bulk int, dataList []interface{}) (err error) {
+	ctx := context.TODO()
+	dataNum := len(dataList)
+	if dataNum <= 0 {
+		return
+	}
+
+	// 不设置每次保存切片数量大小,或者实际数据量小于设置的切片数量大小,那么就直接保存吧
+	if bulk <= 0 || dataNum <= bulk {
+		_, err = coll.InsertMany(ctx, dataList)
+		if err != nil {
+			fmt.Println("BatchInsertData:Err:" + err.Error())
+			return
+		}
+		return
+	}
+
+	// 分批保存
+	i := 0
+	tmpAddDataList := make([]interface{}, 0)
+	for _, v := range dataList {
+		tmpAddDataList = append(tmpAddDataList, v)
+		i++
+		if i >= bulk {
+			_, err = coll.InsertMany(ctx, tmpAddDataList)
+			if err != nil {
+				fmt.Println("BatchInsertData:Err:" + err.Error())
+				return
+			}
+			i = 0
+			tmpAddDataList = make([]interface{}, 0)
+		}
+	}
+
+	if len(tmpAddDataList) > 0 {
+		_, err = coll.InsertMany(ctx, tmpAddDataList)
+		if err != nil {
+			fmt.Println("BatchInsertData:Err:" + err.Error())
+			return
+		}
+	}
+
+	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 *EdbDataThsHf) UpdateData(whereParams, updateParams interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.UpdateDataByColl(coll, whereParams, updateParams)
+}
+
+// UpdateDataByColl
+// @Description: 单条数据修改(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 15:01:51
+// @param whereParams interface{}
+// @param updateParams interface{}
+// @return err error
+func (m *EdbDataThsHf) UpdateDataByColl(coll *qmgo.Collection, whereParams, updateParams interface{}) (err error) {
+	ctx := context.TODO()
+	err = coll.UpdateOne(ctx, whereParams, updateParams)
+	if err != nil {
+		fmt.Println("UpdateDataByColl:Err:" + err.Error())
+		return
+	}
+
+	return
+}
+
+// RemoveMany
+// @Description: 根据条件删除多条数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 13:17:02
+// @param whereParams interface{}
+// @return err error
+func (m *EdbDataThsHf) RemoveMany(whereParams interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.RemoveManyByColl(coll, whereParams)
+}
+
+// RemoveManyByColl
+// @Description: 根据条件删除多条数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 13:18:42
+// @param coll *qmgo.Collection
+// @param whereParams interface{}
+// @return err error
+func (m *EdbDataThsHf) RemoveManyByColl(coll *qmgo.Collection, whereParams interface{}) (err error) {
+	ctx := context.TODO()
+	_, err = coll.RemoveAll(ctx, whereParams)
+	if err != nil {
+		fmt.Println("RemoveManyByColl:Err:" + err.Error())
+		return
+	}
+
+	return
+}
+
+// HandleData
+// @Description: 事务处理数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 10:39:01
+// @param addDataList []AddEdbDataThsHf
+// @param updateDataList []EdbDataThsHf
+// @return result interface{}
+// @return err error
+func (m *EdbDataThsHf) HandleData(addDataList, updateDataList []EdbDataThsHf) (result interface{}, err error) {
+
+	ctx := context.TODO()
+
+	callback := func(sessCtx context.Context) (interface{}, error) {
+		// 重要:确保事务中的每一个操作,都使用传入的sessCtx参数
+
+		db := utils.MgoDataCli.Database(m.DataBaseName())
+		coll := db.Collection(m.CollectionName())
+
+		// 插入数据
+		if len(addDataList) > 0 {
+			_, err = coll.InsertMany(sessCtx, addDataList)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// 修改
+
+		if len(updateDataList) > 0 {
+			for _, v := range updateDataList {
+				err = coll.UpdateOne(ctx, bson.M{"_id": v.ID}, bson.M{"$set": bson.M{"value": v.Value, "modify_time": v.ModifyTime}})
+				if err != nil {
+					fmt.Println("BatchInsertData:Err:" + err.Error())
+					return nil, err
+				}
+			}
+		}
+
+		return nil, nil
+	}
+	result, err = utils.MgoDataCli.DoTransaction(ctx, callback)
+
+	return
+}
+
+// EdbInfoMaxAndMinInfo 指标最新数据记录结构体
+//type EdbInfoMaxAndMinInfo struct {
+//	MinDate     time.Time `description:"最小日期" bson:"min_date"`
+//	MaxDate     time.Time `description:"最大日期" bson:"max_date"`
+//	MinValue    float64   `description:"最小值" bson:"min_value"`
+//	MaxValue    float64   `description:"最大值" bson:"max_value"`
+//	LatestValue float64   `description:"最新值" bson:"latest_value"`
+//	LatestDate  time.Time `description:"实际数据最新日期" bson:"latest_date"`
+//	EndValue    float64   `description:"最新值" bson:"end_value"`
+//}
+
+// GetEdbInfoMaxAndMinInfo
+// @Description: 获取当前指标的最大最小值
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 17:15:39
+// @param whereParams interface{}
+// @return result EdbInfoMaxAndMinInfo
+// @return err error
+func (m *EdbDataThsHf) GetEdbInfoMaxAndMinInfo(whereParams interface{}) (result EdbInfoMaxAndMinInfo, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Aggregate(ctx, whereParams).One(&result)
+	if err != nil {
+		return
+	}
+	result.MinDate = result.MinDate.In(time.Local)
+	result.MaxDate = result.MaxDate.In(time.Local)
+	result.LatestDate = result.LatestDate.In(time.Local)
+
+	return
+}
+
+// LatestValue 指标最新数据记录结构体
+//type LatestValue struct {
+//	Value float64 `description:"值" bson:"value"`
+//}
+
+// GetLatestValue
+// @Description: 获取当前指标的最新数据记录
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 17:16:15
+// @param whereParams interface{}
+// @param selectParam interface{}
+// @return latestValue LatestValue
+// @return err error
+func (m *EdbDataThsHf) GetLatestValue(whereParams, selectParam interface{}) (latestValue LatestValue, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+
+	//var result interface{}
+	//err = coll.Find(ctx, whereParams).Select(selectParam).One(&result)
+	err = coll.Find(ctx, whereParams).Select(selectParam).One(&latestValue)
+	return
+}

+ 25 - 0
models/permission.go

@@ -99,3 +99,28 @@ func UpdateChartPermissionNameFromMappingByKeyword(newKeyword string, classifyId
 	_, err = o.Raw(sql, newKeyword, classifyId, source).Exec()
 	return
 }
+
+// ChartPermissionSearchKeyWordMappingAndPermissionName
+// @Description: 分类关联品种
+type ChartPermissionSearchKeyWordMappingAndPermissionName struct {
+	ChartPermissionId   int    `description:"权限id"`
+	ChartPermissionName string `description:"权限名称"`
+	PermissionName      string `description:"权限名称"`
+	KeyWord             string `description:"二级分类名称"`
+	ClassifyId          int    `description:"分类ID"`
+}
+
+// GetPermissionByClassifyId
+// @Description: 根据分类id获取关联的报告权限
+// @author: Roc
+// @datetime 2024-06-19 14:56:44
+// @param classifyId int
+// @return items []*ChartPermissionSearchKeyWordMappingAndPermissionName
+// @return err error
+func GetPermissionByClassifyId(classifyId int) (items []*ChartPermissionSearchKeyWordMappingAndPermissionName, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.chart_permission_name,a.permission_name,b.chart_permission_id,b.key_word,b.classify_id FROM chart_permission AS a 
+ join chart_permission_search_key_word_mapping AS b ON a.chart_permission_id=b.chart_permission_id WHERE b.from='rddp' AND b.classify_id = ? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
+	return
+}

+ 3 - 3
models/ppt_v2.go

@@ -226,9 +226,9 @@ type PptV2ConfigResp struct {
 }
 
 type PPT2ReportReq struct {
-	PptId            int    `description:"PPT主键"`
-	ClassifyIdSecond int    `description:"报告二级分类ID"`
-	Title            string `description:"标题"`
+	PptId      int    `description:"PPT主键"`
+	ClassifyId int    `description:"报告二级分类ID"`
+	Title      string `description:"标题"`
 }
 
 // AddPptV2Multi 批量新增ppt

+ 426 - 95
models/report.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"errors"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
@@ -63,6 +64,29 @@ type Report struct {
 	AdminRealName      string    `description:"创建者姓名"`
 	ApproveTime        time.Time `description:"审批时间"`
 	ApproveId          int       `description:"审批ID"`
+	DetailImgUrl       string    `description:"报告详情长图地址"`
+	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	Pv                  int       `description:"pv"`
+	Uv                  int       `description:"uv"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	CanvasColor         string    `description:"画布颜色"`
+	NeedSplice          int       `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId      int       `description:"版头资源ID"`
+	EndResourceId       int       `description:"版尾资源ID"`
+	ClassifyIdThird     int       `description:"三级分类id"`
+	ClassifyNameThird   string    `description:"三级分类名称"`
+	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	InheritReportId     int       `description:"待继承的报告ID"`
+	VoiceGenerateType   int       `description:"音频生成方式,0:系统生成,1:人工上传"`
 }
 
 type ReportList struct {
@@ -100,12 +124,31 @@ type ReportList struct {
 	OldReportId        int                       `description:"research_report表ID, 大于0则表示该报告为老后台同步过来的"`
 	MsgSendTime        string                    `description:"模版消息发送时间"`
 	CanEdit            bool                      `description:"是否可编辑"`
+	HasAuth            bool                      `description:"是否可操作"`
 	Editor             string                    `description:"编辑人"`
 	AdminId            int                       `description:"创建者账号"`
 	AdminRealName      string                    `description:"创建者姓名"`
 	ApproveTime        string                    `description:"审批时间"`
 	DetailImgUrl       string                    `description:"报告详情长图地址"`
 	DetailPdfUrl       string                    `description:"报告详情PDF地址"`
+
+	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	CanvasColor         string    `description:"画布颜色"`
+	NeedSplice          int       `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId      int       `description:"版头资源ID"`
+	EndResourceId       int       `description:"版尾资源ID"`
+	ClassifyIdThird     int       `description:"三级分类id"`
+	ClassifyNameThird   string    `description:"三级分类名称"`
+	InheritReportId     int       `description:"待继承的报告ID"`
 }
 
 type ReportListResp struct {
@@ -113,17 +156,114 @@ type ReportListResp struct {
 	Paging *paging.PagingItem `description:"分页数据"`
 }
 
-func GetReportListCount(condition string, pars []interface{}, companyType string) (count int, err error) {
-	//产品权限
-	companyTypeSqlStr := ``
-	if companyType == "ficc" {
-		companyTypeSqlStr = " AND classify_id_first != 40 "
-	} else if companyType == "权益" {
-		companyTypeSqlStr = " AND classify_id_first = 40 "
+// GetReportListCountV1
+// @Description: 获取普通报告列表的报告数量
+// @author: Roc
+// @datetime 2024-05-30 15:14:43
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetReportListCountV1(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM report as a WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetReportListV1
+// @Description: 获取普通报告列表的数据
+// @author: Roc
+// @datetime 2024-05-30 15:14:25
+// @param condition string
+// @param pars []interface{}
+// @param startSize int
+// @param pageSize int
+// @return items []*ReportList
+// @return err error
+func GetReportListV1(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT * FROM report as a WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+type ReportPvUv struct {
+	ReportId int
+	PvTotal  int
+	UvTotal  int
+}
+
+func GetReportPvUvByReportIdList(reportIdList []int) (items []ReportPvUv, err error) {
+	num := len(reportIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT report_id, COUNT(1) as pv_total,COUNT(DISTINCT user_id) as uv_total  FROM report_view_record  WHERE report_id in (` + utils.GetOrmInReplace(num) + `) GROUP BY report_id`
+	_, err = o.Raw(sql, reportIdList).QueryRows(&items)
+	return
+}
+
+// GetReportListCountByGrant
+// @Description: 获取共享报告列表的报告数量
+// @author: Roc
+// @datetime 2024-05-30 15:14:01
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetReportListCountByGrant(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.id  FROM report as a 
+    JOIN report_grant b on a.id=b.report_id 
+ WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += " GROUP BY a.id "
+
+	sql = `SELECT COUNT(1) AS count  FROM (` + sql + `) d`
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetReportListByGrant
+// @Description: 获取共享报告列表的数据
+// @author: Roc
+// @datetime 2024-05-30 15:15:07
+// @param condition string
+// @param pars []interface{}
+// @param startSize int
+// @param pageSize int
+// @return items []*ReportList
+// @return err error
+func GetReportListByGrant(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT a.* FROM report as a JOIN report_grant b on a.id = b.report_id  WHERE 1=1  `
+	if condition != "" {
+		sql += condition
 	}
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += ` GROUP BY a.id ORDER BY FIELD(state,3,1,4,5,6,2), a.modify_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
 
+func GetReportListCount(condition string, pars []interface{}) (count int, err error) {
 	oRddp := orm.NewOrmUsingDB("rddp")
-	sql := `SELECT COUNT(1) AS count  FROM report WHERE 1=1 ` + companyTypeSqlStr
+	sql := `SELECT COUNT(1) AS count  FROM report WHERE 1=1 `
 	if condition != "" {
 		sql += condition
 	}
@@ -131,20 +271,13 @@ func GetReportListCount(condition string, pars []interface{}, companyType string
 	return
 }
 
-func GetReportList(condition string, pars []interface{}, companyType string, startSize, pageSize int) (items []*ReportList, err error) {
+func GetReportList(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	//产品权限
-	companyTypeSqlStr := ``
-	if companyType == "ficc" {
-		companyTypeSqlStr = " AND classify_id_first != 40 "
-	} else if companyType == "权益" {
-		companyTypeSqlStr = " AND classify_id_first = 40 "
-	}
 
 	sql := `SELECT *,
         (SELECT COUNT(1) FROM report_view_record AS rvr WHERE rvr.report_id=report.id) AS pv,
         (SELECT COUNT(DISTINCT user_id) FROM report_view_record AS rvr WHERE rvr.report_id=report.id) AS uv
-        FROM report WHERE 1=1  ` + companyTypeSqlStr
+        FROM report WHERE 1=1  `
 	if condition != "" {
 		sql += condition
 	}
@@ -166,15 +299,15 @@ func PublishReport(reportIds []int) (err error) {
 }
 
 // PublishCancelReport 取消发布报告
-func PublishCancelReport(reportId, state int, publishTimeNullFlag bool) (err error) {
+func PublishCancelReport(reportId, state int, publishTimeNullFlag bool, lastModifyAdminId int, lastModifyAdminName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	var sql string
 	if publishTimeNullFlag {
-		sql = ` UPDATE report SET state=?, publish_time=null, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
+		sql = ` UPDATE report SET state=?, publish_time=null, pre_publish_time=null, pre_msg_send=0,last_modify_admin_id=?,last_modify_admin_name=?,modify_time = NOW() WHERE id =?`
 	} else {
-		sql = ` UPDATE report SET state=?, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
+		sql = ` UPDATE report SET state=?, pre_publish_time=null, pre_msg_send=0,last_modify_admin_id=?,last_modify_admin_name=?,modify_time = NOW() WHERE id =?`
 	}
-	_, err = o.Raw(sql, state, reportId).Exec()
+	_, err = o.Raw(sql, state, lastModifyAdminId, lastModifyAdminName, reportId).Exec()
 	return
 }
 
@@ -213,6 +346,31 @@ type ReportDetail struct {
 	ThsMsgIsSend       int    `description:"客户群消息是否已发送,0:否,1:是"`
 	HasChapter         int    `description:"是否有章节 0-否 1-是"`
 	ChapterType        string `description:"章节类型 day-晨报 week-周报"`
+	AdminId            int    `description:"创建者账号"`
+	AdminRealName      string `description:"创建者姓名"`
+	ReportCode         string `description:"报告唯一编码"`
+
+	// eta1.8.3(研报改版)相关内容
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	Pv                  int       `description:"pv"`
+	Uv                  int       `description:"uv"`
+	HeadImg             string    `description:"报告头图地址"`
+	EndImg              string    `description:"报告尾图地址"`
+	HeadStyle           string    `description:"版头样式"`
+	EndStyle            string    `description:"版尾样式"`
+	CanvasColor         string    `description:"画布颜色"`
+	NeedSplice          int       `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId      int       `description:"版头资源ID"`
+	EndResourceId       int       `description:"版尾资源ID"`
+	ClassifyIdThird     int       `description:"三级分类id"`
+	ClassifyNameThird   string    `description:"三级分类名称"`
+	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
 }
 
 func GetReportById(reportId int) (item *ReportDetail, err error) {
@@ -240,29 +398,79 @@ func GetSimpleReportByIds(reportIds []int) (list []*Report, err error) {
 	return
 }
 
-func GetReportStage(classifyIdFirst, classifyIdSecond int) (count int, err error) {
+// GetReportStage
+// @Description: 获取报告的最大期数(每一年的最大期数)
+// @author: Roc
+// @datetime 2024-06-03 17:44:14
+// @param classifyIdFirst int
+// @param classifyIdSecond int
+// @param classifyIdThird int
+// @return count int
+// @return err error
+func GetReportStage(classifyIdFirst, classifyIdSecond, classifyIdThird int) (count int, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ``
-	if classifyIdSecond > 0 {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_second=? "
-		o.Raw(sql, classifyIdSecond).QueryRow(&count)
+	classifyId := classifyIdThird
+	if classifyId <= 0 {
+		classifyId = classifyIdSecond
+	}
+	if classifyId <= 0 {
+		classifyId = classifyIdFirst
+	}
+	if classifyId <= 0 {
+		err = errors.New("错误的分类id")
+		return
+	}
+	yearStart := time.Date(time.Now().Local().Year(), 1, 1, 0, 0, 0, 0, time.Local)
+
+	sql := `SELECT MAX(stage) AS max_stage FROM report WHERE create_time > ? `
+	if classifyIdThird > 0 {
+		sql += " AND classify_id_third = ? "
+	} else if classifyIdSecond > 0 {
+		sql += " AND classify_id_second = ? "
 	} else {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first=? "
-		o.Raw(sql, classifyIdFirst).QueryRow(&count)
+		sql += " AND classify_id_first = ? "
 	}
+	o.Raw(sql, yearStart, classifyId).QueryRow(&count)
+
 	return
 }
 
-func GetReportStageEdit(classifyIdFirst, classifyIdSecond, reportId int) (count int, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	sql := ``
-	if classifyIdSecond > 0 {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_second=? AND id<>? "
-		o.Raw(sql, classifyIdSecond, reportId).QueryRow(&count)
+// GetReportStageEdit
+// @Description: 获取报告的最大期数(每一年的最大期数)
+// @author: Roc
+// @datetime 2024-06-04 13:50:34
+// @param classifyIdFirst int
+// @param classifyIdSecond int
+// @param classifyIdThird int
+// @param reportId int
+// @return count int
+// @return err error
+func GetReportStageEdit(classifyIdFirst, classifyIdSecond, classifyIdThird, reportId int) (count int, err error) {
+	classifyId := classifyIdThird
+	if classifyId <= 0 {
+		classifyId = classifyIdSecond
+	}
+	if classifyId <= 0 {
+		classifyId = classifyIdFirst
+	}
+	if classifyId <= 0 {
+		err = errors.New("错误的分类id")
+		return
+	}
+	yearStart := time.Date(time.Now().Local().Year(), 1, 1, 0, 0, 0, 0, time.Local)
+
+	sql := `SELECT MAX(stage) AS max_stage FROM report WHERE create_time > ? AND id<>?  `
+	if classifyIdThird > 0 {
+		sql += " AND classify_id_third = ? "
+	} else if classifyIdSecond > 0 {
+		sql += " AND classify_id_second = ? "
 	} else {
-		sql = "SELECT MAX(stage) AS max_stage FROM report WHERE classify_id_first=? AND id<>? "
-		o.Raw(sql, classifyIdFirst, reportId).QueryRow(&count)
+		sql = " AND classify_id_first = ? "
 	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	o.Raw(sql, yearStart, reportId, classifyId).QueryRow(&count)
+
 	return
 }
 
@@ -285,6 +493,8 @@ type AddReq struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdThird    int    `description:"三级分类id"`
+	ClassifyNameThird  string `description:"三级分类名称"`
 	Title              string `description:"标题"`
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
@@ -293,6 +503,18 @@ type AddReq struct {
 	Content            string `description:"内容"`
 	CreateTime         string `description:"创建时间"`
 	ReportVersion      int    `description:"1:旧版,2:新版"`
+	ContentStruct      string `description:"内容组件"`
+	HeadImg            string `description:"报告头图地址"`
+	EndImg             string `description:"报告尾图地址"`
+	CanvasColor        string `description:"画布颜色"`
+	NeedSplice         int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId     int    `description:"版头资源ID"`
+	EndResourceId      int    `description:"版尾资源ID"`
+	CollaborateType    int8   `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish    int8   `description:"是否公开发布,1:是,2:否"`
+	InheritReportId    int    `description:"待继承的报告ID"`
+	GrantAdminIdList   []int  `description:"授权用户id列表"`
 }
 
 type PrePublishReq struct {
@@ -319,6 +541,8 @@ type EditReq struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类id"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyIdThird    int    `description:"三级分类id"`
+	ClassifyNameThird  string `description:"三级分类名称"`
 	Title              string `description:"标题"`
 	Abstract           string `description:"摘要"`
 	Author             string `description:"作者"`
@@ -326,6 +550,17 @@ type EditReq struct {
 	State              int    `description:"状态:1:未发布,2:已发布"`
 	Content            string `description:"内容"`
 	CreateTime         string `description:"创建时间"`
+	ContentStruct      string `description:"内容组件"`
+	HeadImg            string `description:"报告头图地址"`
+	EndImg             string `description:"报告尾图地址"`
+	CanvasColor        string `description:"画布颜色"`
+	NeedSplice         int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId     int    `description:"版头资源ID"`
+	EndResourceId      int    `description:"版尾资源ID"`
+	//CollaborateType    int8   `description:"协作方式,1:个人,2:多人协作。默认:1"`
+	//ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	IsPublicPublish  int8  `description:"是否公开发布,1:是,2:否"`
+	GrantAdminIdList []int `description:"授权用户id列表"`
 }
 
 type EditResp struct {
@@ -357,6 +592,12 @@ func EditReport(item *Report, reportId int64) (err error) {
 	return
 }
 
+func (m *Report) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
 type ReportDetailReq struct {
 	ReportId int `description:"报告id"`
 }
@@ -383,6 +624,13 @@ type SendTemplateMsgReq struct {
 	ReportId int `description:"报告id"`
 }
 
+// SendTemplateMsgResp
+// @Description: 报告推送返回结构体
+type SendTemplateMsgResp struct {
+	ReportId    int    `description:"报告id"`
+	MsgSendTime string `description:"报告推送时间"`
+}
+
 func ModifyReportMsgIsSend(reportId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	report, err := GetReportById(reportId)
@@ -403,6 +651,23 @@ func ModifyReportVideo(reportId int, videoUrl, videoName, videoSize string, play
 	return
 }
 
+// ModifyReportVideoByNoVideo
+// @Description: 修改无音频的报告音频信息
+// @author: Roc
+// @datetime 2024-07-25 18:03:05
+// @param reportId int
+// @param videoUrl string
+// @param videoName string
+// @param videoSize string
+// @param playSeconds float64
+// @return err error
+func ModifyReportVideoByNoVideo(reportId int, videoUrl, videoName, videoSize string, playSeconds float64) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET video_url=?,video_name=?,video_play_seconds=?,video_size=? WHERE id=? AND video_url=""`
+	_, err = o.Raw(sql, videoUrl, videoName, playSeconds, videoSize, reportId).Exec()
+	return
+}
+
 type ReportItem struct {
 	Id                 int       `orm:"column(id)" description:"报告Id"`
 	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
@@ -438,6 +703,15 @@ type SaveReportContent struct {
 	Content  string `description:"内容"`
 	ReportId int    `description:"报告id"`
 	NoChange int    `description:"内容是否未改变:1:内容未改变"`
+
+	// 以下是智能研报相关
+	ContentStruct  string `description:"内容组件"`
+	HeadImg        string `description:"报告头图地址"`
+	EndImg         string `description:"报告尾图地址"`
+	CanvasColor    string `description:"画布颜色"`
+	NeedSplice     int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
+	HeadResourceId int    `description:"版头资源ID"`
+	EndResourceId  int    `description:"版尾资源ID"`
 }
 
 func EditReportContent(reportId int, content, contentSub string) (err error) {
@@ -447,16 +721,16 @@ func EditReportContent(reportId int, content, contentSub string) (err error) {
 	return
 }
 
-func AddReportSaveLog(reportId, adminId int, content, contentSub, adminName string) (err error) {
+func AddReportSaveLog(reportId, adminId int, content, contentSub, contentStruct, canvasColor, adminName string, headResourceId, endResourceId int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ` INSERT INTO report_save_log(report_id, content,content_sub,admin_id,admin_name) VALUES (?,?,?,?,?) `
-	_, err = o.Raw(sql, reportId, content, contentSub, adminId, adminName).Exec()
+	sql := ` INSERT INTO report_save_log(report_id, content,content_sub,content_struct,canvas_color,head_resource_id,end_resource_id,admin_id,admin_name) VALUES (?,?,?,?,?,?,?,?,?) `
+	_, err = o.Raw(sql, reportId, content, contentSub, contentStruct, canvasColor, headResourceId, endResourceId, adminId, adminName).Exec()
 	return
 }
 
 func MultiAddReportChaptersSaveLog(items []*ReportChapter, adminId int, adminRealName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	p, err := o.Raw(`INSERT INTO report_save_log(report_id, report_chapter_id, content, content_sub, admin_id, admin_name) VALUES (?,?,?,?,?,?)`).Prepare()
+	p, err := o.Raw(`INSERT INTO report_save_log(report_id, report_chapter_id, content, content_sub,content_struct,admin_id, admin_name) VALUES (?,?,?,?,?,?,?)`).Prepare()
 	if err != nil {
 		return
 	}
@@ -464,7 +738,7 @@ func MultiAddReportChaptersSaveLog(items []*ReportChapter, adminId int, adminRea
 		_ = p.Close()
 	}()
 	for _, v := range items {
-		_, err = p.Exec(v.ReportId, v.ReportChapterId, v.Content, v.ContentSub, adminId, adminRealName)
+		_, err = p.Exec(v.ReportId, v.ReportChapterId, v.Content, v.ContentSub, v.ContentStruct, adminId, adminRealName)
 		if err != nil {
 			return
 		}
@@ -518,38 +792,6 @@ func GetDayWeekReportStage(classifyIdFirst int, yearStart time.Time) (count int,
 	return
 }
 
-// AddReportAndChapter 新增报告及章节
-func AddReportAndChapter(reportItem *Report, chapterItemList []*ReportChapter) (reportId int64, err error) {
-	o := orm.NewOrmUsingDB("rddp")
-	to, err := o.Begin()
-	if err != nil {
-		return
-	}
-	defer func() {
-		if err != nil {
-			_ = to.Rollback()
-		} else {
-			_ = to.Commit()
-		}
-	}()
-
-	if reportId, err = to.Insert(reportItem); err != nil {
-		return
-	}
-	if len(chapterItemList) > 0 {
-		for _, chapterItem := range chapterItemList {
-			chapterItem.ReportId = int(reportId)
-			cpId, tmpErr := to.Insert(chapterItem)
-			if tmpErr != nil {
-				return
-			}
-			chapterItem.ReportChapterId = int(cpId)
-		}
-	}
-
-	return
-}
-
 // GetReportByReportId 主键获取报告
 func GetReportByReportId(reportId int) (item *Report, err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -601,10 +843,27 @@ func (reportInfo *Report) UpdateReport(cols []string) (err error) {
 	return
 }
 
-// 晨周报详情
+// ReportDetailView
+// @Description: 晨周报详情
 type ReportDetailView struct {
 	*ReportDetail
-	ChapterList []*ReportChapter
+	ChapterList    []*ReportChapter
+	GrandAdminList []ReportDetailViewAdmin
+	PermissionList []ReportDetailViewPermission
+}
+
+// ReportDetailViewAdmin
+// @Description: 报告里面的授权人
+type ReportDetailViewAdmin struct {
+	AdminId   int
+	AdminName string
+}
+
+// ReportDetailViewPermission
+// @Description: 报告分类关联的品种权限
+type ReportDetailViewPermission struct {
+	PermissionId   int
+	PermissionName string
 }
 
 func GetUnPublishDayReport(startTime time.Time, endTime time.Time) (item *Report, err error) {
@@ -640,6 +899,8 @@ type ElasticReportDetail struct {
 	ClassifyNameFirst  string `description:"一级分类名称"`
 	ClassifyIdSecond   int    `description:"二级分类ID"`
 	ClassifyNameSecond string `description:"二级分类名称"`
+	ClassifyId         int    `description:"最小单元的分类ID"`
+	ClassifyName       string `description:"最小单元的分类名称"`
 	Categories         string `description:"关联的品种名称(包括品种别名)"`
 	StageStr           string `description:"报告期数"`
 }
@@ -663,7 +924,7 @@ func GetNewReportExist(oldReportId int) (item *Report, err error) {
 }
 
 // PublishReportAndChapter 发布报告及章节
-func PublishReportAndChapter(reportInfo *Report, publishIds, unPublishIds []int, isPublishReport bool, cols []string) (err error) {
+func PublishReportAndChapter(reportInfo *Report, isPublishReport bool, cols []string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
@@ -678,19 +939,27 @@ func PublishReportAndChapter(reportInfo *Report, publishIds, unPublishIds []int,
 	}()
 	// 更新报告
 	if isPublishReport {
-		if _, err = to.Update(reportInfo, cols...); err != nil {
+		_, err = to.Update(reportInfo, cols...)
+		if err != nil {
 			return
 		}
 	}
+
+	// 发布该报告的所有章节
+	sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ?  `
+	_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id).Exec()
+
 	// 发布章节
-	if len(publishIds) > 0 {
-		sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(publishIds)) + `) `
-		_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id, publishIds).Exec()
-	}
-	if len(unPublishIds) > 0 {
-		sql := ` UPDATE report_chapter SET publish_state = 1, publish_time = NULL, is_edit = 0 WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(unPublishIds)) + `) `
-		_, err = to.Raw(sql, reportInfo.Id, unPublishIds).Exec()
-	}
+	//if len(publishIds) > 0 {
+	//	sql := ` UPDATE report_chapter SET publish_state = 2, publish_time = ? WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(publishIds)) + `) `
+	//	_, err = to.Raw(sql, reportInfo.PublishTime, reportInfo.Id, publishIds).Exec()
+	//}
+
+	//if len(unPublishIds) > 0 {
+	//	sql := ` UPDATE report_chapter SET publish_state = 1, publish_time = NULL, is_edit = 0 WHERE report_id = ? AND report_chapter_id IN (` + utils.GetOrmInReplace(len(unPublishIds)) + `) `
+	//	_, err = to.Raw(sql, reportInfo.Id, unPublishIds).Exec()
+	//}
+
 	return
 }
 
@@ -704,18 +973,18 @@ SELECT DISTINCT report_id FROM report_chapter WHERE publish_state = 2 AND (video
 }
 
 // PublishReportById 发布报告
-func PublishReportById(reportId int, publishTime time.Time) (err error) {
+func PublishReportById(reportId int, publishTime time.Time, lastModifyAdminId int, lastModifyAdminName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `UPDATE report SET state = 2, publish_time = ?, pre_publish_time=null, pre_msg_send=0, modify_time = NOW() WHERE id = ? `
-	_, err = o.Raw(sql, publishTime, reportId).Exec()
+	sql := `UPDATE report SET state = 2, publish_time = ?, pre_publish_time=null, pre_msg_send=0, modify_time = NOW(),last_modify_admin_id=?,last_modify_admin_name=? WHERE id = ? `
+	_, err = o.Raw(sql, publishTime, lastModifyAdminId, lastModifyAdminName, reportId).Exec()
 	return
 }
 
 // ResetReportById 重置报告状态
-func ResetReportById(reportId, state int) (err error) {
+func ResetReportById(reportId, state int, lastModifyAdminId int, lastModifyAdminName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := `UPDATE report SET state = ?, pre_publish_time = null, pre_msg_send = 0, modify_time = NOW() WHERE id = ?`
-	_, err = o.Raw(sql, state, reportId).Exec()
+	sql := `UPDATE report SET state = ?, pre_publish_time = null, pre_msg_send = 0, modify_time = NOW(),last_modify_admin_id=?,last_modify_admin_name=? WHERE id = ?`
+	_, err = o.Raw(sql, state, lastModifyAdminId, lastModifyAdminName, reportId).Exec()
 	return
 }
 
@@ -970,6 +1239,14 @@ func UpdateReportFirstClassifyNameByClassifyId(classifyId int, classifyName stri
 	return
 }
 
+// UpdateReportThirdClassifyNameByClassifyId 更新报告的三级分类名称字段
+func UpdateReportThirdClassifyNameByClassifyId(classifyId int, classifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := " UPDATE report SET classify_name_third = ? WHERE classify_id_third = ? "
+	_, err = o.Raw(sql, classifyName, classifyId).Exec()
+	return
+}
+
 // UpdateReportSecondClassifyFirstNameByClassifyId 更新报告二级分类的一级分类名称和id
 func UpdateReportSecondClassifyFirstNameByClassifyId(classifyId, newClassifyId int, classifyName string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -1033,8 +1310,9 @@ func UpdateReportChapterPublishTime(reportId int, videoNameDate string) (err err
 
 // MarkEditReport 标记编辑英文研报的请求数据
 type MarkEditReport struct {
-	ReportId int `description:"研报id"`
-	Status   int `description:"标记状态,1:编辑中,2:编辑完成"`
+	ReportId        int `description:"研报id"`
+	ReportChapterId int `description:"研报章节id"`
+	Status          int `description:"标记状态,1:编辑中,2:查询状态,3:编辑完成"`
 }
 
 type MarkReportResp struct {
@@ -1114,7 +1392,7 @@ func GetReportStateCount(state int) (count int, err error) {
 }
 
 // UpdateReportsStateByCond 批量更新报告状态
-func UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, newState int) (err error) {
+func UpdateReportsStateByCond(classifyFirstId, classifySecondId, classifyThirdId, oldState, newState int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	cond := ``
 	if classifyFirstId > 0 {
@@ -1123,6 +1401,9 @@ func UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, newSt
 	if classifySecondId > 0 {
 		cond += fmt.Sprintf(` AND classify_id_second = %d`, classifySecondId)
 	}
+	if classifyThirdId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_third = %d`, classifyThirdId)
+	}
 	sql := fmt.Sprintf(`UPDATE report SET state = ?, pre_publish_time = NULL WHERE state = ? %s`, cond)
 	_, err = o.Raw(sql, newState, oldState).Exec()
 	return
@@ -1173,4 +1454,54 @@ func UpdatePdfUrlReportById(reportId int) (err error) {
 	sql := `UPDATE report SET detail_img_url = '',detail_pdf_url='',modify_time=NOW() WHERE id = ? `
 	_, err = o.Raw(sql, reportId).Exec()
 	return
-}
+}
+
+// InsertMultiReport
+// @Description: 批量新增报告
+// @author: Roc
+// @datetime 2024-06-27 15:55:25
+// @param items []*Report
+// @return err error
+func InsertMultiReport(items []*Report) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(500, items)
+
+	return
+}
+
+// ReportLayout
+// @Description: 报告布局
+type ReportLayout struct {
+	Id           int
+	ReportLayout int8 `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+}
+
+// GetReportLayoutByReportId
+// @Description: 根据报告id获取报告的布局
+// @author: Roc
+// @datetime 2024-07-15 15:27:05
+// @param reportId int
+// @return item ReportLayout
+// @return err error
+func GetReportLayoutByReportId(reportId int) (item ReportLayout, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT id, report_layout  FROM report  WHERE id = ? `
+	err = o.Raw(sql, reportId).QueryRow(&item)
+
+	return
+}
+
+func GetReportFieldsByIds(ids []int, fields []string) (items []*Report, err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	field := " * "
+	if len(fields) > 0 {
+		field = fmt.Sprintf(" %s ", strings.Join(fields, ","))
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM report WHERE id IN (%s)`, field, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).QueryRows(&items)
+	return
+}

+ 110 - 0
models/report/report_chapter_grant.go

@@ -0,0 +1,110 @@
+package report
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportChapterGrant
+// @Description: 报告章节授权用户表
+type ReportChapterGrant struct {
+	GrantId         int       `orm:"column(grant_id)"` // 授权id
+	ReportChapterId int       `description:"报告章节id"`
+	AdminId         int       `description:"授权的用户id"`
+	CreateTime      time.Time `description:"授权时间"`
+}
+
+// MultiAddReportChapterGrantGrant
+// @Description: 批量添加报告授权用户
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:36:02
+// @param reportChapterId int
+// @param list []*ReportChapterGrant
+// @return err error
+func (m ReportChapterGrant) MultiAddReportChapterGrantGrant(reportChapterId int, list []*ReportChapterGrant) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_chapter_grant where report_chapter_id=?"
+	_, err = to.Raw(sql, reportChapterId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增授权记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(500, list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// GetGrantListById
+// @Description: 根据id获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterId int
+// @return list []*ReportChapterGrant
+// @return err error
+func (m ReportChapterGrant) GetGrantListById(reportChapterId int) (list []*ReportChapterGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_grant WHERE report_chapter_id=? `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// GetGrantListByIdList
+// @Description: 根据id列表获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterIdList []int
+// @return list []*ReportChapterGrant
+// @return err error
+func (m ReportChapterGrant) GetGrantListByIdList(reportChapterIdList []int) (list []*ReportChapterGrant, err error) {
+	num := len(reportChapterIdList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_grant WHERE report_chapter_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, reportChapterIdList).QueryRows(&list)
+
+	return
+}
+
+// GetGrantByIdAndAdmin
+// @Description: 根据reportId和操作人获取报告章节权限配置
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:49:59
+// @param reportChapterId int
+// @param sysUserId int
+// @return item *ReportGrant
+// @return err error
+func (m ReportChapterGrant) GetGrantByIdAndAdmin(reportChapterId, sysUserId int) (item *ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_grant WHERE report_chapter_id = ? AND admin_id = ? `
+	err = o.Raw(sql, reportChapterId, sysUserId).QueryRow(&item)
+
+	return
+}

+ 152 - 0
models/report/report_chapter_permission_mapping.go

@@ -0,0 +1,152 @@
+package report
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportChapterPermissionMapping
+// @Description: 报告章节的权限关系表
+type ReportChapterPermissionMapping struct {
+	ReportChapterPermissionMappingId int `orm:"column(report_chapter_permission_mapping_id)"`
+	ReportChapterId                  int `description:"报告章节的id"` // 报告章节的id
+	ChartPermissionId                int `description:"权限id"`    // 权限id
+	CreateTime                       time.Time
+}
+
+// MultiAddReportChapterPermissionMappingPermission
+// @Description: 批量添加报告品种权限用户
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:36:02
+// @param reportChapterId int
+// @param list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) MultiAddReportChapterPermissionMappingPermission(reportChapterId int, list []*ReportChapterPermissionMapping) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_chapter_permission_mapping where report_chapter_id=?"
+	_, err = to.Raw(sql, reportChapterId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增品种权限记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(500, list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// GetPermissionListById
+// @Description: 根据id获取品种权限列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterId int
+// @return list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) GetPermissionListById(reportChapterId int) (list []*ReportChapterPermissionMapping, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_permission_mapping WHERE report_chapter_id=? `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// ReportChapterPermissionItem
+// @Description: 报告章节的权限关系表(带有品种名称)
+type ReportChapterPermissionItem struct {
+	ReportChapterPermissionMappingId int    `orm:"column(report_chapter_permission_mapping_id)"`
+	ReportChapterId                  int    `description:"报告章节的id"`
+	ChartPermissionId                int    `description:"权限id"`
+	ChartPermissionName              string `description:"品种名称"`
+	CreateTime                       time.Time
+}
+
+// GetPermissionItemListById
+// @Description: 根据id获取品种权限列表(带有品种名称)
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterId int
+// @return list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) GetPermissionItemListById(reportChapterId int) (list []*ReportChapterPermissionItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.*,b.chart_permission_name FROM report_chapter_permission_mapping AS a 
+         JOIN chart_permission AS b on a.chart_permission_id=b.chart_permission_id WHERE report_chapter_id=? `
+	_, err = o.Raw(sql, reportChapterId).QueryRows(&list)
+
+	return
+}
+
+// GetPermissionListByIdList
+// @Description: 根据id列表获取品种权限列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportChapterIdList []int
+// @return list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) GetPermissionListByIdList(reportChapterIdList []int) (list []*ReportChapterPermissionMapping, err error) {
+	num := len(reportChapterIdList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_chapter_permission_mapping WHERE report_chapter_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, reportChapterIdList).QueryRows(&list)
+
+	return
+}
+
+// MultiAdd
+// @Description: 批量添加
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-20 18:04:33
+// @param list []*ReportChapterPermissionMapping
+// @return err error
+func (m ReportChapterPermissionMapping) MultiAdd(list []*ReportChapterPermissionMapping) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 新增品种权限记录
+	if len(list) > 0 {
+		_, err = to.InsertMulti(500, list)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}

+ 126 - 0
models/report/report_grant.go

@@ -0,0 +1,126 @@
+package report
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ReportGrant
+// @Description: 报告授权用户表
+type ReportGrant struct {
+	GrantId    int       `orm:"column(grant_id)"` // 授权id
+	ReportId   int       `description:"报告id"`
+	AdminId    int       `description:"授权的用户id"`
+	CreateTime time.Time `description:"授权时间"`
+}
+
+// MultiAddReportGrantGrant
+// @Description: 批量添加报告授权用户
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:36:02
+// @param reportId int
+// @param list []*ReportGrant
+// @return err error
+func (m ReportGrant) MultiAddReportGrantGrant(reportId int, list []*ReportGrant) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_grant where report_id=?"
+	_, err = to.Raw(sql, reportId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增授权记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(500, list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// GetGrantListById
+// @Description: 根据id获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportId int
+// @return list []*ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantListById(reportId int) (list []*ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id=? `
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// GetGrantListByIdList
+// @Description: 根据id列表获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param reportIdList []int
+// @return list []*ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantListByIdList(reportIdList []int) (list []*ReportGrant, err error) {
+	num := len(reportIdList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id in (` + utils.GetOrmInReplace(num) + `) `
+	_, err = o.Raw(sql, reportIdList).QueryRows(&list)
+
+	return
+}
+
+// GetGrantByIdAndAdmin
+// @Description: 根据reportId和操作人获取报告权限配置
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:49:59
+// @param reportId int
+// @param sysUserId int
+// @return item *ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantByIdAndAdmin(reportId, sysUserId int) (item *ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id = ? AND admin_id = ? `
+	err = o.Raw(sql, reportId, sysUserId).QueryRow(&item)
+
+	return
+}
+
+// GetGrantListByAdminId
+// @Description: 根据id获取授权列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-04 15:33:58
+// @param adminId int
+// @return list []*ReportGrant
+// @return err error
+func (m ReportGrant) GetGrantListByAdminId(adminId int) (list []*ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE admin_id=? `
+	_, err = o.Raw(sql, adminId).QueryRows(&list)
+
+	return
+}

+ 1 - 1
models/report_approve/constant.go

@@ -8,7 +8,7 @@ const (
 )
 
 var FlowReportTypeMap = map[int]string{
-	FlowReportTypeChinese: "中文研报",
+	FlowReportTypeChinese: "研报",
 	FlowReportTypeEnglish: "英文研报",
 	FlowReportTypeSmart:   "智能研报",
 }

+ 11 - 1
models/report_approve/report_approve.go

@@ -17,6 +17,7 @@ type ReportApprove struct {
 	ReportTitle      string    `description:"报告标题"`
 	ClassifyFirstId  int       `description:"一级分类ID"`
 	ClassifySecondId int       `description:"二级分类ID"`
+	ClassifyThirdId  int       `description:"三级分类ID"`
 	State            int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
 	FlowId           int       `description:"审批流ID"`
 	FlowVersion      int       `description:"审批流版本"`
@@ -37,6 +38,7 @@ var ReportApproveCols = struct {
 	ReportTitle      string `description:"报告标题"`
 	ClassifyFirstId  string `description:"一级分类ID"`
 	ClassifySecondId string `description:"二级分类ID"`
+	ClassifyThirdId  string `description:"三级分类ID"`
 	State            string
 	FlowId           string
 	FlowVersion      string
@@ -55,6 +57,7 @@ var ReportApproveCols = struct {
 	ReportTitle:      "report_title",
 	ClassifyFirstId:  "classify_first_id",
 	ClassifySecondId: "classify_second_id",
+	ClassifyThirdId:  "classify_third_id",
 	State:            "state",
 	FlowId:           "flow_id",
 	FlowVersion:      "flow_version",
@@ -196,6 +199,8 @@ type ReportApproveItem struct {
 	HandleTime            string `description:"处理时间"`
 	CreateTime            string `description:"创建时间"`
 	ModifyTime            string `description:"修改时间"`
+	DetailImgUrl          string `description:"报告详情长图地址"`
+	DetailPdfUrl          string `description:"报告详情PDF地址"`
 }
 
 // FormatReportApproveOrm2Item 格式化报告审批
@@ -258,6 +263,7 @@ type ReportApproveItemOrm struct {
 	ReportTitle           string    `description:"报告标题"`
 	ClassifyFirstId       int       `description:"一级分类ID"`
 	ClassifySecondId      int       `description:"二级分类ID"`
+	ClassifyThirdId       int       `description:"二级分类ID"`
 	State                 int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
 	RecordState           int       `description:"审批记录状态:1-待审批;2-已通过;3-已驳回"`
 	FlowId                int       `description:"审批流ID"`
@@ -271,6 +277,8 @@ type ReportApproveItemOrm struct {
 	HandleTime            time.Time `description:"处理时间"`
 	CreateTime            time.Time `description:"创建时间"`
 	ModifyTime            time.Time `description:"修改时间"`
+	NodeState             int       `description:"当前节点审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回" json:"-"`
+	NodeApproveTime       time.Time `description:"当前节点审批时间" json:"-"`
 }
 
 // GetApprovingReportApproveCount 获取待处理的审批分页列表总数
@@ -320,7 +328,7 @@ func GetApprovedReportApprovePageList(cond string, pars []interface{}, orderRule
 	if orderRule != "" {
 		order = ` ORDER BY ` + orderRule
 	}
-	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.state AS record_state, a.approve_time AS handle_time, b.*
+	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.node_state AS record_state,a.node_state,a.node_approve_time, a.node_approve_time AS handle_time, b.*
 		FROM report_approve_record AS a
 		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id
 		WHERE 1 = 1 %s %s
@@ -380,6 +388,7 @@ type ReportApproveDetailReport struct {
 	ReportTitle    string `description:"报告标题"`
 	ReportCode     string `description:"报告code"`
 	ReportClassify string `description:"报告分类"`
+	ReportLayout   int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
 	//ClassifyFirstId  int    `description:"一级分类ID"`
 	//ClassifySecondId int    `description:"二级分类ID"`
 	//Content          string `description:"报告内容"`
@@ -447,4 +456,5 @@ type ReportApproveCheckApproveOpenReq struct {
 	ReportType       int `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId  int `description:"一级分类ID"`
 	ClassifySecondId int `description:"二级分类ID"`
+	ClassifyThirdId  int `description:"三级分类ID"`
 }

+ 12 - 1
models/report_approve/report_approve_flow.go

@@ -16,9 +16,11 @@ type ReportApproveFlow struct {
 	ReportType          int       `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId     int       `description:"一级分类ID"`
 	ClassifySecondId    int       `description:"二级分类ID"`
+	ClassifyThirdId     int       `description:"三级分类ID"`
 	CurrVersion         int       `description:"当前版本号"`
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
+	Enabled             int       `description:"1:有效,0:禁用"`
 }
 
 var ReportApproveFlowCols = struct {
@@ -27,18 +29,22 @@ var ReportApproveFlowCols = struct {
 	ReportType          string
 	ClassifyFirstId     string
 	ClassifySecondId    string
+	ClassifyThirdId     string
 	CurrVersion         string
 	CreateTime          string
 	ModifyTime          string
+	Enabled             string
 }{
 	ReportApproveFlowId: "report_approve_flow_id",
 	FlowName:            "flow_name",
 	ReportType:          "report_type",
 	ClassifyFirstId:     "classify_first_id",
 	ClassifySecondId:    "classify_second_id",
+	ClassifyThirdId:     "classify_third_id",
 	CurrVersion:         "curr_version",
 	CreateTime:          "create_time",
 	ModifyTime:          "modify_time",
+	Enabled:             "enabled",
 }
 
 func (m *ReportApproveFlow) TableName() string {
@@ -153,6 +159,7 @@ type ReportApproveFlowItem struct {
 	ReportType          int    `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId     int    `description:"一级分类ID"`
 	ClassifySecondId    int    `description:"二级分类ID"`
+	ClassifyThirdId     int    `description:"三级分类ID"`
 	ReportClassify      string `description:"报告类型: XX研报/一级分类/二级分类"`
 	CreateTime          string `description:"创建时间"`
 	ModifyTime          string `description:"修改时间"`
@@ -169,6 +176,7 @@ func FormatReportApproveFlow2Item(origin *ReportApproveFlow) (item *ReportApprov
 	item.ReportType = origin.ReportType
 	item.ClassifyFirstId = origin.ClassifyFirstId
 	item.ClassifySecondId = origin.ClassifySecondId
+	item.ClassifyThirdId = origin.ClassifyThirdId
 	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
 	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
 	return
@@ -180,6 +188,7 @@ type ReportApproveFlowAddReq struct {
 	ReportType       int                        `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId  int                        `description:"一级分类ID"`
 	ClassifySecondId int                        `description:"二级分类ID"`
+	ClassifyThirdId  int                        `description:"三级分类ID"`
 	Nodes            []ReportApproveNodeSaveReq `description:"审批节点信息"`
 }
 
@@ -304,7 +313,7 @@ func (m *ReportApproveFlow) UpdateFlowAndNodes(flowItem *ReportApproveFlow, node
 	}()
 
 	// 更新审批流
-	updateCols := []string{"FlowName", "ReportType", "ClassifyFirstId", "ClassifySecondId", "CurrVersion", "ModifyTime"}
+	updateCols := []string{"FlowName", "ReportType", "ClassifyFirstId", "ClassifySecondId", "ClassifyThirdId", "CurrVersion", "ModifyTime"}
 	if e = flowItem.Update(updateCols); e != nil {
 		err = fmt.Errorf("update flow err: %s", e.Error())
 		return
@@ -355,6 +364,7 @@ func FormatFlowAndNodesItem2Detail(flowItem *ReportApproveFlow, nodeItems []*Rep
 	detail.ReportType = flowItem.ReportType
 	detail.ClassifyFirstId = flowItem.ClassifyFirstId
 	detail.ClassifySecondId = flowItem.ClassifySecondId
+	detail.ClassifyThirdId = flowItem.ClassifyThirdId
 	detail.CreateTime = utils.TimeTransferString(utils.FormatDateTime, flowItem.CreateTime)
 	detail.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, flowItem.ModifyTime)
 	detail.Nodes = make([]*ReportApproveNodeItem, 0)
@@ -376,6 +386,7 @@ type ReportApproveFlowListReq struct {
 	ReportType       int    `form:"ReportType" description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
 	ClassifyFirstId  int    `form:"ClassifyFirstId" description:"一级分类ID"`
 	ClassifySecondId int    `form:"ClassifySecondId" description:"二级分类ID"`
+	ClassifyThirdId  int    `form:"ClassifyThirdId" description:"三级级分类ID"`
 	Keyword          string `form:"Keyword" description:"关键词"`
 	SortRule         int    `form:"SortRule" description:"排序方式: 1-正序; 2-倒序(默认)"`
 }

+ 39 - 0
models/report_approve/report_approve_record.go

@@ -25,6 +25,10 @@ type ReportApproveRecord struct {
 	ApproveTime           time.Time `description:"审批时间"`
 	CreateTime            time.Time `description:"创建时间"`
 	ModifyTime            time.Time `description:"修改时间"`
+	NodeState             int       `description:"当前节点审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	NodeApproveUserId     int       `description:"当前节点审批人ID"`
+	NodeApproveUserName   string    `description:"当前节点审批人姓名"`
+	NodeApproveTime       time.Time `description:"当前节点审批时间"`
 }
 
 var ReportApproveRecordCols = struct {
@@ -43,6 +47,10 @@ var ReportApproveRecordCols = struct {
 	ApproveTime           string
 	CreateTime            string
 	ModifyTime            string
+	NodeState             string `description:"当前节点审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	NodeApproveUserId     string `description:"当前节点审批人ID"`
+	NodeApproveUserName   string `description:"当前节点审批人姓名"`
+	NodeApproveTime       string `description:"当前节点审批时间"`
 }{
 	ReportApproveRecordId: "report_approve_record_id",
 	ReportApproveId:       "report_approve_id",
@@ -59,6 +67,10 @@ var ReportApproveRecordCols = struct {
 	ApproveTime:           "approve_time",
 	CreateTime:            "create_time",
 	ModifyTime:            "modify_time",
+	NodeState:             "node_state",
+	NodeApproveUserId:     "node_approve_user_id",
+	NodeApproveUserName:   "node_approve_user_name",
+	NodeApproveTime:       "node_approve_time",
 }
 
 func (m *ReportApproveRecord) TableName() string {
@@ -209,6 +221,33 @@ func FormatReportApproveRecord2Item(origin *ReportApproveRecord) (item *ReportAp
 	return
 }
 
+// UpdateNodeState
+// @Description: 将该审批的同一个节点的记录标记为已审批
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-11 13:57:20
+// @param reportApproveId int
+// @param nodeId int
+// @param nodeState int
+// @param nodeApproveUserId int
+// @param nodeApproveUserName string
+// @param nodeApproveTime time.Time
+// @return err error
+func (m *ReportApproveRecord) UpdateNodeState(reportApproveId, nodeId, nodeState, nodeApproveUserId int, nodeApproveUserName string, nodeApproveTime time.Time) (err error) {
+	pars := make([]interface{}, 0)
+	pars = append(pars, nodeState, nodeApproveUserId, nodeApproveUserName, nodeApproveTime)
+
+	// 更新条件
+	whereParas := []interface{}{reportApproveId, nodeId}
+	pars = append(pars, whereParas)
+
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`UPDATE %s SET node_state=?,node_approve_user_id=?,node_approve_user_name=?,node_approve_time=? WHERE report_approve_id = ? AND node_id = ?`, m.TableName())
+	_, err = o.Raw(sql, pars).Exec()
+
+	return
+}
+
 // ReportApproveDetailNodeUserRecord 审批详情-节点用户审批记录
 type ReportApproveDetailNodeUserRecord struct {
 	ReportApproveRecordId int    `description:"审批记录ID"`

+ 343 - 43
models/report_chapter.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_api/models/report"
 	"eta/eta_api/utils"
 	"github.com/beego/beego/v2/client/orm"
 	"time"
@@ -8,33 +9,91 @@ import (
 
 // ReportChapter 报告章节
 type ReportChapter struct {
-	ReportChapterId   int       `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
-	ReportId          int       `description:"报告ID"`
-	ReportType        string    `description:"报告类型 day-晨报 week-周报"`
-	ClassifyIdFirst   int       `description:"一级分类id"`
-	ClassifyNameFirst string    `description:"一级分类名称"`
-	TypeId            int       `description:"品种ID"`
-	TypeName          string    `description:"品种名称"`
-	Title             string    `description:"标题"`
-	Abstract          string    `description:"摘要"`
-	AddType           int       `description:"新增方式:1:新增报告,2:继承报告"`
-	Author            string    `description:"作者"`
-	Content           string    `description:"内容"`
-	ContentSub        string    `description:"内容前两个章节"`
-	Stage             int       `description:"期数"`
-	Trend             string    `description:"趋势观点"`
-	Sort              int       `description:"排序: 数值越小越靠前"`
-	IsEdit            int       `description:"是否已编辑 0-待编辑 1-已编辑"`
-	PublishState      int       `description:"发布状态 1-待发布,2-已发布"`
-	PublishTime       time.Time `description:"发布时间"`
-	VideoUrl          string    `description:"音频文件URL"`
-	VideoName         string    `description:"音频文件名称"`
-	VideoPlaySeconds  string    `description:"音频播放时长"`
-	VideoSize         string    `description:"音频文件大小,单位M"`
-	VideoKind         int       `description:"音频生成方式:1,手动上传,2:自动生成"`
-	CreateTime        string    `description:"创建时间"`
-	ModifyTime        time.Time `description:"修改时间"`
-	OriginalVideoUrl  string    `description:"原始音频文件URL"`
+	ReportChapterId     int       `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
+	ReportId            int       `description:"报告ID"`
+	ReportType          string    `description:"报告类型 day-晨报 week-周报"`
+	ClassifyIdFirst     int       `description:"一级分类id"`
+	ClassifyNameFirst   string    `description:"一级分类名称"`
+	TypeId              int       `description:"品种ID"`
+	TypeName            string    `description:"品种名称"`
+	Title               string    `description:"标题"`
+	Abstract            string    `description:"摘要"`
+	AddType             int       `description:"新增方式:1:新增报告,2:继承报告"`
+	Author              string    `description:"作者"`
+	Content             string    `description:"内容"`
+	ContentSub          string    `description:"内容前两个章节"`
+	Stage               int       `description:"期数"`
+	Trend               string    `description:"趋势观点"`
+	Sort                int       `description:"排序: 数值越小越靠前"`
+	IsEdit              int       `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState        int       `description:"发布状态 1-待发布,2-已发布"`
+	PublishTime         time.Time `description:"发布时间"`
+	VideoUrl            string    `description:"音频文件URL"`
+	VideoName           string    `description:"音频文件名称"`
+	VideoPlaySeconds    string    `description:"音频播放时长"`
+	VideoSize           string    `description:"音频文件大小,单位M"`
+	VideoKind           int       `description:"音频生成方式:1,手动上传,2:自动生成"`
+	CreateTime          string    `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+	OriginalVideoUrl    string    `description:"原始音频文件URL"`
+	ContentStruct       string    `description:"内容组件"`
+	LastModifyAdminId   int       `description:"最后更新人ID"`
+	LastModifyAdminName string    `description:"最后更新人姓名"`
+	ContentModifyTime   time.Time `description:"内容更新时间"`
+	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	ReportCreateTime    time.Time `description:"报告时间创建时间"`
+	VoiceGenerateType   int       `description:"音频生成方式,0:系统生成,1:人工上传"`
+}
+
+// ReportChapterItem 报告章节详情
+type ReportChapterItem struct {
+	ReportChapterId     int    `orm:"column(report_chapter_id);pk" description:"报告章节ID"`
+	ReportId            int    `description:"报告ID"`
+	ReportType          string `description:"报告类型 day-晨报 week-周报"`
+	ClassifyIdFirst     int    `description:"一级分类id"`
+	ClassifyNameFirst   string `description:"一级分类名称"`
+	TypeId              int    `description:"品种ID"`
+	TypeName            string `description:"品种名称"`
+	Title               string `description:"标题"`
+	Abstract            string `description:"摘要"`
+	AddType             int    `description:"新增方式:1:新增报告,2:继承报告"`
+	Author              string `description:"作者"`
+	Content             string `description:"内容"`
+	ContentSub          string `description:"内容前两个章节"`
+	Stage               int    `description:"期数"`
+	Trend               string `description:"趋势观点"`
+	Sort                int    `description:"排序: 数值越小越靠前"`
+	IsEdit              int    `description:"是否已编辑 0-待编辑 1-已编辑"`
+	PublishState        int    `description:"发布状态 1-待发布,2-已发布"`
+	PublishTime         string `description:"发布时间"`
+	VideoUrl            string `description:"音频文件URL"`
+	VideoName           string `description:"音频文件名称"`
+	VideoPlaySeconds    string `description:"音频播放时长"`
+	VideoSize           string `description:"音频文件大小,单位M"`
+	VideoKind           int    `description:"音频生成方式:1,手动上传,2:自动生成"`
+	CreateTime          string `description:"创建时间"`
+	ModifyTime          string `description:"修改时间"`
+	OriginalVideoUrl    string `description:"原始音频文件URL"`
+	ContentStruct       string `description:"内容组件"`
+	LastModifyAdminId   int    `description:"最后更新人ID"`
+	LastModifyAdminName string `description:"最后更新人姓名"`
+	ContentModifyTime   string `description:"内容更新时间"`
+	ReportLayout        int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	ReportCreateTime    string `description:"报告时间创建时间"`
+}
+
+// ReportChapterItemResp
+// @Description: 章节详情(带有一些额外的数据)
+type ReportChapterItemResp struct {
+	ReportChapterItem
+	GrandAdminIdList []int  `description:"授权的用户id列表"`
+	PermissionIdList []int  `description:"关联的品种id列表"`
+	CanEdit          bool   `description:"是否可编辑"`
+	Editor           string `description:"编辑人"`
+	HeadImg          string `description:"报告头图地址"`
+	EndImg           string `description:"报告尾图地址"`
+	HeadStyle        string `description:"版头样式"`
+	EndStyle         string `description:"版尾样式"`
 }
 
 type ReportChapterResp struct {
@@ -62,6 +121,11 @@ type ReportChapterResp struct {
 	PublishTime      string `description:"发布时间"`
 	CreateTime       string `description:"创建时间"`
 	ModifyTime       string `description:"修改时间"`
+	GrandAdminIdList []int  `description:"授权的用户id列表"`
+	PermissionIdList []int  `description:"关联的品种id列表"`
+	CanEdit          bool   `description:"是否可编辑"`
+	Editor           string `description:"编辑人"`
+	IsAuth           bool   `description:"是否有权限"`
 }
 
 // GetChapterListByReportId 根据ReportId获取章节列表
@@ -82,6 +146,15 @@ func GetPublishedChapterListByReportId(reportId int) (list []*ReportChapter, err
 	return
 }
 
+// AddReportChapterReq
+// @Description: 新增报告章节请求体
+type AddReportChapterReq struct {
+	ReportId         int    `description:"报告ID"`
+	Title            string `description:"标题"`
+	PermissionIdList []int  `description:"报告关联的品种权限"`
+	AdminIdList      []int  `description:"授权的编辑人id列表"`
+}
+
 // EditReportChapterReq 编辑报告章节请求体
 type EditReportChapterReq struct {
 	ReportChapterId  int            `description:"报告章节ID"`
@@ -95,6 +168,14 @@ type EditReportChapterReq struct {
 	VideoName        string         `description:"音频文件名称"`
 	VideoPlaySeconds string         `description:"音频播放时长"`
 	VideoSize        string         `description:"音频文件大小,单位M"`
+
+	// 以下是智能研报相关
+	ContentStruct  string `description:"内容组件"`
+	HeadImg        string `description:"报告头图地址"`
+	EndImg         string `description:"报告尾图地址"`
+	CanvasColor    string `description:"画布颜色"`
+	HeadResourceId int    `description:"版头资源ID"`
+	EndResourceId  int    `description:"版尾资源ID"`
 }
 
 type EditTickList struct {
@@ -112,6 +193,21 @@ func GetReportChapterInfoById(reportChapterId int) (item *ReportChapter, err err
 	return
 }
 
+// GetReportChapterItemById
+// @Description: 根据主键获取报告章节(时间格式为字符串的数据)
+// @author: Roc
+// @datetime 2024-06-27 14:10:29
+// @param reportChapterId int
+// @return item *ReportChapterItem
+// @return err error
+func GetReportChapterItemById(reportChapterId int) (item *ReportChapterItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_chapter_id = ? `
+	err = o.Raw(sql, reportChapterId).QueryRow(&item)
+
+	return
+}
+
 // GetLastPublishedReportChapter 获取上一篇已发表的晨周报章节
 func GetLastPublishedReportChapter(typeId int, reportType string) (item *ReportChapter, err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -121,10 +217,27 @@ func GetLastPublishedReportChapter(typeId int, reportType string) (item *ReportC
 	return
 }
 
+// Add
+// @Description: 新增章节报告
+// @author: Roc
+// @receiver chapterInfo
+// @datetime 2024-06-04 15:14:41
+// @return err error
+func (chapterChapterInfo *ReportChapter) Add() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	lastId, err := o.Insert(chapterChapterInfo)
+	if err != nil {
+		return
+	}
+	chapterChapterInfo.ReportChapterId = int(lastId)
+
+	return
+}
+
 // UpdateChapter 更新报表章节
-func (chapterInfo *ReportChapter) UpdateChapter(cols []string) (err error) {
+func (chapterChapterInfo *ReportChapter) UpdateChapter(cols []string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	_, err = o.Update(chapterInfo, cols...)
+	_, err = o.Update(chapterChapterInfo, cols...)
 
 	return
 }
@@ -136,7 +249,11 @@ type EditChapterTrendTagReq struct {
 }
 
 // UpdateChapterAndTicker 更新章节及ticker
-func UpdateChapterAndTicker(chapterInfo *ReportChapter, updateCols []string, tickerList []*ReportChapterTicker) (err error) {
+func UpdateChapterAndTicker(reportInfo *Report, chapterInfo *ReportChapter, updateCols []string, tickerList []*ReportChapterTicker) (err error) {
+	// 更新报告的最近编辑人信息
+	if err = reportInfo.UpdateReport([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}); err != nil {
+		return
+	}
 	// 更新章节
 	if err = chapterInfo.UpdateChapter(updateCols); err != nil {
 		return
@@ -188,7 +305,7 @@ func GetReportChapterVideoList(reportId int) (list []*ReportChapterVideoList, er
 }
 
 // GetReportChapterVideoListByReportIds 根据报告ID集合获取报告章节音频列表
-func GetReportChapterVideoListByReportIds(reportIds []string) (list []*ReportChapterVideoList, err error) {
+func GetReportChapterVideoListByReportIds(reportIds []int) (list []*ReportChapterVideoList, err error) {
 	if len(reportIds) == 0 {
 		return
 	}
@@ -234,18 +351,8 @@ func GetReportChapterVideoListByChapterIds(chapterIds []int) (list []*ReportChap
 
 // PublishReportChapterReq 发布报告章节请求体
 type PublishReportChapterReq struct {
-	ReportChapterId  int            `description:"报告章节ID"`
-	Title            string         `description:"标题"`
-	AddType          int            `description:"新增方式:1:新增报告,2:继承报告"`
-	Author           string         `description:"作者"`
-	Content          string         `description:"内容"`
-	TickerList       []EditTickList `description:"指标信息"`
-	CreateTime       string         `description:"发布时间"`
-	PublishReport    int            `description:"是否同时发布报告"`
-	VideoUrl         string         `description:"音频文件URL"`
-	VideoName        string         `description:"音频文件名称"`
-	VideoPlaySeconds string         `description:"音频播放时长"`
-	VideoSize        string         `description:"音频文件大小,单位M"`
+	ReportChapterId int `description:"报告章节ID"`
+	PublishReport   int `description:"是否同时发布报告"`
 }
 
 // CountPublishedChapterNum 获取报告已发布的章节数
@@ -257,6 +364,36 @@ func CountPublishedChapterNum(reportId int) (count int, err error) {
 	return
 }
 
+// CountUnPublishedChapterNum
+// @Description: 获取报告未发布的章节数
+// @author: Roc
+// @datetime 2024-06-14 15:59:23
+// @param reportId int
+// @return count int
+// @return err error
+func CountUnPublishedChapterNum(reportId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS ct FROM report_chapter WHERE report_id = ? AND publish_state = 1 `
+	err = o.Raw(sql, reportId).QueryRow(&count)
+
+	return
+}
+
+// GetUnPublishedChapterList
+// @Description: 获取报告未发布的章节列表
+// @author: Roc
+// @datetime 2024-06-14 15:59:23
+// @param reportId int
+// @return list []*ReportChapter
+// @return err error
+func GetUnPublishedChapterList(reportId int) (list []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT report_chapter_id,report_id,title FROM report_chapter WHERE report_id = ? AND publish_state = 1 ORDER BY sort ASC`
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
 // GetChapterListByChapterIds 根据ReportId获取章节列表
 func GetChapterListByChapterIds(chapterIds []int) (list []*ReportChapter, err error) {
 	if len(chapterIds) == 0 {
@@ -305,3 +442,166 @@ func CountReportChapterByTypeId(typeId int) (count int, err error) {
 	err = o.Raw(sql, typeId).QueryRow(&count)
 	return
 }
+
+// AddReportChapter
+// @Description: 待添加的报告章节
+type AddReportChapter struct {
+	ReportChapter       *ReportChapter
+	GrantList           []*report.ReportChapterGrant
+	GrantPermissionList []*report.ReportChapterPermissionMapping
+}
+
+// EditReportChapterBaseInfoAndPermissionReq
+// @Description: 编辑报告章节的基础信息请求
+type EditReportChapterBaseInfoAndPermissionReq struct {
+	ReportChapterId  int    `description:"报告章节ID"`
+	Title            string `description:"标题"`
+	PermissionIdList []int  `description:"报告关联的品种权限"`
+	AdminIdList      []int  `description:"授权的编辑人id列表"`
+}
+
+// GetReportChapterIdList
+// @Description: 获取报告的所有章节id列表
+// @author: Roc
+// @datetime 2024-06-05 11:09:40
+// @param reportId int
+// @return list []int
+// @return err error
+func GetReportChapterIdList(reportId int) (list []int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT report_chapter_id FROM report_chapter
+			WHERE report_id = ? 
+			ORDER BY report_chapter_id ASC `
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+
+	return
+}
+
+// ReportChapterMoveReq
+// @Description:  报告章节移动请求
+type ReportChapterMoveReq struct {
+	ReportChapterId int `description:"报告章节id"`
+	//	ParentChartPermissionId int `description:"父级品种id"`
+	PrevReportChapterId int `description:"上一个兄弟节点报告章节id"`
+	NextReportChapterId int `description:"下一个兄弟节点报告章节id"`
+}
+
+// GetReportChapterById
+// @Description: 获取具体章节
+// @author: Roc
+// @receiver r
+// @datetime 2024-06-06 09:32:40
+// @param reportChapterId int
+// @return item *ReportChapter
+// @return err error
+func (chapterChapterInfo *ReportChapter) GetReportChapterById(reportChapterId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE report_chapter_id = ?`
+	err = o.Raw(sql, reportChapterId).QueryRow(&item)
+	return
+}
+
+// UpdateReportChapterSortByReportId
+// @Description: 根据父类id更新排序
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:39:28
+// @param reportId int
+// @param reportChapterId int
+// @param nowSort int
+// @param updateSort string
+// @return err error
+func (chapterChapterInfo *ReportChapter) UpdateReportChapterSortByReportId(reportId, reportChapterId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	pars := make([]interface{}, 0)
+	sql := ` update report_chapter set sort = ` + updateSort + ` WHERE report_id = ? AND sort > ?`
+	pars = append(pars, reportId, nowSort)
+
+	if reportChapterId > 0 {
+		sql += ` or ( report_chapter_id > ?  and sort = ? )`
+		pars = append(pars, reportChapterId, nowSort)
+	}
+
+	_, err = o.Raw(sql, pars).Exec()
+
+	return
+}
+
+// GetFirstReportChapterByReportId
+// @Description: 获取当前报告下,且排序数相同 的排序第一条的数据
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:45:32
+// @param reportId int
+// @return item *ReportChapter
+// @return err error
+func (chapterChapterInfo *ReportChapter) GetFirstReportChapterByReportId(reportId int) (item *ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter WHERE 1 = 1 AND report_id = ? ORDER BY sort ASC, report_chapter_id ASC LIMIT 1`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+// GetMaxSortByReportId
+// @Description: 获取最大的排序值
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:44:13
+// @param reportId int
+// @return maxSort int
+// @return err error
+func (chapterChapterInfo *ReportChapter) GetMaxSortByReportId(reportId int) (maxSort int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT max(sort) AS sort FROM report_chapter WHERE report_id = ?`
+	err = o.Raw(sql, reportId).QueryRow(&maxSort)
+
+	return
+}
+
+// Update
+// @Description: 数据变更
+// @author: Roc
+// @receiver chapterChapterInfo
+// @datetime 2024-06-06 09:47:46
+// @param cols []string
+// @return err error
+func (chapterChapterInfo *ReportChapter) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(chapterChapterInfo, cols...)
+
+	return
+}
+
+// DelReportChapterReq
+// @Description: 删除报告章节请求体
+type DelReportChapterReq struct {
+	ReportChapterId int `description:"报告章节ID"`
+}
+
+// GetAllReportChapter 获取所有的报告章节
+func GetAllReportChapter() (items []*ReportChapter, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter ORDER BY report_chapter_id asc `
+	_, err = o.Raw(sql).QueryRows(&items)
+
+	return
+}
+
+// GetCountReportChapterByCondition
+// @Description: 根据条件获取章节数量
+// @author: Roc
+// @datetime 2024-07-15 15:37:50
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetCountReportChapterByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(1) AS count FROM report_chapter WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+
+	return
+}

+ 71 - 24
models/report_chapter_type.go

@@ -8,28 +8,30 @@ import (
 )
 
 type ReportChapterType struct {
-	ReportChapterTypeId    int       `orm:"column(report_chapter_type_id);pk" description:"报告章节类型id"`
-	ReportChapterTypeKey   string    `description:"章节key"`
-	ReportChapterTypeThumb string    `description:"H5展示的图片"`
-	BannerUrl              string    `description:"banner显示图片"`
-	ReportChapterTypeName  string    `description:"报告章节类型名称"`
-	Sort                   int       `description:"排序字段"`
-	Enabled                int       `description:"启禁用状态"`
-	CreatedTime            time.Time `description:"创建时间"`
-	LastUpdatedTime        time.Time `description:"更新时间"`
-	ResearchType           string    `description:"研报类型"`
-	SelectedImage          string    `description:"选中时的图片"`
-	UnselectedImage        string    `description:"没选中时的图片"`
-	PcSelectedImage        string    `description:"PC-选中的图片"`
-	PcUnselectedImage      string    `description:"PC-未选中的图片"`
-	EditImgUrl             string    `description:"管理后台编辑时选用的图"`
-	TickerTitle            string    `description:"指标列的标题"`
-	IsShow                 int       `description:"是否显示(研报小程序端根据此字段判断)"`
-	PauseStartTime         string    `description:"暂停开始日期"`
-	PauseEndTime           string    `description:"暂停结束日期"`
-	IsSet                  int       `description:"是否设置:0为设置,1已设置"`
-	YbIconUrl              string    `description:"研报小程序icon"`
-	YbBottomIcon           string    `description:"研报小程序详情底部icon"`
+	ReportChapterTypeId        int       `orm:"column(report_chapter_type_id);pk" description:"报告章节类型id"`
+	ReportChapterTypeKey       string    `description:"章节key"`
+	ReportChapterTypeThumb     string    `description:"H5展示的图片"`
+	BannerUrl                  string    `description:"banner显示图片"`
+	ReportChapterTypeName      string    `description:"报告章节类型名称"`
+	Sort                       int       `description:"排序字段"`
+	Enabled                    int       `description:"启禁用状态"`
+	CreatedTime                time.Time `description:"创建时间"`
+	LastUpdatedTime            time.Time `description:"更新时间"`
+	ResearchType               string    `description:"研报类型"`
+	SelectedImage              string    `description:"选中时的图片"`
+	UnselectedImage            string    `description:"没选中时的图片"`
+	PcSelectedImage            string    `description:"PC-选中的图片"`
+	PcUnselectedImage          string    `description:"PC-未选中的图片"`
+	EditImgUrl                 string    `description:"管理后台编辑时选用的图"`
+	TickerTitle                string    `description:"指标列的标题"`
+	IsShow                     int       `description:"是否显示(研报小程序端根据此字段判断)"`
+	PauseStartTime             string    `description:"暂停开始日期"`
+	PauseEndTime               string    `description:"暂停结束日期"`
+	IsSet                      int       `description:"是否设置:0为设置,1已设置"`
+	YbIconUrl                  string    `description:"研报小程序icon"`
+	YbBottomIcon               string    `description:"研报小程序详情底部icon"`
+	ReportClassifyId           int       `description:"所属分类id"`
+	InheritReportChapterTypeId int       `description:"继承的报告章节类型id"`
 }
 
 func (r *ReportChapterType) Create() (err error) {
@@ -42,6 +44,23 @@ func (r *ReportChapterType) Create() (err error) {
 	return
 }
 
+// MultiCreate
+// @Description: 批量添加报告章节类型
+// @author: Roc
+// @receiver r
+// @datetime 2024-06-17 14:36:09
+// @param addList []*ReportChapterType
+// @return err error
+func (r *ReportChapterType) MultiCreate(addList []*ReportChapterType) (err error) {
+	if len(addList) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(500, addList)
+
+	return
+}
+
 func (r *ReportChapterType) Update(cols []string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	_, err = o.Update(r, cols...)
@@ -341,7 +360,7 @@ type ReportChapterTypeListItem struct {
 // ReportChapterTypeAddReq 新增章节类型请求体
 type ReportChapterTypeAddReq struct {
 	ReportChapterTypeName string `description:"报告章节类型名称"`
-	ResearchType          string `description:"研报类型"`
+	ClassifyId            int    `description:"所属报告分类id"`
 	ChartPermissionIdList []int  `description:"权限id数组"`
 	/*SelectedImage         string `description:"选中时的icon"`
 	UnselectedImage       string `description:"未选中时的icon"`
@@ -354,7 +373,7 @@ type ReportChapterTypeAddReq struct {
 type ReportChapterTypeEditReq struct {
 	ReportChapterTypeId   int    `description:"报告章节类型id"`
 	ReportChapterTypeName string `description:"报告章节类型名称"`
-	ResearchType          string `description:"研报类型"`
+	ClassifyId            int    `description:"所属报告分类id"`
 	ChartPermissionIdList []int  `description:"权限id数组"`
 	/*SelectedImage         string `description:"选中时的icon"`
 	UnselectedImage       string `description:"未选中时的icon"`
@@ -447,3 +466,31 @@ func (r *ReportChapterType) SetEnabled(id, enabled int) (err error) {
 	_, err = o.Raw(sql, enabled, id).Exec()
 	return
 }
+
+// GetReportChapterTypeListByClassifyId
+// @Description: 通过报告类型获取章节类型列表(已启用的)
+// @author: Roc
+// @datetime 2024-06-03 16:00:12
+// @param classifyId int
+// @return list []*ReportChapterType
+// @return err error
+func GetReportChapterTypeListByClassifyId(classifyId int) (list []*ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type WHERE report_classify_id = ? AND enabled = 1`
+	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	return
+}
+
+// GetAllReportChapterTypeListByClassifyId
+// @Description: 通过报告类型获取所有的章节类型列表
+// @author: Roc
+// @datetime 2024-06-03 16:00:12
+// @param classifyId int
+// @return list []*ReportChapterType
+// @return err error
+func GetAllReportChapterTypeListByClassifyId(classifyId int) (list []*ReportChapterType, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type WHERE report_classify_id = ? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	return
+}

+ 60 - 22
models/report_chapter_type_permission.go

@@ -16,16 +16,22 @@ type ReportChapterTypePermission struct {
 	CreatedTime           time.Time `description:"创建时间"`
 }
 
-// GetChapterTypePermissionByTypeIdAndResearchType 根据章节类型ID及研报类型获取章节类型权限列表
-func GetChapterTypePermissionByTypeIdAndResearchType(typeId int, researchType string) (list []*ReportChapterTypePermission, err error) {
+// GetChapterTypePermissionByReportChapterTypeId
+// @Description: 根据章节类型ID获取章节类型权限列表
+// @author: Roc
+// @datetime 2024-06-03 15:42:47
+// @param typeId int
+// @return list []*ReportChapterTypePermission
+// @return err error
+func GetChapterTypePermissionByReportChapterTypeId(typeId int) (list []*ReportChapterTypePermission, err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ? ORDER BY chart_permission_id ASC `
-	_, err = o.Raw(sql, typeId, researchType).QueryRows(&list)
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id = ?  ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, typeId).QueryRows(&list)
 	return
 }
 
 // SetReportChapterTypePermission 设置报告章节类型权限
-func SetReportChapterTypePermission(chapterTypeId int, researchType string, newPermissions []*ReportChapterTypePermission, newWeekPermissions []*ChartPermissionChapterMapping) (err error) {
+func SetReportChapterTypePermission(chapterTypeId int, newPermissions []*ReportChapterTypePermission) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	to, err := o.Begin()
 	if err != nil {
@@ -40,8 +46,8 @@ func SetReportChapterTypePermission(chapterTypeId int, researchType string, newP
 	}()
 
 	// 删除原章节类型权限
-	sql := `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ?`
-	_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
+	sql := `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ?`
+	_, err = to.Raw(sql, chapterTypeId).Exec()
 	if err != nil {
 		return
 	}
@@ -55,22 +61,23 @@ func SetReportChapterTypePermission(chapterTypeId int, researchType string, newP
 	}
 
 	// 周报章节调整chart_permission_chapter_mapping表
-	if researchType == utils.REPORT_TYPE_WEEK {
-		// 删除原权限
-		sql = `DELETE FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ?`
-		_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
-		if err != nil {
-			return
-		}
+	//if researchType == utils.REPORT_TYPE_WEEK {
+	//	// 删除原权限
+	//	sql = `DELETE FROM chart_permission_chapter_mapping WHERE report_chapter_type_id = ? AND research_type = ?`
+	//	_, err = to.Raw(sql, chapterTypeId, researchType).Exec()
+	//	if err != nil {
+	//		return
+	//	}
+	//
+	//	// 新增权限
+	//	if len(newWeekPermissions) > 0 {
+	//		_, err = to.InsertMulti(len(newWeekPermissions), newWeekPermissions)
+	//		if err != nil {
+	//			return
+	//		}
+	//	}
+	//}
 
-		// 新增权限
-		if len(newWeekPermissions) > 0 {
-			_, err = to.InsertMulti(len(newWeekPermissions), newWeekPermissions)
-			if err != nil {
-				return
-			}
-		}
-	}
 	return
 }
 
@@ -81,3 +88,34 @@ func GetChapterTypePermissionByResearchType(researchType string) (list []*Report
 	_, err = o.Raw(sql, researchType).QueryRows(&list)
 	return
 }
+
+// GetChapterTypePermissionByChapterTypeIdList
+// @Description: 根据章节类型ID列表获取章节类型权限列表
+// @author: Roc
+// @datetime 2024-06-03 14:10:17
+// @param chapterTypeIdList []int
+// @return list []*ReportChapterTypePermission
+// @return err error
+func GetChapterTypePermissionByChapterTypeIdList(chapterTypeIdList []int) (list []*ReportChapterTypePermission, err error) {
+	num := len(chapterTypeIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type_permission WHERE report_chapter_type_id in (` + utils.GetOrmInReplace(num) + `) ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql, chapterTypeIdList).QueryRows(&list)
+	return
+}
+
+// GetAllChapterTypePermission
+// @Description: 获取所有章节类型ID获取章节类型权限列表
+// @author: Roc
+// @datetime 2024-06-03 15:42:47
+// @return list []*ReportChapterTypePermission
+// @return err error
+func GetAllChapterTypePermission() (list []*ReportChapterTypePermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM report_chapter_type_permission ORDER BY chart_permission_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}

+ 66 - 0
models/report_grant.go

@@ -0,0 +1,66 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// ReportGrant Ppt授权表
+type ReportGrant struct {
+	GrantId      int64     `orm:"column(grant_id);pk;auto" description:"自增序号"`
+	ReportId     int64     `description:"report ID"`
+	DepartmentId int64     `description:"授权部门id"`
+	GrantAdminId int64     `description:"授权部门id"`
+	CreateTime   time.Time `orm:"auto_now_add;type(datetime)" description:"创建时间"`
+}
+
+// GetReportGrantInfo 获取已经有权限的report列表
+func GetReportGrantInfo(reportId int) (list []*ReportGrant, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report_grant WHERE report_id=? `
+	_, err = o.Raw(sql, reportId).QueryRows(&list)
+	return
+}
+
+// MultiAddReportGrant 批量添加授权记录
+func MultiAddReportGrant(reportId int, list []*ReportGrant) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	sql := "DELETE from report_grant where report_id=?"
+	_, err = to.Raw(sql, reportId).Exec()
+	if err != nil {
+		return
+	}
+
+	// 新增授权记录
+	if len(list) > 0 {
+		_, tmpErr := to.InsertMulti(len(list), list)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+
+	return
+}
+
+// DeleteReportGrant 移除授权记录
+func DeleteReportGrant(reportId int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := "DELETE from report_grant where report_id=?"
+	_, err = o.Raw(sql, reportId).Exec()
+
+	return
+}

+ 453 - 0
models/report_v2.go

@@ -0,0 +1,453 @@
+package models
+
+import (
+	"errors"
+	"eta/eta_api/models/report"
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// AddReportAndChapter
+// @Description: 新增报告及章节
+// @author: Roc
+// @datetime 2024-06-06 17:08:34
+// @param reportItem *Report
+// @param allGrantUserList []*report.ReportGrant
+// @param addReportChapterList []AddReportChapter
+// @return reportId int64
+// @return err error
+func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGrant, addReportChapterList []AddReportChapter) (reportId int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 新增报告
+	reportId, err = to.Insert(reportItem)
+	if err != nil {
+		return
+	}
+	reportItem.Id = int(reportId)
+
+	// 新增报告授权
+	if len(allGrantUserList) > 0 {
+		for _, v := range allGrantUserList {
+			v.ReportId = reportItem.Id
+		}
+		_, err = to.InsertMulti(500, allGrantUserList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节
+	if len(addReportChapterList) > 0 {
+		for _, addReportChapter := range addReportChapterList {
+			// 新增章节
+			chapterItem := addReportChapter.ReportChapter
+			chapterItem.ReportId = int(reportId)
+			cpId, tmpErr := to.Insert(chapterItem)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			chapterItem.ReportChapterId = int(cpId)
+
+			// 新增章节授权
+			if len(addReportChapter.GrantList) > 0 {
+				grantList := addReportChapter.GrantList
+				for _, v := range grantList {
+					v.ReportChapterId = chapterItem.ReportChapterId
+				}
+				_, err = to.InsertMulti(500, grantList)
+				if err != nil {
+					return
+				}
+			}
+
+			// 新增报告章节关联的品种
+			if len(addReportChapter.GrantPermissionList) > 0 {
+				permissionList := addReportChapter.GrantPermissionList
+				for _, v := range permissionList {
+					v.ReportChapterId = chapterItem.ReportChapterId
+				}
+				_, err = to.InsertMulti(500, permissionList)
+				if err != nil {
+					return
+				}
+			}
+
+		}
+	}
+
+	return
+}
+
+// EditReportAndPermission
+// @Description: 修改报告的基础信息、授权用户权限
+// @author: Roc
+// @datetime 2024-06-06 17:11:12
+// @param reportInfo *Report
+// @param updateCols []string
+// @param addReportGrantList []*report.ReportGrant
+// @param delReportGrantIdList []int
+// @return err error
+func EditReportAndPermission(reportInfo *Report, updateCols []string, addReportGrantList []*report.ReportGrant, delReportGrantIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 变更报告章节信息
+	if len(updateCols) > 0 {
+		_, err = to.Update(reportInfo, updateCols...)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告授权用户
+	if len(addReportGrantList) > 0 {
+		_, err = to.InsertMulti(500, addReportGrantList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告授权用户
+	delNum := len(delReportGrantIdList)
+	if delNum > 0 {
+		sql := `DELETE FROM report_grant WHERE grant_id IN (` + utils.GetOrmInReplace(delNum) + `)`
+		_, err = to.Raw(sql, delReportGrantIdList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// AddChapterBaseInfoAndPermission
+// @Description: 新增报告章节的基础信息、授权用户权限
+// @author: Roc
+// @datetime 2024-06-11 15:33:50
+// @param reportChapterInfo *ReportChapter
+// @param addReportChapterGrantList []*report.ReportChapterGrant
+// @param addChapterPermissionMap []*report.ReportChapterPermissionMapping
+// @return err error
+func AddChapterBaseInfoAndPermission(reportChapterInfo *ReportChapter, addReportChapterGrantList []*report.ReportChapterGrant, addChapterPermissionMap []*report.ReportChapterPermissionMapping) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	lastId, err := to.Insert(reportChapterInfo)
+	if err != nil {
+		return
+	}
+	reportChapterInfo.ReportChapterId = int(lastId)
+
+	// 新增报告章节授权用户
+	if len(addReportChapterGrantList) > 0 {
+		for k, _ := range addReportChapterGrantList {
+			addReportChapterGrantList[k].ReportChapterId = reportChapterInfo.ReportChapterId
+		}
+		_, err = to.InsertMulti(500, addReportChapterGrantList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节关联的品种配置
+	if len(addChapterPermissionMap) > 0 {
+		for k, _ := range addChapterPermissionMap {
+			addChapterPermissionMap[k].ReportChapterId = reportChapterInfo.ReportChapterId
+		}
+
+		_, err = to.InsertMulti(500, addChapterPermissionMap)
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// EditChapterBaseInfoAndPermission
+// @Description: 修改报告章节的基础信息、授权用户权限、品种权限
+// @author: Roc
+// @datetime 2024-06-05 11:45:04
+// @param reportChapterInfo *ReportChapter
+// @param updateCols []string
+// @param addReportChapterGrantList []report.ReportChapterGrant
+// @param addChapterPermissionMap []*report.ReportChapterPermissionMapping
+// @param delReportChapterGrantIdList []int
+// @param delChapterPermissionMappingIdList []int
+// @return err error
+func EditChapterBaseInfoAndPermission(reportInfo *Report, reportChapterInfo *ReportChapter, updateCols []string, addReportChapterGrantList []*report.ReportChapterGrant, addChapterPermissionMap []*report.ReportChapterPermissionMapping, delReportChapterGrantIdList, delChapterPermissionMappingIdList []int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 变更报告的最后编辑人信息
+	{
+		_, err = to.Update(reportInfo, "LastModifyAdminId", "LastModifyAdminName", "ModifyTime")
+		if err != nil {
+			return
+		}
+	}
+
+	// 变更报告章节信息
+	if len(updateCols) > 0 {
+		_, err = to.Update(reportChapterInfo, updateCols...)
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节授权用户
+	if len(addReportChapterGrantList) > 0 {
+		_, err = to.InsertMulti(500, addReportChapterGrantList)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节授权用户
+	delNum := len(delReportChapterGrantIdList)
+	if delNum > 0 {
+		sql := `DELETE FROM report_chapter_grant WHERE grant_id IN (` + utils.GetOrmInReplace(delNum) + `)`
+		_, err = to.Raw(sql, delReportChapterGrantIdList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 新增报告章节的品种配置
+	if len(addChapterPermissionMap) > 0 {
+		_, err = to.InsertMulti(500, addChapterPermissionMap)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节的品种配置
+	delNum = len(delChapterPermissionMappingIdList)
+	if delNum > 0 {
+		sql := `DELETE FROM report_chapter_permission_mapping WHERE report_chapter_permission_mapping_id IN (` + utils.GetOrmInReplace(delNum) + `)`
+		_, err = to.Raw(sql, delChapterPermissionMappingIdList).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// DelChapterAndPermission
+// @Description: 删除报告章节、授权用户权限、品种权限
+// @author: Roc
+// @datetime 2024-06-06 17:25:47
+// @param reportInfo *Report
+// @param updateReportCols []string
+// @param reportChapterInfo *ReportChapter
+// @return err error
+func DelChapterAndPermission(reportInfo *Report, updateReportCols []string, reportChapterInfo *ReportChapter) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	// 变更报告信息
+	if len(updateReportCols) > 0 {
+		_, err = to.Update(reportInfo, updateReportCols...)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告对应章节
+	{
+		_, err = to.Delete(reportChapterInfo)
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节的授权用户权限
+	{
+		sql := `DELETE FROM report_chapter_grant WHERE report_chapter_id = ? `
+		_, err = to.Raw(sql, reportChapterInfo.ReportChapterId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	// 删除报告章节的品种配置
+	{
+		sql := `DELETE FROM report_chapter_permission_mapping WHERE report_chapter_id = ? `
+		_, err = to.Raw(sql, reportChapterInfo.ReportChapterId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// GetReportListCountByAuthorized
+// @Description: 获取有权限的报告列表的报告数量
+// @author: Roc
+// @datetime 2024-05-30 15:14:01
+// @param condition string
+// @param pars []interface{}
+// @return count int
+// @return err error
+func GetReportListCountByAuthorized(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count  FROM report as a 
+ WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetReportListByAuthorized
+// @Description: 获取有权限的报告列表的数据
+// @author: Roc
+// @datetime 2024-05-30 15:15:07
+// @param condition string
+// @param pars []interface{}
+// @param startSize int
+// @param pageSize int
+// @return items []*ReportList
+// @return err error
+func GetReportListByAuthorized(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	sql := `SELECT id,classify_id_first,classify_name_first,classify_id_second,classify_name_second,classify_id_third,classify_name_third,title,stage,create_time,author,report_layout,collaborate_type,is_public_publish,abstract,has_chapter FROM report as a WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += ` GROUP BY a.id ORDER BY  report_create_time DESC LIMIT ?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ModifyReportClassifyAndReportChapterTypeByCondition
+// @Description:
+// @author: Roc
+// @datetime 2024-06-17 16:12:44
+// @param condition string
+// @param pars []interface{}
+// @param updateStr string
+// @param chapterTypeIdMap map[int]int 当前的章节类型ID ---> 继承的章节类型ID
+// @param oldClassifyId int
+// @param currClassifyId int
+// @param currClassifyName string
+// @return err error
+func ModifyReportClassifyAndReportChapterTypeByCondition(condition string, pars []interface{}, updateStr string, chapterTypeIdMap map[int]int, oldClassifyId, currClassifyId int, currClassifyName string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	if condition == `` {
+		err = errors.New("condition不能为空")
+		return
+	}
+	// 修改报告的所属分类
+	sql := `UPDATE report as a SET ` + updateStr + ` WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	_, err = to.Raw(sql, pars).Exec()
+	if err != nil {
+		return
+	}
+
+	// 修改历史报告中的章节分类归属
+	sql = `UPDATE report_chapter set classify_id_first=?,classify_name_first=? where classify_id_first = ?`
+	_, err = to.Raw(sql, currClassifyId, currClassifyName, oldClassifyId).Exec()
+	if err != nil {
+		return
+	}
+
+	for currTypeId, oldTypeId := range chapterTypeIdMap {
+		// 没有章节类型的不处理
+		if oldTypeId == 0 {
+			continue
+		}
+		tmpSql := `UPDATE report_chapter set type_id=? where type_id = ?`
+		_, err = to.Raw(tmpSql, currTypeId, oldTypeId).Exec()
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// EditLayoutImgReq
+// @Description: 版图设置请求
+type EditLayoutImgReq struct {
+	ReportId       int64  `description:"报告id"`
+	HeadImg        string `description:"报告头图地址"`
+	EndImg         string `description:"报告尾图地址"`
+	CanvasColor    string `description:"画布颜色"`
+	HeadResourceId int    `description:"版头资源ID"`
+	EndResourceId  int    `description:"版尾资源ID"`
+}

+ 23 - 0
models/smart_report/smart_resource.go

@@ -83,6 +83,29 @@ func (m *SmartReportResource) GetPageItemsByCondition(condition string, pars []i
 	return
 }
 
+// GetItemsByCondition
+// @Description: 根据条件获取所有的审批单
+// @author: Roc
+// @receiver m
+// @datetime 2024-06-27 16:12:55
+// @param condition string
+// @param pars []interface{}
+// @param fieldArr []string
+// @return items []*SmartReportResourceItem
+// @return err error
+func (m *SmartReportResource) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string) (items []*SmartReportResourceItem, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := ` ORDER BY create_time DESC`
+
+	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
+}
+
 // SmartReportResourceEditReq 智能研报资源编辑请求体
 type SmartReportResourceEditReq struct {
 	ResourceId int    `description:"资源ID"`

+ 19 - 0
models/system/sys_admin.go

@@ -1,6 +1,7 @@
 package system
 
 import (
+	"eta/eta_api/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
@@ -377,3 +378,21 @@ func GetSysAdminList(condition string, pars []interface{}, fieldArr []string, or
 	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
+
+// GetSysAdminByIdList
+// @Description: 根据账户id列表获取账户信息列表
+// @author: Roc
+// @datetime 2024-06-05 10:49:50
+// @param adminIdList []int
+// @return items []*Admin
+// @return err error
+func GetSysAdminByIdList(adminIdList []int) (items []*Admin, err error) {
+	num := len(adminIdList)
+	if num <= 0 {
+		return
+	}
+	sql := `SELECT * FROM admin WHERE admin_id in (` + utils.GetOrmInReplace(num) + `) `
+	o := orm.NewOrm()
+	_, err = o.Raw(sql, adminIdList).QueryRows(&items)
+	return
+}

+ 3 - 3
models/wechat_send_msg.go

@@ -21,7 +21,7 @@ func GetOpenIdArr() (items []string, err error) {
 	return
 }
 
-func GetOpenIdArrByClassifyNameSecond(classifyNameSecond string) (items []string, err error) {
+func GetOpenIdArrByClassifyId(classifyId int) (items []string, err error) {
 	sql := ` SELECT DISTINCT ur.open_id FROM wx_user AS wu 
 			INNER JOIN company AS c ON c.company_id = wu.company_id 
 			INNER JOIN company_product AS d ON c.company_id=d.company_id
@@ -31,9 +31,9 @@ func GetOpenIdArrByClassifyNameSecond(classifyNameSecond string) (items []string
 			INNER JOIN chart_permission_search_key_word_mapping AS g ON f.chart_permission_id=g.chart_permission_id
 			WHERE ur.open_id != "" AND ur.subscribe=1 AND ur.create_platform=1 AND  d.status IN('正式','试用','永续') AND  e.status IN('正式','试用','永续') 
 			AND g.from='rddp'
-			AND g.key_word=?
+			AND g.classify_id=?
 			ORDER BY FIELD(c.company_id, 16) DESC, ur.user_record_id ASC  `
 	o := orm.NewOrmUsingDB("weekly")
-	_, err = o.Raw(sql, classifyNameSecond).QueryRows(&items)
+	_, err = o.Raw(sql, classifyId).QueryRows(&items)
 	return
 }

+ 522 - 9
routers/commentsRouter.go

@@ -16,6 +16,177 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AddPrompt",
+            Router: `/prompt/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "DeletePrompt",
+            Router: `/prompt/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "DetailPrompt",
+            Router: `/prompt/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "EditPrompt",
+            Router: `/prompt/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "PromptGroupList",
+            Router: `/prompt/groups`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AiPromptList",
+            Router: `/prompt/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "MoveAiPrompt",
+            Router: `/prompt/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "SharePrompt",
+            Router: `/prompt/share`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AddAiSummary",
+            Router: `/summary/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AddAiSummaryClassify",
+            Router: `/summary/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "DeleteAiSummaryClassify",
+            Router: `/summary/classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "DeleteSandboxClassifyCheck",
+            Router: `/summary/classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "EditAiSummaryClassify",
+            Router: `/summary/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AiSummaryClassifyItems",
+            Router: `/summary/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AiSummaryClassifyMove",
+            Router: `/summary/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AiSummaryClassifyList",
+            Router: `/summary/classifyList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AiSummaryDetail",
+            Router: `/summary/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "GenerateAiSummary",
+            Router: `/summary/generate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
+        beego.ControllerComments{
+            Method: "AiSummaryList",
+            Router: `/summary/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/ai:AiController"],
         beego.ControllerComments{
             Method: "TopicDelete",
@@ -1348,6 +1519,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoImgSetBySvg",
+            Router: `/chart_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"],
         beego.ControllerComments{
             Method: "ChartList",
@@ -2077,6 +2257,168 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ClassifyAdd",
+            Router: `/ths_hf/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ClassifyEdit",
+            Router: `/ths_hf/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ClassifyList",
+            Router: `/ths_hf/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ClassifyMove",
+            Router: `/ths_hf/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ClassifyRemove",
+            Router: `/ths_hf/classify/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/ths_hf/classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/ths_hf/index/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/ths_hf/index/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/ths_hf/index/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ExistCheck",
+            Router: `/ths_hf/index/exist_check`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/ths_hf/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "ListChoice",
+            Router: `/ths_hf/index/list_choice`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "MultiOpt",
+            Router: `/ths_hf/index/multi_opt`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/ths_hf/index/refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/ths_hf/index/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Save2Edb",
+            Router: `/ths_hf/index/save2edb`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Save2EdbPre",
+            Router: `/ths_hf/index/save2edb_pre`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromThsHfController"],
+        beego.ControllerComments{
+            Method: "Search",
+            Router: `/ths_hf/index/search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BloombergDataController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BloombergDataController"],
         beego.ControllerComments{
             Method: "AddCheck",
@@ -2455,6 +2797,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoImgSetBySvg",
+            Router: `/chart_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "ChartInfoMove",
@@ -2482,6 +2833,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "PreviewSeasonChartInfo",
+            Router: `/chart_info/preview/season`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "PreviewSectionScatterChartInfo",
@@ -3481,6 +3841,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "ChartImageSetBySvg",
+            Router: `/edb_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "SetEdbDataInsertConfig",
@@ -4003,6 +4372,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "BatchAdd",
+            Router: `/mysteel_chemical/batch_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "MysteelChemicalBatchSearch",
+            Router: `/mysteel_chemical/batch_search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "MysteelChemicalClassify",
@@ -4030,6 +4417,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/mysteel_chemical/edb_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "AddCheck",
+            Router: `/mysteel_chemical/edb_info/add_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "MysteelChemicalFrequency",
@@ -4048,6 +4453,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "NameCheck",
+            Router: `/mysteel_chemical/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "MysteelChemicalRefresh",
@@ -4939,6 +5353,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"],
+        beego.ControllerComments{
+            Method: "ChartImageSetBySvg",
+            Router: `/predict_edb_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"],
         beego.ControllerComments{
             Method: "ClassifyEdbInfoItems",
@@ -7216,6 +7639,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyPermissionV2",
+            Router: `/permission/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
         beego.ControllerComments{
             Method: "ClassifyPermission",
@@ -8163,8 +8595,8 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "AddDayWeekReport",
-            Router: `/addDayWeekReport`,
+            Method: "CancelApprove",
+            Router: `/approve/cancel`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -8172,8 +8604,8 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "CancelApprove",
-            Router: `/approve/cancel`,
+            Method: "SubmitApprove",
+            Router: `/approve/submit`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -8181,8 +8613,17 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "SubmitApprove",
-            Router: `/approve/submit`,
+            Method: "Author",
+            Router: `/author`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "AddChapter",
+            Router: `/chapter/add`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -8190,13 +8631,58 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "Author",
-            Router: `/author`,
+            Method: "EditChapterBaseInfoAndPermission",
+            Router: `/chapter/base_info/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "DelChapter",
+            Router: `/chapter/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "ChapterMove",
+            Router: `/chapter/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditChapterTitle",
+            Router: `/chapter/title/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetUnPublishReportChapterList",
+            Router: `/chapter/un_publish/list`,
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "VoiceUpload",
+            Router: `/chapter/voice/upload`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "ClassifyIdDetail",
@@ -8224,6 +8710,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "BaseDetail",
+            Router: `/detail/base`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "Edit",
@@ -8359,6 +8854,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditLayoutImg",
+            Router: `/layout_img/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "ListReport",
@@ -8368,6 +8872,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "AuthorizedListReport",
+            Router: `/list/authorized`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "MarkEditStatus",
@@ -8397,7 +8910,7 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "PublishCancleReport",
+            Method: "PublishCancelReport",
             Router: `/publish/cancle`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),

+ 1 - 0
routers/router.go

@@ -170,6 +170,7 @@ func init() {
 				&data_manage_permission.DataMangePermissionController{},
 				&data_manage.BloombergDataController{},
 				&data_manage.EdbBusinessController{},
+				&data_manage.BaseFromThsHfController{},
 				&data_manage.EdbInfoRelationController{},
 				&data_manage.FactorEdbSeriesController{},
 			),

+ 184 - 0
services/ai_summary/ai_summary.go

@@ -0,0 +1,184 @@
+package ai_summary
+
+import (
+	"errors"
+	"eta/eta_api/models/ai_summary"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"strconv"
+)
+
+// GetAiSummaryClassifyListForMe 获取我创建的纪要
+func GetAiSummaryClassifyListForMe(adminInfo system.Admin, resp *ai_summary.AiSummaryClassifyListResp, ai_summaryClassifyId int) (errMsg string, err error) {
+	rootList, err := ai_summary.GetAiSummaryClassifyByParentId(ai_summaryClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	for k := range rootList {
+		rootList[k].UniqueCode = strconv.Itoa(rootList[k].AiSummaryClassifyId) + "_" + strconv.Itoa(rootList[k].AiSummaryId)
+	}
+
+	classifyAll, err := ai_summary.GetAiSummaryClassifyByParentId(ai_summaryClassifyId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	for k := range classifyAll {
+		classifyAll[k].UniqueCode = strconv.Itoa(classifyAll[k].AiSummaryClassifyId) + "_" + strconv.Itoa(classifyAll[k].AiSummaryId)
+	}
+
+	ai_summaryAll, err := ai_summary.GetAiSummaryInfoByAdminId(adminInfo.AdminId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		return
+	}
+
+	for k := range ai_summaryAll {
+		ai_summaryAll[k].UniqueCode = strconv.Itoa(ai_summaryAll[k].AiSummaryClassifyId) + "_" + strconv.Itoa(ai_summaryAll[k].AiSummaryId)
+	}
+
+	sandListMap := make(map[int][]*ai_summary.AiSummaryClassifyItems)
+	for _, v := range ai_summaryAll {
+		if _, ok := sandListMap[v.AiSummaryClassifyId]; !ok {
+			list := make([]*ai_summary.AiSummaryClassifyItems, 0)
+			list = append(list, v)
+			sandListMap[v.AiSummaryClassifyId] = list
+		} else {
+			sandListMap[v.AiSummaryClassifyId] = append(sandListMap[v.AiSummaryClassifyId], v)
+		}
+	}
+
+	nodeAll := make([]*ai_summary.AiSummaryClassifyItems, 0)
+	for k := range rootList {
+		rootNode := rootList[k]
+		AiSummaryClassifyItemsMakeTree(&adminInfo, classifyAll, rootNode)
+		nodeAll = append(nodeAll, rootNode)
+	}
+
+	newAll := AiSummaryItemsMakeTree(nodeAll, sandListMap, ai_summaryClassifyId)
+	resp.AllNodes = newAll
+
+	return
+}
+
+func aiSummaryClassifyHaveChild(allNode []*ai_summary.AiSummaryClassifyItems, node *ai_summary.AiSummaryClassifyItems) (childs []*ai_summary.AiSummaryClassifyItems, yes bool) {
+	for _, v := range allNode {
+		if v.ParentId == node.AiSummaryClassifyId {
+			childs = append(childs, v)
+		}
+	}
+	if len(childs) > 0 {
+		yes = true
+	}
+	return
+}
+
+func AiSummaryClassifyItemsMakeTree(sysUser *system.Admin, allNode []*ai_summary.AiSummaryClassifyItems, node *ai_summary.AiSummaryClassifyItems) {
+
+	childs, _ := aiSummaryClassifyHaveChild(allNode, node) //判断节点是否有子节点并返回
+	if len(childs) > 0 {
+
+		node.Children = append(node.Children, childs[0:]...) //添加子节点
+		for _, v := range childs {                           //查询子节点的子节点,并添加到子节点
+			_, has := aiSummaryClassifyHaveChild(allNode, v)
+			if has {
+				AiSummaryClassifyItemsMakeTree(sysUser, allNode, v) //递归添加节点
+			} else {
+				childrenArr := make([]*ai_summary.AiSummaryClassifyItems, 0)
+				v.Children = childrenArr
+			}
+		}
+	} else {
+		childrenArr := make([]*ai_summary.AiSummaryClassifyItems, 0)
+		node.Children = childrenArr
+	}
+}
+
+func AiSummaryItemsMakeTree(allNode []*ai_summary.AiSummaryClassifyItems, sandListMap map[int][]*ai_summary.AiSummaryClassifyItems, sandboxClassifyId int) (nodeAll []*ai_summary.AiSummaryClassifyItems) {
+	for k := range allNode {
+		if len(allNode[k].Children) > 0 {
+			AiSummaryItemsMakeTree(allNode[k].Children, sandListMap, sandboxClassifyId)
+			allNode = append(allNode, sandListMap[allNode[k].ParentId]...)
+			nodeAll = allNode
+		} else if k == len(allNode)-1 {
+			allNode = append(allNode, sandListMap[allNode[k].ParentId]...)
+			nodeAll = allNode
+		}
+	}
+	if len(allNode) == 0 {
+		nodeAll = append(nodeAll, sandListMap[sandboxClassifyId]...)
+	}
+	return
+}
+
+func AiSummaryClassifyItemsMakeTreeV2(sysUser *system.Admin, allNode []*ai_summary.AiSummaryClassifyItems, node *ai_summary.AiSummaryClassifyItems) {
+
+	childs, _ := aiSummaryClassifyHaveChildV2(allNode, node) //判断节点是否有子节点并返回
+	if len(childs) > 0 {
+
+		node.Children = append(node.Children, childs[0:]...) //添加子节点
+		for _, v := range childs {                           //查询子节点的子节点,并添加到子节点
+			_, has := aiSummaryClassifyHaveChildV2(allNode, v)
+			if has {
+				AiSummaryClassifyItemsMakeTreeV2(sysUser, allNode, v) //递归添加节点
+			} else {
+				//childrenArr := make([]*ai_summary.AiSummaryClassifyItems, 0)
+				//v.Children = childrenArr
+			}
+		}
+	} else {
+		//childrenArr := make([]*ai_summary.AiSummaryClassifyItems, 0)
+		//node.Children = childrenArr
+	}
+}
+
+func aiSummaryClassifyHaveChildV2(allNode []*ai_summary.AiSummaryClassifyItems, node *ai_summary.AiSummaryClassifyItems) (childs []*ai_summary.AiSummaryClassifyItems, yes bool) {
+	for _, v := range allNode {
+		if v.ParentId == node.AiSummaryClassifyId && node.AiSummaryId == 0 {
+			childs = append(childs, v)
+		}
+	}
+	if len(childs) > 0 {
+		yes = true
+	}
+	return
+}
+
+// SharePrompt
+func SharePrompt(promptId int, adminId int) (err error) {
+	//判断当前登录者是否有共享的权限
+	prompt, err := ai_summary.GetAiPromptById(promptId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			err = errors.New("当前目录下的提示词不存在")
+			return
+		}
+		err = errors.New("目录下的提示词查询出错:" + err.Error())
+		return
+	}
+	if prompt.SysAdminId != adminId {
+		err = errors.New("该提示词不是本人创建")
+		return
+	}
+
+	//判断当前的共享状态
+	if prompt.IsShare > 0 {
+		prompt.IsShare = 0
+		err = prompt.Update([]string{"IsShare"})
+		if err != nil {
+			err = errors.New(err.Error())
+			return
+		}
+	} else {
+		prompt.IsShare = 1
+		err = prompt.Update([]string{"IsShare"})
+		if err != nil {
+			err = errors.New(err.Error())
+			return
+		}
+	}
+	return
+}

+ 671 - 0
services/classify.go

@@ -1,9 +1,12 @@
 package services
 
 import (
+	"errors"
 	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/utils"
 	"fmt"
+	"sort"
 	"time"
 )
 
@@ -177,3 +180,671 @@ func moveReportClassify(classifyInfo, prevClassify, nextClassify *models.Classif
 	}
 	return
 }
+
+// AddReportClassify
+// @Description: 添加报告分类
+// @author: Roc
+// @datetime 2024-06-17 11:01:21
+// @param classifyName string
+// @param parentId int
+// @param chartPermissionIdList []int
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func AddReportClassify(classifyName string, parentId int, chartPermissionIdList []int) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	errMsg = `添加失败`
+	item, err := models.GetClassifyByName(classifyName, parentId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取分类信息失败"
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类名称:" + classifyName + "已存在"
+			isSendEmail = false
+		}
+		return
+	}
+	if item != nil {
+		errMsg = "分类名称:" + classifyName + "已存在"
+		isSendEmail = false
+		err = errors.New(errMsg)
+		return
+	}
+
+	level := 1
+
+	// 父级分类
+	var parentClassifyItem *models.Classify
+	// 父级分类下的子分类数量
+	var childClassifyCount int
+
+	if parentId > 0 {
+		// 获取父级分类信息
+		parentClassifyItem, err = models.GetClassifyById(parentId)
+		if err != nil {
+			errMsg = "获取父级分类信息失败"
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "父级分类不存在"
+			}
+			return
+		}
+		level = parentClassifyItem.Level + 1
+
+		if level > 3 {
+			errMsg = "分类层级不可超过三级"
+			isSendEmail = false
+			return
+		}
+
+		// 判断是否分类存在待操作的审批单
+		err, errMsg = checkClassifyApprove(parentClassifyItem)
+		if err != nil {
+			return
+		}
+
+		// 获取父级分类下的子分类数量
+		childClassifyCount, err = models.GetCountClassifyChildByParentId(parentId)
+		if err != nil {
+			errMsg = "获取父级分类的子分类信息失败"
+			return
+		}
+
+	}
+
+	nowTime := time.Now().Local()
+	classify := new(models.Classify)
+
+	maxSort, err := classify.GetMaxSort()
+	if err != nil {
+		errMsg = "操作失败"
+		err = errors.New("查询品种排序失败, Err: " + err.Error())
+		return
+	}
+	classify.ClassifyName = classifyName
+	classify.ParentId = parentId
+	classify.CreateTime = nowTime
+	classify.ModifyTime = nowTime
+	classify.Sort = maxSort + 1
+	classify.Enabled = 1
+	classify.ShowType = 1             //默认列表格式
+	classify.ReportDetailShowType = 1 //默认列表格式
+	classify.IsShow = 1
+	classify.Level = level
+	/*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)
+	if err != nil {
+		return
+	}
+
+	//获取报告分类权限列表
+	err = models.EditChartPermissionSearchKeyWordMappingMulti(classifyName, chartPermissionIdList, classify.Id)
+	if err != nil {
+		errMsg = "修改分类权限失败"
+		return
+	}
+
+	// 修改CRM权限
+	go func() {
+		_ = EditClassifyChartPermissionSync(classifyName, classify.Id)
+	}()
+
+	// 如果父级分类不为空的话,那么就标记有子级分类,同时
+	if parentClassifyItem != nil {
+		parentClassifyItem.HasChild = 1
+		parentClassifyItem.UpdateClassify([]string{"HasChild"})
+
+		// 如果以前没有子级分类,那么就继承父级分类下的章节类型(创建新的章节与分类的关系)
+		if childClassifyCount <= 0 {
+			// 继承父级分类下的章节类型(创建新的章节与分类的关系)
+			tmpErr := inheritReportChapterType(parentId, classify.Id)
+			if tmpErr != nil {
+				return
+			}
+
+			// 继承父级分类审批流
+			go inheritReportApproveFlow(parentClassifyItem, classify)
+
+			moveReportByAddClassify(parentClassifyItem, classify)
+		}
+	}
+
+	return
+}
+
+// checkClassifyApprove
+// @Description: 判断分类是否存在待操作的审批单
+// @author: Roc
+// @datetime 2024-06-27 13:19:15
+// @param currClassify *models.Classify
+// @return err error
+// @return errMsg string
+func checkClassifyApprove(currClassify *models.Classify) (err error, errMsg string) {
+	errMsg = `判断是否有审批流关联失败`
+	var firstClassifyId, secondClassifyId int
+	if currClassify.ParentId > 0 {
+		parentClassifyItem, tmpErr := models.GetClassifyById(currClassify.ParentId)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "获取父级分类信息失败"
+			if tmpErr.Error() == utils.ErrNoRow() {
+				errMsg = "父级分类不存在"
+			}
+			return
+		}
+		firstClassifyId = parentClassifyItem.Id
+		secondClassifyId = currClassify.Id
+	} else {
+		firstClassifyId = currClassify.Id
+	}
+	// 校验审批流是否关联了进行中的审批
+	{
+		flowOb := new(report_approve.ReportApproveFlow)
+
+		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, report_approve.FlowReportTypeChinese, firstClassifyId, secondClassifyId, 0)
+		flowItem, e := flowOb.GetItemByCondition(existCond, existPars, "")
+		if e != nil {
+			// 父级分类如果没有审批流,那么就正常进行就好了
+			if e.Error() != utils.ErrNoRow() {
+				err = errors.New("获取审批流是否已存在失败, Err: " + e.Error())
+				return
+			}
+			err = nil
+			return
+		}
+		if flowItem == nil {
+			return
+		}
+
+		approvingOb := new(report_approve.ReportApprove)
+		approvingCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveCols.FlowId, report_approve.ReportApproveCols.FlowVersion, report_approve.ReportApproveCols.State)
+		approvingPars := make([]interface{}, 0)
+		approvingPars = append(approvingPars, flowItem.ReportApproveFlowId, flowItem.CurrVersion, report_approve.ReportApproveStateApproving)
+		count, e := approvingOb.GetCountByCondition(approvingCond, approvingPars)
+		if e != nil {
+			err = errors.New("获取审批流关联进行中的审批数失败. Err: " + e.Error())
+			return
+		}
+		if count > 0 {
+			errMsg = "当前有未走完流程的报告,请走完流程后再做变更"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	return
+}
+
+// 关于研报分类,因只允许报告或品种关联在最小分类下,所以当某个父分类(非三级分类)已关联报告或品种,需要在该父分类下增加子分类,则第一个子分类添加成功时,默认把该父分类关联的品种和报告转移至新创建的子分类(第一个子分类)下
+// moveReportByAddClassify
+// @Description: 报告和章节的转移
+// @author: Roc
+// @datetime 2024-06-17 16:29:56
+// @param parentClassifyInfo *models.Classify
+// @param currClassifyInfo *models.Classify
+// @return err error
+func moveReportByAddClassify(parentClassifyInfo, currClassifyInfo *models.Classify) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprint("历史报告更改分类失败,父级分类ID:", parentClassifyInfo.Id, ";当前分类ID:", currClassifyInfo.Id, ";错误信息:", err.Error()))
+		}
+	}()
+	if currClassifyInfo.Level > 3 {
+		err = errors.New("父级分类不支持三级分类以上")
+		return
+	}
+
+	// 报告的分类归属调整,转为下一级的分类
+
+	var condition, updateStr string
+	pars := make([]interface{}, 0)
+	switch currClassifyInfo.Level {
+	case 3: // 当前分类是3级分类
+		updateStr += ` classify_id_third = ?,classify_name_third = ?`
+		condition += ` AND classify_id_second = ? `
+	case 2: // 当前分类是2级分类
+		updateStr += ` classify_id_second = ?,classify_name_second = ?`
+		condition += ` AND classify_id_first = ? `
+	default:
+		err = errors.New("错误的分类层级")
+		return
+	}
+
+	pars = append(pars, currClassifyInfo.Id, currClassifyInfo.ClassifyName, parentClassifyInfo.Id)
+
+	// 获取当前分类下的所有章节类型
+	currReportChapterTypeList, err := models.GetAllReportChapterTypeListByClassifyId(currClassifyInfo.Id)
+	if err != nil {
+		return
+	}
+	// 当前的章节类型ID ---> 继承的章节类型ID
+	chapterTypeIdMap := make(map[int]int)
+	for _, v := range currReportChapterTypeList {
+		chapterTypeIdMap[v.ReportChapterTypeId] = v.InheritReportChapterTypeId
+	}
+
+	// 报告转移后,历史章节报告中的type_id也要修复成最新的type_id
+	err = models.ModifyReportClassifyAndReportChapterTypeByCondition(condition, pars, updateStr, chapterTypeIdMap, parentClassifyInfo.Id, currClassifyInfo.Id, currClassifyInfo.ClassifyName)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// inheritReportChapterType
+// @Description: 继承父级分类下的章节类型
+// @author: Roc
+// @datetime 2024-06-17 14:41:04
+// @param parentClassifyId int
+// @param currClassifyId int
+// @return err error
+func inheritReportChapterType(parentClassifyId, currClassifyId int) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprint("继承父级分类下的章节类型失败,父级分类ID:", parentClassifyId, ";当前分类ID:", currClassifyId, ";错误信息:", err.Error()))
+		}
+	}()
+	parentReportChapterTypeList, err := models.GetAllReportChapterTypeListByClassifyId(parentClassifyId)
+	if err != nil {
+		return
+	}
+
+	// 如果没有章节类型,那么就直接返回
+	if len(parentReportChapterTypeList) <= 0 {
+		return
+	}
+
+	addList := make([]*models.ReportChapterType, 0)
+	for _, v := range parentReportChapterTypeList {
+		addList = append(addList, &models.ReportChapterType{
+			//ReportChapterTypeId:        0,
+			ReportChapterTypeKey:       v.ReportChapterTypeKey,
+			ReportChapterTypeThumb:     v.ReportChapterTypeThumb,
+			BannerUrl:                  v.BannerUrl,
+			ReportChapterTypeName:      v.ReportChapterTypeName,
+			Sort:                       v.Sort,
+			Enabled:                    v.Enabled,
+			CreatedTime:                time.Now(),
+			LastUpdatedTime:            time.Now(),
+			ResearchType:               v.ResearchType,
+			SelectedImage:              v.SelectedImage,
+			UnselectedImage:            v.UnselectedImage,
+			PcSelectedImage:            v.PcSelectedImage,
+			PcUnselectedImage:          v.PcUnselectedImage,
+			EditImgUrl:                 v.EditImgUrl,
+			TickerTitle:                v.TickerTitle,
+			IsShow:                     v.IsShow,
+			PauseStartTime:             v.PauseStartTime,
+			PauseEndTime:               v.PauseEndTime,
+			IsSet:                      v.IsSet,
+			YbIconUrl:                  v.YbIconUrl,
+			YbBottomIcon:               v.YbBottomIcon,
+			ReportClassifyId:           currClassifyId,
+			InheritReportChapterTypeId: v.ReportChapterTypeId,
+		})
+	}
+
+	obj := models.ReportChapterType{}
+	err = obj.MultiCreate(addList)
+
+	// 修改CRM权限
+	go func() {
+		var syncReq ChapterTypeSyncReq
+		_, _ = ReportChapterTypeSync(&syncReq)
+	}()
+
+	return
+}
+
+// inheritReportApproveFlow
+// @Description: 继承父级分类下的审批流
+// @author: Roc
+// @datetime 2024-06-17 14:41:04
+// @param parentClassifyId int
+// @param currClassifyId int
+// @return err error
+func inheritReportApproveFlow(parentClassifyItem, currClassifyItem *models.Classify) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprint("继承父级分类下的审批流失败,父级分类ID:", parentClassifyItem.Id, ";当前分类ID:", currClassifyItem.Id, ";错误信息:", err.Error()))
+		}
+	}()
+
+	var firstClassify, secondClassify, thirdClassify *models.Classify
+	if parentClassifyItem.ParentId > 0 {
+		// 获取父级分类信息
+		firstClassify, err = models.GetClassifyById(parentClassifyItem.ParentId)
+		if err != nil {
+			return
+		}
+
+		secondClassify = parentClassifyItem
+		thirdClassify = currClassifyItem
+	} else {
+		firstClassify = parentClassifyItem
+		secondClassify = currClassifyItem
+	}
+
+	flowObj := report_approve.ReportApproveFlow{}
+
+	// 获取父级的审批流
+	existCond := fmt.Sprintf(` AND %s = ? AND %s = ? `, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId)
+	existPars := make([]interface{}, 0)
+	existPars = append(existPars, report_approve.FlowReportTypeChinese, firstClassify.Id)
+
+	// 如果这是第三级,那么说明只需要查找第二级的审批配置就好了
+	if thirdClassify != nil {
+		existCond = fmt.Sprintf(`%s AND %s = ?`, existCond, report_approve.ReportApproveFlowCols.ClassifySecondId)
+		existPars = append(existPars, secondClassify.Id)
+	}
+	//if thirdClassify != nil {
+	//	existCond = fmt.Sprintf(`%s AND %s = ?`, existCond, report_approve.ReportApproveFlowCols.ClassifyThirdId)
+	//	existPars = append(existPars, thirdClassify.Id)
+	//}
+
+	parentFlow, err := flowObj.GetItemByCondition(existCond, existPars, "")
+	if err != nil {
+		// 如果没有配置审批流,那么就直接返回
+		if err.Error() == utils.ErrNoRow() {
+			err = nil
+		}
+		return
+	}
+
+	// 获取父级的审批节点
+	nodeObj := report_approve.ReportApproveNode{}
+	nodeCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveNodeCols.ReportApproveFlowId, report_approve.ReportApproveNodeCols.CurrVersion)
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, parentFlow.ReportApproveFlowId, parentFlow.CurrVersion)
+	parentNodeList, err := nodeObj.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if err != nil {
+		return
+	}
+
+	// 新审批流
+	currFlow := &report_approve.ReportApproveFlow{
+		ReportApproveFlowId: 0,
+		FlowName:            currClassifyItem.ClassifyName,
+		ReportType:          parentFlow.ReportType,
+		ClassifyFirstId:     firstClassify.Id,
+		ClassifySecondId:    secondClassify.Id,
+		//ClassifyThirdId:     0,
+		CurrVersion: 1,
+		Enabled:     1,
+		CreateTime:  time.Now().Local(),
+		ModifyTime:  time.Now().Local(),
+	}
+	if thirdClassify != nil {
+		currFlow.ClassifyThirdId = thirdClassify.Id
+	}
+
+	// 新审批流的节点
+	nodeItems := make([]*report_approve.ReportApproveNode, 0)
+	for _, v := range parentNodeList {
+		n := &report_approve.ReportApproveNode{
+			//ReportApproveNodeId: 0,
+			//ReportApproveFlowId: 0,
+			PrevNodeId:  0,
+			NextNodeId:  0,
+			NodeType:    v.NodeType,
+			ApproveType: v.ApproveType,
+			Users:       v.Users,
+			CurrVersion: 1,
+			CreateTime:  time.Now().Local(),
+		}
+		nodeItems = append(nodeItems, n)
+	}
+
+	// 新增审批流和节点
+	err = flowObj.CreateFlowAndNodes(currFlow, nodeItems)
+	if err != nil {
+		return
+	}
+
+	parentFlow.Enabled = 0
+	err = parentFlow.Update([]string{"Enabled"})
+
+	return
+}
+
+// EditReportClassify
+// @Description: 编辑报告分类
+// @author: Roc
+// @datetime 2024-06-17 13:31:33
+// @param classifyId int
+// @param classifyName string
+// @param chartPermissionIdList []int
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func EditReportClassify(classifyId int, classifyName string, chartPermissionIdList []int) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	errMsg = `修改失败`
+
+	item, err := models.GetClassifyById(classifyId)
+	if err != nil {
+		errMsg = "获取分类信息失败"
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "分类不存在, 或已被删除"
+			isSendEmail = false
+		}
+		return
+	}
+	originName := item.ClassifyName
+
+	// 重名校验
+	existName, e := models.GetClassifyByName(classifyName, item.ParentId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		errMsg = "获取信息失败"
+		err = errors.New("获取重名分类失败, Err: " + err.Error())
+		return
+	}
+	if existName != nil && existName.Id != item.Id {
+		errMsg = "分类名称:" + classifyName + "已存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	item.ClassifyName = classifyName
+
+	// ETA1.8.3:不允许修改上级分类  2024-6-17 13:21:01
+	//item.ParentId = req.ParentId
+	item.ModifyTime = time.Now().Local()
+	cols := make([]string, 0)
+	cols = append(cols, "ClassifyName", "ModifyTime")
+	err = item.UpdateClassify(cols)
+	if err != nil {
+		return
+	}
+
+	err = models.EditChartPermissionSearchKeyWordMappingMulti(item.ClassifyName, chartPermissionIdList, item.Id)
+	if err != nil {
+		errMsg = "修改分类权限失败"
+		return
+	}
+
+	// 修改CRM权限
+	go func() {
+		_ = EditClassifyChartPermissionSync(item.ClassifyName, item.Id)
+	}()
+
+	// TODO 修改分类的关联品种时,历史报告中关联的品种怎么处理?
+	// 更新报告分类名称/父级分类后
+	go func() {
+		_ = AfterUpdateClassifyNameOrParent(item.Id, item.ParentId, item.ParentId, originName, item.ClassifyName, item.Level)
+	}()
+
+	return
+}
+
+// GetClassifyTreeRecursive 递归获取分类树形结构
+func GetClassifyTreeRecursive(list []*models.ClassifyItem, parentId int) []*models.ClassifyItem {
+	res := make([]*models.ClassifyItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Child = GetClassifyTreeRecursive(list, v.Id)
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// GetParentClassifyListByParentIdList
+// @Description: 递归获取父级分类信息,正常来讲只有三次
+// @author: Roc
+// @datetime 2024-06-19 13:23:33
+// @param parentClassifyIdList []int
+// @return list []*models.ClassifyList
+// @return err error
+func GetParentClassifyListByParentIdList(parentClassifyIdList []int) (list []*models.ClassifyList, err error) {
+	num := len(parentClassifyIdList)
+	if num <= 0 {
+		return
+	}
+	list, err = models.GetClassifyListByParentIdList(parentClassifyIdList)
+	if err != nil {
+		return
+	}
+
+	// 是否还有上级
+	{
+		currParentClassifyIdList := make([]int, 0)
+		for _, v := range list {
+			if v.ParentId > 0 {
+				currParentClassifyIdList = append(currParentClassifyIdList, v.ParentId)
+			}
+		}
+
+		if len(currParentClassifyIdList) > 0 {
+			tmpList, tmpErr := GetParentClassifyListByParentIdList(currParentClassifyIdList)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			list = append(tmpList, list...)
+		}
+	}
+
+	return
+}
+
+// GetClassifyListTreeRecursive
+// @Description: 递归获取分类树形结构
+// @author: Roc
+// @datetime 2024-06-19 13:23:28
+// @param list []*models.ClassifyList
+// @param parentId int
+// @return []*models.ClassifyList
+func GetClassifyListTreeRecursive(list []*models.ClassifyList, parentId int) []*models.ClassifyList {
+	res := make([]*models.ClassifyList, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			v.Child = GetClassifyListTreeRecursive(list, v.Id)
+			res = append(res, v)
+		}
+	}
+
+	// 前端的JP需要我这么返回
+	if len(res) <= 0 {
+		res = nil
+	}
+
+	return res
+}
+
+// BySortAndCreateTime 用来排序,先按Sort字段升序排序,若Sort相同,则按照CreateTime字段升序排序。
+type BySortAndCreateTime []*models.ClassifyList
+
+func (a BySortAndCreateTime) Len() int {
+	return len(a)
+}
+
+func (a BySortAndCreateTime) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a BySortAndCreateTime) Less(i, j int) bool {
+	if a[i].Sort == a[j].Sort {
+		return a[i].CreateTime.Before(a[j].CreateTime)
+	}
+	return a[i].Sort < a[j].Sort
+}
+
+// SortClassifyListBySortAndCreateTime sorts the ClassifyList slice by Sort and then CreateTime in ascending order.
+func SortClassifyListBySortAndCreateTime(classifyList []*models.ClassifyList) {
+	sort.Sort(BySortAndCreateTime(classifyList))
+}

+ 12 - 9
services/data/base_edb_lib.go

@@ -104,16 +104,13 @@ func AddEdbDataWindWsd(source int, stockCode, edbCode string) (resp *models.Base
 }
 
 // AddEdbDataThsDs 新增指标数据
-func AddEdbDataThsDs(source int, stockCode, edbCode string) (resp *models.BaseResponse, err error) {
+func AddEdbDataThsDs(source int, stockCode, edbCode, extraPars string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["StockCode"] = stockCode
 	param["Source"] = source
+	param["ExtraPars"] = extraPars
 	urlStr := `ths/ds/add`
-	if urlStr == "" {
-		err = fmt.Errorf("未实现该指标的刷新接口,请联系管理员")
-		return
-	}
 	resp, err = postRefreshEdbData(param, urlStr)
 	return
 }
@@ -205,11 +202,13 @@ func RefreshEdbData(edbInfoId, source, subSource int, edbCode, startDate string)
 	urlStr := ``
 	switch source {
 	case utils.DATA_SOURCE_THS:
-		urlStr = "ths/refresh"
-		if subSource == 0 {
-			urlStr = "ths/refresh"
-		} else {
+		switch subSource {
+		case utils.DATA_SUB_SOURCE_DATE:
 			urlStr = "ths/ds/refresh"
+		case utils.DATA_SUB_SOURCE_HIGH_FREQUENCY:
+			urlStr = "ths/hf/edb/refresh"
+		default:
+			urlStr = "ths/refresh"
 		}
 	case utils.DATA_SOURCE_WIND:
 		if subSource == 0 {
@@ -482,6 +481,10 @@ func AddBaseEdbInfo(addBaseEdbInfoReqStr string, source, subSource int, lang str
 	switch source {
 	case utils.DATA_SOURCE_BUSINESS:
 		urlStr = "business_index/add"
+	case utils.DATA_SOURCE_THS:
+		if subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY {
+			urlStr = "ths/hf/edb/add"
+		}
 	default:
 		edbSource := data_manage.EdbSourceIdMap[source]
 		if edbSource != nil {

+ 66 - 0
services/data/base_from_business.go

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

+ 302 - 0
services/data/base_from_ths_hf.go

@@ -0,0 +1,302 @@
+package data
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/mgo"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"go.mongodb.org/mongo-driver/bson"
+	"time"
+)
+
+// CheckExistThsHfEdb 校验已存在的同花顺高频指标
+func CheckExistThsHfEdb(stockCodes, edbCodes []string) (checkResp data_manage.ThsHfExistCheckResp, existMap map[string]bool, err error) {
+	// 待校验的指标编码
+	var indexCode []string
+	prefix := utils.ThsHf
+	for _, sv := range stockCodes {
+		for _, ev := range edbCodes {
+			t := fmt.Sprintf("%s%s%s", prefix, sv, ev)
+			indexCode = append(indexCode, t)
+		}
+	}
+
+	baseIndexes := make([]*data_manage.BaseFromThsHfIndex, 0)
+	{
+		ob := new(data_manage.BaseFromThsHfIndex)
+		fields := []string{ob.Cols().PrimaryId, ob.Cols().IndexCode, ob.Cols().IndexName, ob.Cols().StockCode, ob.Cols().Indicator}
+		list, e := ob.GetItemsByCondition(``, make([]interface{}, 0), fields, "")
+		if e != nil {
+			err = fmt.Errorf("获取高频指标列表失败, %v", e)
+			return
+		}
+		baseIndexes = list
+	}
+	var existNum int
+	existMap = make(map[string]bool)
+	for _, bv := range baseIndexes {
+		code := fmt.Sprintf("%s%s%s", prefix, bv.StockCode, bv.Indicator)
+		for _, iv := range indexCode {
+			if code != iv {
+				continue
+			}
+			existMap[fmt.Sprintf("%s-%s", bv.StockCode, bv.Indicator)] = true
+			existNum += 1
+			checkResp.ExistIndex = append(checkResp.ExistIndex, data_manage.ThsHfExistCheckIndex{
+				IndexId:   bv.BaseFromThsHfIndexId,
+				IndexCode: bv.IndexCode,
+				IndexName: bv.IndexName,
+			})
+			break
+		}
+	}
+	if existNum > 0 && existNum == len(indexCode) {
+		checkResp.ExistAll = true
+	}
+	return
+}
+
+// GetEdbDataThsHf 获取同花顺高频数据指标
+func GetEdbDataThsHf(req data_manage.ThsHfSearchEdbReq) (indexes []*data_manage.ThsHfIndexWithData, err error) {
+	param := make(map[string]interface{})
+	param["StockCode"] = req.StockCode
+	param["EdbCode"] = req.EdbCode
+	param["StartTime"] = req.StartTime
+	param["EndTime"] = req.EndTime
+	param["Interval"] = req.Interval
+	param["Fill"] = req.Fill
+	param["CPS"] = req.CPS
+	param["BaseDate"] = req.BaseDate
+	uri := `ths/hf/edb_data`
+	resp, e := postThsHfEdbData(param, uri)
+	if e != nil {
+		err = fmt.Errorf("postThsHfEdbData, %v", e)
+		return
+	}
+	if resp.Ret == 200 {
+		indexes = resp.Data
+	}
+	return
+}
+
+// postThsHfEdbData 刷新指标数据
+func postThsHfEdbData(param map[string]interface{}, urlStr string) (resp *data_manage.ThsHfIndexDataLibResp, err error) {
+	postUrl := utils.EDB_LIB_URL + urlStr
+	postData, err := json.Marshal(param)
+	if err != nil {
+		return
+	}
+	result, err := HttpPost(postUrl, string(postData), utils.ZhLangVersion, "application/json")
+	if err != nil {
+		return
+	}
+	utils.FileLog.Info("postRefreshEdbData:" + postUrl + ";" + string(postData) + ";result:" + string(result))
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+	return resp, nil
+}
+
+// BaseAddThsHf 新增数据源
+func BaseAddThsHf(req data_manage.ThsHfBaseAddReq) (resp *models.BaseResponse, err error) {
+	param := make(map[string]interface{})
+	param["StartTime"] = req.StartTime
+	param["EndTime"] = req.EndTime
+	param["Interval"] = req.Interval
+	param["Fill"] = req.Fill
+	param["CPS"] = req.CPS
+	param["BaseDate"] = req.BaseDate
+	param["SysAdminId"] = req.SysAdminId
+	param["SysAdminName"] = req.SysAdminName
+	param["ClassifyId"] = req.ClassifyId
+	param["Unit"] = req.Unit
+	param["IndexName"] = req.IndexName
+	param["Frequency"] = req.Frequency
+	param["StockCode"] = req.StockCode
+	param["EdbCode"] = req.EdbCode
+	uri := `ths/hf/base/add`
+	res, e := postRefreshEdbData(param, uri)
+	if e != nil {
+		err = fmt.Errorf("postRefreshEdbData, %v", e)
+		return
+	}
+	resp = res
+	return
+}
+
+// RefreshBaseThsHfIndex 刷新源指标
+func RefreshBaseThsHfIndex(indexIds []int, refreshType int) (isAsync bool, err error) {
+	if len(indexIds) == 0 {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("RefreshBaseThsHfIndex-刷新同花顺高频指标失败, %v", err)
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 3)
+		}
+	}()
+
+	indexes := make([]*data_manage.BaseFromThsHfIndex, 0)
+	{
+		ob := new(data_manage.BaseFromThsHfIndex)
+		cond := fmt.Sprintf(" AND %s IN (%s)", ob.Cols().PrimaryId, utils.GetOrmInReplace(len(indexIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, indexIds)
+		list, e := ob.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取源指标失败, %v", e)
+			return
+		}
+		if len(list) == 0 {
+			return
+		}
+		indexes = list
+	}
+
+	refreshUrl := "ths/hf/base/refresh"
+	// 异步刷新
+	if len(indexes) > 10 {
+		isAsync = true
+
+		go func() {
+			for _, v := range indexes {
+				param := make(map[string]interface{})
+				param["BaseIndexCode"] = v.IndexCode
+				param["RefreshType"] = refreshType
+				resp, e := postRefreshEdbData(param, refreshUrl)
+				if e != nil {
+					utils.FileLog.Info(fmt.Sprintf("thsHf-postRefreshEdbData, code: %s, err: %v", v.IndexCode, e))
+					continue
+				}
+				if resp != nil && resp.Ret != 200 {
+					utils.FileLog.Info(fmt.Sprintf("thsHf-postRefreshEdbData, code: %s, Ret: %d, ErrMsg: %s", v.IndexCode, resp.Ret, resp.ErrMsg))
+					continue
+				}
+			}
+		}()
+		return
+	}
+
+	// 同步刷新
+	for _, v := range indexes {
+		param := make(map[string]interface{})
+		param["BaseIndexCode"] = v.IndexCode
+		param["RefreshType"] = refreshType
+		resp, e := postRefreshEdbData(param, refreshUrl)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("thsHf-postRefreshEdbData, code: %s, err: %v", v.IndexCode, e))
+			continue
+		}
+		if resp.Ret != 200 {
+			utils.FileLog.Info(fmt.Sprintf("thsHf-postRefreshEdbData, code: %s, Ret: %d, ErrMsg: %s", v.IndexCode, resp.Ret, resp.ErrMsg))
+			continue
+		}
+	}
+	return
+}
+
+// GetThsHfBaseIndexData 获取指标数据
+func GetThsHfBaseIndexData(indexCode, startTime, endTime string) (dataList []*data_manage.BaseFromThsHfDataItem, err error) {
+	if utils.UseMongo {
+		list, e := getThsHfBaseIndexDataByMongo(indexCode, startTime, endTime)
+		if e != nil {
+			err = fmt.Errorf("获取指标数据失败-Mongo, %v", e)
+			return
+		}
+		dataList = list
+		return
+	}
+
+	// MySQL
+	var (
+		cond string
+		pars []interface{}
+	)
+	dataOb := new(data_manage.BaseFromThsHfData)
+	if startTime != "" && endTime != "" {
+		cond += fmt.Sprintf(" AND %s = ? AND (%s BETWEEN ? AND ?)", dataOb.Cols().IndexCode, dataOb.Cols().DataTime)
+		pars = append(pars, indexCode, startTime, endTime)
+	}
+	if startTime != "" && endTime == "" {
+		cond += fmt.Sprintf(" AND %s = ? AND %s > ?)", dataOb.Cols().IndexCode, dataOb.Cols().DataTime)
+		pars = append(pars, indexCode, startTime)
+	}
+	list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+	if e != nil {
+		err = fmt.Errorf("获取指标数据失败-MySQL, %v", e)
+		return
+	}
+	for _, v := range list {
+		dataList = append(dataList, v.Format2Item())
+	}
+	return
+}
+
+// getThsHfBaseIndexDataByMongo
+func getThsHfBaseIndexDataByMongo(indexCode, startTime, endTime string) (dataList []*data_manage.BaseFromThsHfDataItem, err error) {
+	dataList = make([]*data_manage.BaseFromThsHfDataItem, 0)
+
+	mogDataObj := mgo.BaseFromThsHfData{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"index_code": indexCode,
+	}
+
+	// 时间区间
+	if startTime != "" && endTime != "" {
+		st, e := time.ParseInLocation(utils.FormatDateTime, startTime, time.Local)
+		if e != nil {
+			err = fmt.Errorf("start time parse err: %v", e)
+			return
+		}
+		ed, e := time.ParseInLocation(utils.FormatDateTime, endTime, time.Local)
+		if e != nil {
+			err = fmt.Errorf("end time parse err: %v", e)
+			return
+		}
+		queryConditions["data_time"] = bson.M{
+			"$gte": st,
+			"$lte": ed,
+		}
+	}
+	if startTime != "" && endTime == "" {
+		st, e := time.ParseInLocation(utils.FormatDateTime, startTime, time.Local)
+		if e != nil {
+			err = fmt.Errorf("start time parse err: %v", e)
+			return
+		}
+		queryConditions["data_time"] = bson.M{
+			"$gte": st,
+		}
+	}
+
+	// 获取列表数据
+	list, e := mogDataObj.GetAllDataList(queryConditions, []string{"-data_time"})
+	if e != nil {
+		err = fmt.Errorf("GetAllDataList err: %v", e)
+		return
+	}
+	for _, v := range list {
+		dataList = append(dataList, formatMgoBaseThsHfData2Item(v))
+	}
+	return
+}
+
+func formatMgoBaseThsHfData2Item(origin *mgo.BaseFromThsHfData) (item *data_manage.BaseFromThsHfDataItem) {
+	if origin == nil {
+		return
+	}
+	item = new(data_manage.BaseFromThsHfDataItem)
+	item.DataId = int(origin.BaseFromThsHfDataId)
+	item.IndexId = int(origin.BaseFromThsHfIndexId)
+	item.IndexCode = origin.IndexCode
+	item.DataTime = utils.TimeTransferString(utils.FormatDateTime, origin.DataTime)
+	item.Value = origin.Value
+	item.UniqueCode = origin.UniqueCode
+	return
+}

+ 526 - 0
services/data/base_from_ths_hf_classify.go

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

+ 1233 - 5
services/data/chart_info.go

@@ -357,6 +357,19 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			// 季节性图计算不管图上数据时间,拿所有数据
+			_, tempEdbList, e := getEdbDataMapList(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			if e != nil {
+				err = e
+				return
+			}
+			dataResp, err = SeasonChartData(tempEdbList, seasonExtraConfig)
+		} else {
+			// 兼容无配置的老图
+			dataResp = new(data_manage.SeasonChartResp)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -481,6 +494,9 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -586,7 +602,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && item.IsAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -624,6 +640,60 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && item.IsAxis == 0 {
+			// 右轴数据处理
+			xStartDate := "01-01"
+
+			jumpYear := 0
+			var seasonExtra data_manage.SeasonExtraItem
+			if seasonExtraConfig != "" {
+				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+				if err != nil {
+					return
+				}
+			}
+
+			if seasonExtra.XStartDate != "" {
+				xStartDate = seasonExtra.XStartDate
+				jumpYear = seasonExtra.JumpYear
+			}
+
+			length := len(dataList)
+			if length == 0 {
+				return
+			}
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+
+			var rightAxisDate time.Time
+			if jumpYear == 1 {
+				latestDate = latestDate.AddDate(-1, 0, 0)
+			}
+			latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
+			rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+			if err != nil {
+				return
+			}
+
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -2789,7 +2859,7 @@ func RadarChartData(mappingList []*data_manage.ChartEdbInfoMapping, edbDataListM
 }
 
 // GetChartConvertEdbData 获取图表数据转换的指标数据
-func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []data_manage.YData, dataResp interface{}, err error, errMsg string) {
+func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string, isAxis int) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []data_manage.YData, dataResp interface{}, err error, errMsg string) {
 	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
 	xEdbIdValue = make([]int, 0)
 	yDataList = make([]data_manage.YData, 0)
@@ -2845,13 +2915,26 @@ func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, end
 	}
 
 	// 指标对应的所有数据
-	edbDataListMap, edbList, err := getEdbConvertDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig)
+	edbDataListMap, edbList, err := getEdbConvertDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig, isAxis)
 	if err != nil {
 		return
 	}
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			// 季节性图计算不管图上数据时间,拿所有数据
+			_, tempEdbList, e := getEdbDataMapList(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			if e != nil {
+				err = e
+				return
+			}
+			dataResp, err = SeasonChartData(tempEdbList, seasonExtraConfig)
+		} else {
+			// 兼容无配置的老图
+			dataResp = new(data_manage.SeasonChartResp)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -2899,7 +2982,7 @@ func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, end
 	return
 }
 
-func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
+func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string, isAxis int) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
 
@@ -2968,6 +3051,9 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -3069,7 +3155,7 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && isAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -3107,6 +3193,29 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && isAxis == 0 {
+			// 右轴数据处理,只要最新一年
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Year() == latestDate.Year() {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -3182,6 +3291,19 @@ func GetChartEdbDataV2(chartInfoId, chartType int, calendar, startDate, endDate
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			// 季节性图计算不管图上数据时间,拿所有数据
+			_, tempEdbList, e := getEdbDataMapList(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			if e != nil {
+				err = e
+				return
+			}
+			dataResp, err = SeasonChartData(tempEdbList, seasonExtraConfig)
+		} else {
+			// 兼容无配置的老图
+			dataResp = new(data_manage.SeasonChartResp)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -3228,3 +3350,1109 @@ func GetChartEdbDataV2(chartInfoId, chartType int, calendar, startDate, endDate
 	}
 	return
 }
+
+// SeasonChartData 季节性图的数据处理
+func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (dataResp data_manage.SeasonChartResp, err error) {
+	var seasonConfig data_manage.SeasonExtraItem
+	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonConfig)
+	if err != nil {
+		err = errors.New("季节性图配置异常, Err:" + err.Error())
+		return
+	}
+
+	for _, mappingItem := range dataList {
+		if mappingItem.IsAxis == 0 {
+			continue
+		}
+		quarterDataList, ok := mappingItem.DataList.(data_manage.QuarterDataList)
+		if !ok {
+			continue
+		}
+		// 上下限区间
+		if seasonConfig.MaxMinLimits.Year > 0 {
+			yearRange := time.Now().Year() - seasonConfig.MaxMinLimits.Year
+			startYear := time.Now().AddDate(-yearRange, 0, 0).Year()
+			dataResp.MaxMinLimits.List = make([]*data_manage.MaxMinLimitsData, 0)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			maxValueMap := make(map[time.Time]float64)
+			minValueMap := make(map[time.Time]float64)
+
+			maxMinDataList := make([]*data_manage.MaxMinLimitsData, 0)
+			var startDateStr string
+			var endDateStr string
+			var newDate time.Time
+
+			startDateStr = fmt.Sprintf("%d-%s", time.Now().Year(), seasonConfig.XStartDate)
+			endDateStr = fmt.Sprintf("%d-%s", time.Now().Year(), seasonConfig.XEndDate)
+			startDate, e := time.Parse(utils.FormatDate, startDateStr)
+			if e != nil {
+				err = e
+				return
+			}
+			endDate, e := time.Parse(utils.FormatDate, endDateStr)
+			if e != nil {
+				err = e
+				return
+			}
+			// 日度 周度插值
+			for _, v := range quarterDataList {
+				if mappingItem.Frequency == "日度" || mappingItem.Frequency == "周度" {
+					handleDataMap := make(map[string]float64)
+					dataTimeList, _, err = HandleDataByLinearRegressionToList(v.DataList, handleDataMap)
+					if err != nil {
+						err = errors.New("插值处理数据异常, Err:" + err.Error())
+						return
+					}
+					// 不包含当年
+					if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
+						continue
+					}
+					for _, date := range dataTimeList {
+						dateTime, e := time.Parse(utils.FormatDate, date)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						if dateTime.Year() < startYear {
+							continue
+						}
+						// 不包含2月29号
+						if dateTime.Month() == 2 && dateTime.Day() == 29 {
+							continue
+						}
+						newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						if seasonConfig.JumpYear == 1 {
+							if startDate.After(endDate) {
+								// 如果跨年且不到一年
+								// 全年截取一部分
+								if newDate.Before(startDate.AddDate(0, 0, 1)) && newDate.After(endDate) {
+									continue
+								}
+								if newDate.After(startDate.AddDate(0, 0, -1)) {
+									// 减一年
+									newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()-1, 0, 0)
+								}
+								// 处理上下限列表
+								if value, ok := maxValueMap[newDate]; ok {
+									if value < handleDataMap[date] {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+								} else {
+									maxValueMap[newDate] = handleDataMap[date]
+								}
+
+								if value, ok := minValueMap[newDate]; ok {
+									if value > handleDataMap[date] {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+								} else {
+									minValueMap[newDate] = handleDataMap[date]
+								}
+
+								dataTimeMap[newDate] = newDate
+							} else {
+								// 如果跨年且大于等于一年
+								// double后截取
+								if newDate.After(startDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < handleDataMap[date] {
+											maxValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > handleDataMap[date] {
+											minValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+								newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()+1, 0, 0)
+								newEndDate := endDate.AddDate(1, 0, 0)
+								if newDate.Before(newEndDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < handleDataMap[date] {
+											maxValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > handleDataMap[date] {
+											minValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+							}
+						} else {
+							// 如果不跨年 正常获取
+							// 获取当前日期所在区间
+							// 处理上下限列表
+							if value, ok := maxValueMap[newDate]; ok {
+								if value < handleDataMap[date] {
+									maxValueMap[newDate] = handleDataMap[date]
+								}
+							} else {
+								maxValueMap[newDate] = handleDataMap[date]
+							}
+
+							if value, ok := minValueMap[newDate]; ok {
+								if value > handleDataMap[date] {
+									minValueMap[newDate] = handleDataMap[date]
+								}
+							} else {
+								minValueMap[newDate] = handleDataMap[date]
+							}
+
+							dataTimeMap[newDate] = newDate
+						}
+					}
+				} else {
+					// 旬度、月度、季度、半年度 不插值,需要先把日期列表和数据map取出来
+					for _, vv := range v.DataList {
+						dateTime, e := time.Parse(utils.FormatDate, vv.DataTime)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+
+						// 不包含当年
+						if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
+							continue
+						}
+
+						if dateTime.Year() < startYear {
+							continue
+						}
+						// 不包含2月29号
+						if dateTime.Month() == 2 && dateTime.Day() == 29 {
+							continue
+						}
+						newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						if seasonConfig.JumpYear == 1 {
+							if startDate.After(endDate) {
+								// 如果跨年且不到一年
+								// 全年截取一部分
+								if newDate.Before(startDate.AddDate(0, 0, 1)) && newDate.After(endDate) {
+									continue
+								}
+								if newDate.After(startDate.AddDate(0, 0, -1)) {
+									// 减一年
+									newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()-1, 0, 0)
+								}
+								// 处理上下限列表
+								if value, ok := maxValueMap[newDate]; ok {
+									if value < vv.Value {
+										maxValueMap[newDate] = vv.Value
+									}
+								} else {
+									maxValueMap[newDate] = vv.Value
+								}
+
+								if value, ok := minValueMap[newDate]; ok {
+									if value > vv.Value {
+										minValueMap[newDate] = vv.Value
+									}
+								} else {
+									minValueMap[newDate] = vv.Value
+								}
+
+								dataTimeMap[newDate] = newDate
+							} else {
+								// 如果跨年且大于等于一年
+								// double后截取
+								if newDate.After(startDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < vv.Value {
+											maxValueMap[newDate] = vv.Value
+										}
+									} else {
+										maxValueMap[newDate] = vv.Value
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > vv.Value {
+											minValueMap[newDate] = vv.Value
+										}
+									} else {
+										minValueMap[newDate] = vv.Value
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+								newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()+1, 0, 0)
+								newEndDate := endDate.AddDate(1, 0, 0)
+								if newDate.Before(newEndDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < vv.Value {
+											maxValueMap[newDate] = vv.Value
+										}
+									} else {
+										maxValueMap[newDate] = vv.Value
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > vv.Value {
+											minValueMap[newDate] = vv.Value
+										}
+									} else {
+										minValueMap[newDate] = vv.Value
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+							}
+						} else {
+							// 如果不跨年 正常获取
+							// 获取当前日期所在区间
+							// 处理上下限列表
+							if value, ok := maxValueMap[newDate]; ok {
+								if value < vv.Value {
+									maxValueMap[newDate] = vv.Value
+								}
+							} else {
+								maxValueMap[newDate] = vv.Value
+							}
+
+							if value, ok := minValueMap[newDate]; ok {
+								if value > vv.Value {
+									minValueMap[newDate] = vv.Value
+								}
+							} else {
+								minValueMap[newDate] = vv.Value
+							}
+
+							dataTimeMap[newDate] = newDate
+						}
+
+					}
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				maxMinItem := &data_manage.MaxMinLimitsData{}
+				if maxValue, ok := maxValueMap[v]; ok {
+					maxMinItem.MaxValue = maxValue
+				} else {
+					maxMinItem.MaxValue = minValueMap[v]
+				}
+
+				if minValue, ok := minValueMap[v]; ok {
+					maxMinItem.MinValue = minValue
+				} else {
+					maxMinItem.MinValue = maxValueMap[v]
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				maxMinDataList = append(maxMinDataList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(maxMinDataList, func(i, j int) bool {
+				return maxMinDataList[i].DataTime < maxMinDataList[j].DataTime
+			})
+
+			dataResp.MaxMinLimits.List = maxMinDataList
+			dataResp.MaxMinLimits.Color = seasonConfig.MaxMinLimits.Color
+			dataResp.MaxMinLimits.Legend = seasonConfig.MaxMinLimits.Legend
+			dataResp.MaxMinLimits.IsShow = seasonConfig.MaxMinLimits.IsShow
+			dataResp.MaxMinLimits.IsAdd = seasonConfig.MaxMinLimits.IsAdd
+			dataResp.MaxMinLimits.Year = seasonConfig.MaxMinLimits.Year
+		}
+
+		// 自定义同期均线
+		if seasonConfig.SamePeriodAverage.Year > 0 {
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+			averageDataList := make([]*data_manage.SamePeriodAverageData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToList(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					yearRange := time.Now().Year() - seasonConfig.SamePeriodAverage.Year
+					startYear := time.Now().AddDate(-yearRange, 0, 0).Year()
+					if dateTime.Year() < startYear {
+						continue
+					}
+					// 不包含2月29号
+					if dateTime.Month() == 2 && dateTime.Day() == 29 {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				averageItem := &data_manage.SamePeriodAverageData{}
+				if value, ok := valueMap[v]; ok {
+					averageItem.Value = value
+				}
+				averageItem.DataTime = v.Format(utils.FormatDate)
+				averageItem.DataTimestamp = v.UnixNano() / 1e6
+
+				averageDataList = append(averageDataList, averageItem)
+
+			}
+
+			// 排序
+			sort.Slice(averageDataList, func(i, j int) bool {
+				return averageDataList[i].DataTime < averageDataList[j].DataTime
+			})
+
+			dataResp.SamePeriodAverage.List = averageDataList
+			dataResp.SamePeriodAverage.Year = seasonConfig.SamePeriodAverage.Year
+			dataResp.SamePeriodAverage.Color = seasonConfig.SamePeriodAverage.Color
+			dataResp.SamePeriodAverage.Legend = seasonConfig.SamePeriodAverage.Legend
+			dataResp.SamePeriodAverage.IsShow = seasonConfig.SamePeriodAverage.IsShow
+			dataResp.SamePeriodAverage.LineType = seasonConfig.SamePeriodAverage.LineType
+			dataResp.SamePeriodAverage.LineWidth = seasonConfig.SamePeriodAverage.LineWidth
+			dataResp.SamePeriodAverage.IsAdd = seasonConfig.SamePeriodAverage.IsAdd
+
+		}
+
+		// 自定义同期标准差
+		if seasonConfig.SamePeriodStandardDeviation.Year > 1 && seasonConfig.SamePeriodStandardDeviation.Multiple > 0 {
+			yearRange := time.Now().Year() - seasonConfig.SamePeriodAverage.Year
+			startYear := time.Now().AddDate(-yearRange, 0, 0).Year()
+
+			// 先算均值,再算标准差
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeValueMap := make(map[time.Time][]float64)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			samePeriodStandardDeviationList := make([]*data_manage.MaxMinLimitsData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					if dateTime.Year() < startYear {
+						continue
+					}
+					// 不包含2月29号
+					if dateTime.Month() == 2 && dateTime.Day() == 29 {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+					valueList := dataTimeValueMap[newDate]
+					valueList = append(valueList, handleDataMap[date])
+					dataTimeValueMap[newDate] = valueList
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				valueList := dataTimeValueMap[v]
+				stdev := utils.CalculateStandardDeviation(valueList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				maxMinItem := &data_manage.MaxMinLimitsData{}
+
+				if value, ok := valueMap[v]; ok {
+					maxMinItem.MaxValue = value + stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+					maxMinItem.MinValue = value - stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				samePeriodStandardDeviationList = append(samePeriodStandardDeviationList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(samePeriodStandardDeviationList, func(i, j int) bool {
+				return samePeriodStandardDeviationList[i].DataTime < samePeriodStandardDeviationList[j].DataTime
+			})
+
+			dataResp.SamePeriodStandardDeviation.List = samePeriodStandardDeviationList
+			dataResp.SamePeriodStandardDeviation.Color = seasonConfig.SamePeriodStandardDeviation.Color
+			dataResp.SamePeriodStandardDeviation.Legend = seasonConfig.SamePeriodStandardDeviation.Legend
+			dataResp.SamePeriodStandardDeviation.IsShow = seasonConfig.SamePeriodStandardDeviation.IsShow
+			dataResp.SamePeriodStandardDeviation.Multiple = seasonConfig.SamePeriodStandardDeviation.Multiple
+			dataResp.SamePeriodStandardDeviation.Year = seasonConfig.SamePeriodStandardDeviation.Year
+			dataResp.SamePeriodStandardDeviation.IsAdd = seasonConfig.SamePeriodStandardDeviation.IsAdd
+		}
+
+		// 自定义右轴
+		if seasonConfig.RightAxis.IndicatorType != 0 {
+			if seasonConfig.RightAxis.IndicatorType == 1 {
+				startTime, _ := time.Parse(utils.FormatDate, mappingItem.StartDate)
+				for i := len(quarterDataList) - 1; i > len(quarterDataList)-2 && i > 0; i-- {
+					var rightMappingItem data_manage.ChartEdbInfoMapping
+					rightMappingItem = *mappingItem
+					// 计算同比值
+					tbzDataList, minValue, maxValue, e := GetEdbDataTbzForSeason(mappingItem.Frequency, quarterDataList[i].DataList, startTime)
+					if e != nil {
+						err = errors.New("计算同比值失败, Err:" + e.Error())
+						return
+					}
+					rightMappingItem.DataList = tbzDataList
+					rightMappingItem.MaxData = maxValue
+					rightMappingItem.MinData = minValue
+					dataResp.RightAxis.EdbInfoList = append(dataResp.RightAxis.EdbInfoList, &rightMappingItem)
+				}
+			}
+			dataResp.RightAxis.SeasonRightAxis = seasonConfig.RightAxis
+		}
+	}
+
+	return
+}
+
+// 计算百分位对应的值
+func PercentileAlgorithm(x float64, data []float64) float64 {
+	N := float64(len(data))
+	n := (x / 100) * (N - 1)
+
+	// 如果n是整数,则直接返回第n个数值
+	if n == float64(int(n)) {
+		return data[int(n)-1]
+	}
+
+	// 如果n是小数,执行插值
+	nInt := int(n)
+	e := n - float64(nInt)
+
+	// 取出相邻的两个数值
+	Sn := data[nInt-1]
+	Sn1 := data[nInt]
+
+	// 计算插值结果
+	y := Sn + e*(Sn1-Sn)
+
+	return y
+}
+
+func MarkerLineCalculate(markerLine data_manage.MarkersLine, dataList interface{}, chartInfo *data_manage.ChartInfoView) (value string, err error) {
+	if markerLine.Calculation == 1 {
+		// 区间均值
+		averge := 0.0
+		length := 0
+		// 计算左轴
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							averge += vv.Value
+							length += 1
+						}
+					} else {
+						averge += vv.Value
+						length += 1
+					}
+				}
+				averge = averge / float64(length)
+				value = fmt.Sprintf("%.2f", averge)
+			}
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						averge += dataItem.Value
+						length += 1
+					}
+				} else {
+					averge += dataItem.Value
+					length += 1
+				}
+			}
+			averge = averge / float64(length)
+
+			value = fmt.Sprintf("%.2f", averge)
+		}
+	} else if markerLine.Calculation == 2 {
+		// 区间均值加N倍标准差
+		averge := 0.0
+		length := 0
+		// 计算左轴
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			faloatList := make([]float64, 0)
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							faloatList = append(faloatList, vv.Value)
+							averge += vv.Value
+							length += 1
+						}
+					} else {
+						faloatList = append(faloatList, vv.Value)
+						averge += vv.Value
+						length += 1
+					}
+				}
+				averge = averge / float64(length)
+				stdev := utils.CalculateStandardDeviation(faloatList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				value = fmt.Sprintf("%.2f", averge+stdev*markerLine.CalculationValue)
+			}
+
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			floatList := make([]float64, 0)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						floatList = append(floatList, dataItem.Value)
+						averge += dataItem.Value
+						length += 1
+					}
+				} else {
+					floatList = append(floatList, dataItem.Value)
+					averge += dataItem.Value
+					length += 1
+				}
+			}
+			averge = averge / float64(length)
+
+			stdev := utils.CalculateStandardDeviation(floatList)
+			stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+			value = fmt.Sprintf("%.2f", averge+stdev*markerLine.CalculationValue)
+		}
+	} else if markerLine.Calculation == 3 {
+		// 区间个数分位
+		markerLineValue := 0.0
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			faloatList := make([]float64, 0)
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							faloatList = append(faloatList, vv.Value)
+						}
+					} else {
+						faloatList = append(faloatList, vv.Value)
+					}
+				}
+				sort.Float64s(faloatList)
+				markerLineValue = PercentileAlgorithm(markerLine.CalculationValue, faloatList)
+				value = fmt.Sprintf("%.2f", markerLineValue)
+			}
+
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			floatList := make([]float64, 0)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						floatList = append(floatList, dataItem.Value)
+					}
+				} else {
+					floatList = append(floatList, dataItem.Value)
+				}
+			}
+			sort.Float64s(floatList)
+			markerLineValue = PercentileAlgorithm(markerLine.CalculationValue, floatList)
+			value = fmt.Sprintf("%.2f", markerLineValue)
+		}
+	} else if markerLine.Calculation == 4 {
+		// 数值分位
+		markerLineValue := 0.0
+		maxValue := 0.0
+		minValue := 0.0
+		if chartInfo.ChartType == 2 && markerLine.EdbType == 0 {
+			//季节性图结构体不一样
+			quarterDataList := dataList.(data_manage.QuarterDataList)
+			for _, quarterData := range quarterDataList[len(quarterDataList)-1:] {
+				for _, vv := range quarterData.DataList {
+					if markerLine.TimeIntervalType == 1 {
+						startDate := markerLine.StartDate.Date
+						endDate := time.Now().Format(utils.FormatDate)
+						if markerLine.StartDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								startDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								startDate = time.Now().Format(utils.FormatDate)
+							}
+							startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+						}
+						if markerLine.EndDate.TimeType == 1 {
+							// 固定
+							endDate = markerLine.EndDate.Date
+						} else if markerLine.EndDate.TimeType == 2 {
+							// 动态
+							if markerLine.StartDate.Conf.BaseDate == 1 {
+								// 指标最新日期
+								endDate = quarterData.DataList[len(quarterData.DataList)-1].DataTime
+							} else {
+								// 系统日期
+								endDate = time.Now().Format(utils.FormatDate)
+							}
+							endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+						}
+						if vv.DataTime >= startDate && vv.DataTime <= endDate {
+							if maxValue < vv.Value {
+								maxValue = vv.Value
+							}
+							if minValue > vv.Value {
+								minValue = vv.Value
+							}
+						}
+					} else {
+						if maxValue < vv.Value {
+							maxValue = vv.Value
+						}
+						if minValue > vv.Value {
+							minValue = vv.Value
+						}
+					}
+				}
+				markerLineValue = CalculatePercentile(markerLine.CalculationValue, minValue, maxValue)
+				value = fmt.Sprintf("%.2f", markerLineValue)
+			}
+
+		} else {
+			dataList := dataList.([]*data_manage.EdbDataList)
+			for _, dataItem := range dataList {
+				if markerLine.TimeIntervalType == 1 {
+					startDate := markerLine.StartDate.Date
+					endDate := time.Now().Format(utils.FormatDate)
+					if markerLine.StartDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							startDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							startDate = time.Now().Format(utils.FormatDate)
+						}
+						startDate, err = HandleDateChange(startDate, markerLine.StartDate.Conf)
+					}
+					if markerLine.EndDate.TimeType == 1 {
+						// 固定
+						endDate = markerLine.EndDate.Date
+					} else if markerLine.EndDate.TimeType == 2 {
+						// 动态
+						if markerLine.StartDate.Conf.BaseDate == 1 {
+							// 指标最新日期
+							endDate = dataList[len(dataList)-1].DataTime
+						} else {
+							// 系统日期
+							endDate = time.Now().Format(utils.FormatDate)
+						}
+						endDate, err = HandleDateChange(endDate, markerLine.StartDate.Conf)
+					}
+					if dataItem.DataTime >= startDate && dataItem.DataTime <= endDate {
+						if maxValue < dataItem.Value {
+							maxValue = dataItem.Value
+						}
+						if minValue > dataItem.Value {
+							minValue = dataItem.Value
+						}
+					}
+				} else {
+					if maxValue < dataItem.Value {
+						maxValue = dataItem.Value
+					}
+					if minValue > dataItem.Value {
+						minValue = dataItem.Value
+					}
+				}
+			}
+			markerLineValue = CalculatePercentile(markerLine.CalculationValue, minValue, maxValue)
+			value = fmt.Sprintf("%.2f", markerLineValue)
+		}
+	}
+	return
+}
+
+// HandleDateChange 处理日期变换
+func HandleDateChange(date string, edbDateConf data_manage.EdbDateChangeConf) (newDate string, err error) {
+	newDate = date
+	if newDate != "" {
+		if len(edbDateConf.DateChange) > 0 {
+			var dateTime time.Time
+			dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+			if err != nil {
+				err = fmt.Errorf("日期解析失败: %s", err.Error())
+				return
+			}
+			for _, v := range edbDateConf.DateChange {
+				if v.ChangeType == 1 {
+					dateTime = dateTime.AddDate(v.Year, v.Month, v.Day)
+					newDate = dateTime.Format(utils.FormatDate)
+				} else if v.ChangeType == 2 {
+					newDate, err, _ = handleSystemAppointDateT2(dateTime, v.FrequencyDay, v.Frequency)
+					if err != nil {
+						return
+					}
+					dateTime, err = time.ParseInLocation(utils.FormatDate, newDate, time.Local)
+					if err != nil {
+						err = fmt.Errorf("日期解析失败: %s", err.Error())
+						return
+					}
+				}
+			}
+		}
+	}
+
+	return
+}
+
+// handleSystemAppointDateT
+// @Description: 处理系统日期相关的指定频率(所在周/旬/月/季/半年/年的最后/最早一天)
+// @author: Roc
+// @datetime2023-10-27 09:31:35
+// @param Frequency string
+// @param Day string
+// @return date string
+// @return err error
+// @return errMsg string
+func handleSystemAppointDateT2(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
+	//currDate := time.Now()
+	switch frequency {
+	case "本周":
+		day := int(currDate.Weekday())
+		if day == 0 { // 周日
+			day = 7
+		}
+		num := 0
+		switch appointDay {
+		case "周一":
+			num = 1
+		case "周二":
+			num = 2
+		case "周三":
+			num = 3
+		case "周四":
+			num = 4
+		case "周五":
+			num = 5
+		case "周六":
+			num = 6
+		case "周日":
+			num = 7
+		}
+		day = num - day
+		date = currDate.AddDate(0, 0, day).Format(utils.FormatDate)
+	case "本旬":
+		day := currDate.Day()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if day <= 10 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location())
+			} else if day <= 20 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 11, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 21, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if day <= 10 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 10, 0, 0, 0, 0, currDate.Location())
+			} else if day <= 20 {
+				tmpDate = time.Date(currDate.Year(), currDate.Month(), 20, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1)
+			}
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本月":
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			tmpDate = time.Date(currDate.Year(), currDate.Month(), 1, 0, 0, 0, 0, currDate.Location())
+		case "最后一天":
+			tmpDate = time.Date(currDate.Year(), currDate.Month()+1, 1, 0, 0, 0, 0, currDate.Location()).AddDate(0, 0, -1)
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本季":
+		month := currDate.Month()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if month <= 3 {
+				tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 4, 1, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 9 {
+				tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 10, 1, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if month <= 3 {
+				tmpDate = time.Date(currDate.Year(), 3, 31, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location())
+			} else if month <= 9 {
+				tmpDate = time.Date(currDate.Year(), 9, 30, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+			}
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本半年":
+		month := currDate.Month()
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 7, 1, 0, 0, 0, 0, currDate.Location())
+			}
+		case "最后一天":
+			if month <= 6 {
+				tmpDate = time.Date(currDate.Year(), 6, 30, 0, 0, 0, 0, currDate.Location())
+			} else {
+				tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+			}
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	case "本年":
+		var tmpDate time.Time
+		switch appointDay {
+		case "第一天":
+			tmpDate = time.Date(currDate.Year(), 1, 1, 0, 0, 0, 0, currDate.Location())
+		case "最后一天":
+			tmpDate = time.Date(currDate.Year(), 12, 31, 0, 0, 0, 0, currDate.Location())
+		}
+		date = tmpDate.Format(utils.FormatDate)
+	default:
+		errMsg = "错误的日期频度:" + frequency
+		err = errors.New(errMsg)
+		return
+	}
+
+	return
+}
+
+// CalculatePercentile 计算数值分位
+func CalculatePercentile(x float64, min float64, max float64) float64 {
+	return (x/100)*(max-min) + min
+}

+ 58 - 1
services/data/chart_info_excel_balance.go

@@ -835,6 +835,9 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -937,7 +940,7 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && item.IsAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				//item.DataList = dataList
@@ -973,6 +976,60 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 				item.DataList = quarterDataList
 			}
 
+		} else if chartType == 2 && item.IsAxis == 0 {
+			// 右轴数据处理
+			xStartDate := "01-01"
+
+			jumpYear := 0
+			var seasonExtra data_manage.SeasonExtraItem
+			if seasonExtraConfig != "" {
+				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+				if err != nil {
+					return
+				}
+			}
+
+			if seasonExtra.XStartDate != "" {
+				xStartDate = seasonExtra.XStartDate
+				jumpYear = seasonExtra.JumpYear
+			}
+
+			length := len(dataList)
+			if length == 0 {
+				return
+			}
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+
+			var rightAxisDate time.Time
+			if jumpYear == 1 {
+				latestDate = latestDate.AddDate(-1, 0, 0)
+				latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(),xStartDate)
+				rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+				if err != nil {
+					return
+				}
+			}
+
+			newDataList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {

+ 10 - 0
services/data/chart_theme.go

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

+ 347 - 23
services/data/correlation/chart_info.go

@@ -14,6 +14,7 @@ import (
 	"sort"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -1550,34 +1551,357 @@ func GetFactorChartDataByChartId(chartInfoId int, extraConfig string) (xEdbIdVal
 	return
 }
 
-// FormatChartEdbInfoMappings 补充指标信息
-func FormatChartEdbInfoMappings(chartInfoId int, mappings []*data_manage.ChartEdbInfoMapping) (edbList []*data_manage.ChartEdbInfoMapping, err error) {
-	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
-	if len(mappings) == 0 {
+// RemoveCorrelationRelate 删除相关性图表关联信息
+func RemoveCorrelationRelate(chartInfoId int) (err error) {
+	if chartInfoId <= 0 {
+		return
+	}
+	// 相关性图表
+	chartCorrelate := new(data_manage.ChartInfoCorrelation)
+	if e := chartCorrelate.GetItemById(chartInfoId); e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取相关性图表信息失败, %v", e)
+		return
+	}
+	if chartCorrelate == nil {
 		return
 	}
 
-	for _, v := range mappings {
-		if chartInfoId <= 0 {
-			v.IsAxis = 1
-			v.LeadValue = 0
-			v.LeadUnit = ""
-			v.ChartEdbMappingId = 0
-			v.ChartInfoId = 0
-			v.IsOrder = false
-			v.EdbInfoType = 1
-			v.ChartStyle = ""
-			v.ChartColor = ""
-			v.ChartWidth = 0
-		} else {
-			v.LeadUnitEn = data.GetLeadUnitEn(v.LeadUnit)
-			v.LeadUnitEn = data.GetLeadUnitEn(v.LeadUnit)
+	// 删除相关性图
+	if e := chartCorrelate.Delete(); e != nil {
+		err = fmt.Errorf("删除相关性图表失败, %v", e)
+		return
+	}
+
+	// 多因子
+	if chartCorrelate.AnalysisMode != 1 {
+		return
+	}
+	seriesIds := make([]int, 0)
+
+	// 删除图表关联
+	chartMappingOb := new(data_manage.FactorEdbSeriesChartMapping)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", chartMappingOb.Cols().ChartInfoId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, chartCorrelate.CorrelationChartInfoId)
+		items, e := chartMappingOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取图表关联指标系列失败, %v", e)
+			return
+		}
+		for _, v := range items {
+			if !utils.InArrayByInt(seriesIds, v.FactorEdbSeriesId) {
+				seriesIds = append(seriesIds, v.FactorEdbSeriesId)
+			}
+		}
+		removeCond := fmt.Sprintf(" %s = ?", chartMappingOb.Cols().ChartInfoId)
+		if e = chartMappingOb.RemoveByCondition(removeCond, pars); e != nil {
+			err = fmt.Errorf("删除图表关联指标系列失败, %v", e)
+			return
+		}
+	}
+
+	// 删除系列
+	if len(seriesIds) == 0 {
+		return
+	}
+	seriesOb := new(data_manage.FactorEdbSeries)
+	if e := seriesOb.MultiRemove(seriesIds); e != nil {
+		err = fmt.Errorf("删除系列失败, %v", e)
+		return
+	}
+
+	edbMappingOb := new(data_manage.FactorEdbSeriesMapping)
+	{
+		cond := fmt.Sprintf(" %s IN (%s)", edbMappingOb.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, seriesIds)
+		if e := edbMappingOb.RemoveByCondition(cond, pars); e != nil {
+			err = fmt.Errorf("删除系列指标失败, %v", e)
+			return
+		}
+	}
+	calculateOb := new(data_manage.FactorEdbSeriesCalculateData)
+	{
+		cond := fmt.Sprintf(" %s IN (%s)", calculateOb.Cols().FactorEdbSeriesId, utils.GetOrmInReplace(len(seriesIds)))
+		pars := make([]interface{}, 0)
+		pars = append(pars, seriesIds)
+		if e := calculateOb.RemoveByCondition(cond, pars); e != nil {
+			err = fmt.Errorf("删除系列指标计算失败, %v", e)
+			return
+		}
+	}
+	return
+}
+
+// CalculateCorrelationMatrix 计算相关性矩阵
+func CalculateCorrelationMatrix(req data_manage.CalculateCorrelationMatrixPars) (resp data_manage.FactorEdbSeriesCorrelationMatrixResp, chartMappings []*data_manage.FactorEdbSeriesChartMapping, err error) {
+	if req.BaseEdbInfoId <= 0 {
+		err = fmt.Errorf("请选择标的指标")
+		return
+	}
+	if len(req.SeriesIds) == 0 {
+		err = fmt.Errorf("请选择因子指标系列")
+		return
+	}
+	if req.Correlation.LeadValue <= 0 {
+		err = fmt.Errorf("分析周期不允许设置为负数或0")
+		return
+	}
+	if req.Correlation.LeadUnit == "" {
+		err = fmt.Errorf("请选择分析周期频度")
+		return
+	}
+	leadUnitDays, ok := utils.FrequencyDaysMap[req.Correlation.LeadUnit]
+	if !ok {
+		err = fmt.Errorf("错误的分析周期频度: %s", req.Correlation.LeadUnit)
+		return
+	}
+
+	if req.Correlation.CalculateUnit == "" {
+		err = fmt.Errorf("请选择计算窗口频度")
+		return
+	}
+	calculateUnitDays, ok := utils.FrequencyDaysMap[req.Correlation.CalculateUnit]
+	if !ok {
+		err = fmt.Errorf("计算窗口频度有误: %s", req.Correlation.CalculateUnit)
+		return
+	}
+	leadDays := 2 * req.Correlation.LeadValue * leadUnitDays
+	calculateDays := req.Correlation.CalculateValue * calculateUnitDays
+	if calculateDays < leadDays {
+		err = fmt.Errorf("计算窗口必须≥2*分析周期")
+		return
+	}
+
+	// 获取标的指标信息及数据
+	baseEdb, e := data_manage.GetEdbInfoById(req.BaseEdbInfoId)
+	if e != nil {
+		err = fmt.Errorf("获取标的指标失败, %v", e)
+		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:
+			err = fmt.Errorf("标的指标类型异常: %d", baseEdb.EdbInfoType)
+			return
+		}
+		if e != nil {
+			err = fmt.Errorf("获取标的指标数据失败, %v", e)
+			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 {
+			err = fmt.Errorf("获取因子指标系列失败, %v", e)
+			return
+		}
+		for _, v := range items {
+			seriesIdItem[v.FactorEdbSeriesId] = v
 		}
-		v.FrequencyEn = data.GetFrequencyEn(v.Frequency)
-		if v.Unit == `无` {
-			v.Unit = ``
+	}
+
+	// 获取因子指标
+	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 {
+			err = fmt.Errorf("获取系列指标失败, %v", e)
+			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 {
+		err = fmt.Errorf("获取因子指标信息失败, %v", e)
+		return
+	}
+	for _, v := range edbItems {
+		edbIdItem[v.EdbInfoId] = v
+	}
+
+	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 := 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,
+				}
+			}
+
+			// 图表关联
+			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
 		}
-		edbList = append(edbList, v)
+		chartMappings = append(chartMappings, item)
 	}
 	return
 }

+ 13 - 0
services/data/edb_classify.go

@@ -903,6 +903,19 @@ func Delete(classifyId, edbInfoId int, sysUser *system.Admin, requestBody, reque
 			return
 		}
 
+		// 如果是同花顺高频数据或类似数据, 还需要删除base_from_edb_mapping对应关系
+		baseMappingOb := new(data_manage.BaseFromEdbMapping)
+		{
+			cond := fmt.Sprintf(" %s = ? AND %s = ? AND %s = ?", baseMappingOb.Cols().EdbCode, baseMappingOb.Cols().Source, baseMappingOb.Cols().SubSource)
+			pars := make([]interface{}, 0)
+			pars = append(pars, edbInfo.EdbCode, edbInfo.Source, edbInfo.SubSource)
+			if e := baseMappingOb.RemoveByCondition(cond, pars); e != nil {
+				errMsg = "删除失败"
+				err = fmt.Errorf("删除源指标映射失败, %v", e)
+				return
+			}
+		}
+
 		go data_stat.AddEdbDeleteLog(edbInfo, sysUser)
 
 		// 删除指标后的操作

+ 154 - 1
services/data/edb_data.go

@@ -409,9 +409,12 @@ func GetPageData(edbInfoId, source, subSource int, endDataTime string, startSize
 	dataList = make([]*data_manage.EdbData, 0)
 
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getPageDataByMongo(edbInfoId, source, subSource, endDataTime, startSize, pageSize)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return getThsHfPageDataByMongo(edbInfoId, source, subSource, endDataTime, startSize, pageSize)
+	}
 
 	// 默认走mysql
 	return getPageDataByMysql(edbInfoId, source, subSource, endDataTime, startSize, pageSize)
@@ -515,3 +518,153 @@ func getPageDataByMongo(edbInfoId, source, subSource int, endDataTime string, st
 
 	return
 }
+
+// GetEdbDataTbzForSeason 获取指标的同比值数据
+func GetEdbDataTbzForSeason(frequency string, tmpDataList []*data_manage.EdbDataList, startDateTime time.Time) (dataList []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
+	dataList = make([]*data_manage.EdbDataList, 0)
+
+	// 数据处理
+	var dateArr []string
+	dataMap := make(map[string]*data_manage.EdbDataList)
+	for _, v := range tmpDataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	for _, av := range dateArr {
+		currentItem, ok := dataMap[av]
+		// 如果找不到当前日期的数据,那么终止当前循环,进入下一循环
+		if !ok {
+			continue
+		}
+		tmpItem := *currentItem
+		var isOk bool //是否计算出来结果
+
+		//当前日期
+		currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 如果存在开始日期,同时,当前日期早于开始日期,那么终止当前循环,进入下一循环
+		if !startDateTime.IsZero() && currentDate.Before(startDateTime) {
+			continue
+		}
+		//上一年的日期
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+			tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+			isOk = true
+		} else {
+			if frequency == "月度" { //向上和向下,各找一个月
+				for i := 0; i <= 35; i++ {
+					nextDateDay := preDate.AddDate(0, 0, i)
+					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						preDateDay := preDate.AddDate(0, 0, -i)
+						preDateDayStr := preDateDay.Format(utils.FormatDate)
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			} else if frequency == "季度" || frequency == "年度" {
+				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期->下一个月找到
+					tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+					isOk = true
+					break
+				}
+			} else {
+				nextDateDay := preDate.AddDate(0, 0, 1)
+				nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+
+				preDateDay := preDate.AddDate(0, 0, -1)
+				preDateDayStr := preDateDay.Format(utils.FormatDate)
+
+				for i := 0; i < 35; i++ {
+					if i >= 1 {
+						nextDateDay = nextDateDay.AddDate(0, 0, i)
+						nextDateDayStr = nextDateDay.Format(utils.FormatDate)
+					}
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						if i >= 1 {
+							preDateDay = preDate.AddDate(0, 0, -i)
+							preDateDayStr = nextDateDay.Format(utils.FormatDate)
+						}
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			}
+		}
+
+		if isOk {
+			if tmpItem.Value > maxValue {
+				maxValue = tmpItem.Value
+			}
+			if tmpItem.Value < minValue {
+				minValue = tmpItem.Value
+			}
+			dataList = append(dataList, &tmpItem)
+		}
+	}
+
+	return
+}
+
+func getThsHfPageDataByMongo(edbInfoId, source, subSource int, endDataTime string, startSize, pageSize int) (dataCount int, dataList []*data_manage.EdbData, err error) {
+	dataList = make([]*data_manage.EdbData, 0)
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_info_id": edbInfoId,
+	}
+
+	// 结束日期
+	dateCondition, err := mgo.BuildDateCondition("", endDataTime)
+	if err != nil {
+		return
+	}
+	if len(dateCondition) > 0 {
+		queryConditions["data_time"] = dateCondition
+	}
+
+	// 获取数据总量
+	tmpCount, tmpErr := mogDataObj.GetCountDataList(queryConditions)
+	if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+		err = tmpErr
+		return
+	}
+	dataCount = int(tmpCount)
+
+	// 获取列表数据
+	tmpDataList, tmpErr := mogDataObj.GetPageDataList(queryConditions, int64(startSize), int64(pageSize), []string{"-data_time"})
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	for k, v := range tmpDataList {
+		dataList = append(dataList, &data_manage.EdbData{
+			EdbDataId: k + 1,
+			EdbInfoId: v.EdbInfoId,
+			DataTime:  v.DataTime.Format(utils.FormatDate),
+			Value:     v.Value,
+		})
+	}
+
+	return
+}

+ 4 - 1
services/data/edb_info.go

@@ -1871,7 +1871,7 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 	edbInfo.EdbNameSource = edbName
 	edbInfo.Frequency = frequency
 	edbInfo.Unit = unit
-	edbInfo.UnitEn = frequency
+	edbInfo.UnitEn = unit
 	edbInfo.StartDate = startDate
 	edbInfo.EndDate = endDate
 	edbInfo.ClassifyId = classifyId
@@ -2566,8 +2566,10 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	edbInfo.EdbCode = item.EdbCode
 	edbInfo.EdbName = item.EdbName
 	edbInfo.EdbNameSource = item.EdbName
+	edbInfo.EdbNameEn = item.EdbNameEn
 	edbInfo.Frequency = item.Frequency
 	edbInfo.Unit = item.Unit
+	edbInfo.UnitEn = item.UnitEn
 	edbInfo.ClassifyId = item.ClassifyId
 	edbInfo.SysUserId = item.SysUserId
 	edbInfo.SysUserRealName = item.SysUserRealName
@@ -2584,6 +2586,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 	edbInfo.IndicatorCode = item.IndicatorCode
 	edbInfo.StockCode = item.StockCode
 	edbInfo.TerminalCode = item.TerminalCode
+	edbInfo.Extra = item.Extra
 	edbInfoId, err := data_manage.AddEdbInfo(edbInfo)
 	if err != nil {
 		errMsg = "保存失败"

+ 140 - 0
services/data/edb_info_calculate.go

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

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

@@ -730,7 +730,7 @@ func HandleMixTableDateChange(date, conf string) (newDate string, err error) {
 					dateTime = dateTime.AddDate(v.Year, v.Month, v.Day)
 					newDate = dateTime.Format(utils.FormatDate)
 				} else if v.ChangeType == 2 {
-					newDate, err, _ = handleSystemAppointDateT(dateTime, v.FrequencyDay, v.Frequency)
+					newDate, err, _ = HandleSystemAppointDateT(dateTime, v.FrequencyDay, v.Frequency)
 					if err != nil {
 						return
 					}
@@ -841,7 +841,7 @@ func handleSystemCalculateDateT(num int, frequency string) (date string, err err
 // @return date string
 // @return err error
 // @return errMsg string
-func handleSystemAppointDateT(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
+func HandleSystemAppointDateT(currDate time.Time, appointDay, frequency string) (date string, err error, errMsg string) {
 	//currDate := time.Now()
 	switch frequency {
 	case "本周":

+ 266 - 128
services/data/future_good/chart_info.go

@@ -15,42 +15,46 @@ import (
 )
 
 // GetChartEdbData 获取图表的指标数据
-func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping, futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping, barChartInfoDateList []data_manage.BarChartInfoDateReq, needData bool) (barConfigEdbInfoIdList []data_manage.BarChartInfoEdbItemReq, edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, xDataList []data_manage.XData, yDataList []data_manage.YData, err error) {
+func GetChartEdbData(chartInfoId int, startDate, endDate string, baseEdbInfoMapping *data_manage.ChartEdbInfoMapping, edbInfoMappingList []*data_manage.ChartEdbInfoMapping, futureGoodEdbInfoMapping *data_manage.ChartEdbInfoMapping, barChartInfoConf data_manage.FutureGoodBarChartInfoReq, needData bool) (barConfigEdbInfoIdList []data_manage.BarChartInfoEdbItemReq, edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, xDataList []data_manage.XData, yDataList []data_manage.YData, err error) {
 	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
-
+	barChartInfoDateList := barChartInfoConf.DateList
 	if futureGoodEdbInfoMapping == nil {
 		err = errors.New("商品指标未选取")
 		return
 	}
-	if edbInfoMapping == nil {
+	if len(edbInfoMappingList) == 0 {
 		err = errors.New("ETA指标未选取")
 		return
 	}
+
 	// 指标对应的所有数据
 	edbDataListMap := make(map[int][]*data_manage.EdbDataList)
 
-	item := new(data_manage.ChartEdbInfoMapping)
-	edbInfoMapping.FrequencyEn = data.GetFrequencyEn(edbInfoMapping.Frequency)
+	// todo item
+	//item := new(data_manage.ChartEdbInfoMapping)
 
-	if edbInfoMapping.Unit == `无` {
-		edbInfoMapping.Unit = ``
-	}
 	if futureGoodEdbInfoMapping.Unit == `无` {
 		futureGoodEdbInfoMapping.Unit = ``
 	}
 
 	if chartInfoId <= 0 {
-		edbInfoMapping.IsAxis = 1
-		edbInfoMapping.LeadValue = 0
-		edbInfoMapping.LeadUnit = ""
-		edbInfoMapping.ChartEdbMappingId = 0
-		edbInfoMapping.ChartInfoId = 0
-		edbInfoMapping.IsOrder = false
-		edbInfoMapping.EdbInfoType = 1
-		edbInfoMapping.ChartStyle = ""
-		edbInfoMapping.ChartColor = ""
-		edbInfoMapping.ChartWidth = 0
-
+		for k, edbInfoMapping := range edbInfoMappingList {
+			edbInfoMapping.FrequencyEn = data.GetFrequencyEn(edbInfoMapping.Frequency)
+			if edbInfoMapping.Unit == `无` {
+				edbInfoMapping.Unit = ``
+			}
+			edbInfoMapping.IsAxis = 1
+			edbInfoMapping.LeadValue = 0
+			edbInfoMapping.LeadUnit = ""
+			edbInfoMapping.ChartEdbMappingId = 0
+			edbInfoMapping.ChartInfoId = 0
+			edbInfoMapping.IsOrder = false
+			edbInfoMapping.EdbInfoType = 1
+			edbInfoMapping.ChartStyle = ""
+			edbInfoMapping.ChartColor = ""
+			edbInfoMapping.ChartWidth = 0
+			edbInfoMappingList[k] = edbInfoMapping
+		}
 		futureGoodEdbInfoMapping.IsAxis = 1
 		futureGoodEdbInfoMapping.LeadValue = 0
 		futureGoodEdbInfoMapping.LeadUnit = ""
@@ -62,21 +66,43 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 		futureGoodEdbInfoMapping.ChartColor = ""
 		futureGoodEdbInfoMapping.ChartWidth = 0
 	} else {
-		edbInfoMapping.LeadUnitEn = data.GetLeadUnitEn(edbInfoMapping.LeadUnit)
+		for k, edbInfoMapping := range edbInfoMappingList {
+			edbInfoMapping.FrequencyEn = data.GetFrequencyEn(edbInfoMapping.Frequency)
+			if edbInfoMapping.Unit == `无` {
+				edbInfoMapping.Unit = ``
+			}
+			edbInfoMapping.LeadUnitEn = data.GetLeadUnitEn(edbInfoMapping.LeadUnit)
+			edbInfoMappingList[k] = edbInfoMapping
+		}
 		futureGoodEdbInfoMapping.LeadUnitEn = data.GetLeadUnitEn(futureGoodEdbInfoMapping.LeadUnit)
 	}
 
+	//查询横轴配置项
+	xDataItemMap := make(map[int]data_manage.XData)
+	for k, v := range barChartInfoConf.XDataList {
+		xDataItemMap[k] = v
+	}
+
 	// 普通的指标数据
 	{
-		edbList = append(edbList, edbInfoMapping)
-
-		barConfigEdbInfoIdList = append(barConfigEdbInfoIdList, data_manage.BarChartInfoEdbItemReq{
-			EdbInfoId: edbInfoMapping.EdbInfoId,
-			//Name:      edbInfoMapping.EdbName,
-			Name:   "现货价",
-			NameEn: "Spot Price",
-			Source: edbInfoMapping.Source,
-		})
+		edbList = edbInfoMappingList
+
+		for k, edbInfoMapping := range edbInfoMappingList {
+			tmp := data_manage.BarChartInfoEdbItemReq{
+				EdbInfoId: edbInfoMapping.EdbInfoId,
+				Name:      edbInfoMapping.EdbName,
+				NameEn:    edbInfoMapping.EdbNameEn,
+				Source:    edbInfoMapping.Source,
+			}
+			// 如果有配置,则用配置中的指标名称替换
+			xItem, ok := xDataItemMap[k]
+			if ok && xItem.Name != "" {
+				tmp.Name = xItem.Name
+				tmp.NameEn = xItem.NameEn
+				tmp.IsHide = xItem.IsHide
+			}
+			barConfigEdbInfoIdList = append(barConfigEdbInfoIdList, tmp)
+		}
 	}
 
 	// 获取主力合约和最新日期
@@ -88,6 +114,7 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 		// 寻找主力合约
 		zlFutureGoodEdbInfo, err = future_good2.GetFutureGoodEdbInfo(futureGoodEdbInfoMapping.EdbInfoId)
 		if err != nil {
+			err = fmt.Errorf("查不到期货指标,GetFutureGoodEdbInfo err: %v", err)
 			return
 		}
 		regionType = zlFutureGoodEdbInfo.RegionType
@@ -97,7 +124,7 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 		if zlFutureGoodEdbInfo.DateSourceId != zlFutureGoodEdbInfo.FutureGoodEdbInfoId {
 			sourceDateFutureGoodEdbInfo, tmpErr := future_good2.GetFutureGoodEdbInfo(zlFutureGoodEdbInfo.DateSourceId)
 			if tmpErr != nil {
-				err = tmpErr
+				err = fmt.Errorf("获取期货指标 %d 的日期来源的指标信息失败,错误:%s", zlFutureGoodEdbInfo.DateSourceId, tmpErr.Error())
 				return
 			}
 			latestDate = sourceDateFutureGoodEdbInfo.EndDate // 最新日期是这个
@@ -119,6 +146,7 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 	latestDateTime, _ := time.ParseInLocation(utils.FormatDate, latestDate, time.Local)
 	_, futureGoodEdbInfoList, err := getFutureGoodEdbInfoList(latestDateTime, tmpFutureGoodEdbInfoList, barChartInfoDateList)
 	if err != nil {
+		err = fmt.Errorf("获取期货指标列表失败,getFutureGoodEdbInfoList 错误:%s", err.Error())
 		return
 	}
 
@@ -132,30 +160,30 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 			EdbName:             v.FutureGoodEdbName,
 			EdbAliasName:        v.FutureGoodEdbName,
 			EdbNameEn:           v.FutureGoodEdbNameEn,
-			EdbType:             edbInfoMapping.EdbType,
-			Frequency:           edbInfoMapping.Frequency,
-			FrequencyEn:         edbInfoMapping.FrequencyEn,
-			Unit:                edbInfoMapping.Unit,
-			UnitEn:              edbInfoMapping.UnitEn,
+			EdbType:             baseEdbInfoMapping.EdbType,
+			Frequency:           baseEdbInfoMapping.Frequency,
+			FrequencyEn:         baseEdbInfoMapping.FrequencyEn,
+			Unit:                baseEdbInfoMapping.Unit,
+			UnitEn:              baseEdbInfoMapping.UnitEn,
 			StartDate:           v.StartDate,
 			EndDate:             v.EndDate,
 			ModifyTime:          v.ModifyTime.Format(utils.FormatDateTime),
 			ChartEdbMappingId:   v.FutureGoodEdbInfoId,
-			ChartInfoId:         edbInfoMapping.ChartInfoId,
+			ChartInfoId:         baseEdbInfoMapping.ChartInfoId,
 			MaxData:             v.MaxValue,
 			MinData:             v.MinValue,
-			IsOrder:             edbInfoMapping.IsOrder,
-			IsAxis:              edbInfoMapping.IsAxis,
-			EdbInfoType:         edbInfoMapping.EdbInfoType,
-			EdbInfoCategoryType: edbInfoMapping.EdbInfoCategoryType,
-			LeadValue:           edbInfoMapping.LeadValue,
-			LeadUnit:            edbInfoMapping.LeadUnit,
-			LeadUnitEn:          edbInfoMapping.LeadUnitEn,
-			ChartStyle:          edbInfoMapping.ChartStyle,
-			ChartColor:          edbInfoMapping.ChartColor,
-			PredictChartColor:   edbInfoMapping.PredictChartColor,
-			ChartWidth:          edbInfoMapping.ChartWidth,
-			ChartType:           edbInfoMapping.ChartType,
+			IsOrder:             baseEdbInfoMapping.IsOrder,
+			IsAxis:              baseEdbInfoMapping.IsAxis,
+			EdbInfoType:         baseEdbInfoMapping.EdbInfoType,
+			EdbInfoCategoryType: baseEdbInfoMapping.EdbInfoCategoryType,
+			LeadValue:           baseEdbInfoMapping.LeadValue,
+			LeadUnit:            baseEdbInfoMapping.LeadUnit,
+			LeadUnitEn:          baseEdbInfoMapping.LeadUnitEn,
+			ChartStyle:          baseEdbInfoMapping.ChartStyle,
+			ChartColor:          baseEdbInfoMapping.ChartColor,
+			PredictChartColor:   baseEdbInfoMapping.PredictChartColor,
+			ChartWidth:          baseEdbInfoMapping.ChartWidth,
+			ChartType:           baseEdbInfoMapping.ChartType,
 			LatestDate:          v.LatestDate.Format(utils.FormatDateTime),
 			LatestValue:         v.LatestValue,
 			UniqueCode:          futureGoodEdbInfoMapping.UniqueCode + strconv.Itoa(k),
@@ -166,12 +194,21 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 		}
 		futureGoodMappingList = append(futureGoodMappingList, newMappingInfo)
 
-		barConfigEdbInfoIdList = append(barConfigEdbInfoIdList, data_manage.BarChartInfoEdbItemReq{
+		tmp := data_manage.BarChartInfoEdbItemReq{
 			EdbInfoId: newMappingInfo.EdbInfoId,
 			Name:      fmt.Sprint("M+", v.Month),
 			NameEn:    fmt.Sprint("M+", v.Month),
 			Source:    newMappingInfo.Source,
-		})
+		}
+		// 判断如果有配置,需要替换为配置内容
+		xItem, ok := xDataItemMap[k]
+		if ok && xItem.Name != "" {
+			tmp.Name = xItem.Name
+			tmp.NameEn = xItem.NameEn
+			tmp.IsHide = xItem.IsHide
+		}
+		barConfigEdbInfoIdList = append(barConfigEdbInfoIdList, tmp)
+
 	}
 
 	if regionType == `海外` {
@@ -183,30 +220,30 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 			EdbName:             zlFutureGoodEdbInfo.FutureGoodEdbName,
 			EdbAliasName:        zlFutureGoodEdbInfo.FutureGoodEdbName,
 			EdbNameEn:           zlFutureGoodEdbInfo.FutureGoodEdbNameEn,
-			EdbType:             edbInfoMapping.EdbType,
-			Frequency:           edbInfoMapping.Frequency,
-			FrequencyEn:         edbInfoMapping.FrequencyEn,
-			Unit:                edbInfoMapping.Unit,
-			UnitEn:              edbInfoMapping.UnitEn,
+			EdbType:             baseEdbInfoMapping.EdbType,
+			Frequency:           baseEdbInfoMapping.Frequency,
+			FrequencyEn:         baseEdbInfoMapping.FrequencyEn,
+			Unit:                baseEdbInfoMapping.Unit,
+			UnitEn:              baseEdbInfoMapping.UnitEn,
 			StartDate:           zlFutureGoodEdbInfo.StartDate,
 			EndDate:             zlFutureGoodEdbInfo.EndDate,
 			ModifyTime:          zlFutureGoodEdbInfo.ModifyTime.Format(utils.FormatDateTime),
 			ChartEdbMappingId:   zlFutureGoodEdbInfo.FutureGoodEdbInfoId,
-			ChartInfoId:         edbInfoMapping.ChartInfoId,
+			ChartInfoId:         baseEdbInfoMapping.ChartInfoId,
 			MaxData:             zlFutureGoodEdbInfo.MaxValue,
 			MinData:             zlFutureGoodEdbInfo.MinValue,
-			IsOrder:             edbInfoMapping.IsOrder,
-			IsAxis:              edbInfoMapping.IsAxis,
-			EdbInfoType:         edbInfoMapping.EdbInfoType,
-			EdbInfoCategoryType: edbInfoMapping.EdbInfoCategoryType,
-			LeadValue:           edbInfoMapping.LeadValue,
-			LeadUnit:            edbInfoMapping.LeadUnit,
-			LeadUnitEn:          edbInfoMapping.LeadUnitEn,
-			ChartStyle:          edbInfoMapping.ChartStyle,
-			ChartColor:          edbInfoMapping.ChartColor,
-			PredictChartColor:   edbInfoMapping.PredictChartColor,
-			ChartWidth:          edbInfoMapping.ChartWidth,
-			ChartType:           edbInfoMapping.ChartType,
+			IsOrder:             baseEdbInfoMapping.IsOrder,
+			IsAxis:              baseEdbInfoMapping.IsAxis,
+			EdbInfoType:         baseEdbInfoMapping.EdbInfoType,
+			EdbInfoCategoryType: baseEdbInfoMapping.EdbInfoCategoryType,
+			LeadValue:           baseEdbInfoMapping.LeadValue,
+			LeadUnit:            baseEdbInfoMapping.LeadUnit,
+			LeadUnitEn:          baseEdbInfoMapping.LeadUnitEn,
+			ChartStyle:          baseEdbInfoMapping.ChartStyle,
+			ChartColor:          baseEdbInfoMapping.ChartColor,
+			PredictChartColor:   baseEdbInfoMapping.PredictChartColor,
+			ChartWidth:          baseEdbInfoMapping.ChartWidth,
+			ChartType:           baseEdbInfoMapping.ChartType,
 			LatestDate:          zlFutureGoodEdbInfo.LatestDate.Format(utils.FormatDateTime),
 			LatestValue:         zlFutureGoodEdbInfo.LatestValue,
 			UniqueCode:          futureGoodEdbInfoMapping.UniqueCode + strconv.Itoa(0),
@@ -228,24 +265,45 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 	// 获取数据
 
 	// 现货数据
-	{
+	for _, edbInfoMapping := range edbInfoMappingList {
 		dataList := make([]*data_manage.EdbDataList, 0)
 		dataList, err = data_manage.GetEdbDataList(edbInfoMapping.Source, edbInfoMapping.SubSource, edbInfoMapping.EdbInfoId, startDate, endDate)
 		if err != nil {
 			return
 		}
 		edbDataListMap[edbInfoMapping.EdbInfoId] = dataList
-		item.DataList = dataList
+		// todo item
+		//item.DataList = dataList
 	}
-
-	// 期货数据
+	futureEdbInfoIds := make([]int, 0)
 	for _, v := range futureGoodMappingList {
-		dataList := make([]*data_manage.EdbDataList, 0)
-
-		tmpDataList, tmpErr := future_good2.GetFutureGoodEdbDataListByDate(v.EdbInfoId, startDate, endDate)
+		futureEdbInfoIds = append(futureEdbInfoIds, v.EdbInfoId)
+	}
+	tmpDataListMap := make(map[int][]*future_good2.FutureGoodEdbData)
+	if len(futureEdbInfoIds) > 0 {
+		// 期货数据
+		tmpDataList, tmpErr := future_good2.GetFutureGoodEdbDataListByIdsAndDate(futureEdbInfoIds, startDate, endDate)
 		if tmpErr != nil {
+			err = tmpErr
 			return
 		}
+
+		for _, v := range tmpDataList {
+			if _, ok := tmpDataListMap[v.FutureGoodEdbInfoId]; !ok {
+				tmpDataListMap[v.FutureGoodEdbInfoId] = make([]*future_good2.FutureGoodEdbData, 0)
+			}
+			tmpDataListMap[v.FutureGoodEdbInfoId] = append(tmpDataListMap[v.FutureGoodEdbInfoId], v)
+		}
+	}
+
+	for _, v := range futureGoodMappingList {
+		dataList := make([]*data_manage.EdbDataList, 0)
+		tmpDataList, ok := tmpDataListMap[v.EdbInfoId]
+		if !ok {
+			/*err = fmt.Errorf("期货数据不存在 FutureGoodEdbInfoId: %d", v.EdbInfoId)
+			return*/
+			continue
+		}
 		for _, tmpData := range tmpDataList {
 			dataList = append(dataList, &data_manage.EdbDataList{
 				EdbDataId:     tmpData.FutureGoodEdbDataId,
@@ -259,14 +317,20 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 		v.DataList = dataList
 	}
 
-	xEdbIdValue, yDataList, err = BarChartData(edbList[0], futureGoodEdbInfoList, edbDataListMap, barChartInfoDateList, regionType, edbInfoMapping.EndDate)
+	xEdbIdValue, yDataList, err = BarChartData(baseEdbInfoMapping, edbInfoMappingList, futureGoodEdbInfoList, edbDataListMap, barChartInfoDateList, regionType, baseEdbInfoMapping.EndDate)
 
-	xDataList = []data_manage.XData{
-		{
-			Name:   "现货价",
-			NameEn: "Spot Price",
-		},
+	if len(barChartInfoConf.XDataList) > 0 {
+		xDataList = barChartInfoConf.XDataList
+	} else {
+		for _, v := range edbInfoMappingList {
+			xDataList = append(xDataList, data_manage.XData{
+				Name:   v.EdbName,
+				NameEn: v.EdbNameEn,
+				IsHide: 0,
+			})
+		}
 	}
+
 	//yDataList =make([]data_manage.YData,0)
 	//data_manage.YData{
 	//	Date:           "",
@@ -296,14 +360,15 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 		}
 
 		// 基础指标
-		{
-			baseEdbInfId := edbList[0].EdbInfoId
-			edbInfoIdList = append(edbInfoIdList, baseEdbInfId)
-			tmpVal, ok := yDataMap[baseEdbInfId]
+		baseEdbIds := make([]int, 0)
+		for _, tmp := range edbInfoMappingList {
+			edbInfoIdList = append(edbInfoIdList, tmp.EdbInfoId)
+			baseEdbIds = append(baseEdbIds, tmp.EdbInfoId)
+			tmpVal, ok := yDataMap[tmp.EdbInfoId]
 			valueList = append(valueList, tmpVal)
 			if !ok || tmpVal == 0 {
-				noDataEdbInfoIdList = append(noDataEdbInfoIdList, baseEdbInfId)
-				noDataEdbIdMap[baseEdbInfId] = baseEdbInfId
+				noDataEdbInfoIdList = append(noDataEdbInfoIdList, tmp.EdbInfoId)
+				noDataEdbIdMap[tmp.EdbInfoId] = tmp.EdbInfoId
 			}
 		}
 
@@ -326,8 +391,8 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 
 		for i := lenEdbId - 1; i >= 0; i-- {
 			// 如果没有在无数据的指标列表中找到,那么就找到了最大x轴的下标
-			if _, ok := noDataEdbIdMap[edbInfoIdList[i]]; !ok {
-				// 如果最大x轴的下标 小于 当前下标,那么就重新赋值
+			if _, ok := noDataEdbIdMap[edbInfoIdList[i]]; !ok || utils.InArrayByInt(baseEdbIds, edbInfoIdList[i]) { //以往的逻辑是碰到第一个无数据的期货指标,那后续的月份都是无数据的,因此需要特殊处理,改成多个现货指标之后需要排除无值的现货指标
+				// 如果最大x轴的下标 小于 当前下标,那么就重新赋值
 				if maxIndex < i-1 {
 					maxIndex = i - 1
 				}
@@ -362,22 +427,44 @@ func GetChartEdbData(chartInfoId int, startDate, endDate string, edbInfoMapping,
 			yDataList[k].Value = v.Value[0 : maxIndex+1]
 		}
 	}
-
-	tmpXDataList, newYDataList, err := handleResultData(regionType, futureGoodEdbType, yDataList, futureGoodEdbInfoList, maxIndex)
+	baseEdbLen := len(edbInfoMappingList)
+	tmpXDataList, newYDataList, err := handleResultData(regionType, futureGoodEdbType, baseEdbLen, yDataList, futureGoodEdbInfoList, maxIndex)
 	if err != nil {
 		return
 	}
-	xDataList = append(xDataList, tmpXDataList...)
+	if len(barChartInfoConf.XDataList) == 0 {
+		xDataList = append(xDataList, tmpXDataList...)
+	}
+
 	yDataList = newYDataList
 
 	return
 }
 
 // BarChartData 获取数据
-func BarChartData(edbInfoMapping *data_manage.ChartEdbInfoMapping, futureGoodMappingList []*future_good2.FutureGoodEdbInfo, edbDataListMap map[int][]*data_manage.EdbDataList, barChartInfoDateList []data_manage.BarChartInfoDateReq, regionType, latestDate string) (edbIdList []int, yDataList []data_manage.YData, err error) {
+func BarChartData(baseEdbInfoMapping *data_manage.ChartEdbInfoMapping, edbInfoMappingList []*data_manage.ChartEdbInfoMapping, futureGoodMappingList []*future_good2.FutureGoodEdbInfo, edbDataListMap map[int][]*data_manage.EdbDataList, barChartInfoDateList []data_manage.BarChartInfoDateReq, regionType, latestDate string) (edbIdList []int, yDataList []data_manage.YData, err error) {
 	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
+	// 现货指标数据map
+	baseEdbDataMap := make(map[int]map[string]float64)
+	edbInfoMappingMap := make(map[int]struct{})
+	for _, v := range edbInfoMappingList {
+		edbInfoMappingMap[v.EdbInfoId] = struct{}{}
+	}
+	for edbInfoId, edbDataList := range edbDataListMap {
+		if _, ok1 := edbInfoMappingMap[edbInfoId]; ok1 {
+			edbDateData := make(map[string]float64)
+			for _, edbData := range edbDataList {
+				edbDateData[edbData.DataTime] = edbData.Value
+			}
+			baseEdbDataMap[edbInfoId] = edbDateData
+		}
+	}
+
 	edbDataMap := make(map[int]map[string]float64)
 	for edbInfoId, edbDataList := range edbDataListMap {
+		if _, ok := edbInfoMappingMap[edbInfoId]; ok {
+			continue
+		}
 		edbDateData := make(map[string]float64)
 		for _, edbData := range edbDataList {
 			edbDateData[edbData.DataTime] = edbData.Value
@@ -387,7 +474,10 @@ func BarChartData(edbInfoMapping *data_manage.ChartEdbInfoMapping, futureGoodMap
 
 	// edbIdList 指标展示顺序;x轴的指标顺序
 	edbIdList = make([]int, 0)
-	edbIdList = append(edbIdList, edbInfoMapping.EdbInfoId)
+	for _, v := range edbInfoMappingList {
+		edbIdList = append(edbIdList, v.EdbInfoId)
+	}
+
 	for _, v := range futureGoodMappingList {
 		edbIdList = append(edbIdList, v.FutureGoodEdbInfoId)
 	}
@@ -428,23 +518,43 @@ func BarChartData(edbInfoMapping *data_manage.ChartEdbInfoMapping, futureGoodMap
 		noDataIdMap := make(map[int]int, 0) // 没有数据的指标map
 		xEdbInfoIdList := make([]int, 0)    // 当前数据的指标id列表
 
-		// 现货指标
-		realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, edbDataListMap[edbInfoMapping.EdbInfoId], edbDataMap[edbInfoMapping.EdbInfoId])
+		// 先找到基准日期
+		var realDateTime time.Time
+		realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, edbDataListMap[baseEdbInfoMapping.EdbInfoId], baseEdbDataMap[baseEdbInfoMapping.EdbInfoId], edbDataMap)
 		if tmpErr != nil {
 			err = tmpErr
 			return
 		}
-		findDataList = append(findDataList, findDataValue)
-		yDataMap[edbInfoMapping.EdbInfoId] = findDataValue
-		if isFind {
-			maxDate = realDateTime
-		} else {
-			noDataIdList = append(noDataIdList, edbInfoMapping.EdbInfoId)
-			noDataIdMap[edbInfoMapping.EdbInfoId] = edbInfoMapping.EdbInfoId
+		// 处理其余现货指标
+		for _, v := range edbInfoMappingList {
+			if v.EdbInfoId == baseEdbInfoMapping.EdbInfoId {
+				findDataList = append(findDataList, findDataValue)
+				yDataMap[baseEdbInfoMapping.EdbInfoId] = findDataValue
+				if isFind {
+					maxDate = realDateTime
+				} else {
+					noDataIdList = append(noDataIdList, baseEdbInfoMapping.EdbInfoId)
+					noDataIdMap[baseEdbInfoMapping.EdbInfoId] = baseEdbInfoMapping.EdbInfoId
+				}
+				xEdbInfoIdList = append(xEdbInfoIdList, v.EdbInfoId)
+				continue
+			}
+			findDataValueTmp, isFindTmp := baseEdbDataMap[v.EdbInfoId][realDateTime.Format(utils.FormatDate)]
+			findDataList = append(findDataList, findDataValueTmp)
+			yDataMap[v.EdbInfoId] = findDataValueTmp
+			if !isFindTmp {
+				noDataIdList = append(noDataIdList, v.EdbInfoId)
+				noDataIdMap[v.EdbInfoId] = v.EdbInfoId
+			}
+			xEdbInfoIdList = append(xEdbInfoIdList, v.EdbInfoId)
 		}
-		currMonth := findDateTime.Month() // 当前月份
-		currYear := findDateTime.Year()   // 当前年份
-		xEdbInfoIdList = append(xEdbInfoIdList, edbInfoMapping.EdbInfoId)
+		//currMonth := findDateTime.Month() // 当前月份
+		//currYear := findDateTime.Year()   // 当前年份
+
+		// 用实际日期的月份作为基准,往前推12个月(2024-5-13 16:26:43修改)
+		currMonth := realDateTime.Month() // 当前月份
+		currYear := realDateTime.Year()   // 当前年份
+
 		mList := make([]int, 0) // 间隔月份
 		indexList := make([]int, 0)
 		if regionType == `国内` {
@@ -564,10 +674,9 @@ func BarChartData(edbInfoMapping *data_manage.ChartEdbInfoMapping, futureGoodMap
 }
 
 // handleResultData 处理成最终的结果数据
-func handleResultData(regionType string, futureGoodEdbType int, yDataList []data_manage.YData, futureGoodEdbInfoList []*future_good2.FutureGoodEdbInfo, maxIndex int) (xDataList []data_manage.XData, newYDataList []data_manage.YData, err error) {
+func handleResultData(regionType string, futureGoodEdbType, baseEdbLen int, yDataList []data_manage.YData, futureGoodEdbInfoList []*future_good2.FutureGoodEdbInfo, maxIndex int) (xDataList []data_manage.XData, newYDataList []data_manage.YData, err error) {
 	xDataList = make([]data_manage.XData, 0)
 	newYDataList = yDataList
-
 	if regionType == `国内` {
 		for i := 1; i < 12; i++ {
 			if i > maxIndex {
@@ -601,11 +710,11 @@ func handleResultData(regionType string, futureGoodEdbType int, yDataList []data
 			tmpNList := nList
 
 			//当前的主力合约
-			newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, yData.XEdbInfoIdList[0])
-			newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[0])
+			newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, yData.XEdbInfoIdList[0:baseEdbLen]...)
+			newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[0:baseEdbLen]...)
 
-			xEdbInfoIdList := yData.XEdbInfoIdList[1:]
-			valIndex := 1
+			xEdbInfoIdList := yData.XEdbInfoIdList[baseEdbLen:]
+			valIndex := baseEdbLen
 			needNum := 0
 			for _, n := range tmpNList {
 				if len(xEdbInfoIdList) > 0 {
@@ -677,8 +786,8 @@ func handleResultData(regionType string, futureGoodEdbType int, yDataList []data
 		currYear := findDateTime.Year()   // 当前年份
 		//v.XEdbInfoIdList
 		for edbInfoIndex, edbInfoId := range v.XEdbInfoIdList {
-			// 第一个不处理
-			if edbInfoIndex == 0 {
+			// 现货指标不处理
+			if edbInfoIndex <= baseEdbLen-1 {
 				continue
 			}
 
@@ -717,12 +826,12 @@ func handleResultData(regionType string, futureGoodEdbType int, yDataList []data
 		tmpNList := nList
 
 		//当前的主力合约
-		newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, yData.XEdbInfoIdList[0])
-		newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[0])
+		newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, yData.XEdbInfoIdList[0:baseEdbLen]...)
+		newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[0:baseEdbLen]...)
 
-		xEdbInfoIdList := yData.XEdbInfoIdList[1:]
+		xEdbInfoIdList := yData.XEdbInfoIdList[baseEdbLen:]
 		currDataTime := yData.ConfigDate
-		valIndex := 1
+		valIndex := baseEdbLen
 		needNum := 0
 		for _, n := range tmpNList {
 			if len(xEdbInfoIdList) > 0 {
@@ -860,7 +969,7 @@ func getFutureGoodEdbInfoList(latestDateTime time.Time, tmpFutureGoodEdbInfoList
 }
 
 // GetNeedDateData 获取合约内需要的日期数据
-func GetNeedDateData(needDateTime time.Time, dataList []*data_manage.EdbDataList, edbDataMap map[string]float64) (findDateTime time.Time, findDataValue float64, isFind bool, err error) {
+func GetNeedDateData(needDateTime time.Time, dataList []*data_manage.EdbDataList, edbDataMap map[string]float64, allEdbDataMap map[int]map[string]float64) (findDateTime time.Time, findDataValue float64, isFind bool, err error) {
 	//dataList := edbDataListMap[edbInfoId] //指标的所有数据值
 	if len(dataList) <= 0 {
 		// 没有数据的指标id
@@ -873,18 +982,43 @@ func GetNeedDateData(needDateTime time.Time, dataList []*data_manage.EdbDataList
 		return
 	}
 
+	// 该日期存在数据的期货指标的最小数量,目前是现货和期货各1个,总共2个
+	maxCount := 1
+
 	for tmpDateTime := needDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
 		tmpDate := tmpDateTime.Format(utils.FormatDate)
-		if tmpValue, ok := edbDataMap[tmpDate]; ok { //如果能找到数据,那么就返回
-			// 数据为0,也直接返回,做无值处理
-			if tmpValue == 0 {
-				return
+		tmpValue, ok := edbDataMap[tmpDate]
+		if !ok {
+			continue
+		}
+
+		// 该日期存在数据的指标数量
+		count := 0
+
+		for _, currEdbDataMap := range allEdbDataMap {
+			_, tmpIsFind := currEdbDataMap[tmpDate]
+			if tmpIsFind {
+				count++
+				if count >= maxCount {
+					continue
+				}
 			}
-			findDateTime, _ = time.ParseInLocation(utils.FormatDate, tmpDate, time.Local)
-			findDataValue = tmpValue
-			isFind = true
+		}
+
+		// 该日期存在数据的期货指标数量小于2个,那么要继续往前找
+		if count < maxCount {
+			continue
+		}
+
+		//如果能找到数据,那么就返回
+		// 数据为0,也直接返回,做无值处理
+		if tmpValue == 0 {
 			return
 		}
+		findDateTime, _ = time.ParseInLocation(utils.FormatDate, tmpDate, time.Local)
+		findDataValue = tmpValue
+		isFind = true
+		return
 	}
 
 	return
@@ -903,11 +1037,15 @@ func FutureGoodChartInfoRefresh(chartInfoId int) (err error) {
 		}
 	}()
 
-	edbInfoMapping, err := data_manage.GetEtaEdbChartEdbMapping(chartInfoId)
+	edbInfoMappingList, err := data_manage.GetEtaEdbChartEdbMappingList(chartInfoId)
 	if err != nil {
 		errMsg = "获取需要刷新的ETA指标失败:Err:" + err.Error()
 		return
 	}
+	edbInfoIds := make([]int, 0)
+	for _, edbInfoMapping := range edbInfoMappingList {
+		edbInfoIds = append(edbInfoIds, edbInfoMapping.EdbInfoId)
+	}
 	// 获取期货指标
 	futureGoodEdbInfoMapping, err := data_manage.GetFutureGoodEdbChartEdbMapping(chartInfoId)
 	if err != nil {
@@ -922,7 +1060,7 @@ func FutureGoodChartInfoRefresh(chartInfoId int) (err error) {
 	}
 
 	// 批量刷新ETA指标
-	err, _ = data.EdbInfoRefreshAllFromBaseV3([]int{edbInfoMapping.EdbInfoId}, false, true, false)
+	err, _ = data.EdbInfoRefreshAllFromBaseV3(edbInfoIds, false, true, false)
 	if err != nil {
 		return
 	}

+ 137 - 43
services/data/future_good/profit_chart_info.go

@@ -17,13 +17,16 @@ import (
 )
 
 // GetProfitChartEdbData 获取利润图表的指标数据
-func GetProfitChartEdbData(baseEdbInfo *data_manage.EdbInfo, zlFutureGoodEdbInfoList []*future_good.FutureGoodEdbInfo, chartInfoDateList []request.ChartInfoDateReq, formulaStr string, edbInfoFromTagList []data_manage.EdbInfoFromTag) (barConfigEdbInfoIdList []data_manage.BarChartInfoEdbItemReq, edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, xDataList []data_manage.XData, yDataList []data_manage.YData, err error) {
+func GetProfitChartEdbData(baseEdbInfo *data_manage.EdbInfo, edbInfoList []*data_manage.EdbInfo, zlFutureGoodEdbInfoList []*future_good.FutureGoodEdbInfo, chartInfoDateList []request.ChartInfoDateReq, formulaStr string, edbInfoFromTagList []data_manage.EdbInfoFromTag, reqXDataList []data_manage.XData) (barConfigEdbInfoIdList []data_manage.BarChartInfoEdbItemReq, edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, xDataList []data_manage.XData, yDataList []data_manage.YData, err error) {
 	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
 
 	if baseEdbInfo == nil {
 		err = errors.New("ETA指标未选取")
 		return
 	}
+	if len(edbInfoList) == 0 {
+		edbInfoList = append(edbInfoList, baseEdbInfo)
+	}
 	if len(zlFutureGoodEdbInfoList) <= 0 {
 		err = errors.New("商品指标未选取")
 		return
@@ -55,10 +58,14 @@ func GetProfitChartEdbData(baseEdbInfo *data_manage.EdbInfo, zlFutureGoodEdbInfo
 	}
 
 	// 普通的指标数据
-	baseDataList := make([]*data_manage.EdbDataList, 0)
-	baseDataList, err = data_manage.GetEdbDataList(baseEdbInfo.Source, baseEdbInfo.SubSource, baseEdbInfo.EdbInfoId, "", "")
-	if err != nil {
-		return
+	baseDataListMap := make(map[int][]*data_manage.EdbDataList)
+	for _, v := range edbInfoList {
+		baseDataList := make([]*data_manage.EdbDataList, 0)
+		baseDataList, err = data_manage.GetEdbDataList(v.Source, v.SubSource, v.EdbInfoId, "", "")
+		if err != nil {
+			return
+		}
+		baseDataListMap[v.EdbInfoId] = baseDataList
 	}
 
 	latestDate := zlFutureGoodEdbInfoList[0].EndDate
@@ -170,33 +177,47 @@ func GetProfitChartEdbData(baseEdbInfo *data_manage.EdbInfo, zlFutureGoodEdbInfo
 	sort.Slice(dateList, func(i, j int) bool {
 		return dateList[i] < dateList[j]
 	})
+	var reqEdbInfoIds []int
+	for _, v := range edbInfoList {
+		reqEdbInfoIds = append(reqEdbInfoIds, v.EdbInfoId)
+		tmp := data_manage.XData{
+			Name:   v.EdbName,
+			NameEn: v.EdbNameEn,
+		}
+		xDataList = append(xDataList, tmp)
+	}
+	var edbIdList []int
 
-	_, yDataList, err = ProfitChartChartData(baseDataList, futureGoodEdbInfoDateMap, futureGoodDataListMap, chartInfoDateList, baseEdbInfo.EndDate, specialFutureGoodEdbInfoMap, formulaStr, tagEdbIdMap, dateList, maxN)
+	edbIdList, yDataList, err = ProfitChartChartData(baseEdbInfo, baseDataListMap, futureGoodEdbInfoDateMap, futureGoodDataListMap, chartInfoDateList, baseEdbInfo.EndDate, specialFutureGoodEdbInfoMap, formulaStr, tagEdbIdMap, dateList, maxN, reqEdbInfoIds)
 
-	tmpXDataList, newYDataList, err := handleProfitResultData(baseEdbInfo, yDataList, earliestDateTime)
+	// todo 最后处理数据
+	tmpXDataList, newYDataList, err := handleProfitResultData(xDataList, baseEdbInfo, yDataList, earliestDateTime, edbIdList)
 	if err != nil {
 		return
 	}
-	xDataList = []data_manage.XData{
-		{
-			Name:   "现货利润",
-			NameEn: "Spot Price",
-		},
+	if len(reqXDataList) == 0 {
+		xDataList = tmpXDataList
+	} else {
+		xDataList = reqXDataList
 	}
-	xDataList = append(xDataList, tmpXDataList...)
+
 	yDataList = newYDataList
 
 	return
 }
 
 // ProfitChartChartData 获取数据
-func ProfitChartChartData(baseDataList []*data_manage.EdbDataList, futureGoodEdbInfoMap map[int]map[string]*future_good.FutureGoodEdbInfo, futureGoodEdbDataListMap map[int][]*data_manage.EdbDataList, chartInfoDateList []request.ChartInfoDateReq, latestDate string, specialFutureGoodEdbInfoMap map[int]map[int]*future_good.FutureGoodEdbInfo, formulaStr string, tagEdbIdMap map[string]int, dateList []string, maxN int) (edbIdList []int, yDataList []data_manage.YData, err error) {
+func ProfitChartChartData(baseEdbInfo *data_manage.EdbInfo, baseDataListMap map[int][]*data_manage.EdbDataList, futureGoodEdbInfoMap map[int]map[string]*future_good.FutureGoodEdbInfo, futureGoodEdbDataListMap map[int][]*data_manage.EdbDataList, chartInfoDateList []request.ChartInfoDateReq, latestDate string, specialFutureGoodEdbInfoMap map[int]map[int]*future_good.FutureGoodEdbInfo, formulaStr string, tagEdbIdMap map[string]int, dateList []string, maxN int, reqEdbInfoIds []int) (edbIdList []int, yDataList []data_manage.YData, err error) {
 	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
 	//earliestDateTime time.Time
 	// ETA指标数据
-	baseEdbDateData := make(map[string]float64)
-	for _, edbData := range baseDataList {
-		baseEdbDateData[edbData.DataTime] = edbData.Value
+	allBaseEdbDateDataMap := make(map[int]map[string]float64)
+	for edbInfoId, baseDataList := range baseDataListMap {
+		baseEdbDateData := make(map[string]float64)
+		for _, edbData := range baseDataList {
+			baseEdbDateData[edbData.DataTime] = edbData.Value
+		}
+		allBaseEdbDateDataMap[edbInfoId] = baseEdbDateData
 	}
 
 	// 商品指标数据
@@ -243,23 +264,55 @@ func ProfitChartChartData(baseDataList []*data_manage.EdbDataList, futureGoodEdb
 			return
 		}
 
-		findDataList := make([]float64, 0) // 当前日期的数据值
-		noDataIdList := make([]int, 0)     // 没有数据的指标id
-		xEdbInfoIdList := make([]int, 0)   // 当前数据的指标id列表
+		findDataList := make([]float64, 0)  // 当前日期的数据值
+		noDataIdList := make([]int, 0)      // 没有数据的指标id
+		noDataIdMap := make(map[int]int, 0) // 没有数据的指标map
+		xEdbInfoIdList := make([]int, 0)    // 当前数据的指标id列表
 
 		// 现货指标
-		realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, baseDataList, baseEdbDateData)
+		index := 0
+		var realDateTime time.Time
+		// 现货指标
+		baseEdbDateData, ok := allBaseEdbDateDataMap[baseEdbInfo.EdbInfoId]
+		if !ok {
+			err = fmt.Errorf("指标id: %d 没有数据", baseEdbInfo.EdbInfoId)
+			return
+		}
+		realDateTime, findDataValue, isFind, tmpErr := GetNeedDateData(findDateTime, baseDataListMap[baseEdbInfo.EdbInfoId], baseEdbDateData, edbDataMap)
 		if tmpErr != nil {
 			err = tmpErr
 			return
 		}
-		findDataList = append(findDataList, findDataValue)
-		yDataMap[0] = findDataValue
 		if isFind {
 			maxDate = realDateTime
 		}
+		edbIdList = make([]int, 0) //普通指标ID
+		for _, edbInfoId := range reqEdbInfoIds {
+			if edbInfoId == baseEdbInfo.EdbInfoId {
+				findDataList = append(findDataList, findDataValue)
+				yDataMap[index] = findDataValue
+				xEdbInfoIdList = append(xEdbInfoIdList, edbInfoId)
+				edbIdList = append(edbIdList, edbInfoId)
+				index += 1
+				continue
+			}
+			baseEdbDateDataTmp, ok := allBaseEdbDateDataMap[edbInfoId]
+			if !ok {
+				err = fmt.Errorf("指标id: %d 没有数据", edbInfoId)
+				return
+			}
+			findDataValueTmp, isFindTmp := baseEdbDateDataTmp[realDateTime.Format(utils.FormatDate)]
+			if !isFindTmp {
+				noDataIdList = append(noDataIdList, edbInfoId)
+				noDataIdMap[edbInfoId] = edbInfoId
+			}
+			findDataList = append(findDataList, findDataValueTmp)
+			yDataMap[index] = findDataValueTmp
 
-		xEdbInfoIdList = append(xEdbInfoIdList, 0)
+			xEdbInfoIdList = append(xEdbInfoIdList, edbInfoId)
+			edbIdList = append(edbIdList, edbInfoId)
+			index += 1
+		}
 
 		mList := make([]int, 0) // 间隔月份
 
@@ -274,7 +327,7 @@ func ProfitChartChartData(baseDataList []*data_manage.EdbDataList, futureGoodEdb
 			//findDateTime
 
 			// 获取当前日期相对开始日期的期数
-			tmpN := (currDate.Year()-findDateTime.Year())*12 + int(currDate.Month()-findDateTime.Month())
+			tmpN := (currDate.Year()-realDateTime.Year())*12 + int(currDate.Month()-realDateTime.Month())
 			if tmpN <= 0 {
 				continue
 			}
@@ -329,7 +382,7 @@ func ProfitChartChartData(baseDataList []*data_manage.EdbDataList, futureGoodEdb
 			//计算公式异常,那么就移除该指标
 			if formulaFormStr == `` {
 				//removeDateList = append(removeDateList, sk)
-				//fmt.Println("异常了")
+				fmt.Println("异常了")
 				continue
 			}
 
@@ -382,6 +435,33 @@ func ProfitChartChartData(baseDataList []*data_manage.EdbDataList, futureGoodEdb
 			yDate = maxDate.Format(utils.FormatDate)
 		}
 
+		{
+			hasDataIndexList := make([]int, 0)
+			for dataK, edbInfoId := range xEdbInfoIdList {
+				if _, ok := noDataIdMap[edbInfoId]; !ok { // 如果是没有数据的指标id
+					hasDataIndexList = append(hasDataIndexList, dataK)
+				}
+			}
+			lenHasDataIndex := len(hasDataIndexList)
+			if lenHasDataIndex > 0 {
+				for lenHasDataI := 1; lenHasDataI < lenHasDataIndex; lenHasDataI++ {
+					perK := hasDataIndexList[lenHasDataI-1] //上一个有数据的指标下标
+					currK := hasDataIndexList[lenHasDataI]  //当前有数据的指标下标
+					preVal := findDataList[perK]            //上一个有数据的坐标的值
+					currVal := findDataList[currK]          //当前有数据的指标的值
+
+					// 环差值
+					hcValDeci := decimal.NewFromFloat(currVal).Sub(decimal.NewFromFloat(preVal)).Div(decimal.NewFromInt(int64(currK - perK)))
+					var tmpI int64
+					// 将两个中间的数据做平均值补全
+					for hcI := perK + 1; hcI < currK; hcI++ {
+						tmpI++
+						findDataList[hcI], _ = decimal.NewFromFloat(preVal).Add(hcValDeci.Mul(decimal.NewFromInt(tmpI))).RoundCeil(4).Float64()
+					}
+				}
+			}
+		}
+
 		yDataList = append(yDataList, data_manage.YData{
 			Date:           yDate,
 			ConfigDate:     realDateTime,
@@ -473,29 +553,37 @@ func getProfitFutureGoodEdbInfoList(earliestDateTime time.Time, zlFutureGoodEdbI
 }
 
 // handleProfitResultData 处理成最终的结果数据
-func handleProfitResultData(baseEdbInfo *data_manage.EdbInfo, yDataList []data_manage.YData, earliestDateTime time.Time) (xDataList []data_manage.XData, newYDataList []data_manage.YData, err error) {
-	xDataList = make([]data_manage.XData, 0)
+func handleProfitResultData(xDataListInit []data_manage.XData, baseEdbInfo *data_manage.EdbInfo, yDataList []data_manage.YData, earliestDateTime time.Time, allEdbInfoIds []int) (xDataList []data_manage.XData, newYDataList []data_manage.YData, err error) {
 	newYDataList = yDataList
+	xDataList = xDataListInit
 
 	nMap := make(map[int]int)
-
+	nList := make([]int, 0)
+	nListEdbMap := make(map[int]struct{})
 	for _, v := range yDataList {
 		for _, n := range v.XEdbInfoIdList {
-			nMap[n] = n
+			if utils.InArrayByInt(allEdbInfoIds, n) {
+				if _, ok := nListEdbMap[n]; !ok {
+					nList = append(nList, n)
+					nListEdbMap[n] = struct{}{}
+				}
+			} else {
+				nMap[n] = n
+			}
 		}
 	}
 
 	// 找出所有的N值,并进行正序排列
-	nList := make([]int, 0)
+	nListTmp := make([]int, 0)
 	for _, n := range nMap {
-		nList = append(nList, n)
+		nListTmp = append(nListTmp, n)
 	}
-	sort.Slice(nList, func(i, j int) bool {
-		return nList[i] < nList[j]
+	sort.Slice(nListTmp, func(i, j int) bool {
+		return nListTmp[i] < nListTmp[j]
 	})
-
+	nList = append(nList, nListTmp...)
 	for _, n := range nList {
-		if n == 0 {
+		if utils.InArrayByInt(allEdbInfoIds, n) {
 			continue
 		}
 		xDataList = append(xDataList, data_manage.XData{
@@ -516,7 +604,7 @@ func handleProfitResultData(baseEdbInfo *data_manage.EdbInfo, yDataList []data_m
 			if len(xEdbInfoIdList) > 0 {
 				currN := xEdbInfoIdList[0]
 				// 当前距离最早的日期相差的N数
-				if n == currN {
+				if n == currN { // todo 改成所有的基础现货指标
 					if needNum > 0 {
 						currVal := yData.Value[valIndex]
 						preVal := yData.Value[valIndex-1]
@@ -530,8 +618,12 @@ func handleProfitResultData(baseEdbInfo *data_manage.EdbInfo, yDataList []data_m
 							newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, tmpVal)
 						}
 					}
+					if utils.InArrayByInt(allEdbInfoIds, currN) {
+						newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN)
+					} else {
+						newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN+1)
+					}
 
-					newYDataList[yIndex].XEdbInfoIdList = append(newYDataList[yIndex].XEdbInfoIdList, currN+1)
 					newYDataList[yIndex].Value = append(newYDataList[yIndex].Value, yData.Value[valIndex])
 					valIndex++
 					needNum = 0
@@ -558,7 +650,7 @@ func handleProfitResultData(baseEdbInfo *data_manage.EdbInfo, yDataList []data_m
 	}
 
 	earliestDateTime = time.Date(earliestDateTime.Year(), earliestDateTime.Month(), 1, 0, 0, 0, 0, time.Local)
-	xDataList = xDataList[0:maxI]
+	xDataList = xDataList[0 : maxI+1]
 	for yIndex, yData := range newYDataList {
 		if len(yData.XEdbInfoIdList) > maxI+1 {
 			newYDataList[yIndex].XEdbInfoIdList = yData.XEdbInfoIdList[0 : maxI+1]
@@ -571,10 +663,11 @@ func handleProfitResultData(baseEdbInfo *data_manage.EdbInfo, yDataList []data_m
 
 		nameList := make([]string, 0)
 		enNameList := make([]string, 0)
-		for _, n := range newYDataList[yIndex].XEdbInfoIdList {
-			if n == 1 { // 现货价不处理
-				nameList = append(nameList, baseEdbInfo.EdbName)
-				enNameList = append(enNameList, baseEdbInfo.EdbNameEn)
+		for k1, n := range newYDataList[yIndex].XEdbInfoIdList {
+			if utils.InArrayByInt(allEdbInfoIds, n) { // 现货价不处理
+				tmpItem := xDataListInit[k1]
+				nameList = append(nameList, tmpItem.Name)
+				enNameList = append(enNameList, tmpItem.NameEn)
 				continue
 			}
 			if n <= 0 {
@@ -639,6 +732,7 @@ func ReplaceFormula(tagEdbIdMap map[string]int, valArr map[int]float64, formulaS
 	for k, v := range funMap {
 		formulaStr = strings.Replace(formulaStr, v, k, -1)
 	}
+	fmt.Println(formulaStr)
 	if replaceCount == len(tagEdbIdMap) {
 		return formulaStr
 	} else {

+ 161 - 2
services/data/mysteel_chemical.go

@@ -3,13 +3,16 @@ package data
 import (
 	"encoding/json"
 	"errors"
+	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/http"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/http"
 )
 
 // AddMysteelChemicalClassify 添加钢联化工分类
@@ -301,8 +304,137 @@ func DelMysteelChemicalClassify(classifyId int, sysUser *system.Admin) (err erro
 	return
 }
 
+// BatchAddMysteelChemicalIndex 批量添加钢联化工指标
+func BatchAddMysteelChemicalIndex(items []*data_manage.BaseFromMysteelChemicalIndex, lang string) (baseFromMysteelChemicalIndexs []*data_manage.BaseFromMysteelChemicalIndex, err error, errMsg string) {
+	indexCodeList := make([]string, 0)
+	for _, v := range items {
+		indexCodeList = append(indexCodeList, v.IndexCode)
+	}
+
+	baseFromMysteelChemicalIndexs, err = data_manage.GetBaseFromMysteelChemicalIndexByCodeList(indexCodeList)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = `获取数据失败`
+		return
+	}
+	err = nil
+	// 过滤已存在的指标
+	filterMap := make(map[string]struct{})
+	if len(baseFromMysteelChemicalIndexs) > 0 {
+		existIndexCodeList := make([]string, 0)
+		for _, v := range baseFromMysteelChemicalIndexs {
+			existIndexCodeList = append(existIndexCodeList, v.IndexCode)
+			filterMap[v.IndexCode] = struct{}{}
+		}
+		if lang == utils.EnLangVersion {
+			errMsg = fmt.Sprintf(`Metric ID: %s already exists, please re-enter.`, strings.Join(existIndexCodeList, ","))
+		} else {
+			errMsg = fmt.Sprintf(`指标ID: %s 已存在,请重新输入`, strings.Join(existIndexCodeList, ","))
+		}
+		return
+	}
+
+	terminal := getEdbTerminal(utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+	addItems := make([]*data_manage.BaseFromMysteelChemicalIndex, 0)
+	preAddIndexCodeItems := make([]string, 0)
+	for _, v := range items {
+		if _, ok := filterMap[v.IndexCode]; ok {
+			continue
+		}
+		v.TerminalCode = terminal.TerminalCode
+		addItems = append(addItems, v)
+		preAddIndexCodeItems = append(preAddIndexCodeItems, v.IndexCode)
+	}
+
+	_, err = data_manage.BatchAddBaseFromMysteelChemicalIndex(addItems)
+	if err != nil {
+		errMsg = "保存分类失败"
+		return
+	}
+	baseFromMysteelChemicalIndexs, err = data_manage.GetBaseFromMysteelChemicalIndexByCodeList(preAddIndexCodeItems)
+	if err != nil {
+		errMsg = "获取数据失败"
+		return
+	}
+	return
+}
+
+type HandleMysteelChemicalIndexReq struct {
+	List []*data_manage.BaseFromMysteelChemicalIndex
+}
+
+func BatchAddMysteelChemicalData(items []*data_manage.BaseFromMysteelChemicalIndex, lang string) (resp *models.BaseResponse, err error) {
+	url := utils.EDB_LIB_URL + "mysteel_chemical/handle/api/mysteel/index"
+
+	req := new(HandleMysteelChemicalIndexReq)
+	req.List = items
+	postData, err := json.Marshal(req)
+	if err != nil {
+		return
+	}
+	body, err := HttpPost(url, string(postData), lang)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(body, &resp); err != nil {
+		return
+	}
+	return
+}
+
+type MysteelChemicalIndexSource2EdbReq struct {
+	EdbCode       string
+	EdbName       string
+	Frequency     string
+	Unit          string
+	ClassifyId    int
+	AdminId       int
+	AdminRealName string
+}
+
+// MysteelChemicalIndexSource2Edb 新增钢联化工数据源到指标库
+func MysteelChemicalIndexSource2Edb(req MysteelChemicalIndexSource2EdbReq, lang string) (edb *data_manage.EdbInfo, err error, errMsg string, skip bool) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("MysteelChemicalIndexSource2Edb新增失败, Err: %s", err.Error())
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_MYSTEEL_CHEMICAL
+
+	// 是否新增过指标
+	exist, e := data_manage.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取指标是否存在失败, err: %s", e.Error())
+		return
+	}
+	if exist != nil {
+		skip = true
+		return
+	}
+
+	// 开始结束时间
+	var startDate, endDate string
+
+	// 新增指标库
+	edbInfo, e, msg, _ := EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, startDate, endDate, req.AdminId, req.AdminRealName, lang)
+	if e != nil {
+		errMsg = msg
+		err = fmt.Errorf("EdbInfo: 新增指标失败, err: %s", e.Error())
+		return
+	}
+
+	edb = edbInfo
+
+	return
+}
+
 // AddMysteelChemicalIndex 添加钢联化工指标
-func AddMysteelChemicalIndex(classifyId int, indexCode, updateWeek, updateTimeStr string, sysUserId int, sysUserName,lang string) (baseFromMysteelChemicalIndex *data_manage.BaseFromMysteelChemicalIndex, err error, errMsg string) {
+func AddMysteelChemicalIndex(classifyId int, indexCode, updateWeek, updateTimeStr string, sysUserId int, sysUserName, lang string) (baseFromMysteelChemicalIndex *data_manage.BaseFromMysteelChemicalIndex, err error, errMsg string) {
 	baseFromMysteelChemicalIndex, err = data_manage.GetBaseFromMysteelChemicalIndexByCode(indexCode)
 	if err != nil && err.Error() != utils.ErrNoRow() {
 		errMsg = `获取数据失败`
@@ -688,3 +820,30 @@ func getEdbTerminal(source int) (edbTerminal *data_manage.EdbTerminal) {
 
 	return
 }
+
+// RefreshMysteelChemicalData 根据手工指标code刷新手工指标数据
+func RefreshMysteelChemicalData(edbCode string) {
+	var err error
+	var errMsg string
+	defer func() {
+		if err != nil {
+			go alarm_msg.SendAlarmMsg("根据钢联化工的code刷新指标数据失败提醒,Err"+err.Error(), 3)
+			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"导入手工数据后,根据手工指标code刷新手工指标数据失败提醒", "errmsg:"+err.Error(), utils.EmailSendToUsers)
+		}
+		if errMsg != "" {
+			go alarm_msg.SendAlarmMsg("根据钢联化工的code刷新指标数据失败提醒,errMsg"+errMsg, 3)
+		}
+	}()
+
+	baseFromMysteelChemicalIndexs, err := data_manage.GetBaseFromMysteelChemicalIndexByCodeList([]string{edbCode})
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+	resp, err := BatchAddMysteelChemicalData(baseFromMysteelChemicalIndexs, "zh")
+	if err != nil {
+		return
+	}
+	if !resp.Success {
+		errMsg = resp.ErrMsg
+	}
+}

+ 2 - 0
services/elastic.go

@@ -107,6 +107,8 @@ func EsAddOrEditReport(indexName, docId string, item *models.ElasticReportDetail
 			"ClassifyNameFirst":  item.ClassifyNameFirst,
 			"ClassifyIdSecond":   item.ClassifyIdSecond,
 			"ClassifyNameSecond": item.ClassifyNameSecond,
+			"ClassifyId":         item.ClassifyId,
+			"ClassifyName":       item.ClassifyName,
 			"Categories":         item.Categories,
 			"StageStr":           item.StageStr,
 		}).Do(context.Background())

+ 3 - 1
services/eta_forum/eta_forum_hub.go

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

+ 99 - 0
services/file.go

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

+ 52 - 10
services/ppt.go

@@ -41,7 +41,7 @@ type PPTContentElements struct {
 }
 
 // SavePPTReport 保存PPT报告
-func SavePPTReport(pptId, classifyIdSecond int, title string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
+func SavePPTReport(pptId, classifyId int, title string, adminInfo *system.Admin) (reportId int, reportCode, errMsg string, err error) {
 	defer func() {
 		if err != nil {
 			utils.FileLog.Info("%s", err.Error())
@@ -68,7 +68,7 @@ func SavePPTReport(pptId, classifyIdSecond int, title string, adminInfo *system.
 	}
 
 	// 2023-02-21 PPT可多次转为报告, 不做关联
-	if classifyIdSecond == 0 {
+	if classifyId == 0 {
 		errMsg = "请选择报告类型"
 		err = errors.New("请选择报告类型")
 		return
@@ -90,17 +90,48 @@ func SavePPTReport(pptId, classifyIdSecond int, title string, adminInfo *system.
 		classifyMap[v.Id] = v
 	}
 	classifyIdFirst := 0
+	classifyIdSecond := 0
+	classifyIdThird := 0
 	classifyNameFirst := ""
 	classifyNameSecond := ""
-	secondClassify := classifyMap[classifyIdSecond]
-	if secondClassify != nil {
-		classifyNameSecond = secondClassify.ClassifyName
-		firstClassify := classifyMap[secondClassify.ParentId]
-		if firstClassify != nil {
-			classifyIdFirst = firstClassify.Id
-			classifyNameFirst = firstClassify.ClassifyName
-		}
+	classifyNameThird := ""
+
+	// 最小单元分类,第二级别的分类 ,最大的分类
+	var baseClassify, twoClassify, threeClassify *models.Classify
+
+	var hasTwo, hasThird bool
+	baseClassify, ok := classifyMap[classifyId]
+	if !ok {
+		errMsg = "分类异常"
+		err = errors.New("获取分类失败 ")
+		return
+	}
+	twoClassify, hasTwo = classifyMap[baseClassify.ParentId]
+	if hasTwo {
+		threeClassify, hasThird = classifyMap[twoClassify.ParentId]
+	}
+
+	if hasThird { // 如果确实是有三级分类
+		classifyIdFirst = threeClassify.Id
+		classifyNameFirst = threeClassify.ClassifyName
+
+		classifyIdSecond = twoClassify.Id
+		classifyNameSecond = twoClassify.ClassifyName
+
+		classifyIdThird = baseClassify.Id
+		classifyNameThird = baseClassify.ClassifyName
+	} else if hasTwo {
+		classifyIdFirst = twoClassify.Id
+		classifyNameFirst = twoClassify.ClassifyName
+
+		classifyIdSecond = baseClassify.Id
+		classifyNameSecond = baseClassify.ClassifyName
+
+	} else {
+		classifyIdFirst = baseClassify.Id
+		classifyNameFirst = baseClassify.ClassifyName
 	}
+
 	// 新增报告
 	nowTime := time.Now().Local()
 	reportReq := &models.AddReq{
@@ -109,6 +140,8 @@ func SavePPTReport(pptId, classifyIdSecond int, title string, adminInfo *system.
 		ClassifyNameFirst:  classifyNameFirst,
 		ClassifyIdSecond:   classifyIdSecond,
 		ClassifyNameSecond: classifyNameSecond,
+		ClassifyIdThird:    classifyIdThird,
+		ClassifyNameThird:  classifyNameThird,
 		Title:              title,
 		Abstract:           "",
 		Author:             "FICC团队",
@@ -117,7 +150,16 @@ func SavePPTReport(pptId, classifyIdSecond int, title string, adminInfo *system.
 		Content:            htm,
 		CreateTime:         nowTime.Format(utils.FormatDateTime),
 		ReportVersion:      2,
+		CollaborateType:    1, // 协作方式,1:个人,2:多人协作。默认:1
+		ReportLayout:       1, // 报告布局,1:常规布局,2:智能布局。默认:1
+		IsPublicPublish:    2, // 是否公开发布,1:是,2:否
+	}
+
+	// 如果PPT是公开的,则报告也公开发布
+	if item.IsShare == 1 {
+		reportReq.IsPublicPublish = 1
 	}
+
 	newReportId, newCode, _, e := CreateNewReport(*reportReq, adminInfo)
 	if e != nil {
 		errMsg = "转换失败"

Some files were not shown because too many files changed in this diff