Browse Source

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

zqbao 8 months ago
parent
commit
6d777c2552
89 changed files with 13783 additions and 525 deletions
  1. 3 2
      controllers/base_auth.go
  2. 3 0
      controllers/business_conf.go
  3. 51 0
      controllers/data_manage/base_from_ly_classify_controller.go
  4. 525 0
      controllers/data_manage/base_from_ly_index_controller.go
  5. 133 1
      controllers/data_manage/bloomberg_data.go
  6. 134 1
      controllers/data_manage/business_data.go
  7. 4 2
      controllers/data_manage/chart_info.go
  8. 85 1
      controllers/data_manage/edb_info.go
  9. 37 0
      controllers/data_manage/edb_info_refresh.go
  10. 614 11
      controllers/data_manage/eia_steo.go
  11. 556 1
      controllers/data_manage/fenwei_data.go
  12. 1 0
      controllers/data_manage/future_good/future_good_chart_info.go
  13. 1 0
      controllers/data_manage/future_good/future_good_profit_chart_info.go
  14. 74 2
      controllers/data_manage/manual_edb.go
  15. 1 101
      controllers/data_manage/mysteel_chemical_data.go
  16. 87 38
      controllers/data_manage/range_analysis/chart_info.go
  17. 430 0
      controllers/document_manage/document_manage_controller.go
  18. 1111 0
      controllers/hisugar_data.go
  19. 80 0
      controllers/resource.go
  20. 7 2
      controllers/sys_admin.go
  21. 334 2
      controllers/trade_analysis/trade_analysis.go
  22. 1812 0
      controllers/trade_analysis/warehouse.go
  23. 683 0
      controllers/trade_analysis/warehouse_classify.go
  24. 22 1
      controllers/user_login.go
  25. 1 1
      go.mod
  26. 13 1
      models/chart_permission.go
  27. 21 0
      models/classify.go
  28. 80 0
      models/common_classify.go
  29. 36 16
      models/data_manage/base_from_eia_stero.go
  30. 141 0
      models/data_manage/base_from_fenwei.go
  31. 13 0
      models/data_manage/base_from_fenwei_classify.go
  32. 393 0
      models/data_manage/base_from_hisugar.go
  33. 48 0
      models/data_manage/base_from_ly_classify.go
  34. 108 0
      models/data_manage/base_from_ly_data.go
  35. 244 0
      models/data_manage/base_from_ly_index.go
  36. 14 2
      models/data_manage/chart_info.go
  37. 20 0
      models/data_manage/chart_series.go
  38. 1 0
      models/data_manage/edb_classify.go
  39. 4 0
      models/data_manage/edb_data_base.go
  40. 39 0
      models/data_manage/edb_data_hisugar.go
  41. 6 0
      models/data_manage/edb_info.go
  42. 18 17
      models/data_manage/edb_info_calculate.go
  43. 16 5
      models/data_manage/edb_info_relation.go
  44. 7 0
      models/data_manage/multiple_graph_config_chart_mapping.go
  45. 23 0
      models/data_manage/mysteel_chemical_index.go
  46. 122 0
      models/data_manage/trade_analysis/base_from_trade_exchange.go
  47. 20 0
      models/data_manage/trade_analysis/request/warehouse.go
  48. 17 0
      models/data_manage/trade_analysis/response/warehouse.go
  49. 372 0
      models/data_manage/trade_analysis/trade_analysis.go
  50. 76 0
      models/data_manage/trade_analysis/trade_classify.go
  51. 129 0
      models/data_manage/trade_analysis/trade_futures_company.go
  52. 103 0
      models/data_manage/trade_analysis/warehouse.go
  53. 340 0
      models/data_manage/trade_analysis/warehouse_process_classify.go
  54. 120 0
      models/document_manage_model/outside_report.go
  55. 48 0
      models/document_manage_model/outside_report_attachment.go
  56. 33 0
      models/manual_edb.go
  57. 34 2
      models/report.go
  58. 31 20
      models/target.go
  59. 567 0
      routers/commentsRouter.go
  60. 10 0
      routers/router.go
  61. 43 72
      services/classify.go
  62. 6 0
      services/data/base_edb_lib.go
  63. 79 0
      services/data/base_from_eia_steo.go
  64. 162 0
      services/data/base_from_fenwei_index_service.go
  65. 115 0
      services/data/base_from_hisugar.go
  66. 47 0
      services/data/base_from_ly_classify_service.go
  67. 249 0
      services/data/base_from_ly_index_service.go
  68. 320 4
      services/data/chart_info.go
  69. 485 0
      services/data/common_classify.go
  70. 106 0
      services/data/common_classify_ctx.go
  71. 2 1
      services/data/edb_classify.go
  72. 1 1
      services/data/edb_data.go
  73. 24 0
      services/data/edb_info.go
  74. 80 0
      services/data/edb_info_calculate.go
  75. 33 5
      services/data/edb_info_relation.go
  76. 2 0
      services/data/excel/custom_analysis_edb.go
  77. 53 16
      services/data/manual.go
  78. 7 5
      services/data/predict_edb_info.go
  79. 16 16
      services/data/predict_edb_info_rule.go
  80. 13 6
      services/data/trade_analysis/trade_analysis.go
  81. 647 0
      services/data/trade_analysis/trade_analysis_data.go
  82. 181 0
      services/data/trade_analysis/trade_analysis_interface.go
  83. 418 0
      services/data/trade_analysis/warehouse.go
  84. 436 0
      services/document_manage_service/document_manage_service.go
  85. 44 166
      services/email.go
  86. 1 1
      services/ppt.go
  87. 12 3
      services/report_v2.go
  88. 106 0
      utils/common.go
  89. 39 0
      utils/constants.go

+ 3 - 2
controllers/base_auth.go

@@ -209,7 +209,9 @@ func (c *BaseAuthController) Prepare() {
 			}
 			var api string
 			for _, v := range list {
-				api += v.Api + "&"
+				if v.Api != "" {
+					api += v.Api + "&"
+				}
 			}
 			//处理uri请求,去除前缀和参数
 			api = strings.TrimRight(api, "&")
@@ -290,7 +292,6 @@ func (c *BaseAuthController) ServeJSON(encoding ...bool) {
 		}
 	}
 
-
 	//新增uuid记录
 	{
 		if _, ok := AdminOperateRecordMap[urlPath]; !ok && !strings.Contains(urlPath, "cygx") {

+ 3 - 0
controllers/business_conf.go

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

+ 51 - 0
controllers/data_manage/base_from_ly_classify_controller.go

@@ -0,0 +1,51 @@
+// Package data_manage
+// @Author gmy 2024/8/12 14:32:00
+package data_manage
+
+import (
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/services/data"
+)
+
+type BaseFromLyClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// LyClassifyList
+// @Title 包含分类下的指标名称
+// @Description 包含分类下的指标名称
+// @Success 200 {object} []data_manage.BaseFromLyClassifyAndIndexInfo
+// @router /ly/classify/list [get]
+func (this *BaseFromLyClassifyController) LyClassifyList() {
+	br := new(models.BaseResponse).Init()
+
+	// 下面段代码能否统一处理???
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	// 获取分类列表
+	lyClassifyList, err := data.LyClassifyList()
+	if err != nil {
+		br.Msg = "获取分类列表失败"
+		br.ErrMsg = "获取分类列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = lyClassifyList
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 525 - 0
controllers/data_manage/base_from_ly_index_controller.go

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

+ 133 - 1
controllers/data_manage/bloomberg_data.go

@@ -10,10 +10,11 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // BloombergDataController 彭博数据源
@@ -521,3 +522,134 @@ func (this *BloombergDataController) AddCheck() {
 	br.Ret = 200
 	br.Success = true
 }
+
+// EdbInfoAdd
+// @Title 加入到指标库
+// @Description 加入到指标库
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /bloomberg_source/edb_add [post]
+func (this *BloombergDataController) EdbInfoAdd() {
+	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_SINGLE_ADD_BLOOMBERG_" + 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
+	}
+	req := new(data_manage.AddEdbInfoReq)
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.EdbCode == "" {
+		br.Msg = "指标ID不可为空"
+		return
+	}
+	req.EdbName = strings.TrimSpace(req.EdbName)
+	if req.EdbName == "" {
+		br.Msg = "请输入指标名称"
+		return
+	}
+	req.Frequency = strings.TrimSpace(req.Frequency)
+	if req.Frequency == "" {
+		br.Msg = "请选择频度"
+		return
+	}
+	req.Unit = strings.TrimSpace(req.Unit)
+	if req.Unit == "" {
+		br.Msg = "请输入单位"
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	// 新增指标
+	var r data.BloombergIndexSource2EdbReq
+	r.EdbCode = req.EdbCode
+	r.EdbName = req.EdbName
+	r.Frequency = req.Frequency
+	r.Unit = req.Unit
+	r.ClassifyId = req.ClassifyId
+	r.AdminId = this.SysUser.AdminId
+	r.AdminRealName = this.SysUser.RealName
+
+	edbInfo, e, errMsg, skip := data.BloombergIndexSource2Edb(r, this.Lang)
+	if e != nil {
+		br.Msg = "操作失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = e.Error()
+		return
+	}
+	if skip {
+		br.Msg = "指标已存在"
+		return
+	}
+
+	// 试用平台更新用户累计新增指标数
+	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
+}

+ 134 - 1
controllers/data_manage/business_data.go

@@ -11,10 +11,11 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // EdbBusinessController 自有数据(商家)
@@ -589,3 +590,135 @@ func (c *EdbBusinessController) DataList() {
 	br.Msg = "获取成功"
 	br.Data = resp
 }
+
+// EdbInfoAdd
+// @Title 加入到指标库
+// @Description 加入到指标库
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /business_data/edb_add [post]
+func (c *EdbBusinessController) EdbInfoAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_SINGLE_ADD_BUSINESS_" + 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(c.Ctx.Input.RequestBody)
+		return
+	}
+
+	req := new(data_manage.AddEdbInfoReq)
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	if req.EdbCode == "" {
+		br.Msg = "指标ID不可为空"
+		return
+	}
+	req.EdbName = strings.TrimSpace(req.EdbName)
+	if req.EdbName == "" {
+		br.Msg = "请输入指标名称"
+		return
+	}
+	req.Frequency = strings.TrimSpace(req.Frequency)
+	if req.Frequency == "" {
+		br.Msg = "请选择频度"
+		return
+	}
+	req.Unit = strings.TrimSpace(req.Unit)
+	if req.Unit == "" {
+		br.Msg = "请输入单位"
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	// 新增指标
+	var r data.BusinessIndexSource2EdbReq
+	r.EdbCode = req.EdbCode
+	r.EdbName = req.EdbName
+	r.Frequency = req.Frequency
+	r.Unit = req.Unit
+	r.ClassifyId = req.ClassifyId
+	r.AdminId = c.SysUser.AdminId
+	r.AdminRealName = c.SysUser.RealName
+
+	edbInfo, e, errMsg, skip := data.BusinessIndexSource2Edb(r, c.Lang)
+	if e != nil {
+		br.Msg = "操作失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = e.Error()
+		return
+	}
+	if skip {
+		br.Msg = "指标已存在"
+		return
+	}
+
+	// 试用平台更新用户累计新增指标数
+	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(c.Ctx.Input.RequestBody)
+		edbLog.Status = "新增指标"
+		edbLog.Method = c.Ctx.Input.URI()
+		go data_manage.AddEdbInfoLog(edbLog)
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}

+ 4 - 2
controllers/data_manage/chart_info.go

@@ -873,6 +873,8 @@ func (this *ChartInfoController) ChartInfoMove() {
 			br.ErrMsg = "修改失败,Err:" + err.Error()
 			return
 		}
+		//添加es数据
+		go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 	}
 
 	if err != nil {
@@ -3105,6 +3107,7 @@ func (this *ChartInfoController) CopyChartInfo() {
 	chartInfo := &data_manage.ChartInfo{
 		ChartInfoId:     0,
 		ChartName:       req.ChartName,
+		ChartNameEn:     req.ChartName,
 		ChartClassifyId: req.ChartClassifyId,
 		SysUserId:       sysUser.AdminId,
 		SysUserRealName: sysUser.RealName,
@@ -4461,7 +4464,6 @@ func (this *ChartInfoController) PreviewSeasonChartInfo() {
 	br.Data = resp
 }
 
-
 // ChartInfoImgSetBySvg
 // @Title 图表图片上传
 // @Param   Img   query   string  true       "图片"
@@ -4519,4 +4521,4 @@ func (this *ChartInfoController) ChartInfoImgSetBySvg() {
 	br.Success = true
 	br.Data = resp
 	return
-}
+}

+ 85 - 1
controllers/data_manage/edb_info.go

@@ -1888,7 +1888,54 @@ func (this *EdbInfoController) EdbInfoSearch() {
 			indexInfo, err := data_manage.GetBaseInfoFromOilchemByIndexCode(edbCode)
 			if err != nil && err.Error() != utils.ErrNoRow() {
 				br.Msg = "获取失败"
-				br.ErrMsg = "获取上期所指标详情失败,Err:" + err.Error()
+				br.ErrMsg = "获取隆众资讯指标详情失败,Err:" + err.Error()
+				return
+			}
+
+			if indexInfo != nil {
+				searchItem.Frequency = indexInfo.Frequency
+				searchItem.Unit = indexInfo.Unit
+				searchItem.EdbName = indexInfo.IndexName
+			}
+		}  else if source == utils.DATA_SOURCE_HISUGAR { //泛糖科技
+			dataItems, err := data_manage.GetEdbDataAllByEdbCode(edbCode, source, subSource, utils.EDB_DATA_LIMIT)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取泛糖科技已存在信息失败,Err:" + err.Error()
+				return
+			}
+
+			if len(dataItems) > 0 {
+				searchItem.EdbCode = edbCode
+				minDate, maxDate, err := data_manage.GetEdbDataHisugarMaxOrMinDate(edbCode)
+				if err != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取泛糖科技日期信息失败,Err:" + err.Error()
+					return
+				}
+				searchItem.DataList = dataItems
+				searchItem.StartDate = minDate
+				searchItem.EndDate = maxDate
+			} else {
+				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
+				}
+				isAdd = true
+			}
+
+			//获取指标信息
+			indexInfo, err := data_manage.GetBaseInfoFromHisugarByIndexCode(edbCode)
+			if err != nil && err.Error() != utils.ErrNoRow() {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取泛糖科技指标详情失败,Err:" + err.Error()
 				return
 			}
 
@@ -2232,6 +2279,8 @@ func (this *EdbInfoController) EdbInfoList() {
 
 	//获取英文频度
 	edbInfoItem.FrequencyEn = data.GetFrequencyEn(edbInfoItem.Frequency)
+	// 获取是否供应商停更
+	edbInfoItem.IsSupplierStop = data.GetIsSupplierStop(edbInfoItem.Source, edbInfoItem.EdbCode)
 
 	//查询目录
 	classifyList, err, errMsg := data.GetFullClassifyByClassifyId(edbInfoItem.ClassifyId)
@@ -3147,6 +3196,8 @@ func (this *EdbInfoController) EdbInfoMove() {
 			br.ErrMsg = "修改失败,Err:" + err.Error()
 			return
 		}
+		//添加es
+		data.AddOrEditEdbInfoToEs(req.EdbInfoId)
 	}
 
 	if err != nil {
@@ -3850,6 +3901,9 @@ func (this *ChartInfoController) EdbInfoData() {
 		return
 	}
 	fullEdb.EdbInfo = edbInfo
+	// 是否供应商停更
+	fullEdb.IsSupplierStop = data.GetIsSupplierStop(edbInfo.Source, edbInfo.EdbCode)
+
 	fullEdb.ClassifyList = classifyList
 
 	var currClassifyItem *data_manage.EdbClassifyIdItems
@@ -5506,12 +5560,42 @@ func (this *EdbInfoController) EdbChartList() {
 				return
 			}
 
+			issSupplierStopIndexCodeMap := make(map[string]bool)
+			{
+				tmpEdbCodeList := make([]string, 0)
+				for _, v := range list {
+					if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+						tmpEdbCodeList = append(tmpEdbCodeList, v.EdbCode)
+					}
+				}
+				if len(tmpEdbCodeList) > 0 {
+					notIsSupplierStopIndexList, e := data_manage.GetNotIsSupplierStopIndexByCodeList(tmpEdbCodeList, 1)
+					if e != nil {
+						br.Msg = "获取失败"
+						br.ErrMsg = "获取数据失败,Err:" + e.Error()
+						return
+					}
+
+					// 已被供应商暂停的指标编码
+					for _, v := range notIsSupplierStopIndexList {
+						issSupplierStopIndexCodeMap[v.IndexCode] = true
+					}
+				}
+			}
+
 			for _, v := range list {
 				if currClassify, ok := classifyMap[v.ClassifyId]; ok {
 					v.HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(v.IsJoinPermission, currClassify.IsJoinPermission, v.EdbInfoId, v.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
 
 					v.Button = data.GetEdbOpButton(sysUser, v.SysUserId, v.EdbType, v.EdbInfoType, v.HaveOperaAuth)
 				}
+
+				// 供应商停用
+				if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+					if _, ok := issSupplierStopIndexCodeMap[v.EdbCode]; ok {
+						v.IsSupplierStop = 1
+					}
+				}
 			}
 		}
 

+ 37 - 0
controllers/data_manage/edb_info_refresh.go

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

+ 614 - 11
controllers/data_manage/eia_steo.go

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

+ 556 - 1
controllers/data_manage/fenwei_data.go

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

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

@@ -2862,6 +2862,7 @@ func copyChartInfo(oldChartInfo *data_manage.ChartInfo, chartClassifyId int, cha
 	chartInfo = &data_manage.ChartInfo{
 		ChartInfoId:     0,
 		ChartName:       chartName,
+		ChartNameEn:     chartName,
 		ChartClassifyId: chartClassifyId,
 		SysUserId:       sysUser.AdminId,
 		SysUserRealName: sysUser.RealName,

+ 1 - 0
controllers/data_manage/future_good/future_good_profit_chart_info.go

@@ -771,6 +771,7 @@ func copyProfitChartInfo(oldChartInfo *data_manage.ChartInfo, chartClassifyId in
 	chartInfo = &data_manage.ChartInfo{
 		ChartInfoId:     0,
 		ChartName:       chartName,
+		ChartNameEn:     chartName,
 		ChartClassifyId: chartClassifyId,
 		SysUserId:       sysUser.AdminId,
 		SysUserRealName: sysUser.RealName,

+ 74 - 2
controllers/data_manage/manual_edb.go

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

+ 1 - 101
controllers/data_manage/mysteel_chemical_data.go

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

+ 87 - 38
controllers/data_manage/range_analysis/chart_info.go

@@ -127,6 +127,10 @@ func (this *RangeChartChartInfoController) Preview() {
 		}
 		dataResp.MultipleGraphConfigId = multipleGraphConfig.MultipleGraphConfigId
 	}
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
@@ -1388,7 +1392,6 @@ func (this *RangeChartChartInfoController) MultipleGraphConfigSaveEdb() {
 		Success: make([]data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp, 0),
 	}
 	// 普通指标批量新增\批量编辑
-	var saveReq data_manage.BatchEdbInfoCalculateBatchSaveReq
 	calculateEdbList := make([]data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp, 0)
 	addReqEdbList := make([]*data_manage.CalculateEdbInfoItem, 0)
 	editReqEdbList := make([]*data_manage.CalculateEdbInfoItem, 0)
@@ -1460,56 +1463,102 @@ func (this *RangeChartChartInfoController) MultipleGraphConfigSaveEdb() {
 		return
 	}
 	if req.EdbInfoType == 0 { //普通指标
-		saveReq.Source = utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS
-		saveReq.CalculateFormula = extraConfig
-		saveReq.AdminId = sysUser.AdminId
-		saveReq.AdminName = sysUser.RealName
-		if len(addReqEdbList) > 0 {
-			saveReq.EdbList = addReqEdbList
+		for _, v := range addReqEdbList {
+			req2 := &data_manage.EdbInfoCalculateBatchSaveReqByEdbLib{
+				AdminId:    sysUser.AdminId,
+				AdminName:  sysUser.RealName,
+				EdbInfoId:  v.EdbInfoId,
+				EdbName:    v.EdbName,
+				Frequency:  v.Frequency,
+				Unit:       v.Unit,
+				ClassifyId: v.ClassifyId,
+				//Formula:          v.Formula, //N数值移动平均计算、环比值、环差值
+				FromEdbInfoId:    v.FromEdbInfoId,
+				CalculateFormula: extraConfig,
+				Source:           utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS,
+			}
+
 			// 调用指标库去更新
-			reqJson, err := json.Marshal(saveReq)
+			reqJson, err := json.Marshal(req2)
 			if err != nil {
 				br.Msg = "参数解析异常!"
 				br.ErrMsg = "参数解析失败,Err:" + err.Error()
 				return
 			}
-			respItem, err := data.BatchSaveEdbCalculateMultiData(string(reqJson), this.Lang)
-			if err != nil {
-				br.Msg = "新增失败!"
-				br.ErrMsg = "新增失败,Err:" + err.Error()
-				return
+			respItem, e := data.BatchSaveEdbCalculateData(string(reqJson), this.Lang)
+			if e != nil {
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         "新增失败!",
+					ErrMsg:      "新增失败,Err:" + e.Error(),
+				})
+				continue
 			}
 			if respItem.Ret != 200 {
-				br.Msg = respItem.Msg
-				br.ErrMsg = respItem.ErrMsg
-				return
+				resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+					CalculateId: v.CalculateId,
+					Msg:         respItem.Msg,
+					ErrMsg:      respItem.ErrMsg,
+				})
+				continue
 			}
-			resp = respItem.Data
-			calculateEdbList = respItem.Data.Success
+
+			calculateEdbList = append(calculateEdbList, data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp{
+				CalculateId: v.CalculateId,
+				EdbInfoId:   respItem.Data.EdbInfoId,
+				UniqueCode:  respItem.Data.UniqueCode,
+				ClassifyId:  v.ClassifyId,
+			})
 		}
 
 		if len(editReqEdbList) > 0 {
-			saveReq.EdbList = editReqEdbList
-			// 调用指标库去更新
-			reqJson, err := json.Marshal(saveReq)
-			if err != nil {
-				br.Msg = "参数解析异常!"
-				br.ErrMsg = "参数解析失败,Err:" + err.Error()
-				return
-			}
-			respItem, err := data.BatchEditEdbCalculateMultiData(string(reqJson), this.Lang)
-			if err != nil {
-				br.Msg = "更新失败!"
-				br.ErrMsg = "更新失败,Err:" + err.Error()
-				return
-			}
-			if respItem.Ret != 200 {
-				br.Msg = respItem.Msg
-				br.ErrMsg = respItem.ErrMsg
-				return
+			for _, v := range editReqEdbList {
+				req2 := &data_manage.EdbInfoCalculateBatchEditReqByEdbLib{
+					AdminId:    sysUser.AdminId,
+					AdminName:  sysUser.RealName,
+					EdbInfoId:  v.EdbInfoId,
+					EdbName:    v.EdbName,
+					Frequency:  v.Frequency,
+					Unit:       v.Unit,
+					ClassifyId: v.ClassifyId,
+					//Formula:          v.Formula, //N数值移动平均计算、环比值、环差值
+					FromEdbInfoId:    v.FromEdbInfoId,
+					CalculateFormula: extraConfig,
+					Source:           utils.DATA_SOURCE_CALCULATE_RANGEANLYSIS,
+				}
+
+				// 调用指标库去更新
+				reqJson, err := json.Marshal(req2)
+				if err != nil {
+					br.Msg = "参数解析异常!"
+					br.ErrMsg = "参数解析失败,Err:" + err.Error()
+					return
+				}
+				respItem, e := data.BatchEditEdbCalculateData(string(reqJson), this.Lang)
+				if e != nil {
+					resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+						CalculateId: v.CalculateId,
+						Msg:         "新增失败!",
+						ErrMsg:      "新增失败,Err:" + e.Error(),
+					})
+					continue
+				}
+				if respItem.Ret != 200 {
+					resp.Fail = append(resp.Fail, data_manage.BatchEdbInfoCalculateBatchSaveFailResp{
+						CalculateId: v.CalculateId,
+						Msg:         respItem.Msg,
+						ErrMsg:      respItem.ErrMsg,
+					})
+					continue
+				}
+
+				calculateEdbList = append(calculateEdbList, data_manage.BatchEdbInfoCalculateBatchSaveSuccessResp{
+					CalculateId: v.CalculateId,
+					EdbInfoId:   respItem.Data.EdbInfoId,
+					UniqueCode:  respItem.Data.UniqueCode,
+					ClassifyId:  v.ClassifyId,
+				})
 			}
-			resp.Fail = append(resp.Fail, respItem.Data.Fail...)
-			calculateEdbList = append(calculateEdbList, respItem.Data.Success...)
 		}
 	} else if req.EdbInfoType == 1 {
 		// 预测指标

+ 430 - 0
controllers/document_manage/document_manage_controller.go

@@ -0,0 +1,430 @@
+// Package document_manage
+// @Author gmy 2024/9/19 14:09:00
+package document_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/document_manage_model"
+	"eta/eta_api/services/document_manage_service"
+	"eta/eta_api/utils"
+	"strings"
+)
+
+// DocumentManageController 文档管理库
+type DocumentManageController struct {
+	controllers.BaseAuthController
+}
+
+// ValidateUser
+// 处理响应和校验
+func ValidateUser(this *DocumentManageController, br *models.BaseResponse) bool {
+	// 验证用户是否已登录
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return false
+	}
+
+	return true
+}
+
+// DocumentClassifyList
+// @Title 文档分类列表
+// @Description 文档分类列表
+// @Success 200 {object} []models.ClassifyVO
+// @router /document/classify/list [get]
+func (this *DocumentManageController) DocumentClassifyList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	classifyList, err := document_manage_service.DocumentClassifyList()
+	if err != nil {
+		br.Msg = "获取分类列表失败"
+		br.ErrMsg = "获取分类列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = classifyList
+	return
+}
+
+// DocumentVarietyList
+// @Title 文档品种列表
+// @Description 文档品种列表
+// @Success 200 {object} []models.ChartPermission
+// @router /document/variety/list [get]
+func (this *DocumentManageController) DocumentVarietyList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	chartPermissionList, err := document_manage_service.DocumentVarietyList()
+	if err != nil {
+		br.Msg = "获取品种列表失败"
+		br.ErrMsg = "获取品种列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = chartPermissionList
+	return
+}
+
+// DocumentReportList
+// @Title 文档管理库报告列表
+// @Description 文档管理库报告列表
+// @Success 200 {object} document_manage_model.OutsideReportPage
+// @router /document/report/list [get]
+func (this *DocumentManageController) DocumentReportList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	// 文档类型 1-文档管理库 2-战研中心-pci
+	documentType, err := this.GetInt("DocumentType")
+	if err != nil {
+		br.Msg = "获取文档类型失败"
+		br.ErrMsg = "获取文档类型失败,Err:" + err.Error()
+		return
+	}
+	if documentType == 0 {
+		br.Msg = "文档类型不能为空"
+		br.ErrMsg = "文档类型不能为空"
+		return
+	}
+
+	chartPermissionIdString := this.GetString("ChartPermissionIdList")
+	var chartPermissionIdList []string
+	if strings.TrimSpace(chartPermissionIdString) != "" {
+		chartPermissionIdList = strings.Split(chartPermissionIdString, ",")
+	}
+
+	classifyIdString := this.GetString("ClassifyIdList")
+	var classifyIdList []string
+	if strings.TrimSpace(classifyIdString) != "" {
+		classifyIdList = strings.Split(classifyIdString, ",")
+	}
+
+	keyword := this.GetString("Keyword")
+	orderField := this.GetString("OrderField")
+	orderType := this.GetString("OrderType")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	documentReportPage, err := document_manage_service.DocumentReportList(documentType, chartPermissionIdList, classifyIdList, keyword, orderField, orderType, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取报告列表失败"
+		br.ErrMsg = "获取报告列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = documentReportPage
+	return
+}
+
+// RuiSiReportList
+// @Title 睿思报告列表
+// @Description 睿思报告列表
+// @Success 200 {object} models.ReportListResp
+// @router /document/rui/si/report/list [get]
+func (this *DocumentManageController) RuiSiReportList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	classifyIdFirst, _ := this.GetInt("ClassifyIdFirst", 0)
+	classifyIdSecond, _ := this.GetInt("ClassifyIdSecond", 0)
+	classifyIdThird, _ := this.GetInt("ClassifyIdThird", 0)
+
+	keyword := this.GetString("Keyword")
+	orderField := this.GetString("OrderField")
+	orderType := this.GetString("OrderType")
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	RuiSiReportPage, err := document_manage_service.RuiSiReportList(classifyIdFirst, classifyIdSecond, classifyIdThird, keyword, orderField, orderType, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取报告列表失败"
+		br.ErrMsg = "获取报告列表失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = RuiSiReportPage
+	return
+}
+
+// DocumentRuiSiDetail
+// @Title 睿思报告详情
+// @Description 睿思报告详情
+// @Success 200 “获取成功”
+// @router /document/rui/si/detail [get]
+func (this *DocumentManageController) DocumentRuiSiDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	// 获取指标数据列表
+	this.GetString("DocumentRuiSiDetail")
+
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取报告ID失败"
+		br.ErrMsg = "获取报告ID失败,Err:" + err.Error()
+		return
+	}
+
+	reportDetail, err := document_manage_service.DocumentRuiSiDetail(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = &reportDetail
+	return
+}
+
+// DocumentSave
+// @Title 新建文档
+// @Description 新建文档
+// @Success 200 “操作成功”
+// @router /document/save [post]
+func (this *DocumentManageController) DocumentSave() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	sysUser := this.SysUser
+	var req *document_manage_model.OutsideReportBO
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+
+	req.SysUserId = sysUser.AdminId
+	req.SysUserName = sysUser.RealName
+	err := document_manage_service.DocumentSave(req)
+	if err != nil {
+		br.Msg = "保存文档失败"
+		br.ErrMsg = "保存文档失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}
+
+// DocumentDetail
+// @Title 文档详情
+// @Description 文档详情
+// @Success 200 “操作成功”
+// @router /document/detail [get]
+func (this *DocumentManageController) DocumentDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+	// 获取指标数据列表
+
+	outsideReportId, err := this.GetInt("OutsideReportId")
+	if err != nil {
+		br.Msg = "获取报告ID失败"
+		br.ErrMsg = "获取报告ID失败,Err:" + err.Error()
+		return
+	}
+
+	reportDetail, err := document_manage_service.DocumentReportDetail(outsideReportId)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = &reportDetail
+	return
+}
+
+// DocumentUpdate
+// @Title 编辑文档
+// @Description 编辑文档
+// @Success 200 “操作成功”
+// @router /document/update [post]
+func (this *DocumentManageController) DocumentUpdate() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	var req *document_manage_model.OutsideReportBO
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	err := document_manage_service.DocumentUpdate(req)
+	if err != nil {
+		br.Msg = "修改文档失败"
+		br.ErrMsg = "修改文档失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}
+
+// DocumentDelete
+// @Title 删除文档
+// @Description 删除文档
+// @Success 200 “操作成功”
+// @router /document/delete [post]
+func (this *DocumentManageController) DocumentDelete() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	if !ValidateUser(this, br) {
+		return
+	}
+
+	type DocumentDelete struct {
+		OutsideReportId int `json:"OutsideReportId"`
+	}
+
+	var req *DocumentDelete
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	err := document_manage_service.DocumentDelete(req.OutsideReportId)
+	if err != nil {
+		br.Msg = "删除文档失败"
+		br.ErrMsg = "删除文档失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+	return
+}

+ 1111 - 0
controllers/hisugar_data.go

@@ -0,0 +1,1111 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	etaTrialService "eta/eta_api/services/eta_trial"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// HisugarClassify
+// @title	获取泛糖科技分类列表
+// @Description	获取泛糖科技分类列表
+// @Success 200 {object} models.
+// @router /data/hisugar/classify [get]
+func (this *TradeCommonController) HisugarClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	classifyList, err := data_manage.GetHisugarClassifyAll()
+	if err != nil {
+		br.Msg = "查询失败"
+		br.ErrMsg = "查询失败, Err:" + err.Error()
+		return
+	}
+
+	finalList := make([]*data_manage.BaseFromHisugarClassifyItem, 0)
+	classifyTree := getHisugarClassifyTree(classifyList, 0)
+	finalList = append(finalList, classifyTree...)
+
+	br.Msg = "查询成功"
+	br.Data = finalList
+	br.Success = true
+	br.Ret = 200
+}
+
+// HisugarIndexList
+// @title	获取泛糖科技指标列表
+// @Description	获取泛糖科技指标列表
+// @Success 200 {object} models.
+// @router /data/hisugar/indexList [get]
+func (this *TradeCommonController) HisugarIndexList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	//pageSize, _ := this.GetInt("PageSize")
+	//currentIndex, _ := this.GetInt("CurrentIndex")
+	//var startSize int
+	//
+	//if pageSize <= 0 {
+	//	pageSize = utils.PageSize20
+	//}
+	//if currentIndex <= 0 {
+	//	currentIndex = 1
+	//}
+	//startSize = utils.StartIndex(currentIndex, pageSize)
+
+	classifyId, _ := this.GetInt("ClassifyId")
+
+	var condition string
+	var pars []interface{}
+
+	if classifyId > 0 {
+		condition += ` AND b.classify_id=? `
+		pars = append(pars, classifyId)
+	}
+
+	//keyword := this.GetString("KeyWord")
+	//if keyword != "" {
+	//	condition += ` AND (index_code =? OR index_name LIKE ?)  `
+	//	pars = append(pars, keyword)
+	//	pars = append(pars, "%"+keyword+"%")
+	//}
+
+	indexList, e := data_manage.GetHisugarIndexViewList(condition, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取泛糖科技分类数据分类失败, Err: " + e.Error()
+		return
+	}
+	indexIdList := make([]int, 0)
+	for _, v := range indexList {
+		indexIdList = append(indexIdList, v.BaseFromHisugarIndexId)
+	}
+	dataList, err := data_manage.GetHisugarDataViewList(indexIdList)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取泛糖科技分类数据分类失败, Err: " + err.Error()
+		return
+	}
+	for i, v := range indexList {
+		for _, v1 := range dataList {
+			if v.BaseFromHisugarIndexId == v1.BaseFromHisugarIndexId {
+				indexList[i].DataTime = v1.DataTime
+				indexList[i].Value = v1.Value
+			}
+		}
+	}
+
+	//total, err := data_manage.GetHisugarIndexViewListCount(condition, pars)
+	//if err != nil {
+	//	br.Msg = "获取失败"
+	//	br.ErrMsg = "获取泛糖科技分类数据分类失败, Err: " + err.Error()
+	//	return
+	//}
+
+	//page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resp := data_manage.BaseFromHisugarIndexListResp{}
+	resp.List = indexList
+	//resp.Paging = page
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// HisugarMineData
+// @title	获取泛糖科技详细数据列表
+// @Description	获取泛糖科技详细数据接口
+// @Param	ClassifyId query int true	"数据id"
+// @Param	GroupName query string true	"分组名"
+// @Param	Frequency query string true	"频度"
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Success 200 {object} []data_manage.CoalmineDataResp
+// @router /data/hisugar/data [get]
+func (this *TradeCommonController) HisugarData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	indexCode := this.GetString("IndexCode")
+	if indexCode == "" {
+		br.Msg = "请选择指标"
+		br.ErrMsg = "请选择指标"
+		return
+	}
+
+	//获取指标
+	var condition string
+	var pars []interface{}
+
+	condition += ` AND index_code =? `
+	pars = append(pars, indexCode)
+
+	index, err := data_manage.GetHisugarIndexByCode(indexCode)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	dataList, err := data_manage.GetHisugarIndexData(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	resp := data_manage.BaseFromHisugarIndexList{}
+
+	resp.BaseFromHisugarIndexId = index.BaseFromHisugarIndexId
+	resp.ClassifyId = index.ClassifyId
+	resp.IndexCode = index.IndexCode
+	resp.IndexName = index.IndexName
+	resp.Frequency = index.Frequency
+	resp.Unit = index.Unit
+	resp.EdbExist = index.EdbExist
+	resp.EdbInfoId = index.EdbInfoId
+
+	total, err := data_manage.GetHisugarDataListCount(condition, pars)
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	if len(dataList) > 0 {
+		resp.ModifyTime = dataList[0].ModifyTime
+	}
+
+	resp.Paging = page
+	resp.DataList = dataList
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// CoalSearchList
+// @Title 泛糖科技模糊搜索
+// @Description 泛糖科技模糊搜索
+// @Param   Keyword   query   string  ture       "关键字搜索"
+// @Success 200 {object} models.BaseResponse
+// @router /data/hisugar/search [get]
+func (this *TradeCommonController) HisugarSearchList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	//关键字
+	keyword := this.GetString("Keyword")
+
+	list, err := data_manage.GetHisugarItemList(keyword)
+	if err != nil {
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		br.Msg = "获取失败"
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// BatchIndexList
+// @Title 泛糖科技批量操作指标列表
+// @Description 泛糖科技批量操作指标列表
+// @Param   request	body   request.SciHqDataBatchListReq true "type json string"
+// @Success 200 {object} data_manage.BaseFromMysteelChemicalIndexResp
+// @router /data/hisugar/batch_list [post]
+func (this *TradeCommonController) HisugarBatchIndexList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req data_manage.HisugarDataBatchListReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	var condition string
+	var pars []interface{}
+	if req.KeyWord != "" {
+		condition += `AND (index_code=? OR index_name=?)`
+		pars = utils.GetLikeKeywordPars(pars, req.KeyWord, 2)
+	}
+	if req.IsSelectAll {
+		if len(req.SelectedId) > 0 {
+			condition += ` AND base_from_hisugar_index_id NOT IN (` + utils.GetOrmInReplace(len(req.SelectedId)) + `)`
+			pars = append(pars, req.SelectedId)
+		}
+	} else {
+		if len(req.SelectedId) > 0 {
+			condition += ` AND base_from_hisugar_index_id IN (` + utils.GetOrmInReplace(len(req.SelectedId)) + `)`
+			pars = append(pars, req.SelectedId)
+		}
+	}
+	if req.ClassifyId >= 0 {
+		condition += ` AND classify_id=?`
+		pars = append(pars, req.ClassifyId)
+	}
+
+	count, err := data_manage.GetHisugarIndexListCount(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+	MaxLen := 30
+	if count > MaxLen {
+		br.Msg = "批量操作数量不能超过" + strconv.Itoa(MaxLen)
+		return
+	}
+	indexList, err := data_manage.GetHisugarIndexByCondition(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexList
+}
+
+// AddCheck
+// @Title 新增校验
+// @Description 新增校验
+// @Param	request	body request.BusinessDataBatchAddCheckReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /data/hisugar/add_check [post]
+func (this *TradeCommonController) HisugarAddCheck() {
+	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.HisugarDataBatchAddCheckReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	codeMax := 30
+	codeLen := len(req.IndexCodes)
+	if len(req.IndexCodes) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+	// 获取指标库已有指标
+	existsEdb, e := data_manage.GetEdbCodesBySource(utils.DATA_SOURCE_HISUGAR)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取自有数据已添加的指标失败, Err: " + e.Error()
+		return
+	}
+	existMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range existsEdb {
+		existMap[v.EdbCode] = v
+	}
+
+	// 查询选中的指标
+	cond := fmt.Sprintf(` AND index_code IN (%s)`, utils.GetOrmInReplace(codeLen))
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.IndexCodes)
+	list, err := data_manage.GetHisugarIndexAndEdbInfoByCondition(cond, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取卓创红期原始指标列表失败, Err: " + err.Error()
+		return
+	}
+
+	resp := make([]*data_manage.BaseFromHisugarIndexView, 0)
+	for _, v := range list {
+		if v.EdbInfoId > 0 {
+			v.EdbExist = 1
+		}
+		resp = append(resp, v)
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// NameCheck
+// @Title 重名校验
+// @Description 批量新增
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /data/hisugar/name_check [post]
+func (c *TradeCommonController) HisugarNameCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req []*data_manage.NameCheckEdbInfoReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	codeMax := 30
+	codeLen := len(req)
+	if codeLen > codeMax {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMax)
+		return
+	}
+
+	type NameCheckResult struct {
+		EdbCode string
+		EdbName string
+		Exist   bool
+	}
+	indexNames := make([]string, 0)
+	resp := make([]*NameCheckResult, 0)
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		indexNames = append(indexNames, v.EdbName)
+		resp = append(resp, &NameCheckResult{
+			EdbCode: v.EdbCode,
+			EdbName: v.EdbName,
+		})
+		dataItems, err := data_manage.GetEdbDataAllByEdbCode(v.EdbCode, utils.DATA_SOURCE_HISUGAR, 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_HISUGAR, 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 /data/hisugar/batch_add [post]
+func (this *TradeCommonController) HisugarBatchAdd() {
+	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_OILCHEM_" + 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.HisugarIndexSource2EdbReq
+		r.EdbCode = v.EdbCode
+		r.EdbName = v.EdbName
+		r.Frequency = v.Frequency
+		r.Unit = v.Unit
+		r.ClassifyId = v.ClassifyId
+		r.AdminId = sysUser.AdminId
+		r.AdminRealName = sysUser.RealName
+
+		edbInfo, errMsg, skip, e := data.HisugarIndexSource2Edb(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
+}
+
+// ExporthisugarList
+// @Title 导出泛糖科技数据
+// @Description 导出泛糖科技数据
+// @Param   request	body request.ExportHisugarExcelReq true "type json string"
+// @Success 200  导出成功
+// @router /data/export/hisugarList [post]
+func (this *TradeCommonController) HisugarExporthisugarList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	var req data_manage.ExportHisugarExcelReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	secNameList := make([]*data_manage.BaseFromHisugarIndex, 0)
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+
+	downLoadnFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	var condition string
+	var pars []interface{}
+	if req.KeyWord != "" {
+		condition += `AND (index_code=? OR index_name=?)`
+		pars = utils.GetLikeKeywordPars(pars, req.KeyWord, 2)
+	}
+	if req.IsSelectedAll {
+		if len(req.IndexCode) > 0 {
+			condition += ` AND index_code NOT IN (` + utils.GetOrmInReplace(len(req.IndexCode)) + `)`
+			pars = append(pars, req.IndexCode)
+		}
+	} else {
+		if len(req.IndexCode) > 0 {
+			condition += ` AND index_code IN (` + utils.GetOrmInReplace(len(req.IndexCode)) + `)`
+			pars = append(pars, req.IndexCode)
+		}
+	}
+	if req.ClassifyId > 0 {
+		classifyIds, err := data_manage.GetHisugarClassifyById(req.ClassifyId)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if classifyIds != ""{
+			condition += ` AND classify_id IN (`+ classifyIds +`)`
+		}
+	}
+	frequencies, err := data_manage.GetHisugarFrequencyByCondition(condition, pars)
+	if err != nil {
+		fmt.Println("GetSciFrequency err:", err.Error())
+		utils.FileLog.Info("GetSciFrequency err:" + err.Error())
+		return
+	}
+	frequencyMap := map[string]string{
+		"日度":  "Daily",
+		"周度":  "Weekly",
+		"旬度":  "ten-day",
+		"月度":  "Monthly",
+		"季度":  "Quarterly",
+		"半年度": "Semi-annual",
+		"年度":  "Annual",
+	}
+	for _, frequency := range frequencies {
+		//获取指标
+		secNameList, err = data_manage.GetHisugarIndexByConditionAndFrequency(condition, *frequency, pars)
+		if err != nil {
+			fmt.Println("获取数据失败,Err:" + err.Error())
+			return
+		}
+		if len(secNameList) <= 0 {
+			fmt.Println("secNameList长度为0")
+			return
+		}
+
+		sheetNew, err := xlsxFile.AddSheet(*frequency+frequencyMap[*frequency])
+		if err != nil {
+			fmt.Println("新增Sheet失败", err.Error())
+			return
+		}
+
+		secNameRow := sheetNew.AddRow()
+		frequencyRow := sheetNew.AddRow()
+		unitRow := sheetNew.AddRow()
+		lastModifyDateRow := sheetNew.AddRow()
+
+		var indexIdList []int
+		for _, sv := range secNameList {
+			indexIdList = append(indexIdList, sv.BaseFromHisugarIndexId)
+		}
+		dataTimeList, err := data_manage.GetHisugarDataDataTimeByIndexId(indexIdList)
+		if err != nil {
+			fmt.Println("获取数据时间失败", err.Error())
+			return
+		}
+
+		// 添加excel左侧指标日期
+		setRowIndex := 4
+		for rk, dv := range dataTimeList {
+			rowIndex := setRowIndex + rk
+			row := sheetNew.Row(rowIndex)
+			displayDate, _ := time.Parse(utils.FormatDate, dv)
+			displayDateCell := row.AddCell()
+			style := new(xlsx.Style)
+			style.ApplyAlignment = true
+			style.Alignment.WrapText = true
+			displayDateCell.SetStyle(style)
+			displayDateCell.SetDate(displayDate)
+
+		}
+		for k, sv := range secNameList {
+			//获取数据
+			dataList, err := data_manage.GetHisugarIndexDataByCode(sv.IndexCode)
+			if err != nil {
+				br.Msg = "获取数据失败"
+				br.ErrMsg = "获取数据失败,Err:" + err.Error()
+				return
+			}
+			if k == 0 {
+				secNameRow.AddCell().SetValue("指标名称/Metric Name")
+				frequencyRow.AddCell().SetValue("频度/Frequency")
+				unitRow.AddCell().SetValue("单位/Unit")
+				lastModifyDateRow.AddCell().SetValue("更新时间/Update Time")
+				min := k * 3
+				sheetNew.SetColWidth(min, min, 15)
+			}
+			if len(dataList) == 0 {
+				continue
+			}
+			secNameRow.AddCell().SetValue(sv.IndexName)
+			frequencyRow.AddCell().SetValue(sv.Frequency)
+			unitRow.AddCell().SetValue(sv.Unit)
+
+			lastModifyDateRow.AddCell().SetValue(sv.ModifyTime)
+			dataInfoMap := make(map[string]*data_manage.BaseFromHisugarData)
+			for _, v := range dataList {
+				dataInfoMap[v.DataTime] = v
+			}
+
+			for rk, dtv := range dataTimeList {
+				rowIndex := setRowIndex + rk
+				row := sheetNew.Row(rowIndex)
+				displayDateCell := row.AddCell()
+				tmpData, ok := dataInfoMap[dtv]
+				if ok {
+					displayDateCell.SetValue(tmpData.Value)
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadnFilePath)
+	if err != nil {
+		//有指标无数据时先导出一遍空表
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+		err = xlsxFile.Save(downLoadnFilePath)
+		if err != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+	fileName := `泛糖科技`
+	if len(secNameList) > 0 {
+		fileName = secNameList[0].IndexName
+	}
+	fileName += time.Now().Format("2006.01.02") + `.xlsx` //文件名称
+	this.Ctx.Output.Download(downLoadnFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadnFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}
+
+// getHisugarClassifyTree 返回泛糖科技的树形结构
+func getHisugarClassifyTree(items []*data_manage.BaseFromHisugarClassifyItem, parentId int) []*data_manage.BaseFromHisugarClassifyItem {
+	res := make([]*data_manage.BaseFromHisugarClassifyItem, 0)
+	for _, item := range items {
+		if item.ParentId == parentId {
+			t := new(data_manage.BaseFromHisugarClassifyItem)
+			t.BaseFromHisugarClassifyId = item.BaseFromHisugarClassifyId
+			t.ClassifyName = item.ClassifyName
+			t.ParentId = item.ParentId
+			t.Level = item.Level
+			t.Sort = item.Sort
+			t.ModifyTime = item.ModifyTime
+			t.CreateTime = item.CreateTime
+			t.ClassifyNameEn = item.ClassifyNameEn
+			if item.UniqueCode == "" {
+				t.UniqueCode = strconv.Itoa(item.BaseFromHisugarClassifyId)
+			}
+			t.Children = getHisugarClassifyTree(items, item.BaseFromHisugarClassifyId)
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+// FenweiIndexData
+// @Title 获取数据
+// @Description 获取泛糖科技数据接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   string  true       "分类id"
+// @Param   Frequency   query   string  true       "频率"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /data/hisugar/index/data [get]
+func (this *TradeCommonController) HisugarIndexData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+	frequency := this.GetString("Frequency")
+	indexCode := this.GetString("IndexCode")
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if classifyId >= 0 {
+		classifyIds, err := data_manage.GetHisugarClassifyById(classifyId)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if classifyIds != ""{
+			condition += ` AND classify_id IN (`+ classifyIds +`)`
+		}
+	}
+
+	if frequency != "" {
+		condition += ` AND frequency=? `
+		pars = append(pars, frequency)
+	}
+
+	if indexCode != "" {
+		condition += ` AND index_code=? `
+		pars = append(pars, indexCode)
+	}
+
+	indexes, err := data_manage.GetHisugarIndex(condition, pars)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	indexCodes := make([]string, 0)
+	for _, v := range indexes {
+		indexCodes = append(indexCodes, v.IndexCode)
+	}
+	indexCounts, e := data_manage.GetHisugarIndexDataCountGroup(indexCodes)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标数据总量失败, Err:" + err.Error()
+		return
+	}
+	countMap := make(map[string]int)
+	for _, v := range indexCounts {
+		countMap[v.IndexCode] = v.Count
+	}
+
+	// 判断是否存在于指标库
+	edbCodeList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_HISUGAR, indexCodes)
+	if err != nil {
+		return
+	}
+	edbCodeMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range edbCodeList {
+		edbCodeMap[v.EdbCode] = v
+	}
+
+	resultList := make([]*data_manage.BaseFromHisugarIndexList, 0)
+	for _, v := range indexes {
+		product := new(data_manage.BaseFromHisugarIndexList)
+		product.BaseFromHisugarIndexId = v.BaseFromHisugarIndexId
+		product.ClassifyId = v.ClassifyId
+		product.Unit = v.Unit
+		product.IndexCode = v.IndexCode
+		product.IndexName = v.IndexName
+		product.Frequency = v.Frequency
+		product.CreateTime = v.CreateTime
+		product.ModifyTime = v.ModifyTime
+
+		edbInfo := edbCodeMap[v.IndexCode]
+		if edbInfo != nil {
+			product.EdbInfoId = edbInfo.EdbInfoId
+			product.EdbExist = 1
+		}
+
+		total := countMap[v.IndexCode]
+		page := paging.GetPaging(currentIndex, pageSize, total)
+		dataList, e := data_manage.GetHisugarIndexDataV2(v.IndexCode, startSize, pageSize)
+		if e != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + e.Error()
+			return
+		}
+		if dataList == nil {
+			dataList = make([]*data_manage.BaseFromHisugarData, 0)
+		}
+		product.DataList = dataList
+		product.Paging = page
+		resultList = append(resultList, product)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resultList
+}
+
+// GetFenWeiIndexInfo
+// @Title 添加指标-根据条件获取指标信息
+// @Description 添加指标-根据条件获取指标信息
+// @Param   KeyWord   query   string  false       "关键字"
+// @Param   ClassifyIds   query   string  false       "分类id"
+// @Param   Frequencies   query   string  false       "频率"
+// @Param   PageSize   query   int  false       "每页数据条数"
+// @Param   CurrentIndex   query   int  false       "当前页页码,从1开始"
+// @Success 200 {object} data_manage.BaseFromFenWeiIndexPage
+// @router /data/hisugar/index/info [get]
+func (this *TradeCommonController) GetHisugarIndexInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	keyWord := this.GetString("KeyWord")
+	classifyIds := this.GetString("ClassifyIds")
+	frequencies := this.GetString("Frequencies")
+
+	var classifyIdList []string
+	var frequencyList []string
+	if classifyIds != "" {
+		classifyIdList = strings.Split(classifyIds, ",")
+	}
+	if frequencies != "" {
+		frequencyList = strings.Split(frequencies, ",")
+	}
+	indexInfoPage, err := data.GethisugarIndexInfo(keyWord, classifyIdList, frequencyList, currentIndex, pageSize)
+	if err != nil {
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = indexInfoPage
+}
+
+
+// FrequencyList
+// @Title 指标频度列表
+// @Description 指标频度列表
+// @Success 200 string "获取成功"
+// @router /data/hisugar/frequency_list [get]
+func (this *TradeCommonController) FrequencyList() {
+	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
+	}
+
+	classifyId, _ := this.GetInt("ClassifyId")
+
+	var condition string
+	var pars []interface{}
+
+	if classifyId > 0 {
+		classifyIds, err := data_manage.GetHisugarClassifyById(classifyId)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if classifyIds != ""{
+			condition += ` AND classify_id IN (`+ classifyIds +`)`
+		}
+	}
+
+
+	frequencies, err := data_manage.GetHisugarFrequencyByCondition(condition, pars)
+	if err != nil {
+		fmt.Println("GetSciFrequency err:", err.Error())
+		utils.FileLog.Info("GetSciFrequency err:" + err.Error())
+		return
+	}
+	br.Data = frequencies
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 80 - 0
controllers/resource.go

@@ -1090,3 +1090,83 @@ func (this *ResourceAuthController) FileDownload() {
 	br.Success = true
 	this.Ctx.Output.Download(localFilePath, fileName)
 }
+
+// FileUpload
+// @Title 文件上传
+// @Description 文件上传接口
+// @Param   file   query   file  true       "文件"
+// @Success 200 {object} models.ResourceResp
+// @router /file/upload [post]
+func (this *ResourceController) FileUpload() {
+	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
+	}
+
+	uploadFileName := h.Filename //上传的文件名
+	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
+	}
+	defer func() {
+		os.Remove(fpath)
+	}()
+
+	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
+	}
+
+	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
+	}
+	resp := models.ResourceResp{
+		Id:           newId,
+		ResourceUrl:  resourceUrl,
+		ResourceName: uploadFileName,
+	}
+
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 7 - 2
controllers/sys_admin.go

@@ -12,10 +12,11 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // 系统用户
@@ -459,7 +460,7 @@ func (this *SysAdminController) Add() {
 			br.Msg = "请输入密码"
 			return
 		}
-		if !utils.CheckPwd(originPass) {
+		if !utils.CheckPwdV2(originPass) {
 			br.Msg = "密码格式错误,请重新输入"
 			return
 		}
@@ -1251,6 +1252,10 @@ func (this *SysAdminController) ResetPass() {
 	}
 	pwd := string(b)
 	//pwd = strings.ToLower(pwd)
+	if !utils.CheckPwdV2(pwd) {
+		br.Msg = "密码格式错误,请重新输入"
+		return
+	}
 	pwd = utils.MD5(pwd)
 
 	adminInfo.Password = pwd

+ 334 - 2
controllers/trade_analysis/trade_analysis.go

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

+ 1812 - 0
controllers/trade_analysis/warehouse.go

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

+ 683 - 0
controllers/trade_analysis/warehouse_classify.go

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

+ 22 - 1
controllers/user_login.go

@@ -10,10 +10,11 @@ import (
 	"eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/mojocn/base64Captcha"
 	"image/color"
 	"strings"
 	"time"
+
+	"github.com/mojocn/base64Captcha"
 )
 
 // UserLoginController 登录-无需Token
@@ -973,6 +974,7 @@ func (this *UserLoginController) CheckUserLdap() {
 	if e != nil {
 		// 无该用户视作普通用户
 		if e.Error() == utils.ErrNoRow() {
+			br.Ret = 200
 			br.Data = false
 			br.Success = true
 			br.Msg = "操作成功"
@@ -1042,12 +1044,29 @@ func (this *UserLoginController) BaseInfo() {
 		return
 	}
 
+	logoCnMini, e := models.GetBusinessConfByKey("LogoCNMini")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
+		return
+	}
+
+	logoEnMini, e := models.GetBusinessConfByKey("LogoENMini")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
+		return
+	}
+
+
 	type BaseInfoResp struct {
 		Icp      *models.BusinessConf `description:"Icp信息"`
 		ETATitle *models.BusinessConf `description:"eta系统名称"`
 		TabName  *models.BusinessConf `description:"tab页名称"`
 		LogoCn   *models.BusinessConf `description:"中文logo"`
 		LogoEn   *models.BusinessConf `description:"英文logo"`
+		LogoCnMini   *models.BusinessConf `description:"中文logoMini"`
+		LogoEnMini   *models.BusinessConf `description:"英文logoMini"`
 	}
 
 	resp := BaseInfoResp{
@@ -1056,6 +1075,8 @@ func (this *UserLoginController) BaseInfo() {
 		TabName:  tabName,
 		LogoCn:   logoCn,
 		LogoEn:   logoEn,
+		LogoCnMini:   logoCnMini,
+		LogoEnMini:   logoEnMini,
 	}
 
 	br.Data = resp

+ 1 - 1
go.mod

@@ -33,6 +33,7 @@ require (
 	github.com/mojocn/base64Captcha v1.3.6
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.32
+	github.com/pdfcpu/pdfcpu v0.8.0
 	github.com/qiniu/qmgo v1.1.8
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
@@ -108,7 +109,6 @@ require (
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
-	github.com/pdfcpu/pdfcpu v0.8.0 // indirect
 	github.com/pelletier/go-toml v1.9.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.16.0 // indirect

+ 13 - 1
models/chart_permission.go

@@ -39,15 +39,27 @@ type ChartPermission struct {
 	IsPublic              int       `description:"是否是公有权限1:公有权限,0私有权限" json:"is_public"`
 }
 
+type ChartPermissionVO struct {
+	ChartPermissionId   int       `orm:"column(chart_permission_id);pk" description:"问题ID" json:"chart_permission_id"`
+	ChartPermissionName string    `description:"名称" json:"chart_permission_name"`
+	PermissionName      string    `description:"权限名" json:"permission_name"`
+	Sort                int       `description:"排序" json:"sort"`
+	Enabled             int       `description:"是否可用" json:"enabled"`
+	CreatedTime         time.Time `description:"创建时间" json:"created_time"`
+	LastUpdatedTime     time.Time `description:"更新时间" json:"last_updated_time"`
+	ParentId            int       `description:"父级权限id" json:"parent_id"`
+	Children            []*ChartPermissionVO
+}
+
 type ChartPermissionItem struct {
 	PermissionId   int    `description:"品种权限ID"`
 	PermissionName string `description:"品种权限名称"`
 	ParentId       int    `description:"父级ID"`
-	IsPublic       int    `description:"是否是公有权限1:公有权限,0私有权限" `
 	Enabled        int    `description:"是否可用:1可用,0不可用" `
 	Sort           int    `description:"排序"`
 	CreateTime     string `description:"创建时间"`
 	Child          []*ChartPermissionItem
+	IsPublic       int
 }
 
 // Update 更新

+ 21 - 0
models/classify.go

@@ -46,6 +46,17 @@ type Classify struct {
 	ReportDetailShowType int       `description:"报告详情的展示类型:1-拼接;2:目录"`
 }
 
+type ClassifyVO struct {
+	Id            int    `orm:"column(id);pk"`
+	ClassifyName  string `description:"分类名称"`
+	Sort          int    `json:"-"`
+	ParentId      int    `description:"父级分类id"`
+	ClassifyLabel string `description:"分类标签"`
+	Enabled       int    `description:"是否可用,1可用,0禁用"`
+	Level         int    `description:"层级"`
+	Children      *[]ClassifyVO
+}
+
 type ClassifyAddReq struct {
 	ClassifyName          string `description:"分类名称"`
 	ParentId              int    `description:"父级分类id,没有父级分类传0"`
@@ -573,3 +584,13 @@ func GetClassifyListByIdList(classifyIdList []int) (items []*Classify, err error
 	_, err = o.Raw(sql, classifyIdList).QueryRows(&items)
 	return
 }
+
+// GetClassifyListByParentId 查询父分类下所有的子分类
+func GetClassifyListByParentId(parentId int) (items []*Classify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM classify WHERE parent_id = ? and enabled = 1`
+
+	_, err = o.Raw(sql, parentId).QueryRows(&items)
+
+	return items, err
+}

+ 80 - 0
models/common_classify.go

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

+ 36 - 16
models/data_manage/base_from_eia_stero.go

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

+ 141 - 0
models/data_manage/base_from_fenwei.go

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

+ 13 - 0
models/data_manage/base_from_fenwei_classify.go

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

+ 393 - 0
models/data_manage/base_from_hisugar.go

@@ -0,0 +1,393 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"time"
+)
+
+type BaseFromHisugarClassify struct {
+	BaseFromHisugarClassifyId int    // 分类ID
+	ClassifyName              string // 分类名称
+	ParentID                  int    // 上级ID
+	Level                     int    // 层级
+	Sort                      int    // 排序
+	CreateTime                string // 创建时间
+	ModifyTime                string // 修改时间
+}
+
+type BaseFromHisugarIndex struct {
+	BaseFromHisugarIndexId int    // 主键ID
+	IndexCode              string // 指标编码
+	IndexName              string // 指标名称
+	ClassifyId             uint   // 分类ID
+	Unit                   string // 单位
+	Frequency              string // 频度
+	Describe               string // 指标描述
+	Sort                   int    // 排序
+	CreateTime             string // 创建时间
+	ModifyTime             string // 修改时间
+}
+
+type BaseFromHisugarData struct {
+	BaseFromHisugarDataId  int    // 数据表ID
+	BaseFromHisugarIndexId int    // 指标ID
+	IndexCode              string // 指标编码
+	DataTime               string
+	Value                  string
+	CreateTime             string
+	ModifyTime             string
+}
+
+func GetHisugarClassifyList() (list []*BaseFromHisugarClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM base_from_hisugar_classify ORDER BY sort ASC"
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+type BaseFromHisugarIndexList struct {
+	BaseFromHisugarIndexId int    // 主键ID
+	IndexCode              string // 指标编码
+	IndexName              string // 指标名称
+	ClassifyId             int    // 分类ID
+	Unit                   string // 单位
+	Frequency              string // 频度
+	Describe               string // 指标描述
+	Sort                   int    // 排序
+	CreateTime             string // 创建时间
+	ModifyTime             string // 修改时间
+	EdbExist               int    `description:"edb是否存在"`
+	DataList               []*BaseFromHisugarData
+	Paging                 *paging.PagingItem `description:"分页数据"`
+	EdbInfoId     int                `description:"指标库主键id"`
+}
+
+type BaseFromHisugarIndexListResp struct {
+	List   []*BaseFromHisugarIndexView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+func GetHisugarIndexById(indexId int) (item *BaseFromHisugarIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_index WHERE 1=1 base_from_hisugar_index_id = ? `
+	sql += `ORDER BY base_from_hisugar_index_id ASC `
+	err = o.Raw(sql, indexId).QueryRow(&item)
+	return
+}
+func GetHisugarIndexByCode(indexCode string) (item *BaseFromHisugarIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT a.*,CASE WHEN e.edb_info_id IS NULL THEN 0 ELSE 1 END AS edb_exist,e.edb_info_id
+	FROM base_from_hisugar_index as a 
+LEFT JOIN edb_info AS e ON a.index_code=e.edb_code AND e.source=93 
+	WHERE 1=1 and a.index_code = ? `
+	sql += `ORDER BY a.base_from_hisugar_index_id ASC `
+	err = o.Raw(sql, indexCode).QueryRow(&item)
+	return
+}
+
+func GetHisugarIndexList(condition string, pars interface{}, startSize, pageSize int) (items []*BaseFromHisugarIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+
+	sql += `group BY index_code ASC order by create_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetHisugarIndexListCount(condition string, pars interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM base_from_hisugar_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetHisugarDataListCount(condition string, pars interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM base_from_hisugar_data WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetHisugarIndexData(condition string, pars interface{}, startSize, pageSize int) (items []*BaseFromHisugarData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_data WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+
+	sql += ` order by data_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetHisugarItemList 模糊查询泛糖科技数据库指标列表
+func GetHisugarItemList(keyword string) (items []*BaseFromHisugarIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM base_from_hisugar_index WHERE CONCAT(index_name,index_code) LIKE ? "
+	_, err = o.Raw(sql, utils.GetLikeKeyword(keyword)).QueryRows(&items)
+	return
+}
+
+// HisugarDataBatchListReq 泛糖科技指标批量列表
+type HisugarDataBatchListReq struct {
+	ClassifyId  int    `description:"分类id"`
+	KeyWord     string `description:"关键字"`
+	SelectedId  []int  `description:"已选指标id, 为true时表示反选"`
+	IsSelectAll bool   `description:"是否查询全部, 默认false, true:全选, false:查询已选"`
+}
+
+// GetHisugarIndexByCondition 根据条件获取泛糖科技指标列表
+func GetHisugarIndexByCondition(condition string, pars []interface{}) (items []*BaseFromHisugarIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_hisugar_index_id ASC`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// HisugarDataBatchAddCheckReq 泛糖科技指标批量添加校验
+type HisugarDataBatchAddCheckReq struct {
+	IndexCodes []string `description:"指标编码"`
+}
+
+// GetHisugarIndexAndEdbInfoByCondition 根据条件获取泛糖科技index和指标库的信息
+func GetHisugarIndexAndEdbInfoByCondition(condition string, pars []interface{}) (items []*BaseFromHisugarIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT b.*, e.edb_info_id, e.unique_code, e.classify_id AS edb_classify_id FROM base_from_hisugar_index AS b LEFT JOIN edb_info AS e ON b.index_code=e.edb_code AND e.source=? WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC `
+	_, err = o.Raw(sql, utils.DATA_SOURCE_HISUGAR, pars).QueryRows(&items)
+	return
+}
+
+type BaseFromHisugarIndexView struct {
+	BaseFromHisugarIndexId int    `orm:"pk"`
+	EdbInfoId              int    `description:"指标库id"`
+	ClassifyId             int    `description:"指标分类id"`
+	IndexCode              string `description:"指标编码"`
+	IndexName              string `description:"指标名称"`
+	UniqueCode             string `description:"唯一code"`
+	Frequency              string `description:"频度"`
+	Unit                   string `description:"单位"`
+	StartDate              string `description:"开始日期"`
+	EndDate                string `description:"结束日期"`
+	Sort                   int    `description:"排序"`
+	EdbExist               int    `description:"edb是否存在"`
+	EdbClassifyId          int    `description:"edb分类id"`
+	ModifyTime             string
+	DataTime               string `description:"数据时间"`
+	Value                  string `description:"值"`
+}
+
+// ExportHisugarExcelReq 导出泛糖科技excel指标
+type ExportHisugarExcelReq struct {
+	KeyWord       string   `description:"关键字, 指标编码或指标ID"`
+	IndexCode     []string `description:"指标编码,全选时,表示反选"`
+	IsSelectedAll bool     `description:"是否全选:true:全选|false: 无"`
+	ClassifyId    int      `description:"指标id"`
+}
+
+// GetHisugarIndexByConditionAndFrequency 根据条件获取泛糖科技指标列表
+func GetHisugarIndexByConditionAndFrequency(condition, frequency string, pars []interface{}) (items []*BaseFromHisugarIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` AND frequency=?`
+	sql += ` ORDER BY sort ASC, base_from_hisugar_index_id ASC`
+	_, err = o.Raw(sql, pars, frequency).QueryRows(&items)
+	return
+}
+
+func GetHisugarDataMaxCount(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT MAX(t.num) AS count FROM (
+				SELECT COUNT(1) AS num  FROM base_from_hisugar_index AS a
+				INNER JOIN base_from_hisugar_data AS b ON a.index_code=b.index_code
+				WHERE a.classify_id=?
+				GROUP BY a.base_from_hisugar_index_id
+			)AS t `
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+func GetHisugarIndexDataByCode(indexCode string) (items []*BaseFromHisugarData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_hisugar_data WHERE index_code=? ORDER BY data_time DESC  `
+	_, err = o.Raw(sql, indexCode).QueryRows(&items)
+	return
+}
+
+func GetHisugarFrequencyByCondition(condition string, pars []interface{}) (items []*string, err error) {
+	sql := `SELECT DISTINCT frequency FROM base_from_hisugar_index WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY FIELD(frequency,'日度','周度','月度','季度','半年','年度') `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
+}
+
+// GetHisugarIndexViewList 根据分类id获取泛糖科技指标列表
+func GetHisugarIndexViewList(condition string, pars []interface{}) (items []*BaseFromHisugarIndexView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT b.*, e.edb_info_id,
+	CASE WHEN e.edb_info_id IS NULL THEN 0 ELSE 1 END AS edb_exist
+	FROM base_from_hisugar_index AS b
+	LEFT JOIN edb_info AS e ON b.index_code=e.edb_code AND e.source=93
+	WHERE 1=1   `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY b.modify_time ASC `
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+// GetHisugarIndexViewListCount 根据分类id获取泛糖科技指标列表
+func GetHisugarIndexViewListCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count
+	FROM base_from_hisugar_index AS b
+	LEFT JOIN edb_info AS e ON b.index_code=e.edb_code AND e.source=93
+	WHERE 1=1   `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY b.modify_time ASC `
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetHisugarDataViewList 根据指标id获取泛糖科技指标列表
+func GetHisugarDataViewList(indexIds []int) (items []*BaseFromHisugarData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_data WHERE base_from_hisugar_index_id IN (` + utils.GetOrmInReplace(len(indexIds)) + `) ORDER BY data_time desc  `
+	_, err = o.Raw(sql, indexIds).QueryRows(&items)
+	return
+}
+
+// GetHisugarDataDataTimeByIndexId 根据指标id获取指标数据的日期列表
+func GetHisugarDataDataTimeByIndexId(indexIdList []int) (items []string, err error) {
+	if len(indexIdList) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT DISTINCT data_time FROM base_from_hisugar_data WHERE base_from_hisugar_index_id IN (` + utils.GetOrmInReplace(len(indexIdList)) + `) ORDER BY data_time DESC`
+	_, err = o.Raw(sql, indexIdList).QueryRows(&items)
+	return
+}
+
+type BaseFromHisugarClassifyItem struct {
+	BaseFromHisugarClassifyId int                            `orm:"column(base_from_hisugar_classify_id);pk"`
+	ClassifyName              string                         `description:"分类名称"`
+	ParentId                  int                            `description:"父级id"`
+	Level                     int                            `description:"层级"`
+	Sort                      int                            `description:"排序字段"`
+	UniqueCode                string                         `description:"唯一code"`
+	ModifyTime                time.Time                      `description:"修改时间"`
+	CreateTime                time.Time                      `description:"创建时间"`
+	ClassifyNameEn            string                         `description:"英文分类名称"`
+	Children                  []*BaseFromHisugarClassifyItem `description:"子分类"`
+}
+
+// 获取所有分类
+func GetHisugarClassifyAll() (items []*BaseFromHisugarClassifyItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_classify ORDER BY sort ASC, base_from_hisugar_classify_id ASC`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetHisugarIndex(condition string, pars interface{}) (items []*BaseFromHisugarIndexList, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_hisugar_index_id asc`
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+type HisugarIndexDataCountGroup struct {
+	IndexCode string
+	Count     int
+}
+
+func GetHisugarIndexDataCountGroup(indexCodes []string) (items []*HisugarIndexDataCountGroup, err error) {
+	if len(indexCodes) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count, index_code FROM base_from_hisugar_data WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodes)) + `) GROUP BY index_code`
+	_, err = o.Raw(sql, indexCodes).QueryRows(&items)
+	return
+}
+
+func GetHisugarIndexDataV2(indexCode string, startSize, pageSize int) (items []*BaseFromHisugarData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT *  FROM base_from_hisugar_data WHERE index_code=? ORDER BY data_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, indexCode, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+
+// GetHisugarIndexInfoCount 查询指标信息总数
+func GetHisugarIndexInfoCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(1) AS count FROM base_from_hisugar_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+
+type BaseFromHisugarIndexPage struct {
+	List   []*BaseFromHisugarIndex `description:"指标列表"`
+	Paging *paging.PagingItem     `description:"分页数据"`
+}
+
+// GetHisugarIndexInfoPage 分页查询指标信息
+func GetHisugarIndexInfoPage(condition string, pars []interface{}, size int, pageSize int) (items []*BaseFromHisugarIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM base_from_hisugar_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY base_from_hisugar_index_id asc LIMIT ?,?`
+	_, err = o.Raw(sql, pars, size, pageSize).QueryRows(&items)
+	return
+
+}
+
+// 获取所有分类
+func GetHisugarClassifyById(classifyId int) (ClassifyIds string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT GROUP_CONCAT(base_from_hisugar_classify_id) AS classify_ids FROM base_from_hisugar_classify WHERE base_from_hisugar_classify_id=? OR parent_id=?`
+	err = o.Raw(sql,classifyId,classifyId).QueryRow(&ClassifyIds)
+	return
+}

+ 48 - 0
models/data_manage/base_from_ly_classify.go

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

+ 108 - 0
models/data_manage/base_from_ly_data.go

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

+ 244 - 0
models/data_manage/base_from_ly_index.go

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

+ 14 - 2
models/data_manage/chart_info.go

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

+ 20 - 0
models/data_manage/chart_series.go

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

+ 1 - 0
models/data_manage/edb_classify.go

@@ -258,6 +258,7 @@ type EdbClassifyItems struct {
 type EdbClassifyIdItems struct {
 	ClassifyId       int `description:"分类id"`
 	ClassifyName     string
+	ClassifyNameEn   string
 	UniqueCode       string `description:"唯一编码"`
 	ParentId         int    `description:"父级分类id"`
 	Level            int    `description:"层级"`

+ 4 - 0
models/data_manage/edb_data_base.go

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

+ 39 - 0
models/data_manage/edb_data_hisugar.go

@@ -0,0 +1,39 @@
+package data_manage
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type HisugarData struct {
+	InputValue string `orm:"column(DATA_VALUE)" description:"日期"`
+	DataTime   string `orm:"column(DATA_DATE)" description:"值"`
+}
+
+func GetEdbDataHisugarMaxOrMinDate(edbCode string) (minDate, maxDate string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT MIN(data_time) AS minDate,MAX(data_time) AS maxDate FROM edb_data_hisugar WHERE edb_code=? `
+	err = o.Raw(sql, edbCode).QueryRow(&minDate, &maxDate)
+	return
+}
+
+type BaseFromHisugarIndexItem struct {
+	BaseFromSciIndexId int    `orm:"column(base_from_hisugar_index_id);pk"` // 主键,自动递增
+	IndexCode          string // 指标编码
+	IndexName          string // 指标名称
+	ClassifyId         int    // 分类Id
+	Unit               string // 单位
+	Frequency          string // 频度
+	Describe           string // 指标描述
+	CreateTime         string // 创建时间
+	ModifyTime         string // 修改时间
+}
+
+// GetBaseInfoFromHisugarByIndexCode 获取指标信息
+func GetBaseInfoFromHisugarByIndexCode(indexCode string) (item *BaseFromHisugarIndexItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM base_from_hisugar_index WHERE index_code=? `
+	sql = fmt.Sprintf(sql)
+	err = o.Raw(sql, indexCode).QueryRow(&item)
+	return
+}

+ 6 - 0
models/data_manage/edb_info.go

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

+ 18 - 17
models/data_manage/edb_info_calculate.go

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

+ 16 - 5
models/data_manage/edb_info_relation.go

@@ -236,6 +236,7 @@ type BaseRelationEdbInfo struct {
 	SysUserRealName string `description:"创建人姓名"`
 	Frequency       string `description:"频度"`
 	IsStop          int    `description:"是否停更:1:停更,0:未停更"`
+	IsSupplierStop  int    `description:"是否供应商停更:1:停更,0:未停更"`
 	RelationNum     int    `description:"引用次数"`
 	RelationTime    string `description:"引用时间"`
 }
@@ -261,11 +262,16 @@ type BaseRelationEdbInfoDetailResp struct {
 }
 
 // 查询指标引用列表
-func GetEdbInfoRelationList(condition string, pars []interface{}, orderBy string, startSize, pageSize int) (total int, items []*BaseRelationEdbInfo, err error) {
+func GetEdbInfoRelationList(condition string, pars []interface{}, addFieldStr, joinTableStr, orderBy string, startSize, pageSize int) (total int, items []*BaseRelationEdbInfo, err error) {
 	o := orm.NewOrmUsingDB("data")
 	// 数量汇总
 	totalSql := ` SELECT count(1) FROM edb_info e LEFT JOIN (
-SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as relation_time FROM edb_info_relation GROUP BY edb_info_id) r on e.edb_info_id=r.edb_info_id WHERE 1=1 `
+SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as relation_time FROM edb_info_relation GROUP BY edb_info_id) r on e.edb_info_id=r.edb_info_id  `
+
+	if joinTableStr != "" {
+		totalSql += joinTableStr
+	}
+	totalSql += ` WHERE 1=1 `
 	if condition != "" {
 		totalSql += condition
 	}
@@ -274,10 +280,15 @@ SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as re
 		return
 	}
 
+	fieldStr := ` e.edb_info_id, e.classify_id,e.edb_code,e.edb_name,e.sys_user_id,e.sys_user_real_name,e.frequency,e.no_update as is_stop, r.relation_num, r.relation_time ` + addFieldStr
 	// 列表数据
-	sql := ` SELECT e.edb_info_id, e.classify_id,e.edb_code,e.edb_name,e.sys_user_id,e.sys_user_real_name,e.frequency,e.no_update as is_stop, r.relation_num, r.relation_time from edb_info e LEFT JOIN (
-SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as relation_time FROM edb_info_relation GROUP BY edb_info_id) r on e.edb_info_id=r.edb_info_id WHERE 1=1
- `
+	sql := ` SELECT ` + fieldStr + ` from edb_info e LEFT JOIN (
+SELECT count(edb_info_id) as relation_num, edb_info_id, max(relation_time) as relation_time FROM edb_info_relation GROUP BY edb_info_id) r on e.edb_info_id=r.edb_info_id  `
+	if joinTableStr != "" {
+		sql += joinTableStr
+	}
+	sql += ` WHERE 1=1 `
+
 	if condition != "" {
 		sql += condition
 	}

+ 7 - 0
models/data_manage/multiple_graph_config_chart_mapping.go

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

+ 23 - 0
models/data_manage/mysteel_chemical_index.go

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

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

@@ -0,0 +1,122 @@
+package trade_analysis
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// BaseFromTradeExchange 交易所表
+type BaseFromTradeExchange struct {
+	BaseFromTradeExchangeId int       `orm:"column(base_from_trade_exchange_id);pk"`
+	Exchange                string    `description:"交易所标识"`
+	ExchangeName            string    `description:"交易所名称"`
+	ExchangeNameEn          string    `description:"交易所英文名称"`
+	Sort                    int       `description:"排序"`
+	AnalysisState           int       `description:"持仓分析状态:0-隐藏;1-显示"`
+	CreateTime              time.Time `description:"创建时间"`
+	ModifyTime              time.Time `description:"修改时间"`
+}
+
+func (m *BaseFromTradeExchange) TableName() string {
+	return "base_from_trade_exchange"
+}
+
+type BaseFromTradeExchangeCols struct {
+	PrimaryId      string
+	Exchange       string
+	ExchangeName   string
+	ExchangeNameEn string
+	Sort           string
+	AnalysisState  string
+	CreateTime     string
+	ModifyTime     string
+}
+
+func (m *BaseFromTradeExchange) Cols() BaseFromTradeExchangeCols {
+	return BaseFromTradeExchangeCols{
+		PrimaryId:      "base_from_trade_exchange_id",
+		Exchange:       "exchange",
+		ExchangeName:   "exchange_name",
+		ExchangeNameEn: "exchange_name_en",
+		Sort:           "sort",
+		AnalysisState:  "analysis_state",
+		CreateTime:     "create_time",
+		ModifyTime:     "modify_time",
+	}
+}
+
+func (m *BaseFromTradeExchange) GetItemById(id int) (item *BaseFromTradeExchange, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *BaseFromTradeExchange) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromTradeExchange, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *BaseFromTradeExchange) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *BaseFromTradeExchange) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromTradeExchange, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *BaseFromTradeExchange) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromTradeExchange, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// BaseFromTradeExchangeItem 交易所信息
+type BaseFromTradeExchangeItem struct {
+	ExchangeId     int    `description:"交易所ID"`
+	Exchange       string `description:"交易所标识"`
+	ExchangeName   string `description:"交易所名称"`
+	ExchangeNameEn string `description:"交易所英文名称"`
+	Sort           int    `description:"排序"`
+}
+
+func (m *BaseFromTradeExchange) Format2Item() (item *BaseFromTradeExchangeItem) {
+	item = new(BaseFromTradeExchangeItem)
+	item.ExchangeId = m.BaseFromTradeExchangeId
+	item.Exchange = m.Exchange
+	item.ExchangeName = m.ExchangeName
+	item.ExchangeNameEn = m.ExchangeNameEn
+	item.Sort = m.Sort
+	return
+}

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,129 @@
+package trade_analysis
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const TradeFuturesCompanyTop20 = "TOP20"
+
+// TradeFuturesCompany 期货公司表
+type TradeFuturesCompany struct {
+	TradeFuturesCompanyId int       `orm:"column(trade_futures_company_id);pk"`
+	CompanyName           string    `description:"标准公司名称"`
+	ZhengzhouName         string    `description:"郑商所下的名称"`
+	DalianName            string    `description:"大商所下的名称"`
+	ShanghaiName          string    `description:"上期所下的名称"`
+	IneName               string    `description:"上期能源下的名称"`
+	GuangzhouName         string    `description:"广期所下的名称"`
+	CffexName             string    `description:"中金所下的名称"`
+	Sort                  int       `description:"排序"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+func (m *TradeFuturesCompany) TableName() string {
+	return "trade_futures_company"
+}
+
+type TradeFuturesCompanyCols struct {
+	PrimaryId     string
+	CompanyName   string
+	ZhengzhouName string
+	DalianName    string
+	ShanghaiName  string
+	IneName       string
+	GuangzhouName string
+	CffexName     string
+	Sort          string
+	CreateTime    string
+	ModifyTime    string
+}
+
+func (m *TradeFuturesCompany) Cols() TradeFuturesCompanyCols {
+	return TradeFuturesCompanyCols{
+		PrimaryId:     "trade_futures_company_id",
+		CompanyName:   "company_name",
+		ZhengzhouName: "zhengzhou_name",
+		DalianName:    "dalian_name",
+		ShanghaiName:  "shanghai_name",
+		IneName:       "ine_name",
+		GuangzhouName: "guangzhou_name",
+		CffexName:     "cffex_name",
+		Sort:          "sort",
+		CreateTime:    "create_time",
+		ModifyTime:    "modify_time",
+	}
+}
+
+func (m *TradeFuturesCompany) GetItemById(id int) (item *TradeFuturesCompany, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *TradeFuturesCompany) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *TradeFuturesCompany, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *TradeFuturesCompany) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *TradeFuturesCompany) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*TradeFuturesCompany, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *TradeFuturesCompany) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*TradeFuturesCompany, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// TradeFuturesCompanyItem 期货公司信息
+type TradeFuturesCompanyItem struct {
+	CompanyId   int    `description:"期货公司ID"`
+	CompanyName string `description:"标准公司名称"`
+	Sort        int    `description:"排序"`
+}
+
+func (m *TradeFuturesCompany) Format2Item() (item *TradeFuturesCompanyItem) {
+	item = new(TradeFuturesCompanyItem)
+	item.CompanyId = m.TradeFuturesCompanyId
+	item.CompanyName = m.CompanyName
+	item.Sort = m.Sort
+	return
+}

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

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

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

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

+ 120 - 0
models/document_manage_model/outside_report.go

@@ -0,0 +1,120 @@
+// @Author gmy 2024/9/19 14:53:00
+package document_manage_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type OutsideReport struct {
+	OutsideReportId  int    `orm:"column(outside_report_id);pk" description:"外部报告ID"`
+	Source           int    `orm:"column(source)" description:"来源,1:ETA系统录入;2:API接口录入;3:邮件监听录入"`
+	Title            string `orm:"column(title)" description:"报告标题"`
+	Abstract         string `orm:"column(abstract)" description:"摘要"`
+	ClassifyId       int    `orm:"column(classify_id)" description:"所属分类id"`
+	ClassifyName     string `orm:"column(classify_name)" description:"所属分类名称(整个分类链条)"`
+	Content          string `orm:"column(content)" description:"报告富文本内容"`
+	SysUserId        int    `orm:"column(sys_user_id)" description:"创建人id"`
+	SysUserName      string `orm:"column(sys_user_name)" description:"创建人姓名"`
+	EmailMessageUid  int    `orm:"column(email_message_uid)" description:"该邮件在邮箱中的唯一id"`
+	ReportUpdateTime string `orm:"column(report_update_time)" description:"报告更新时间,如果来源于邮件,那么取邮件的收件时间"`
+	ModifyTime       string `orm:"column(modify_time)" description:"最近一次修改时间"`
+	CreateTime       string `orm:"column(create_time)" description:"创建时间"`
+	ReportCode       string `orm:"column(report_code)" description:"报告唯一编码"`
+}
+
+type OutsideReportPage struct {
+	List   []OutsideReport    `description:"报告列表"`
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type OutsideReportBO struct {
+	OutsideReportId int    `orm:"column(outside_report_id);pk" description:"外部报告ID"`
+	Source          int    `orm:"column(source)" description:"来源,1:ETA系统录入;2:API接口录入;3:邮件监听录入"`
+	Title           string `orm:"column(title)" description:"报告标题"`
+	Abstract        string `orm:"column(abstract)" description:"摘要"`
+	ClassifyId      int    `orm:"column(classify_id)" description:"所属分类id"`
+	ClassifyName    string `orm:"column(classify_name)" description:"所属分类名称(整个分类链条)"`
+	Content         string `orm:"column(content)" description:"报告富文本内容"`
+	SysUserId       int    `orm:"column(sys_user_id)" description:"创建人id"`
+	SysUserName     string `orm:"column(sys_user_name)" description:"创建人姓名"`
+	ReportCode      string `orm:"column(report_code)" description:"报告唯一编码"`
+	ModifyTime      string `orm:"column(modify_time)" description:"最近一次修改时间"`
+	CreateTime      string `orm:"column(create_time)" description:"创建时间"`
+	AttachmentList  []*OutsideReportAttachment
+}
+
+// 在 init 函数中注册模型
+func init() {
+	orm.RegisterModel(new(OutsideReport))
+}
+
+// GetOutsideReportListByConditionCount 根据条件查询列表条数
+func GetOutsideReportListByConditionCount(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select count(distinct t1.outside_report_id) from outside_report t1 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
+	sql += condition
+	err = o.Raw(sql, pars).QueryRow(&count)
+	if err != nil {
+		return 0, err
+	}
+
+	return count, err
+}
+
+// GetOutsideReportListByCondition 根据条件查询列表
+func GetOutsideReportListByCondition(condition string, pars []interface{}, currentIndex int, pageSize int) (list []OutsideReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select distinct t1.* from outside_report t1 left join chart_permission_search_key_word_mapping t2 on t1.classify_id = t2.classify_id  where 1 = 1 `
+	sql += condition
+	sql += ` limit ?, ?`
+	_, err = o.Raw(sql, pars, (currentIndex-1)*pageSize, pageSize).QueryRows(&list)
+	if err != nil {
+		return nil, err
+	}
+
+	return list, err
+}
+
+// SaveOutsideReport 保存报告
+func SaveOutsideReport(outsideReport OutsideReport) (id int64, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err = o.Insert(&outsideReport)
+	return
+}
+
+// GetOutsideReportById 根据ID获取报告
+func GetOutsideReportById(id int) (outsideReport *OutsideReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+
+	outsideReport = &OutsideReport{}
+
+	err = o.QueryTable("outside_report").Filter("outside_report_id", id).One(outsideReport)
+	return
+}
+
+// UpdateOutsideReport 更新报告
+func UpdateOutsideReport(outsideReport *OutsideReport) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(outsideReport)
+	return
+}
+
+// DeleteOutsideReport 删除报告
+func DeleteOutsideReport(id int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.QueryTable("outside_report").Filter("outside_report_id", id).Delete()
+	return
+}
+
+// GetOutsideReportListByClassifyId 根据分类id查询报告列表
+func GetOutsideReportListByClassifyId(classifyId int) (list []OutsideReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `select * from outside_report where classify_id = ?`
+	_, err = o.Raw(sql, classifyId).QueryRows(&list)
+	if err != nil {
+		return nil, err
+	}
+
+	return list, err
+}

+ 48 - 0
models/document_manage_model/outside_report_attachment.go

@@ -0,0 +1,48 @@
+// @Author gmy 2024/9/19 15:13:00
+package document_manage_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type OutsideReportAttachment struct {
+	OutsideReportAttachmentId int    `orm:"column(outside_report_attachment_id);pk" description:"外部报告附件ID"`
+	OutsideReportId           int    `orm:"column(outside_report_id)" description:"报告id"`
+	Title                     string `orm:"column(title)" description:"附件名称"`
+	Url                       string `orm:"column(url)" description:"附件地址"`
+	CreateTime                string `orm:"column(create_time)" description:"附件新增时间"`
+	FileSize                  int64  `orm:"column(file_size)" description:"附件大小"`
+}
+
+// 在 init 函数中注册模型
+func init() {
+	orm.RegisterModel(new(OutsideReportAttachment))
+}
+
+// SaveOutsideReportAttachment 保存附件
+func SaveOutsideReportAttachment(attachment *OutsideReportAttachment) (int64, error) {
+	o := orm.NewOrmUsingDB("rddp")
+	return o.Insert(attachment)
+}
+
+// GetOutsideReportAttachmentListByReportId 根据报告id获取附件列表
+func GetOutsideReportAttachmentListByReportId(outsideReportId int) ([]*OutsideReportAttachment, error) {
+	o := orm.NewOrmUsingDB("rddp")
+	var attachmentList []*OutsideReportAttachment
+	_, err := o.QueryTable("outside_report_attachment").
+		Filter("outside_report_id", outsideReportId).
+		All(&attachmentList)
+	if err != nil {
+		return nil, err
+	}
+	return attachmentList, nil
+}
+
+// DeleteReportAttachmentByReportId 根据报告id删除附件
+func DeleteReportAttachmentByReportId(outsideReportId int) error {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err := o.QueryTable("outside_report_attachment").
+		Filter("outside_report_id", outsideReportId).
+		Delete()
+	return err
+}

+ 33 - 0
models/manual_edb.go

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

+ 34 - 2
models/report.go

@@ -101,7 +101,7 @@ type ReportList struct {
 	Author             string                    `description:"作者"`
 	Frequency          string                    `description:"频度"`
 	CreateTime         string                    `description:"创建时间"`
-	ModifyTime         time.Time                 `description:"修改时间"`
+	ModifyTime         string                    `description:"修改时间"`
 	State              int                       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	PublishTime        string                    `description:"发布时间"`
 	PrePublishTime     string                    `description:"预发布时间"`
@@ -192,11 +192,24 @@ func GetReportListV1(condition string, pars []interface{}, startSize, pageSize i
 		sql += condition
 	}
 	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
-	sql += `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
+	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
 }
 
+// GetReportListByCondition 获取报告列表
+func GetReportListByCondition(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
+	}
+	sql += ` LIMIT ?,?`
+	_, err = o.Raw(sql, pars, (startSize-1)*pageSize, pageSize).QueryRows(&items)
+	return
+}
+
 type ReportPvUv struct {
 	ReportId int
 	PvTotal  int
@@ -1505,3 +1518,22 @@ func GetReportFieldsByIds(ids []int, fields []string) (items []*Report, err erro
 	_, err = o.Raw(sql, ids).QueryRows(&items)
 	return
 }
+
+// GetReportListByClassifyId 根据分类id 获取报告列表
+func GetReportListByClassifyId(classifyId int) (items []*Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE classify_id_first = ? or classify_id_second = ? or classify_id_third = ?`
+	_, err = o.Raw(sql, classifyId, classifyId, classifyId).QueryRows(&items)
+	return items, err
+}
+
+// UpdateReportInfo 修改报告
+func UpdateReportInfo(reports *Report) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(reports)
+	if err != nil {
+		return err
+	}
+
+	return
+}

+ 31 - 20
models/target.go

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

+ 567 - 0
routers/commentsRouter.go

@@ -2500,6 +2500,60 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyClassifyController"],
+        beego.ControllerComments{
+            Method: "LyClassifyList",
+            Router: `/ly/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexAdd",
+            Router: `/ly/index/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexAddValidate",
+            Router: `/ly/index/add/validate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexDataExport",
+            Router: `/ly/index/data/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexDataList",
+            Router: `/ly/index/data/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromLyIndexController"],
+        beego.ControllerComments{
+            Method: "LyIndexList",
+            Router: `/ly/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromNationalStatisticsController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromNationalStatisticsController"],
         beego.ControllerComments{
             Method: "ClassifyList",
@@ -2716,6 +2770,15 @@ func init() {
             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: "EdbInfoAdd",
+            Router: `/bloomberg_source/edb_add`,
+            AllowHTTPMethods: []string{"post"},
+            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: "List",
@@ -3535,6 +3598,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbBusinessController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbBusinessController"],
+        beego.ControllerComments{
+            Method: "EdbInfoAdd",
+            Router: `/business_data/edb_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbBusinessController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbBusinessController"],
         beego.ControllerComments{
             Method: "List",
@@ -4462,6 +4534,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "EiaSteoBatchAdd",
+            Router: `/eia_steo/batch_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "EiaSteoBatchSearch",
+            Router: `/eia_steo/batch_search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "EiaSteoClassify",
@@ -4480,6 +4570,33 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "EiaSteoAdd",
+            Router: `/eia_steo/edb_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "EiaSteoAddCheck",
+            Router: `/eia_steo/edb_info/add_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "EiaSteoNameCheck",
+            Router: `/eia_steo/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "EiaSteoSearchList",
@@ -4561,6 +4678,42 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "GetFenWeiFrequencyList",
+            Router: `/fenwei/frequency/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "GetFenWeiIndexInfo",
+            Router: `/fenwei/get/index/info`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "FenWeiIndexAdd",
+            Router: `/fenwei/index/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "FenWeiIndexAddValidate",
+            Router: `/fenwei/index/add/validate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "FenweiIndexData",
@@ -4570,6 +4723,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "FenWeiIndexDataExport",
+            Router: `/fenwei/index/data/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "FenweiSearchList",
@@ -6244,6 +6406,87 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentClassifyList",
+            Router: `/document/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentDelete",
+            Router: `/document/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentDetail",
+            Router: `/document/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentReportList",
+            Router: `/document/report/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentRuiSiDetail",
+            Router: `/document/rui/si/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "RuiSiReportList",
+            Router: `/document/rui/si/report/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentSave",
+            Router: `/document/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentUpdate",
+            Router: `/document/update`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/document_manage:DocumentManageController"],
+        beego.ControllerComments{
+            Method: "DocumentVarietyList",
+            Router: `/document/variety/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"],
         beego.ControllerComments{
             Method: "Add",
@@ -8044,6 +8287,42 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeClassifyList",
+            Router: `/classify_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeFuturesCompanyList",
+            Router: `/company_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeContractList",
+            Router: `/contract_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
+        beego.ControllerComments{
+            Method: "GetTradeExchangeList",
+            Router: `/exchange_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:TradeAnalysisController"],
         beego.ControllerComments{
             Method: "GetPositionTop",
@@ -8053,6 +8332,177 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "AddChartClassify",
+            Router: `/warehouse/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassify",
+            Router: `/warehouse/classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "DeleteChartClassifyCheck",
+            Router: `/warehouse/classify/delete/check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "EditChartClassify",
+            Router: `/warehouse/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyItems",
+            Router: `/warehouse/classify/items`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyList",
+            Router: `/warehouse/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ChartClassifyMove",
+            Router: `/warehouse/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/warehouse/classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/warehouse/chart/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Copy",
+            Router: `/warehouse/chart/copy`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/warehouse/chart/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/warehouse/chart/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/warehouse/chart/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Preview",
+            Router: `/warehouse/chart/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/warehouse/chart/refresh`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "ChartInfoSave",
+            Router: `/warehouse/chart/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/warehouse/chart/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "EdbSave",
+            Router: `/warehouse/edb/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/trade_analysis:WarehouseController"],
+        beego.ControllerComments{
+            Method: "EdbSaveCheck",
+            Router: `/warehouse/edb/save_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:BannerController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:BannerController"],
         beego.ControllerComments{
             Method: "Upload",
@@ -9646,6 +10096,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ResourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ResourceController"],
+        beego.ControllerComments{
+            Method: "FileUpload",
+            Router: `/file/upload`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ResourceController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ResourceController"],
         beego.ControllerComments{
             Method: "Upload",
@@ -10582,6 +11041,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarExporthisugarList",
+            Router: `/data/export/hisugarList`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
         beego.ControllerComments{
             Method: "ExportoilchemList",
@@ -10591,6 +11059,105 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarAddCheck",
+            Router: `/data/hisugar/add_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarBatchAdd",
+            Router: `/data/hisugar/batch_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarBatchIndexList",
+            Router: `/data/hisugar/batch_list`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarClassify",
+            Router: `/data/hisugar/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarData",
+            Router: `/data/hisugar/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "FrequencyList",
+            Router: `/data/hisugar/frequency_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarIndexData",
+            Router: `/data/hisugar/index/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "GetHisugarIndexInfo",
+            Router: `/data/hisugar/index/info`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarIndexList",
+            Router: `/data/hisugar/indexList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarNameCheck",
+            Router: `/data/hisugar/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
+        beego.ControllerComments{
+            Method: "HisugarSearchList",
+            Router: `/data/hisugar/search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:TradeCommonController"],
         beego.ControllerComments{
             Method: "IneDataList",

+ 10 - 0
routers/router.go

@@ -22,6 +22,7 @@ import (
 	"eta/eta_api/controllers/data_manage/supply_analysis"
 	"eta/eta_api/controllers/data_source"
 	"eta/eta_api/controllers/data_stat"
+	"eta/eta_api/controllers/document_manage"
 	"eta/eta_api/controllers/english_report"
 	"eta/eta_api/controllers/eta_trial"
 	"eta/eta_api/controllers/fe_calendar"
@@ -177,6 +178,8 @@ func init() {
 				&data_manage.EdbInfoRelationController{},
 				&data_manage.FactorEdbSeriesController{},
 				&data_manage.SciHqDataController{},
+				&data_manage.BaseFromLyClassifyController{},
+				&data_manage.BaseFromLyIndexController{},
 			),
 		),
 		web.NSNamespace("/my_chart",
@@ -287,6 +290,8 @@ func init() {
 		web.NSNamespace("/trade_analysis",
 			web.NSInclude(
 				&trade_analysis.TradeAnalysisController{},
+				&trade_analysis.WarehouseClassifyController{},
+				&trade_analysis.WarehouseController{},
 			),
 		),
 		web.NSNamespace("/custom_analysis",
@@ -387,6 +392,11 @@ func init() {
 				&range_analysis.RangeChartChartInfoController{},
 			),
 		),
+		web.NSNamespace("/document_manage",
+			web.NSInclude(
+				&document_manage.DocumentManageController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 43 - 72
services/classify.go

@@ -3,6 +3,7 @@ package services
 import (
 	"errors"
 	"eta/eta_api/models"
+	"eta/eta_api/models/document_manage_model"
 	"eta/eta_api/models/report_approve"
 	"eta/eta_api/utils"
 	"fmt"
@@ -269,83 +270,53 @@ func AddReportClassify(classifyName string, parentId int, chartPermissionIdList
 	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
+
+	err = models.AddClassify(classify)
+	if err != nil {
+		return
+	}
+
+	// 如果父级分类下有报告,修改报告到子级分类下
+	reports, err := models.GetReportListByClassifyId(parentId)
+	if err != nil {
+		return err, "查询报告列表失败", false
+	}
+	if len(reports) > 0 {
+		for _, report := range reports {
+			if report.ClassifyIdFirst == 0 {
+				report.ClassifyIdFirst = classify.Id
+				report.ClassifyNameFirst = classifyName
+			} else if report.ClassifyIdSecond == 0 {
+				report.ClassifyIdSecond = classify.Id
+				report.ClassifyNameSecond = classifyName
+			} else {
+				report.ClassifyIdThird = classify.Id
+				report.ClassifyNameThird = classifyName
 			}
-		} 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
-				}
+
+			// beego orm 不支持批量修改,所以只能一个一个修改
+			err := models.UpdateReportInfo(report)
+			if err != nil {
+				return err, "修改报告分类失败", false
 			}
 		}
-		if updateParent {
-			if err = parentClassify.UpdateClassify(updateCols); err != nil {
-				br.Msg = "更新父级分类失败"
-				br.ErrMsg = "更新父级分类失败, Err:" + err.Error()
-				return
+	}
+	outsideReports, err := document_manage_model.GetOutsideReportListByClassifyId(parentId)
+	if err != nil {
+		return err, "查询外部报告列表失败", false
+	}
+	if len(outsideReports) > 0 {
+		for _, report := range outsideReports {
+			tempReport := report
+
+			tempReport.ClassifyId = classify.Id
+			tempReport.ClassifyName = classifyName
+			// 修改报告
+			err := document_manage_model.UpdateOutsideReport(&tempReport)
+			if err != nil {
+				return err, "修改外部报告分类失败", false
 			}
 		}
-	}*/
-	err = models.AddClassify(classify)
-	if err != nil {
-		return
 	}
 
 	//获取报告分类权限列表

+ 6 - 0
services/data/base_edb_lib.go

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

+ 79 - 0
services/data/base_from_eia_steo.go

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

+ 162 - 0
services/data/base_from_fenwei_index_service.go

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

+ 115 - 0
services/data/base_from_hisugar.go

@@ -0,0 +1,115 @@
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type HisugarIndexSource2EdbReq struct {
+	EdbCode       string
+	EdbName       string
+	Frequency     string
+	Unit          string
+	ClassifyId    int
+	AdminId       int
+	AdminRealName string
+}
+
+
+
+// HisugarIndexSource2Edb 新增泛糖科技数据从数据源到指标库
+func HisugarIndexSource2Edb(req HisugarIndexSource2EdbReq, lang string) (edb *data_manage.EdbInfo, errMsg string, skip bool, err error) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("HisugarIndexSource2Edb新增失败, Err: %s", err.Error())
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_HISUGAR
+
+	// 是否新增过指标
+	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
+}
+
+
+
+// GethisugarIndexInfo 获取指标信息-分页
+func GethisugarIndexInfo(keyWord string, classifyIdList []string, frequencyList []string, currentIndex int, pageSize int) (baseFromHisugarIndexPage *data_manage.BaseFromHisugarIndexPage, err error) {
+
+	// 获取指标
+	var condition string
+	var pars []interface{}
+	if keyWord != "" {
+		condition += ` AND CONCAT(index_name,index_code) LIKE '%` + keyWord + `%'`
+	}
+	if len(classifyIdList) > 0 {
+		condition += ` AND classify_id IN (`
+		for _, v := range classifyIdList {
+			condition += `?,`
+			pars = append(pars, v)
+		}
+		condition = condition[:len(condition)-1] + `)`
+	}
+	if len(frequencyList) > 0 {
+		condition += ` AND frequency IN (`
+		for _, v := range frequencyList {
+			condition += `?,`
+			pars = append(pars, v)
+		}
+		condition = condition[:len(condition)-1] + `)`
+	}
+
+	count, err := data_manage.GetHisugarIndexInfoCount(condition, pars)
+	if err != nil {
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, count)
+	if count <= 0 {
+		baseFromHisugarIndexPage = &data_manage.BaseFromHisugarIndexPage{
+			List:   []*data_manage.BaseFromHisugarIndex{},
+			Paging: page,
+		}
+		return
+	}
+
+	startSize := utils.StartIndex(currentIndex, pageSize)
+	fenWeiIndexInfoList, err := data_manage.GetHisugarIndexInfoPage(condition, pars, startSize, pageSize)
+	if err != nil {
+		return
+	}
+	baseFromHisugarIndexPage = &data_manage.BaseFromHisugarIndexPage{
+		List:   fenWeiIndexInfoList,
+		Paging: page,
+	}
+	return
+}

+ 47 - 0
services/data/base_from_ly_classify_service.go

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

+ 249 - 0
services/data/base_from_ly_index_service.go

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

+ 320 - 4
services/data/chart_info.go

@@ -534,7 +534,7 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 	case 2: // 季节性图
 		if seasonExtraConfig != "" {
 			// 季节性图计算不管图上数据时间,拿所有数据
-			_, tempEdbList, e := getEdbDataMapList(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			_, tempEdbList, e := getEdbDataMapListForSeason(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
 			if e != nil {
 				err = e
 				return
@@ -3772,7 +3772,7 @@ func GetChartEdbDataV2(chartInfoId, chartType int, calendar, startDate, endDate
 	case 2: // 季节性图
 		if seasonExtraConfig != "" {
 			// 季节性图计算不管图上数据时间,拿所有数据
-			_, tempEdbList, e := getEdbDataMapList(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			_, tempEdbList, e := getEdbDataMapListForSeason(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
 			if e != nil {
 				err = e
 				return
@@ -3901,6 +3901,11 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 					if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
 						continue
 					}
+					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+					chartLegend,_ := strconv.Atoi(v.ChartLegend)
+					if chartLegend < startYear {
+						continue
+					}
 					for _, date := range dataTimeList {
 						dateTime, e := time.Parse(utils.FormatDate, date)
 						if e != nil {
@@ -4032,6 +4037,11 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 						if dateTime.Year() < startYear {
 							continue
 						}
+						// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+						chartLegend,_ := strconv.Atoi(v.ChartLegend)
+						if chartLegend < startYear {
+							continue
+						}
 						// 月度的2月29号日期改为2月28日
 						if dateTime.Month() == 2 && dateTime.Day() == 29 {
 							dateTime = dateTime.AddDate(0, 0, -1)
@@ -4182,6 +4192,7 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 			dataTimeList := make([]string, 0)
 			valueMap := make(map[time.Time]float64)
 			averageDataList := make([]*data_manage.SamePeriodAverageData, 0)
+			dateNumMap := make(map[time.Time]float64)
 			for i := len(quarterDataList) - 1; i >= 0; i-- {
 				// 插值成日度
 				dataTimeList, _, err = HandleDataByLinearRegressionToList(quarterDataList[i].DataList, handleDataMap)
@@ -4199,6 +4210,11 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 					if dateTime.Year() < startYear {
 						continue
 					}
+					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+					chartLegend,_ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					if chartLegend < startYear {
+						continue
+					}
 					// 不包含2月29号
 					if dateTime.Month() == 2 && dateTime.Day() == 29 {
 						continue
@@ -4214,16 +4230,22 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 					} else {
 						valueMap[newDate] = handleDataMap[date]
 					}
+					dateNumMap[newDate] += 1
 
 					dataTimeMap[newDate] = newDate
 				}
 			}
 
-			year := float64(seasonConfig.SamePeriodAverage.Year)
+			//year := float64(seasonConfig.SamePeriodAverage.Year)
 			for _, v := range dataTimeMap {
 				averageItem := &data_manage.SamePeriodAverageData{}
+				year := dateNumMap[v]
 				if value, ok := valueMap[v]; ok {
-					averageItem.Value = value / year
+					if year > 0 {
+						averageItem.Value = value / year
+					} else {
+						averageItem.Value = value
+					}
 				}
 				averageItem.DataTime = v.Format(utils.FormatDate)
 				averageItem.DataTimestamp = v.UnixNano() / 1e6
@@ -4276,6 +4298,11 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 					if dateTime.Year() < startYear {
 						continue
 					}
+					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+					chartLegend,_ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					if chartLegend < startYear {
+						continue
+					}
 					// 不包含2月29号
 					if dateTime.Month() == 2 && dateTime.Day() == 29 {
 						continue
@@ -4952,3 +4979,292 @@ func handleSystemAppointDateT2(currDate time.Time, appointDay, frequency string)
 func CalculatePercentile(x float64, min float64, max float64) float64 {
 	return (x/100)*(max-min) + min
 }
+
+// getEdbDataMapListForSeason 获取指标最后的基础数据-插值补充好数据-季节性图用
+func getEdbDataMapListForSeason(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) {
+	// 指标对应的所有数据
+	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
+
+	for _, v := range mappingList {
+		//fmt.Println("v:", v.EdbInfoId)
+		item := new(data_manage.ChartEdbInfoMapping)
+		item.EdbInfoId = v.EdbInfoId
+		item.SourceName = v.SourceName
+		item.Source = v.Source
+		item.EdbCode = v.EdbCode
+		item.EdbName = v.EdbName
+		item.EdbNameEn = v.EdbNameEn
+		item.Frequency = v.Frequency
+		item.EdbType = v.EdbType
+		item.FrequencyEn = GetFrequencyEn(v.Frequency)
+		if v.Unit != `无` {
+			item.Unit = v.Unit
+		}
+		item.UnitEn = v.UnitEn
+		item.StartDate = v.StartDate
+		item.EndDate = v.EndDate
+		item.ModifyTime = v.ModifyTime
+		item.EdbInfoCategoryType = v.EdbInfoCategoryType
+		item.PredictChartColor = v.PredictChartColor
+		item.ClassifyId = v.ClassifyId
+		if chartInfoId <= 0 {
+			item.IsAxis = 1
+			item.LeadValue = 0
+			item.LeadUnit = ""
+			item.ChartEdbMappingId = 0
+			item.ChartInfoId = 0
+			item.IsOrder = false
+			item.EdbInfoType = 1
+			item.ChartStyle = ""
+			item.ChartColor = ""
+			item.ChartWidth = 0
+			item.MaxData = v.MaxValue
+			item.MinData = v.MinValue
+		} else {
+			item.IsAxis = v.IsAxis
+			item.EdbInfoType = v.EdbInfoType
+			item.LeadValue = v.LeadValue
+			item.LeadUnit = v.LeadUnit
+			item.LeadUnitEn = GetLeadUnitEn(v.LeadUnit)
+			item.ChartEdbMappingId = v.ChartEdbMappingId
+			item.ChartInfoId = v.ChartInfoId
+			item.ChartStyle = v.ChartStyle
+			item.ChartColor = v.ChartColor
+			item.ChartWidth = v.ChartWidth
+			item.IsOrder = v.IsOrder
+			item.MaxData = v.MaxData
+			item.MinData = v.MinData
+		}
+		item.LatestValue = v.LatestValue
+		item.LatestDate = v.LatestDate
+		item.UniqueCode = v.UniqueCode
+		item.MoveLatestDate = v.LatestDate
+		item.EdbAliasName = v.EdbAliasName
+		item.IsConvert = v.IsConvert
+		item.ConvertType = v.ConvertType
+		item.ConvertValue = v.ConvertValue
+		item.ConvertUnit = v.ConvertUnit
+		item.ConvertEnUnit = v.ConvertEnUnit
+		item.IsJoinPermission = v.IsJoinPermission
+
+		var startDateReal string
+		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
+				startDateParse, _ := time.Parse(utils.FormatDate, startDate)
+				switch v.LeadUnit {
+				case "天":
+					startTimeRealTemp = startDateParse.AddDate(0, 0, -v.LeadValue)
+				case "月":
+					startTimeRealTemp = startDateParse.AddDate(0, -v.LeadValue, 0)
+				case "季":
+					startTimeRealTemp = startDateParse.AddDate(0, -3*v.LeadValue, 0)
+				case "周":
+					startTimeRealTemp = startDateParse.AddDate(0, 0, -7*v.LeadValue)
+				case "年":
+					startTimeRealTemp = startDateParse.AddDate(-v.LeadValue, 0, 0)
+				}
+				if startTimeRealTemp.Before(startDateParse) {
+					startDateReal = startTimeRealTemp.Format(utils.FormatDate)
+					diffSeconds = (int64(startTimeRealTemp.UnixNano()) - int64(startDateParse.UnixNano())) / 1e6
+				} else {
+					startDateReal = startDate
+					diffSeconds = 0
+				}
+
+				// 预测指标的开始日期也要偏移
+				{
+					day, tmpErr := utils.GetDaysBetween2Date(utils.FormatDate, startDate, startDateReal)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					moveLatestDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, item.MoveLatestDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					item.MoveLatestDate = moveLatestDateTime.AddDate(0, 0, day).Format(utils.FormatDate)
+				}
+			} else {
+				startDateReal = startDate
+			}
+		}
+		//fmt.Println("line 1011 chart:", v.Source, v.EdbInfoId, startDateReal, endDate)
+		calendarPreYear := 0
+		if calendar == "农历" {
+			newStartDateReal, err := time.Parse(utils.FormatDate, startDateReal)
+			if err != nil {
+				fmt.Println("time.Parse:" + err.Error())
+			}
+			calendarPreYear = newStartDateReal.Year() - 1
+			newStartDateReal = newStartDateReal.AddDate(-1, 0, 0)
+			startDateReal = newStartDateReal.Format(utils.FormatDate)
+		}
+		dataList := make([]*data_manage.EdbDataList, 0)
+		//fmt.Println("chart:", v.Source, v.EdbInfoId, startDateReal, endDate)
+		//fmt.Println("calendarPreYear:", calendarPreYear)
+		//var newEdbInfo *data_manage.EdbInfo
+		switch v.EdbInfoCategoryType {
+		case 0:
+			dataList, err = data_manage.GetEdbDataList(v.Source, v.SubSource, v.EdbInfoId, startDateReal, endDate)
+		case 1:
+			_, dataList, _, _, err, _ = GetPredictDataListByPredictEdbInfoId(v.EdbInfoId, startDateReal, endDate, true)
+		default:
+			err = errors.New(fmt.Sprint("获取失败,指标类型异常", v.EdbInfoCategoryType))
+		}
+		if err != nil {
+			return
+		}
+
+		handleDataMap := make(map[string]float64)
+		newDataList, _, _, e := HandleDataByLinearRegressionToListV3(dataList, handleDataMap)
+		if e != nil {
+			err = e
+			return
+		}
+
+		if v.IsConvert == 1 {
+			switch v.ConvertType {
+			case 1:
+				for i, data := range newDataList {
+					newDataList[i].Value = data.Value * v.ConvertValue
+				}
+				//item.MaxData = item.MaxData * v.ConvertValue
+				//item.MinData = item.MinData * v.ConvertValue
+			case 2:
+				for i, data := range newDataList {
+					newDataList[i].Value = data.Value / v.ConvertValue
+				}
+				//item.MaxData = item.MaxData / v.ConvertValue
+				//item.MinData = item.MinData / v.ConvertValue
+			case 3:
+				for i, data := range newDataList {
+					if data.Value <= 0 {
+						err = errors.New("数据中含有负数或0,无法对数运算")
+						return
+					}
+					newDataList[i].Value = math.Log(data.Value) / math.Log(v.ConvertValue)
+				}
+				//item.MaxData = math.Log(item.MaxData) / math.Log(v.ConvertValue)
+				//item.MinData = math.Log(item.MinData) / math.Log(v.ConvertValue)
+			}
+		}
+
+		edbDataListMap[v.EdbInfoId] = newDataList
+
+		if diffSeconds != 0 && v.EdbInfoType == 0 {
+			newDataListLen := len(newDataList)
+			for i := 0; i < newDataListLen; i++ {
+				newDataList[i].DataTimestamp = newDataList[i].DataTimestamp - diffSeconds
+			}
+		}
+
+		if chartType == 2 && item.IsAxis == 1 {
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = newDataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+
+			if calendar == "农历" {
+				if len(newDataList) <= 0 {
+					result := new(data_manage.EdbDataResult)
+					item.DataList = result
+				} else {
+					result, tmpErr := data_manage.AddCalculateQuarterV6(newDataList)
+					if tmpErr != nil {
+						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
+						return
+					}
+					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					if tErr != nil {
+						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+						return
+					}
+					item.DataList = quarterDataList
+				}
+
+			} else {
+				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(newDataList, latestDate, seasonExtraConfig)
+				if tErr != nil {
+					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+					return
+				}
+				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(newDataList)
+			if length == 0 {
+				return
+			}
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = newDataList
+				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
+			}
+
+			newList := make([]*data_manage.EdbDataList, 0)
+			for _, v := range newDataList {
+				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) {
+					newList = append(newList, v)
+				}
+			}
+			item.DataList = newList
+		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
+			//item.DataList = newDataList
+		} else {
+			item.DataList = newDataList
+		}
+		edbList = append(edbList, item)
+	}
+
+	return
+}

+ 485 - 0
services/data/common_classify.go

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

+ 106 - 0
services/data/common_classify_ctx.go

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

+ 2 - 1
services/data/edb_classify.go

@@ -85,7 +85,7 @@ func GetFullClassifyByClassifyId(targetClassifyId int) (targetList []*data_manag
 		targetItem.UniqueCode = targetClassify.UniqueCode
 		targetItem.Level = targetClassify.Level
 		targetItem.ClassifyName = targetClassify.ClassifyName
-		targetItem.ClassifyName = targetClassify.ClassifyName
+		targetItem.ClassifyNameEn = targetClassify.ClassifyNameEn
 		targetItem.IsJoinPermission = targetClassify.IsJoinPermission
 		targetList = append(targetList, targetItem)
 		return
@@ -118,6 +118,7 @@ func GetFullClassifyByClassifyId(targetClassifyId int) (targetList []*data_manag
 				targetItem.UniqueCode = v.UniqueCode
 				targetItem.Level = v.Level
 				targetItem.ClassifyName = v.ClassifyName
+				targetItem.ClassifyNameEn = v.ClassifyNameEn
 				targetItem.IsJoinPermission = v.IsJoinPermission
 				targetList = append(targetList, targetItem)
 			}

+ 1 - 1
services/data/edb_data.go

@@ -327,7 +327,7 @@ func TbzDiv(a, b float64) float64 {
 		bf := decimal.NewFromFloat(b)
 		val, _ := af.Div(bf).Float64()
 		val = val - 1
-		valFloat, _ = decimal.NewFromFloat(val).RoundCeil(4).Float64()
+		valFloat, _ = decimal.NewFromFloat(val).Round(4).Float64()
 	} else {
 		valFloat = 0
 	}

+ 24 - 0
services/data/edb_info.go

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

+ 80 - 0
services/data/edb_info_calculate.go

@@ -799,3 +799,83 @@ func HandleDataByLinearRegressionToListV2(edbInfoDataList []*data_manage.EdbData
 
 	return
 }
+
+// HandleDataByLinearRegressionToListV3 插值法补充数据(线性方程式)-直接补充指标起始日期间的所有数据
+func HandleDataByLinearRegressionToListV3 (edbInfoDataList []*data_manage.EdbDataList, handleDataMap map[string]float64) (newEdbInfoDataList []*data_manage.EdbDataList, 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
+		newEdbInfoDataList = append(newEdbInfoDataList, v)
+		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)
+				newEdbInfoDataList = append(newEdbInfoDataList, &data_manage.EdbDataList{
+					EdbDataId:     v.EdbDataId,
+					EdbInfoId:     v.EdbInfoId,
+					DataTime:      tmpDataTime.Format(utils.FormatDate),
+					DataTimestamp: tmpDataTime.UnixNano() / 1e6,
+					Value:         val,
+				})
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}

+ 33 - 5
services/data/edb_info_relation.go

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

+ 2 - 0
services/data/excel/custom_analysis_edb.go

@@ -119,6 +119,7 @@ func HandleEdbSequenceVal(dateSequenceVal, dataSequenceVal []string) (newDateLis
 			v = strings.Replace(v, ",", "", -1)
 			// 过滤空格
 			v = strings.Replace(v, " ", "", -1)
+			v = strings.TrimSpace(v)
 
 			var tmpVal float64
 			if strings.Contains(v, "%") {
@@ -171,6 +172,7 @@ func HandleEdbSequenceVal(dateSequenceVal, dataSequenceVal []string) (newDateLis
 
 			// 过滤空格
 			v = strings.Replace(v, " ", "", -1)
+			v = strings.TrimSpace(v)
 			t1, tmpErr := dateparse.ParseAny(v)
 			if tmpErr != nil {
 				dateList = append(dateList, "")

+ 53 - 16
services/data/manual.go

@@ -226,7 +226,7 @@ func ManualIndexSource2Edb(req ManualIndexSource2EdbReq, lang string) (edb *data
 // @return failCount int
 // @return err error
 // @return errMsg string
-func ImportManualData(path string, sysUser *system.Admin) (successCount, failCount int, err error, errMsg string) {
+func ImportManualData(path string, sysUser *system.Admin) (successCount, failCount int, indexCount int, err error, errMsg string) {
 	// 错误信息
 	errMsgList := make([]string, 0)
 	// 操作记录
@@ -432,6 +432,11 @@ func ImportManualData(path string, sysUser *system.Admin) (successCount, failCou
 			tmpValue = val
 			break
 		}
+		// 指标名称是空的,说明是空列
+		if v.IndexName == `` {
+			continue
+		}
+
 		if !strings.Contains(strings.Join(classifyNameStrList, ","), v.ClassName) {
 			failItem := &models.EdbdataImportFail{
 				ClassifyName: v.ClassName,
@@ -511,6 +516,9 @@ func ImportManualData(path string, sysUser *system.Admin) (successCount, failCou
 				failDataList = append(failDataList, failItem)
 				continue
 			}
+			// 新增指标记录数
+			indexCount++
+
 			tmpTarget, tmpErr := models.GetTargetBySecName(v.IndexName)
 			target = tmpTarget
 			targetMap[v.IndexName] = target
@@ -540,6 +548,35 @@ func ImportManualData(path string, sysUser *system.Admin) (successCount, failCou
 
 		//更新指标信息
 		{
+			if v.Frequency == "" {
+				failItem := &models.EdbdataImportFail{
+					ClassifyName: v.ClassName,
+					CreateDate:   tmpDate,
+					SecName:      v.IndexName,
+					Close:        tmpValue,
+					Remark:       "更新指标失败,频度字段为空",
+					SysUserId:    fmt.Sprint(sysUser.AdminId),
+					Frequency:    v.Frequency,
+					Unit:         v.Unit,
+				}
+				failDataList = append(failDataList, failItem)
+				continue
+			}
+			if v.Unit == "" {
+				failItem := &models.EdbdataImportFail{
+					ClassifyName: v.ClassName,
+					CreateDate:   tmpDate,
+					SecName:      v.IndexName,
+					Close:        tmpValue,
+					Remark:       "更新指标失败,单位字段为空",
+					SysUserId:    fmt.Sprint(sysUser.AdminId),
+					Frequency:    v.Frequency,
+					Unit:         v.Unit,
+				}
+				failDataList = append(failDataList, failItem)
+				continue
+			}
+
 			updateCols := make([]string, 0)
 			//更新指标分类
 			if target.ClassifyId <= 0 && classify.ClassifyId > 0 {
@@ -812,13 +849,13 @@ func getDataByTemplate1(sheet *xlsx.Sheet, sysUserId int) (indexDataList []Impor
 // @return err error
 // @return errMsg string
 func getDataByTemplate2(sheet *xlsx.Sheet, sysUserId int) (indexDataList []ImportManualIndexData, failDataList []*models.EdbdataImportFail, err error, errMsg string) {
-	fmt.Println("sheet name: ", sheet.Name)
+	//fmt.Println("sheet name: ", sheet.Name)
 	indexDataList = make([]ImportManualIndexData, 0)
 	failDataList = make([]*models.EdbdataImportFail, 0)
 
 	//遍历行读取
 	maxRow := sheet.MaxRow
-	fmt.Println("maxRow:", maxRow)
+	//fmt.Println("maxRow:", maxRow)
 
 	varietyList := make([]string, 0)
 	indexNameList := make([]string, 0)
@@ -896,24 +933,19 @@ func getDataByTemplate2(sheet *xlsx.Sheet, sysUserId int) (indexDataList []Impor
 
 		// 日期是否异常,异常的话,就跳过,进入下一行数据处理
 		var dateErr bool
+		var emptyDate bool
 
 		// 数据处理
 		for k, v := range cells {
 			if k == 0 {
+				// 空日期数据
+				if v.Value == `` {
+					emptyDate = true
+					continue
+				}
+
 				tmpCurrDate, tmpErr := getExcelDate(v.Value)
 				if tmpErr != nil {
-					// TODO 错误数据记录
-					//failItem := new(models.EdbdataImportFail)
-					//failItem.SysUserId = strconv.Itoa(sysUser.AdminId)
-					//failItem.ClassifyName = classifyName
-					//failItem.CreateDate = createDate
-					//failItem.SecName = secName
-					//failItem.Close = closeVal
-					//failItem.Remark = "日期格式异常"
-					//failItem.Frequency = frequency
-					//failItem.Unit = unit
-					//failDataList = append(failDataList, failItem)
-					//go utils.SendEmail(utils.APPNAME+"失败提醒", "导入数据 获取分类:Err:"+err.Error(), utils.EmailSendToUsers)
 					dateErr = true
 				}
 
@@ -922,7 +954,12 @@ func getDataByTemplate2(sheet *xlsx.Sheet, sysUserId int) (indexDataList []Impor
 			}
 			key := k - 1
 
-			// 日期异常,所以不处理该行了
+			// 空日期数据,那就不处理了,不记录该行错误信息
+			if emptyDate {
+				continue
+			}
+
+			// 日期异常,所以不处理该行了同时记录该行错误信息
 			if dateErr {
 				var indexName, unit, classifyName, frequency string
 				if key <= maxNameIndex {

+ 7 - 5
services/data/predict_edb_info.go

@@ -541,13 +541,15 @@ func MovePredictEdbInfo(edbInfoId, classifyId, prevEdbInfoId, nextEdbInfoId int,
 	//更新
 	if len(updateCol) > 0 {
 		err = edbInfo.Update(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("修改失败,Err:" + err.Error())
+			return
+		}
+		//添加es
+		AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
 
 	}
-	if err != nil {
-		errMsg = "移动失败"
-		err = errors.New("修改失败,Err:" + err.Error())
-		return
-	}
 	//新增操作日志
 	{
 		edbLog := new(data_manage.EdbInfoLog)

+ 16 - 16
services/data/predict_edb_info_rule.go

@@ -166,7 +166,7 @@ func TbzDivMul(a, b float64) (result float64) {
 	val := bf.Add(cf)
 
 	// 计算
-	result, _ = val.Mul(af).RoundCeil(4).Float64()
+	result, _ = val.Mul(af).Round(4).Float64()
 	return
 }
 
@@ -287,7 +287,7 @@ func TczDiv(a, b float64) (result float64) {
 		bf := decimal.NewFromFloat(b)
 
 		// 计算
-		result, _ = af.Add(bf).RoundCeil(4).Float64()
+		result, _ = af.Add(bf).Round(4).Float64()
 	} else {
 		result = 0
 	}
@@ -352,7 +352,7 @@ func HbzDiv(a, b float64) (result float64) {
 		val := bf.Add(cf)
 
 		// 计算
-		result, _ = val.Mul(af).RoundCeil(4).Float64()
+		result, _ = val.Mul(af).Round(4).Float64()
 	} else {
 		result = 0
 	}
@@ -411,7 +411,7 @@ func HczDiv(a, b float64) (result float64) {
 		bf := decimal.NewFromFloat(b)
 
 		// 计算
-		result, _ = af.Add(bf).RoundCeil(4).Float64()
+		result, _ = af.Add(bf).Round(4).Float64()
 	} else {
 		result = 0
 	}
@@ -450,7 +450,7 @@ func GetChartPredictEdbInfoDataListByRuleNMoveMeanValue(edbInfoId int, nValue in
 		}
 
 		// N期移动均值计算
-		val, _ := tmpDecimalVal.Div(decimalN).RoundCeil(4).Float64()
+		val, _ := tmpDecimalVal.Div(decimalN).Round(4).Float64()
 
 		currentDateStr := currentDate.Format(utils.FormatDate)
 		tmpData := &data_manage.EdbDataList{
@@ -520,7 +520,7 @@ func GetChartPredictEdbInfoDataListByRuleNLinearRegression(edbInfoId int, nValue
 		xDecimal := decimal.NewFromInt(int64(tmpK))
 		bDecimal := decimal.NewFromFloat(b)
 
-		val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).RoundCeil(4).Float64()
+		val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
 
 		currentDateStr := currentDate.Format(utils.FormatDate)
 		tmpData := &data_manage.EdbDataList{
@@ -583,7 +583,7 @@ func GetChartPredictEdbInfoDataListByRuleTrendsHC(edbInfoId int, dayList []time.
 		lastValueDecimal := decimal.NewFromFloat(lastValue)
 		hcValDecimal := decimal.NewFromFloat(hcVal)
 
-		val, _ := lastValueDecimal.Add(hcValDecimal).RoundCeil(4).Float64()
+		val, _ := lastValueDecimal.Add(hcValDecimal).Round(4).Float64()
 
 		tmpData := &data_manage.EdbDataList{
 			EdbDataId:     edbInfoId + 100000 + lenAllData + k,
@@ -838,7 +838,7 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 			continue
 		}
 		lastDayValDec := decimal.NewFromFloat(lastDayVal)
-		val, _ := tmpHistoryVal.Div(decimal.NewFromInt(int64(tmpHistoryValNum))).Add(lastDayValDec).RoundCeil(4).Float64()
+		val, _ := tmpHistoryVal.Div(decimal.NewFromInt(int64(tmpHistoryValNum))).Add(lastDayValDec).Round(4).Float64()
 
 		currentDateStr := currentDate.Format(utils.FormatDate)
 		tmpData := &data_manage.EdbDataList{
@@ -1002,7 +1002,7 @@ func GetChartPredictEdbInfoDataListByRuleMoveAverageTb(edbInfoId int, nValue, ye
 		tbVal := tmpAverageVal.Div(tmpHistoryAverageVal)
 
 		// 预测值结果 = 同比年份同期值(tmpHistoryCurrentVal的值)* 同比值(tbVal的值)
-		val, _ := decimal.NewFromFloat(tmpHistoryCurrentVal).Mul(tbVal).RoundCeil(4).Float64()
+		val, _ := decimal.NewFromFloat(tmpHistoryCurrentVal).Mul(tbVal).Round(4).Float64()
 
 		currentDateStr := currentDate.Format(utils.FormatDate)
 		tmpData := &data_manage.EdbDataList{
@@ -1139,7 +1139,7 @@ func GetChartPredictEdbInfoDataListByRuleTbzscz(edbInfoId int, tbEndValue float6
 		preDate := currentDate.AddDate(-1, 0, 0)
 		preDateStr := preDate.Format(utils.FormatDate)
 		if preValue, ok := existMap[preDateStr]; ok { //上一年同期找到
-			val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).RoundCeil(4).Float64()
+			val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64()
 			calculateStatus = true
 		} else {
 			switch frequency {
@@ -1150,13 +1150,13 @@ func GetChartPredictEdbInfoDataListByRuleTbzscz(edbInfoId int, tbEndValue float6
 				for i := 0; i <= 35; i++ {
 					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
 					if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
-						val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).RoundCeil(4).Float64()
+						val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64()
 						calculateStatus = true
 						break
 					} else {
 						preDateDayStr := preDateDay.Format(utils.FormatDate)
 						if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到
-							val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).RoundCeil(4).Float64()
+							val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64()
 							calculateStatus = true
 							break
 						}
@@ -1167,7 +1167,7 @@ func GetChartPredictEdbInfoDataListByRuleTbzscz(edbInfoId int, tbEndValue float6
 
 			case "季度", "年度":
 				if preValue, ok := existMap[preDateStr]; ok { //上一年同期->下一个月找到
-					val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).RoundCeil(4).Float64()
+					val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64()
 					calculateStatus = true
 					break
 				}
@@ -1178,13 +1178,13 @@ func GetChartPredictEdbInfoDataListByRuleTbzscz(edbInfoId int, tbEndValue float6
 				for i := 0; i < 35; i++ {
 					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
 					if preValue, ok := existMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
-						val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).RoundCeil(4).Float64()
+						val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64()
 						calculateStatus = true
 						break
 					} else {
 						preDateDayStr := preDateDay.Format(utils.FormatDate)
 						if preValue, ok := existMap[preDateDayStr]; ok { //上一年同期->上一个月找到
-							val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).RoundCeil(4).Float64()
+							val, _ = decimal.NewFromFloat(preValue).Mul(tbValue).Round(4).Float64()
 							calculateStatus = true
 							break
 						} else {
@@ -1383,7 +1383,7 @@ func getCalculateNhccData(secondDataList []*data_manage.EdbDataList, ruleConf Ru
 		bDecimal := decimal.NewFromFloat(b)
 		for _, aData := range aDataList {
 			xDecimal := decimal.NewFromFloat(aData.Value)
-			val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).RoundCeil(4).Float64()
+			val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
 			newBDataMap[aData.DataTime] = val
 		}
 

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

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

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

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

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

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

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

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

+ 436 - 0
services/document_manage_service/document_manage_service.go

@@ -0,0 +1,436 @@
+// @Author gmy 2024/9/19 16:45:00
+package document_manage_service
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/document_manage_model"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/core/logs"
+	"github.com/google/uuid"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"strconv"
+)
+
+func DocumentClassifyList() ([]models.ClassifyVO, error) {
+	logs.Info("DocumentClassifyList")
+
+	//获取所有已启用分类
+	classifyList, err := models.GetClassifyListByKeyword(``, utils.IS_ENABLE)
+	if err != nil {
+		return nil, err
+	}
+
+	// 递归处理分类 拿到父级分类和对应的子级分类
+	classifyVOList := make([]models.ClassifyVO, 0)
+	for _, classify := range classifyList {
+		if classify.ParentId == 0 {
+			classifyVO := models.ClassifyVO{
+				Id:           classify.Id,
+				ClassifyName: classify.ClassifyName,
+				ParentId:     classify.ParentId,
+				Sort:         classify.Sort,
+				Enabled:      classify.Enabled,
+				Level:        classify.Level,
+			}
+			classifyVO.Children = getChildClassify(classifyList, classify.Id)
+			classifyVOList = append(classifyVOList, classifyVO)
+		}
+	}
+
+	return classifyVOList, nil
+}
+
+func getChildClassify(classifyList []*models.ClassifyList, classifyId int) *[]models.ClassifyVO {
+	childList := make([]models.ClassifyVO, 0)
+	for _, classify := range classifyList {
+		if classify.ParentId == classifyId {
+			classifyVO := models.ClassifyVO{
+				Id:           classify.Id,
+				ClassifyName: classify.ClassifyName,
+				ParentId:     classify.ParentId,
+				Sort:         classify.Sort,
+				Enabled:      classify.Enabled,
+			}
+			classifyVO.Children = getChildClassify(classifyList, classify.Id)
+			childList = append(childList, classifyVO)
+		}
+	}
+	return &childList
+}
+
+func DocumentVarietyList() ([]models.ChartPermissionVO, error) {
+	logs.Info("DocumentVarietyList")
+
+	cp := new(models.ChartPermission)
+	var condition string
+	pars := make([]interface{}, 0)
+
+	condition = ` and enabled=?`
+	pars = append(pars, 1)
+	condition += ` and product_id=1`
+
+	chartPermissionList, err := cp.GetItemsByCondition(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+
+	// 递归处理品种 拿到父级品种和对应的子级品种
+	permissionVOList := make([]models.ChartPermissionVO, 0)
+	for _, chartPermission := range chartPermissionList {
+		if chartPermission.ParentId == 0 {
+			permissionVO := models.ChartPermissionVO{
+				ChartPermissionId:   chartPermission.ChartPermissionId,
+				ChartPermissionName: chartPermission.ChartPermissionName,
+				PermissionName:      chartPermission.PermissionName,
+				ParentId:            chartPermission.ParentId,
+				Enabled:             chartPermission.Enabled,
+				Sort:                chartPermission.Sort,
+				CreatedTime:         chartPermission.CreatedTime,
+			}
+			permissionVO.Children = getChildVariety(chartPermissionList, permissionVO.ChartPermissionId)
+			permissionVOList = append(permissionVOList, permissionVO)
+		}
+	}
+
+	return permissionVOList, nil
+}
+
+func getChildVariety(permissionList []*models.ChartPermission, permissionId int) []*models.ChartPermissionVO {
+	childList := make([]*models.ChartPermissionVO, 0)
+	for _, permission := range permissionList {
+		if permission.ParentId == permissionId {
+			permissionVO := models.ChartPermissionVO{
+				ChartPermissionId:   permission.ChartPermissionId,
+				ChartPermissionName: permission.ChartPermissionName,
+				PermissionName:      permission.PermissionName,
+				ParentId:            permission.ParentId,
+				Enabled:             permission.Enabled,
+				Sort:                permission.Sort,
+				CreatedTime:         permission.CreatedTime,
+			}
+			permissionVO.Children = getChildVariety(permissionList, permissionVO.ChartPermissionId)
+			childList = append(childList, &permissionVO)
+		}
+	}
+	return childList
+
+}
+
+func DocumentReportList(documentType int, chartPermissionIdList []string, classifyIdList []string, keyword string, orderField, orderType string, startSize, pageSize int) (*document_manage_model.OutsideReportPage, error) {
+	logs.Info("DocumentVarietyList")
+
+	var condition string
+	pars := make([]interface{}, 0)
+
+	if documentType == 1 {
+		condition = ` and t1.source!=3`
+	}
+	if len(classifyIdList) > 0 {
+		// 递归查询子分类
+		for _, classifyId := range classifyIdList {
+			id, err := strconv.Atoi(classifyId)
+			if err != nil {
+				return nil, err
+			}
+
+			if id == 0 {
+				classifyIdList = append(classifyIdList, classifyId)
+			} else {
+				childrenClassifyIdList, err := GetAllClassifyIdsByParentId(id)
+				if err != nil {
+					return nil, err
+				}
+				classifyIdList = append(classifyIdList, childrenClassifyIdList...)
+			}
+		}
+		condition += ` and t1.classify_id in (` + utils.GetOrmInReplace(len(classifyIdList)) + `)`
+		for _, classifyId := range classifyIdList {
+			pars = append(pars, classifyId)
+		}
+	}
+	if len(chartPermissionIdList) > 0 {
+		condition += ` and t2.chart_permission_id in (` + utils.GetOrmInReplace(len(chartPermissionIdList)) + `)`
+		for _, chartPermissionId := range chartPermissionIdList {
+			pars = append(pars, chartPermissionId)
+		}
+	}
+	if keyword != "" {
+		condition += ` and (t1.title like ? or t1.sys_user_name like ?) `
+		pars = append(pars, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	count, err := document_manage_model.GetOutsideReportListByConditionCount(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	reportPage := document_manage_model.OutsideReportPage{}
+
+	page := paging.GetPaging(startSize, pageSize, count)
+	if count <= 0 {
+		reportPage.Paging = page
+		return &reportPage, nil
+	}
+
+	if orderField != "" && orderType != "" {
+		condition += ` order by t1.` + orderField + ` ` + orderType
+	} else {
+		condition += ` order by t1.modify_time desc`
+	}
+
+	outsideReportList, err := document_manage_model.GetOutsideReportListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		return nil, err
+	}
+	reportPage.Paging = page
+	reportPage.List = outsideReportList
+
+	return &reportPage, nil
+}
+
+// GetAllClassifyIdsByParentId 递归查询子分类
+func GetAllClassifyIdsByParentId(parentId int) ([]string, error) {
+	var classifyIdList []string
+
+	// 获取子分类
+	classifyList, err := models.GetClassifyListByParentId(parentId)
+	if err != nil {
+		return nil, err
+	}
+
+	// 遍历子分类
+	for _, classify := range classifyList {
+		classifyIdList = append(classifyIdList, strconv.Itoa(classify.Id))
+		// 递归调用
+		subClassifyIds, err := GetAllClassifyIdsByParentId(classify.Id)
+		if err != nil {
+			return nil, err
+		}
+		classifyIdList = append(classifyIdList, subClassifyIds...)
+	}
+
+	return classifyIdList, nil
+}
+
+func RuiSiReportList(classifyIdFirst, classifyIdSecond, classifyIdThird int, keyword string, orderField, orderType string, startSize, pageSize int) (*models.ReportListResp, error) {
+	logs.Info("RuiSiReportList")
+
+	var condition string
+	pars := make([]interface{}, 0)
+
+	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 keyword != "" {
+		condition += ` and ( a.title like ? or a.admin_real_name like ? ) `
+		pars = append(pars, "%"+keyword+"%", "%"+keyword+"%")
+	}
+
+	// 作者为 全球市场战略研究中心 PCI Research
+	condition += ` and a.author = '全球市场战略研究中心 PCI Research'`
+	// 已发布的报告
+	condition += ` and a.state = 2`
+
+	count, err := models.GetReportListCountV1(condition, pars)
+	if err != nil {
+		return nil, err
+	}
+	reportPage := models.ReportListResp{}
+
+	page := paging.GetPaging(startSize, pageSize, count)
+	if count <= 0 {
+		reportPage.Paging = page
+		return &reportPage, nil
+	}
+
+	if orderField != "" && orderType != "" {
+		condition += ` order by a.` + orderField + ` ` + orderType
+	} else {
+		condition += ` order by a.publish_time desc`
+	}
+
+	reportList, err := models.GetReportListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		return nil, err
+	}
+
+	reportPage.Paging = page
+	reportPage.List = reportList
+
+	return &reportPage, nil
+}
+
+func DocumentRuiSiDetail(reportId int) (*models.ReportDetail, error) {
+	logs.Info("DocumentRuiSiDetail")
+
+	reportDetail, err := models.GetReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			return nil, fmt.Errorf("报告已被删除")
+		}
+		return nil, err
+	}
+	reportDetail.Content = html.UnescapeString(reportDetail.Content)
+
+	return reportDetail, nil
+}
+
+func DocumentSave(outsideReport *document_manage_model.OutsideReportBO) error {
+	logs.Info("DocumentSave")
+
+	// 保存报告
+	report := document_manage_model.OutsideReport{
+		Source:           outsideReport.Source,
+		Title:            outsideReport.Title,
+		Abstract:         outsideReport.Abstract,
+		ClassifyId:       outsideReport.ClassifyId,
+		ClassifyName:     outsideReport.ClassifyName,
+		Content:          outsideReport.Content,
+		SysUserId:        outsideReport.SysUserId,
+		SysUserName:      outsideReport.SysUserName,
+		ReportUpdateTime: utils.GetCurrentTime(),
+		ModifyTime:       utils.GetCurrentTime(),
+		CreateTime:       utils.GetCurrentTime(),
+		ReportCode:       uuid.New().String(),
+	}
+	id, err := document_manage_model.SaveOutsideReport(report)
+	if err != nil {
+		return err
+	}
+
+	// 保存附件
+	attachmentList := outsideReport.AttachmentList
+	if len(attachmentList) > 0 {
+		for _, attachment := range attachmentList {
+			if attachment.Title == "" || attachment.Url == "" {
+				continue
+			}
+			attachment.OutsideReportId = int(id)
+			attachment.CreateTime = utils.GetCurrentTime()
+			_, err := document_manage_model.SaveOutsideReportAttachment(attachment)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return err
+}
+
+func DocumentReportDetail(outsideReportId int) (*document_manage_model.OutsideReportBO, error) {
+	logs.Info("DocumentReportDetail")
+
+	outsideReport, err := document_manage_model.GetOutsideReportById(outsideReportId)
+	if err != nil {
+		return nil, err
+	}
+
+	attachmentList, err := document_manage_model.GetOutsideReportAttachmentListByReportId(outsideReportId)
+	if err != nil {
+		return nil, err
+	}
+
+	outsideReportBO := document_manage_model.OutsideReportBO{
+		OutsideReportId: outsideReportId,
+		Source:          outsideReport.Source,
+		Title:           outsideReport.Title,
+		Abstract:        outsideReport.Abstract,
+		ClassifyId:      outsideReport.ClassifyId,
+		ClassifyName:    outsideReport.ClassifyName,
+		Content:         html.UnescapeString(outsideReport.Content),
+		SysUserId:       outsideReport.SysUserId,
+		SysUserName:     outsideReport.SysUserName,
+		CreateTime:      outsideReport.CreateTime,
+		ModifyTime:      outsideReport.ModifyTime,
+		ReportCode:      outsideReport.ReportCode,
+		AttachmentList:  attachmentList,
+	}
+	return &outsideReportBO, nil
+}
+
+func DocumentUpdate(outsideReport *document_manage_model.OutsideReportBO) error {
+	logs.Info("DocumentUpdate")
+	report, err := document_manage_model.GetOutsideReportById(outsideReport.OutsideReportId)
+	if err != nil {
+		return err
+	}
+
+	if report == nil {
+		return fmt.Errorf("报告不存在")
+	}
+	// 更新报告
+	if outsideReport.Title != "" {
+		report.Title = outsideReport.Title
+	}
+	if outsideReport.Abstract != "" {
+		report.Abstract = outsideReport.Abstract
+	}
+	if outsideReport.ClassifyId > 0 {
+		report.ClassifyId = outsideReport.ClassifyId
+	}
+	if outsideReport.ClassifyName != "" {
+		report.ClassifyName = outsideReport.ClassifyName
+	}
+	if outsideReport.Content != "" {
+		report.Content = outsideReport.Content
+	}
+	report.ModifyTime = utils.GetCurrentTime()
+	report.ReportUpdateTime = utils.GetCurrentTime()
+	err = document_manage_model.UpdateOutsideReport(report)
+	if err != nil {
+		return fmt.Errorf("更新报告失败, Err: %s", err.Error())
+	}
+
+	// 更新报告附件
+	attachmentList := outsideReport.AttachmentList
+	if len(attachmentList) > 0 {
+		err = document_manage_model.DeleteReportAttachmentByReportId(outsideReport.OutsideReportId)
+		if err != nil {
+			return fmt.Errorf("删除报告附件失败, Err: %s", err.Error())
+		}
+
+		for _, attachment := range attachmentList {
+			if attachment.Title == "" || attachment.Url == "" {
+				continue
+			}
+			attachment.OutsideReportId = outsideReport.OutsideReportId
+			attachment.CreateTime = utils.GetCurrentTime()
+			_, err := document_manage_model.SaveOutsideReportAttachment(attachment)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func DocumentDelete(outsideReportId int) error {
+	logs.Info("DocumentDelete")
+
+	report, err := document_manage_model.GetOutsideReportById(outsideReportId)
+	if err != nil {
+		return err
+	}
+	if report == nil {
+		return fmt.Errorf("报告不存在")
+	}
+
+	err = document_manage_model.DeleteOutsideReport(outsideReportId)
+	if err != nil {
+		return err
+	}
+	err = document_manage_model.DeleteReportAttachmentByReportId(outsideReportId)
+	if err != nil {
+		return err
+	}
+	return nil
+}

+ 44 - 166
services/email.go

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

+ 1 - 1
services/ppt.go

@@ -152,7 +152,7 @@ func SavePPTReport(pptId, classifyId int, title string, adminInfo *system.Admin)
 		ReportVersion:      2,
 		CollaborateType:    1, // 协作方式,1:个人,2:多人协作。默认:1
 		ReportLayout:       1, // 报告布局,1:常规布局,2:智能布局。默认:1
-		IsPublicPublish:    2, // 是否公开发布,1:是,2:否
+		IsPublicPublish:    1, // 是否公开发布,1:是,2:否
 	}
 
 	// 如果PPT是公开的,则报告也公开发布

+ 12 - 3
services/report_v2.go

@@ -73,12 +73,21 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 			reportInfo.Content = inheritReport.Content
 			reportInfo.ContentSub = inheritReport.ContentSub
 			reportInfo.ContentStruct = inheritReport.ContentStruct
-			reportInfo.HeadImg = inheritReport.HeadImg
-			reportInfo.EndImg = inheritReport.EndImg
 			reportInfo.CanvasColor = inheritReport.CanvasColor
-			reportInfo.NeedSplice = inheritReport.NeedSplice
+			//reportInfo.NeedSplice = inheritReport.NeedSplice
+			reportInfo.NeedSplice = 1
 			reportInfo.HeadResourceId = inheritReport.HeadResourceId
+			//reportInfo.HeadImg = inheritReport.HeadImg
+			//reportInfo.EndImg = inheritReport.EndImg
+			reportInfo.HeadImg = ``
+			reportInfo.EndImg = ``
 			reportInfo.EndResourceId = inheritReport.EndResourceId
+			if inheritReport.HeadResourceId > 0 {
+				reportInfo.HeadImg = inheritReport.HeadImg
+			}
+			if inheritReport.EndResourceId > 0 {
+				reportInfo.EndImg = inheritReport.EndImg
+			}
 			reportInfo.InheritReportId = inheritReport.Id
 		}
 	}

+ 106 - 0
utils/common.go

@@ -683,6 +683,22 @@ func CheckPwd(pwd string) bool {
 	return flag
 }
 
+func CheckPwdV2(pwd string) bool {
+	var patternArr = []string{
+		".*[0-9]+.*", ".*[a-z]+.*", ".*[A-Z]+.*", ".*[@#$%^&+=.]+.*",
+	}
+	num := 0
+	for _, pattern := range patternArr {
+		if regexp.MustCompile(pattern).MatchString(pwd) {
+			num++
+		}
+	}
+	if len(pwd) < 8 {
+		num = 0
+	}
+	return num >= 3
+}
+
 func GetMonthStartAndEnd(myYear string, myMonth string) (startDate, endDate string) {
 	// 数字月份必须前置补零
 	if len(myMonth) == 1 {
@@ -727,6 +743,11 @@ func StrTimeToTime(strTime string) time.Time {
 	return resultTime
 }
 
+// TimeToStr time.Time 转 字符串 yyyy-MM-dd
+func TimeToStr(data time.Time, format string) string {
+	return data.Format(format)
+}
+
 // 字符串类型时间转周几
 func StrDateTimeToWeek(strTime string) string {
 	var WeekDayMap = map[string]string{
@@ -865,6 +886,17 @@ func ConvertNumToCny(num float64) (str string, err error) {
 	return
 }
 
+// TimeFormatToYmd 时间格式转化 yyyy-MM-dd HH:mm:ss --> yyyy-MM-dd
+func TimeFormatToYmd(strTime string) string {
+	var Ymd string
+	var resultTime = StrTimeToTime(strTime)
+	year := resultTime.Year()
+	month := resultTime.Format("01")
+	day1 := resultTime.Day()
+	Ymd = strconv.Itoa(year) + "-" + month + "-" + strconv.Itoa(day1)
+	return Ymd
+}
+
 // GetNowWeekMonday 获取本周周一的时间
 func GetNowWeekMonday() time.Time {
 	offset := int(time.Monday - time.Now().Weekday())
@@ -1732,6 +1764,32 @@ func GetDateByDateTypeV2(dateType int, tmpStartDate, tmpEndDate string, startYea
 		baseDate, _ := time.Parse(FormatDate, fmt.Sprintf("%d-01-01", yearMax))
 		startDate = baseDate.AddDate(-startYear, 0, 0).Format(FormatDate)
 		endDate = ""
+	case DateTypeOneWeek:
+		//if startDate != "" {
+		//	st, e := time.ParseInLocation(FormatDate, startDate, time.Local)
+		//	if e != nil {
+		//		FileLog.Info(fmt.Sprintf("日期格式解析失败, %s, %v", startDate, e))
+		//		return
+		//	}
+		//	startDate = st.AddDate(0, 0, -7).Format(FormatDate)
+		//}
+		startDate = time.Now().AddDate(0, 0, -7).Format(FormatDate)
+		endDate = ""
+	case DateTypeOneMonth:
+		startDate = time.Now().AddDate(0, -1, 0).Format(FormatDate)
+		endDate = ""
+	case DateTypeTwoMonth:
+		startDate = time.Now().AddDate(0, -2, 0).Format(FormatDate)
+		endDate = ""
+	case DateTypeThreeMonth:
+		startDate = time.Now().AddDate(0, -3, 0).Format(FormatDate)
+		endDate = ""
+	case DateTypeNMonth:
+		if startYear == 0 {
+			startYear = 6
+		}
+		startDate = time.Now().AddDate(0, -startYear, 0).Format(FormatDate)
+		endDate = ""
 	}
 
 	// 兼容日期错误
@@ -2646,3 +2704,51 @@ func GetDuration(filePath string) (duration string, err error) {
 
 	return duration, nil
 }
+
+// GetTradingDays 获取开始时间至结束时间之间的交易日期(日度)
+func GetTradingDays(startDate, endDate time.Time) []time.Time {
+	var tradingDays []time.Time
+	for curr := startDate; !curr.After(endDate); curr = curr.AddDate(0, 0, 1) {
+		if curr.Weekday() >= time.Monday && curr.Weekday() <= time.Friday {
+			tradingDays = append(tradingDays, curr)
+		}
+	}
+	return tradingDays
+}
+
+// GenerateEdbCodeMap 当前已经生成的指标编码map(暂时不做定时数据清理了,因为数据不大,我们至少每个月会重启一次,所以暂时不做定时数据清理)
+var GenerateEdbCodeMap = map[string]bool{}
+
+// GenerateEdbCode
+// @Description:  生成指标编码
+// @author: Roc
+// @datetime 2024-06-05 09:49:53
+// @param num int
+// @param pre string 前缀
+// @return edbCode string
+// @return err error
+func GenerateEdbCode(num int, pre string) (edbCode string, err error) {
+	if num >= 10 {
+		err = errors.New("指标编码生成失败,请重新生成")
+		return
+	}
+
+	// 4位随机数
+	randStr := GetRandDigit(4)
+	// 年月日时分秒+4位随机数
+	edbCode = `C` + pre + time.Now().Format(FormatShortDateTimeUnSpace) + randStr
+
+	if _, ok := GenerateEdbCodeMap[edbCode]; ok {
+		num++
+		edbCode, err = GenerateEdbCode(num, pre)
+	}
+
+	GenerateEdbCodeMap[edbCode] = true
+
+	return
+}
+
+// GetCurrentTime 获取当前时间 格式为 2024-08-07 15:29:58
+func GetCurrentTime() string {
+	return time.Now().Format("2006-01-02 15:04:05")
+}

+ 39 - 0
utils/constants.go

@@ -2,6 +2,7 @@ package utils
 
 import (
 	"io/fs"
+	"time"
 )
 
 // 常量定义
@@ -10,6 +11,7 @@ const (
 	FormatTimeHm               = "15:04"                   //时间格式
 	FormatDate                 = "2006-01-02"              //日期格式
 	FormatDateUnSpace          = "20060102"                //日期格式
+	ormatDateUnSpace           = "2006.01.02"              //日期格式
 	FormatDateTime             = "2006-01-02 15:04:05"     //完整时间格式
 	HlbFormatDateTime          = "2006-01-02_15:04:05.999" //完整时间格式
 	FormatDateTimeUnSpace      = "20060102150405"          //完整时间格式
@@ -177,6 +179,10 @@ const (
 	DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS       = 90       // 预测指标区间计算->90
 	DATA_SOURCE_SCI_HQ                               = 88       // 卓创红期->88
 	DATA_SOURCE_OILCHEM                              = 89       // 隆众资讯 -> 89
+	DATA_SOURCE_FENWEI                               = 77       // 汾渭数据->92
+	DATA_SOURCE_LY                                   = 91       // 粮油商务网
+	DATA_SOURCE_TRADE_ANALYSIS                       = 92       // 持仓分析
+	DATA_SOURCE_HISUGAR                              = 93       // 泛糖科技 -> 93
 )
 
 // 数据刷新频率
@@ -287,6 +293,7 @@ const (
 	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
 	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
+	CHART_SOURCE_TRADE_ANALYSIS_PROCESS          = 13 // 持仓分析-建仓过程图表
 )
 
 // 批量配置图表的位置来源
@@ -328,6 +335,18 @@ const (
 	PREDICT_EDB_INFO_TYPE = 1 //指标类型 1:预测指标
 )
 
+// 是否关系 无实义
+const (
+	IS_YES = 1
+	IS_NO  = 0
+)
+
+// 是否可用
+const (
+	IS_ENABLE  = 1
+	IS_DISABLE = 0
+)
+
 // FrequencyDaysMap 频度日期的map关系
 var FrequencyDaysMap = map[string]int{
 	"天": 1, "周": 7, "月": 30, "季": 90, "年": 365,
@@ -475,3 +494,23 @@ const (
 
 // MultiAddNum 批量插入的数据量
 const MultiAddNum = 500
+
+const (
+	DateTypeOneWeek    = 21 // 时间类型-近1周
+	DateTypeOneMonth   = 22 // 时间类型-近1月
+	DateTypeTwoMonth   = 23 // 时间类型-近2月
+	DateTypeThreeMonth = 24 // 时间类型-近3月
+	DateTypeNMonth     = 25 // 时间类型-近N月
+)
+
+const (
+	SourceNameTradeAnalysis = "持仓分析"
+)
+
+// 基础数据初始化日期
+var (
+	BASE_START_DATE         = `1900-01-01`                                          //基础数据开始日期
+	BASE_END_DATE           = time.Now().AddDate(4, 0, 0).Format(FormatDate)        //基础数据结束日期
+	BASE_START_DATE_UnSpace = "19000101"                                            //基础数据开始日期
+	BASE_END_DATE_UnSpace   = time.Now().AddDate(4, 0, 0).Format(FormatDateUnSpace) //基础数据结束日期
+)