Browse Source

Merge branch 'master' into ETA_1.4.5

zwxi 1 year ago
parent
commit
350bdd0733
72 changed files with 8402 additions and 494 deletions
  1. 18 1
      controllers/ai/ai.go
  2. 52 0
      controllers/business_conf.go
  3. 24 0
      controllers/classify.go
  4. 116 95
      controllers/commodity_trade_base_index.go
  5. 64 0
      controllers/data_manage/chart_info.go
  6. 661 0
      controllers/data_manage/chart_theme.go
  7. 31 1
      controllers/data_manage/correlation/correlation_chart_info.go
  8. 31 1
      controllers/data_manage/cross_variety/chart_info.go
  9. 8 1
      controllers/data_manage/edb_info.go
  10. 29 0
      controllers/data_manage/future_good/future_good_chart_info.go
  11. 21 1
      controllers/data_manage/future_good/future_good_profit_chart_info.go
  12. 31 1
      controllers/data_manage/line_equation/line_chart_info.go
  13. 25 0
      controllers/data_manage/line_feature/chart_info.go
  14. 1 1
      controllers/data_manage/multiple_graph_config.go
  15. 173 7
      controllers/data_stat/edb_source_stat.go
  16. 18 0
      controllers/english_report/english_classify.go
  17. 274 20
      controllers/english_report/report.go
  18. 7 2
      controllers/ppt_english.go
  19. 9 2
      controllers/ppt_v2.go
  20. 301 57
      controllers/report.go
  21. 931 0
      controllers/report_approve/report_approve.go
  22. 741 0
      controllers/report_approve/report_approve_flow.go
  23. 257 25
      controllers/smart_report/smart_report.go
  24. 7 11
      controllers/sys_admin.go
  25. 11 12
      controllers/sys_role.go
  26. 5 0
      models/aimod/ai.go
  27. 23 6
      models/business_conf.go
  28. 38 25
      models/data_manage/base_from_trade_index.go
  29. 1 0
      models/data_manage/chart_edb_mapping.go
  30. 38 2
      models/data_manage/chart_info.go
  31. 151 0
      models/data_manage/chart_theme/chart_theme.go
  32. 92 0
      models/data_manage/chart_theme/chart_theme_default_data.go
  33. 90 0
      models/data_manage/chart_theme/chart_theme_type.go
  34. 30 0
      models/data_manage/chart_theme/request/theme.go
  35. 3 1
      models/data_manage/cross_variety/chart_tag_variety.go
  36. 7 0
      models/data_manage/cross_variety/request/chart.go
  37. 6 0
      models/data_manage/line_equation/request/line_equation.go
  38. 2 0
      models/data_stat/edb_info_update_log.go
  39. 6 1
      models/data_stat/edb_info_update_stat.go
  40. 30 2
      models/db.go
  41. 76 8
      models/english_report.go
  42. 6 2
      models/ppt_english/ppt_english.go
  43. 6 2
      models/ppt_v2.go
  44. 101 10
      models/report.go
  45. 35 0
      models/report_approve/constant.go
  46. 448 0
      models/report_approve/report_approve.go
  47. 387 0
      models/report_approve/report_approve_flow.go
  48. 204 0
      models/report_approve/report_approve_message.go
  49. 215 0
      models/report_approve/report_approve_node.go
  50. 220 0
      models/report_approve/report_approve_record.go
  51. 41 3
      models/smart_report/smart_report.go
  52. 270 0
      routers/commentsRouter.go
  53. 8 0
      routers/router.go
  54. 5 1
      services/aiser/ai.go
  55. 91 0
      services/data/chart_info.go
  56. 595 0
      services/data/chart_theme.go
  57. 11 1
      services/data/correlation/chart_info.go
  58. 6 0
      services/data/cross_variety/chart.go
  59. 8 5
      services/data/edb_classify.go
  60. 1 1
      services/data/edb_info.go
  61. 240 178
      services/data/excel/custom_analysis_edb.go
  62. 5 0
      services/data/line_equation/chart_info.go
  63. 5 0
      services/data/line_feature/chart_info.go
  64. 2 1
      services/data_stat/edb_info_stat.go
  65. 1 1
      services/english_report.go
  66. 2 0
      services/ppt/ppt_english_group.go
  67. 2 0
      services/ppt/ppt_group.go
  68. 1 1
      services/report.go
  69. 1013 0
      services/report_approve.go
  70. 3 4
      services/smart_report.go
  71. 3 0
      services/user.go
  72. 28 1
      utils/constants.go

+ 18 - 1
controllers/ai/ai.go

@@ -85,7 +85,24 @@ func (this *AiController) List() {
 	if chatMode != nil && chatMode.Answer != "" {
 		answer = chatMode.Answer
 	} else {
-		answer, err = aiser.ChatAutoMsg(req.Ask)
+		//获取主题下的所有信息
+		//AiChatTopicId
+		historyList, err := aimod.GetAiChatList(req.AiChatTopicId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取主题历史数据失败!"
+			br.ErrMsg = "获取主题历史数据失败,Err:" + err.Error()
+			return
+		}
+
+		historyChatList := make([]aimod.HistoryChat, 0)
+		for _, v := range historyList {
+			historyChat := new(aimod.HistoryChat)
+			historyChat.Ask = v.Ask
+			historyChat.Answer = v.Answer
+			historyChatList = append(historyChatList, *historyChat)
+		}
+
+		answer, err = aiser.ChatAutoMsg(req.Ask, historyChatList)
 		if err != nil {
 			br.Msg = "获取数据失败!"
 			br.ErrMsg = "获取数据失败,ChatAutoMsg,Err:" + err.Error()

+ 52 - 0
controllers/business_conf.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_api/models"
+	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
 	"html"
@@ -62,6 +63,9 @@ func (this *BusinessConfController) Save() {
 		confMap[c.ConfKey] = c
 	}
 
+	openApprove := ""
+	approveType := ""
+
 	// 根据配置类型取值
 	updates := make([]models.BusinessConfUpdate, 0)
 	for k, v := range req {
@@ -86,6 +90,18 @@ func (this *BusinessConfController) Save() {
 				ConfKey: k,
 				ConfVal: str,
 			})
+
+			// 取出审批参数
+			if k == models.BusinessConfIsReportApprove {
+				openApprove = str
+			}
+			if k == models.BusinessConfReportApproveType {
+				// 打开审批默认为内部审批方式
+				if openApprove == "true" && str == "" {
+					str = models.BusinessConfReportApproveTypeEta
+				}
+				approveType = str
+			}
 		case 2: // 数值
 			num, ok := v.(float64)
 			if !ok {
@@ -138,6 +154,42 @@ func (this *BusinessConfController) Save() {
 		}
 	}
 
+	// 校验报告审批是否可以切换
+	confOpenApprove := confMap[models.BusinessConfIsReportApprove]
+	confApproveType := confMap[models.BusinessConfReportApproveType]
+	if confOpenApprove != nil && confApproveType != nil {
+		// 仅校验有审批->无审批, 或是有审批->切换审批方式的情况
+		if openApprove == "false" && confOpenApprove.ConfVal == "true" || (openApprove == "true" && openApprove == confOpenApprove.ConfVal && confApproveType.ConfVal != approveType) {
+			ok, e := services.CheckCloseReportApproveConf()
+			if e != nil {
+				br.Msg = "保存失败"
+				br.ErrMsg = "校验是否可以关闭报告审批失败, Err: " + e.Error()
+				return
+			}
+			if !ok {
+				br.Msg = "当前有未走完流程的报告,请走完流程后再做变更"
+				return
+			}
+		}
+		// 审批设置切换对未发布/待提交报告状态的重置
+		needReset := false
+		changeType := ""
+		if openApprove == "false" && confOpenApprove.ConfVal == "true" {
+			needReset = true
+		}
+		if openApprove == "true" && confOpenApprove.ConfVal == "false" {
+			needReset = true
+			changeType = approveType
+		}
+		if openApprove == "true" && openApprove == confOpenApprove.ConfVal && confApproveType.ConfVal != approveType {
+			needReset = true
+			changeType = approveType
+		}
+		if needReset {
+			go services.ConfigChangeResetReportState(changeType)
+		}
+	}
+
 	if len(updates) > 0 {
 		if e = models.UpdateBusinessConfMulti(updates); e != nil {
 			br.Msg = "保存失败"

+ 24 - 0
controllers/classify.go

@@ -3,9 +3,11 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
+	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
 )
@@ -284,6 +286,28 @@ func (this *ClassifyController) CheckDeleteClassify() {
 			return
 		}
 	}
+
+	// 查询该分类是否关联了审批流
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowCond := fmt.Sprintf(` AND (%s = ? OR %s = ?) AND (%s = ? OR %s = ?)`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+	flowPars := make([]interface{}, 0)
+	flowPars = append(flowPars, report_approve.FlowReportTypeChinese, report_approve.FlowReportTypeSmart, classifyId, classifyId)
+	flowCount, e := flowOb.GetCountByCondition(flowCond, flowPars)
+	if e != nil {
+		br.Msg = "检测失败"
+		br.ErrMsg = "获取关联审批流失败, Err: " + e.Error()
+		return
+	}
+	if flowCount > 0 {
+		resp.Code = 5
+		resp.Msg = "该分类关联审批流,不允许删除"
+		br.Data = resp
+		br.Ret = 200
+		br.Msg = resp.Msg
+		br.Success = true
+		return
+	}
+
 	resp.Code = 0
 	resp.Msg = "检测完成,可进行删除操作"
 	br.Ret = 200

+ 116 - 95
controllers/commodity_trade_base_index.go

@@ -1196,117 +1196,138 @@ func (this *TradeCommonController) CoalMineClassify() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-	var coalList []data_manage.BaseFromCoalmineClassify
+	var coalList []data_manage.BaseFromCoalmineClassifyItem
 	var suffix string
-	var coal data_manage.BaseFromCoalmineClassify
+	var coal data_manage.BaseFromCoalmineClassifyItem
+	groups := []*string{}
 	var childs []data_manage.CoalChild
 	var child data_manage.CoalChild
-	suffix = "jsm_index"
-	groups, err := data_manage.GetProvinceFromCoalmineIndex(suffix)
-	if err != nil {
+
+	list, err := data_manage.GetCoalmineClassifyList()
+	if err!= nil {
 		br.Msg = "获取煤炭分类数据失败"
 		br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
 		return
 	}
-	coal.ClassifyId = 1
-	coal.ClassifyName = "晋陕蒙三省煤矿产量周度数据"
-	jsmId := 100
-	for _, group := range groups {
-		jsmId++
-		child.ClassifyId = jsmId
-		child.ClassifyName = *group
-		childs = append(childs, child)
-	}
+	for _, v := range list {
+		if v.Suffix == "jsm_index" {
+			suffix = "jsm_index"
+			groups, err = data_manage.GetProvinceFromCoalmineIndex(suffix)
+			if err != nil {
+				br.Msg = "获取煤炭分类数据失败"
+				br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
+				return
+			}
+			coal.ClassifyId = 1
+			coal.ClassifyName = "晋陕蒙三省煤矿产量周度数据"
+			jsmId := 100
+			for _, group := range groups {
+				jsmId++
+				child.ClassifyId = jsmId
+				child.ClassifyName = *group
+				childs = append(childs, child)
+			}
 
-	coal.Child = childs
-	coalList = append(coalList, coal)
+			coal.Child = childs
+			coalList = append(coalList, coal)
+		}
 
-	suffix = "company_index"
-	groups = []*string{}
-	childs = []data_manage.CoalChild{}
-	groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
-	if err != nil {
-		br.Msg = "获取煤炭分类数据失败"
-		br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
-		return
-	}
-	coal.ClassifyId = 2
-	coal.ClassifyName = "442家晋陕蒙煤矿周度产量"
-	companyId := 200
-	for _, group := range groups {
-		companyId++
-		child.ClassifyId = companyId
-		child.ClassifyName = *group
-		childs = append(childs, child)
-	}
-	coal.Child = childs
-	coalList = append(coalList, coal)
+		if v.Suffix == "company_index" {
+			suffix = "company_index"
+			groups = []*string{}
+			childs = []data_manage.CoalChild{}
+			groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
+			if err != nil {
+				br.Msg = "获取煤炭分类数据失败"
+				br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
+				return
+			}
+			coal.ClassifyId = 2
+			coal.ClassifyName = "442家晋陕蒙煤矿周度产量"
+			companyId := 200
+			for _, group := range groups {
+				companyId++
+				child.ClassifyId = companyId
+				child.ClassifyName = *group
+				childs = append(childs, child)
+			}
+			coal.Child = childs
+			coalList = append(coalList, coal)
+		}
 
-	suffix = "firm_index"
-	groups = []*string{}
-	childs = []data_manage.CoalChild{}
-	groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
-	if err != nil {
-		br.Msg = "获取煤炭分类数据失败"
-		br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
-		return
-	}
-	coal.ClassifyId = 3
-	coal.ClassifyName = "全国分企业煤炭产量旬度数据"
-	firmId := 300
-	for _, group := range groups {
-		firmId++
-		child.ClassifyId = firmId
-		child.ClassifyName = *group
-		childs = append(childs, child)
+		if v.Suffix == "firm_index" {
+			suffix = "firm_index"
+			groups = []*string{}
+			childs = []data_manage.CoalChild{}
+			groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
+			if err != nil {
+				br.Msg = "获取煤炭分类数据失败"
+				br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
+				return
+			}
+			coal.ClassifyId = 3
+			coal.ClassifyName = "全国分企业煤炭产量旬度数据"
+			firmId := 300
+			for _, group := range groups {
+				firmId++
+				child.ClassifyId = firmId
+				child.ClassifyName = *group
+				childs = append(childs, child)
 
-	}
-	coal.Child = childs
-	coalList = append(coalList, coal)
+			}
+			coal.Child = childs
+			coalList = append(coalList, coal)
+		}
 
-	suffix = "coastal_index"
-	groups = []*string{}
-	childs = []data_manage.CoalChild{}
-	groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
-	if err != nil {
-		br.Msg = "获取煤炭分类数据失败"
-		br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
-		return
-	}
-	coal.ClassifyId = 4
-	coal.ClassifyName = "沿海八省动力煤终端用户供耗存"
-	coastalId := 400
-	for _, group := range groups {
-		coastalId++
-		child.ClassifyId = coastalId
-		child.ClassifyName = *group
-		childs = append(childs, child)
+		if v.Suffix == "coastal_index" {
+			suffix = "coastal_index"
+			groups = []*string{}
+			childs = []data_manage.CoalChild{}
+			groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
+			if err != nil {
+				br.Msg = "获取煤炭分类数据失败"
+				br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
+				return
+			}
+			coal.ClassifyId = 4
+			coal.ClassifyName = "沿海八省动力煤终端用户供耗存"
+			coastalId := 400
+			for _, group := range groups {
+				coastalId++
+				child.ClassifyId = coastalId
+				child.ClassifyName = *group
+				childs = append(childs, child)
 
-	}
-	coal.Child = childs
-	coalList = append(coalList, coal)
+			}
+			coal.Child = childs
+			coalList = append(coalList, coal)
+		}
 
-	suffix = "inland_index"
-	groups = []*string{}
-	childs = []data_manage.CoalChild{}
-	groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
-	if err != nil {
-		br.Msg = "获取煤炭分类数据失败"
-		br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
-		return
-	}
-	coal.ClassifyId = 5
-	coal.ClassifyName = "内陆17省动力煤终端用户供耗存"
-	inlandId := 500
-	for _, group := range groups {
-		inlandId++
-		child.ClassifyId = inlandId
-		child.ClassifyName = *group
-		childs = append(childs, child)
 
+		if v.Suffix == "inland_index" {
+			suffix = "inland_index"
+			groups = []*string{}
+			childs = []data_manage.CoalChild{}
+			groups, err = data_manage.GetGroupNameFromCoalmineIndex(suffix)
+			if err != nil {
+				br.Msg = "获取煤炭分类数据失败"
+				br.ErrMsg = "获取煤炭度分类数据失败,Err: " + err.Error()
+				return
+			}
+			coal.ClassifyId = 5
+			coal.ClassifyName = "内陆17省动力煤终端用户供耗存"
+			inlandId := 500
+			for _, group := range groups {
+				inlandId++
+				child.ClassifyId = inlandId
+				child.ClassifyName = *group
+				childs = append(childs, child)
+
+			}
+			coal.Child = childs
+			coalList = append(coalList, coal)
+		}
 	}
-	coal.Child = childs
-	coalList = append(coalList, coal)
 
 	br.Ret = 200
 	br.Success = true

+ 64 - 0
controllers/data_manage/chart_info.go

@@ -786,6 +786,16 @@ func (this *ChartInfoController) ChartInfoDetail() {
 			return
 		}
 		chartType = chartInfo.ChartType
+
+		// 获取主题样式
+		chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, chartInfo.Source, chartInfo.ChartType)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+			return
+		}
+		chartInfo.ChartThemeStyle = chartTheme.Config
+		chartInfo.ChartThemeId = chartTheme.ChartThemeId
 	}
 
 	mappingList := make([]*data_manage.ChartEdbInfoMapping, 0)
@@ -900,6 +910,11 @@ func (this *ChartInfoController) ChartInfoDetail() {
 			}
 		}
 	}
+	
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 
 	//图表操作权限
 	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId)
@@ -1093,6 +1108,11 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -1145,6 +1165,16 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 		return
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, chartInfo.Source, chartInfo.ChartType)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	dateType := chartInfo.DateType
 	fmt.Println("dateType:", dateType)
 
@@ -1285,6 +1315,11 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -2009,6 +2044,16 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, chartInfo.Source, chartInfo.ChartType)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	chartInfoId := chartInfo.ChartInfoId
 
 	dateType := chartInfo.DateType
@@ -2139,6 +2184,11 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.XEdbIdValue = xEdbIdValue
 	resp.YDataList = yDataList
@@ -2462,6 +2512,11 @@ func (this *ChartInfoController) CopyChartInfo() {
 		ExtraConfig:       oldChartInfo.ExtraConfig,
 		SeasonExtraConfig: oldChartInfo.SeasonExtraConfig,
 		StartYear:         oldChartInfo.StartYear,
+		ChartThemeId:      oldChartInfo.ChartThemeId,
+		SourcesFrom:       oldChartInfo.SourcesFrom,
+		Instructions:      oldChartInfo.Instructions,
+		MarkersLines:      oldChartInfo.MarkersLines,
+		MarkersAreas:      oldChartInfo.MarkersAreas,
 	}
 	newId, err := data_manage.AddChartInfo(chartInfo)
 	if err != nil {
@@ -2633,6 +2688,10 @@ func (this *ChartInfoController) PreviewBarChartInfo() {
 	if len(warnEdbList) > 0 {
 		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
 	}
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 
 	//图表操作权限
 	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId)
@@ -2719,6 +2778,11 @@ func (this *ChartInfoController) PreviewSectionScatterChartInfo() {
 		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	//图表操作权限
 	chartInfo.IsEdit = data.CheckOpChartPermission(sysUser, chartInfo.SysUserId)
 	//判断是否需要展示英文标识

+ 661 - 0
controllers/data_manage/chart_theme.go

@@ -0,0 +1,661 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/chart_theme"
+	"eta/eta_api/models/data_manage/chart_theme/request"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// ChartThemeController
+// @Description: 图表主题配置模块
+type ChartThemeController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 获取主题列表
+// @Description 获取主题列表接口
+// @Param   ChartThemeTypeId   query   int  true       "图表主题类型id"
+// @Success 200 {object} []*chart_theme.ChartTheme
+// @router /chart/theme/list [get]
+func (c *ChartThemeController) List() {
+	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
+	}
+
+	chartThemeTypeId, _ := c.GetInt("ChartThemeTypeId")
+	if chartThemeTypeId <= 0 {
+		br.Msg = "请选择图表样式类型"
+		br.ErrMsg = "请选择图表样式类型"
+		br.IsSendEmail = false
+		return
+	}
+	list, err := chart_theme.GetChartThemeItemList(chartThemeTypeId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表样式类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// TypeList
+// @Title 获取主题类型列表
+// @Description 获取主题类型列表接口
+// @Success 200 {object} []*chart_theme.ChartThemeType
+// @router /chart/theme/type/list [get]
+func (c *ChartThemeController) TypeList() {
+	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
+	}
+
+	list, err := chart_theme.GetAllChartThemeTypeList()
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表样式类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// GetThemePreviewData
+// @Title 获取默认的预览图表数据
+// @Description 获取默认的预览图表数据接口
+// @Param   ChartThemeTypeId   query   int  true       "图表主题类型id"
+// @Success 200 {object} data_manage.ChartInfoDetailResp
+// @router /chart/theme/preview_data [get]
+func (c *ChartThemeController) GetThemePreviewData() {
+	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
+	}
+	chartThemeTypeId, _ := c.GetInt("ChartThemeTypeId")
+	if chartThemeTypeId <= 0 {
+		br.Msg = "请选择图表样式类型"
+		br.ErrMsg = "请选择图表样式类型"
+		br.IsSendEmail = false
+		return
+	}
+
+	chartThemeType, err := chart_theme.GetChartThemeTypeById(chartThemeTypeId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表样式类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	dateType := 3 // 曲线图
+	startYear := 5
+
+	chartType := chartThemeType.ChartType
+
+	chartInfo := new(data_manage.ChartInfoView)
+	// 图表额外数据参数
+	extraConfigStr := ``
+
+	// 开始时间,结束时间
+	var tmpStartDate, tmpEndDate string
+
+	edbInfoIdList := make([]int, 0) // 指标列表
+	switch chartType {
+	case 1: // 曲线图
+		edbInfoIdList = []int{1, 2, 3, 4, 5}
+		chartInfo.ChartName = "曲线图"
+	case 4: // 堆积柱状图
+		edbInfoIdList = []int{1, 2, 3, 4, 5}
+		chartInfo.ChartName = "堆积柱状图"
+		dateType = 5
+		tmpStartDate = `2021-03-05`
+	case 6: // 组合图
+		edbInfoIdList = []int{1, 2, 3, 4, 5}
+		chartInfo.ChartName = "组合图"
+		dateType = 5
+		tmpStartDate = `2021-03-05`
+	case 2: // 季节性
+		dateType = 20 // 季节性图
+		edbInfoIdList = []int{6}
+		chartInfo.ChartName = "季节性图"
+	case 5: // 散点图
+		edbInfoIdList = []int{7, 8}
+		chartInfo.ChartName = "散点图"
+	case 7: // 柱形图
+		edbInfoIdList = []int{1, 2, 3, 4, 5}
+		extraConfigStr = `{"EdbInfoIdList":[{"EdbInfoId":1,"Name":"指标1","NameEn":"","Source":0},{"EdbInfoId":2,"Name":"指标2","NameEn":"","Source":0},{"EdbInfoId":3,"Name":"指标3","NameEn":"","Source":0},{"EdbInfoId":4,"Name":"指标4","NameEn":"","Source":0},{"EdbInfoId":5,"Name":"指标5","NameEn":"","Source":0}],"DateList":[{"Type":2,"Date":"","Value":100,"Color":"#00f","Name":""},{"Type":1,"Date":"","Value":0,"Color":"#f00","Name":""}],"Sort":{"Sort":0,"DateIndex":0},"XEdbList":null,"YEdbList":null,"Unit":"千桶","UnitEn":""}`
+		chartInfo.ChartName = "柱形图"
+	case 10: // 截面散点图
+		edbInfoIdList = []int{9, 10, 11, 12, 13, 14, 15, 16, 17, 18}
+		extraConfigStr = `{"XName":"123","XNameEn":"123","XUnitName":"无","XUnitNameEn":"none","YName":"321","YNameEn":"321","YUnitName":"无","YUnitNameEn":"none","XMinValue":"230","XMaxValue":"395","YMinValue":"380","YMaxValue":"850","SeriesList":[{"Name":"2021-11-21","NameEn":"2021-11-21","IsNameDefault":true,"Color":"","EdbInfoList":[{"XEdbInfoId":9,"YEdbInfoId":14,"Name":"指标1","NameEn":"","XDateType":1,"XDate":"","XDateValue":0,"YDateType":1,"YDate":"","YDateValue":0,"IsShow":false},{"XEdbInfoId":10,"YEdbInfoId":15,"Name":"指标2","NameEn":"指标2","XDateType":1,"XDate":"","XDateValue":0,"YDateType":1,"YDate":"","YDateValue":0,"IsShow":false},{"XEdbInfoId":11,"YEdbInfoId":16,"Name":"指标3","NameEn":"","XDateType":1,"XDate":"","XDateValue":0,"YDateType":1,"YDate":"","YDateValue":0,"IsShow":false},{"XEdbInfoId":12,"YEdbInfoId":17,"Name":"指标4","NameEn":"指标4","XDateType":1,"XDate":"","XDateValue":0,"YDateType":1,"YDate":"","YDateValue":0,"IsShow":false},{"XEdbInfoId":13,"YEdbInfoId":18,"Name":"指标5","NameEn":"","XDateType":1,"XDate":"","XDateValue":0,"YDateType":1,"YDate":"","YDateValue":0,"IsShow":false}],"ShowTrendLine":false,"ShowFitEquation":false,"ShowRSquare":false}]}`
+		chartInfo.ChartName = "截面散点图"
+	default:
+		br.Msg = "暂不支持该类型"
+		br.IsSendEmail = false
+		return
+	}
+	calendar := "公历"
+
+	mappingList, err := chart_theme.GetChartEdbMappingListByEdbInfoId(edbInfoIdList)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+	switch chartType {
+	case 6: // 组合图
+		mappingList[0].ChartStyle = "spline"
+		mappingList[1].ChartStyle = "spline"
+		mappingList[2].ChartStyle = "spline"
+		mappingList[3].ChartStyle = "column"
+		mappingList[4].ChartStyle = "column"
+	}
+	chartInfo.Source = chartThemeType.ChartSource
+	chartInfo.ChartType = chartThemeType.ChartType
+
+	yearMax := 0
+	if dateType == utils.DateTypeNYears {
+		for _, v := range mappingList {
+			if v.LatestDate != "" {
+				lastDateT, tErr := time.Parse(utils.FormatDate, v.LatestDate)
+				if tErr != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取图表日期信息失败,Err:" + tErr.Error()
+					return
+				}
+				if lastDateT.Year() > yearMax {
+					yearMax = lastDateT.Year()
+				}
+			}
+		}
+	}
+	// 开始/结束日期
+	startDate, endDate := utils.GetDateByDateTypeV2(dateType, tmpStartDate, tmpEndDate, startYear, yearMax)
+
+	// 获取图表中的指标数据
+	edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := data.GetThemePreviewChartEdbData(chartType, calendar, startDate, endDate, mappingList, extraConfigStr, chartInfo.SeasonExtraConfig)
+	if err != nil {
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	switch chartType {
+	case 6: // 组合图
+		edbList[0].MaxData = 1000
+	case 4: // 堆积柱状图
+		edbList[0].MaxData = 2500
+	}
+
+	// 单位
+	if chartType == utils.CHART_TYPE_BAR && len(yDataList) > 0 {
+		chartInfo.Unit = yDataList[0].Unit
+		chartInfo.UnitEn = yDataList[0].UnitEn
+	}
+
+	resp := new(data_manage.ChartInfoDetailResp)
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+
+}
+
+// Add
+// @Title 新增主题
+// @Description
+// @Param	request	body request.DeleteThemeConfReq true "type json string"
+// @Success 200 Ret=200 添加成功
+// @router /chart/theme/add [post]
+func (c *ChartThemeController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.AddThemeReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.ChartThemeTypeId <= 0 {
+		br.Msg = "请选择图表类型"
+		br.IsSendEmail = false
+		return
+	}
+
+	req.ChartThemeName = utils.TrimLRStr(req.ChartThemeName)
+	if req.ChartThemeName == `` {
+		br.Msg = "请输入主题名称"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取系统默认配置
+	systemChartTheme, err := chart_theme.GetSystemChartTheme(req.ChartThemeTypeId)
+	if err != nil {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,查询系统默认配置异常,Err:" + err.Error()
+		return
+	}
+
+	chartTheme := &chart_theme.ChartTheme{
+		ChartThemeId:     0,
+		ChartThemeName:   req.ChartThemeName,
+		ChartThemeTypeId: req.ChartThemeTypeId,
+		Config:           systemChartTheme.Config,
+		ChartImage:       systemChartTheme.ChartImage,
+		IsDelete:         0,
+		SysUserId:        sysUser.AdminId,
+		SysUserRealName:  sysUser.RealName,
+		ModifyTime:       time.Now(),
+		CreateTime:       time.Now(),
+	}
+
+	// 添加入库
+	err = chartTheme.Add()
+	if err != nil {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "添加成功"
+}
+
+// Edit
+// @Title 新增主题
+// @Description
+// @Param	request	body request.DeleteThemeConfReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /chart/theme/edit [post]
+func (c *ChartThemeController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.EditThemeReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartThemeId < 0 {
+		br.Msg = "请选择配置"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取配置
+	chartTheme, err := chart_theme.GetChartThemeId(req.ChartThemeId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "主题不存在或已删除"
+			br.ErrMsg = "主题不存在或已删除"
+			br.IsSendEmail = false
+		} else {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表配置信息失败,Err:" + err.Error()
+		}
+		return
+	}
+
+	// 更新
+	if req.ChartThemeName != `` {
+		chartTheme.ChartThemeName = req.ChartThemeName
+	}
+	if req.Config != `` {
+		chartTheme.Config = req.Config
+	}
+	if req.ChartImage != `` {
+		chartTheme.ChartImage = req.ChartImage
+	}
+	chartTheme.ModifyTime = time.Now()
+	err = chartTheme.Update([]string{"ChartThemeName", "Config", "ChartImage", "ModifyTime"})
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "修改成功"
+}
+
+// Delete
+// @Title 删除主题
+// @Description
+// @Param	request	body request.DeleteThemeConfReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /chart/theme/delete [post]
+func (c *ChartThemeController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.DeleteThemeReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartThemeId < 0 {
+		br.Msg = "请选择配置"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取配置
+	chartTheme, err := chart_theme.GetChartThemeId(req.ChartThemeId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "主题不存在或已删除"
+			br.ErrMsg = "主题不存在或已删除"
+			br.IsSendEmail = false
+		} else {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表配置信息失败,Err:" + err.Error()
+		}
+		return
+	}
+	if chartTheme.IsSystemTheme == 1 {
+		br.Msg = "系统默认配置,无法删除"
+		br.ErrMsg = "系统默认配置,无法删除"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 根据给定的图表主题类型ID获取图表主题类型
+	chartThemeType, err := chart_theme.GetChartThemeTypeById(chartTheme.ChartThemeTypeId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表主题类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 判断是否是配置为默认设置,如果已经设置了,那么不能删除
+	if chartThemeType.DefaultChartThemeId == chartTheme.ChartThemeId {
+		br.Msg = "该主题已经配置为默认设置,不允许删除"
+		br.ErrMsg = "该主题已经配置为默认设置,不允许删除"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 更新
+	chartTheme.IsDelete = 1
+	err = chartTheme.Update([]string{"IsDelete"})
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// SetDefaultTheme
+// @Title 配置默认主题
+// @Description 配置默认主题
+// @Param	request	body request.DeleteThemeConfReq true "type json string"
+// @Success 200 Ret=200 配置成功
+// @router /chart/theme/set_default [post]
+func (c *ChartThemeController) SetDefaultTheme() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.SetDefaultThemeReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ChartThemeId < 0 {
+		br.Msg = "请选择配置"
+		br.IsSendEmail = false
+		return
+	}
+	if req.ChartThemeTypeId < 0 {
+		br.Msg = "请选择图表类型"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 获取图表配置
+	chartTheme, err := chart_theme.GetChartThemeId(req.ChartThemeId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "主题不存在或已删除"
+			br.ErrMsg = "主题不存在或已删除"
+			br.IsSendEmail = false
+		} else {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表配置信息失败,Err:" + err.Error()
+		}
+		return
+	}
+
+	// 获取图表类型
+	chartThemeType, err := chart_theme.GetChartThemeTypeById(req.ChartThemeTypeId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "获取图表类型不存在或已删除"
+			br.ErrMsg = "获取图表类型不存在或已删除"
+			br.IsSendEmail = false
+		} else {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表配置信息失败,Err:" + err.Error()
+		}
+		return
+	}
+
+	// 判断主题与图表类型是否相符
+	if chartTheme.ChartThemeTypeId != chartThemeType.ChartThemeTypeId {
+		br.Msg = "主题与图表类型不符"
+		br.ErrMsg = "主题与图表类型不符"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 更新
+	chartThemeType.DefaultChartThemeId = chartTheme.ChartThemeId
+	chartThemeType.ModifyTime = time.Now()
+	err = chartThemeType.Update([]string{"DefaultChartThemeId", "ModifyTime"})
+	if err != nil {
+		br.Msg = "配置失败"
+		br.ErrMsg = "配置失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "配置成功"
+}
+
+// TypeListBySource
+// @Title 根据来源获取主题类型列表
+// @Description 获取主题类型列表接口
+// @Param   Source   query   int  true       "图表主题类型id"
+// @Success 200 {object} []*chart_theme.ChartThemeType
+// @router /chart/theme/type/list_by_source [get]
+func (c *ChartThemeController) TypeListBySource() {
+	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
+	}
+
+	source, _ := c.GetInt("Source")
+	if source <= 0 {
+		br.Msg = "请选择来源"
+		br.ErrMsg = "请选择来源"
+		br.IsSendEmail = false
+		return
+	}
+
+	list, err := chart_theme.GetChartThemeTypeListBySource(source)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表样式类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// ListBySource
+// @Title 根据来源获取主题列表
+// @Description 获取主题列表接口
+// @Param   ChartType   query   int  true       "图表类型"
+// @Param   Source   query   int  true       "来源"
+// @Success 200 {object} []*chart_theme.ChartTheme
+// @router /chart/theme/list_by_source [get]
+func (c *ChartThemeController) ListBySource() {
+	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
+	}
+
+	chartType, _ := c.GetInt("ChartType")
+	if chartType < 0 {
+		br.Msg = "请选择图表类型"
+		br.ErrMsg = "请选择图表类型"
+		br.IsSendEmail = false
+		return
+	}
+	source, _ := c.GetInt("Source")
+	if chartType < 0 {
+		br.Msg = "请选择图表来源"
+		br.ErrMsg = "请选择图表来源"
+		br.IsSendEmail = false
+		return
+	}
+
+	chartThemeType, err := chart_theme.GetChartThemeTypeByChartTypeAndSource(chartType, source)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表样式类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	list, err := chart_theme.GetChartThemeItemList(chartThemeType.ChartThemeTypeId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表样式类型信息失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}

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

@@ -672,6 +672,16 @@ func (this *CorrelationChartInfoController) Detail() {
 		return
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	// 相关性图表信息
 	correlationChart := new(data_manage.ChartInfoCorrelation)
 	if e := correlationChart.GetItemById(chartInfoId); e != nil {
@@ -772,6 +782,11 @@ func (this *CorrelationChartInfoController) Detail() {
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
 	chartInfo.UnitEn = edbInfoMappingA.UnitEn
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	isSaveAs := true
 	if chartInfo.Source == utils.CHART_SOURCE_ROLLING_CORRELATION {
 		isSaveAs = false
@@ -1067,7 +1082,7 @@ func (this *CorrelationChartInfoController) Copy() {
 		},
 	}
 
-	chartInfo, err, errMsg, isSendEmail := correlationServ.CopyChartInfo(multipleGraphConfigChartMapping.MultipleGraphConfigId, req.ChartClassifyId, req.ChartName, correlationChartInfoReq, sysUser)
+	chartInfo, err, errMsg, isSendEmail := correlationServ.CopyChartInfo(multipleGraphConfigChartMapping.MultipleGraphConfigId, req.ChartClassifyId, req.ChartName, correlationChartInfoReq, oldChartInfo, sysUser)
 
 	if err != nil {
 		br.Msg = "保存失败"
@@ -1149,6 +1164,16 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	chartInfoId := chartInfo.ChartInfoId
 
 	// 相关性图表信息
@@ -1265,6 +1290,11 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.XEdbIdValue = xEdbIdValue

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

@@ -481,6 +481,16 @@ func (c *ChartInfoController) Detail() {
 		return
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 5)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	if chartInfo.ExtraConfig == `` {
 		br.Msg = "图表配置信息异常"
 		br.ErrMsg = "图表配置信息异常"
@@ -740,6 +750,11 @@ func (c *ChartInfoController) Copy() {
 	}
 	config.ChartName = req.ChartName
 	config.ChartImage = oldChartInfo.ChartImage
+	config.ChartThemeId = oldChartInfo.ChartThemeId
+	config.SourcesFrom = oldChartInfo.SourcesFrom
+	config.Instructions = oldChartInfo.Instructions
+	config.MarkersLines = oldChartInfo.MarkersLines
+	config.MarkersAreas = oldChartInfo.MarkersAreas
 
 	// 添加图表
 	chartInfo, err, errMsg, isSendEmail := cross_variety.AddChartInfo(config, sysUser)
@@ -1483,6 +1498,16 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 5)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	chartInfoId := chartInfo.ChartInfoId
 
 	if chartInfo.ExtraConfig == `` {
@@ -1491,7 +1516,7 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		return
 	}
 	var config request.ChartConfigReq
-	err := json.Unmarshal([]byte(chartInfo.ExtraConfig), &config)
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &config)
 	if err != nil {
 		msg = "解析跨品种分析配置失败"
 		errMsg = "解析跨品种分析配置失败,Err:" + err.Error()
@@ -1543,6 +1568,11 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.DataResp = dataResp
 	resp.EdbInfoList = edbList

+ 8 - 1
controllers/data_manage/edb_info.go

@@ -2183,6 +2183,11 @@ func (this *EdbInfoController) EdbInfoAdd() {
 
 	//新增操作日志
 	{
+		// 添加钢联指标更新日志
+		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
@@ -2362,7 +2367,9 @@ func (this *EdbInfoController) EdbInfoEdit() {
 
 	// 添加钢联指标更新日志
 	if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
-		go data_stat.AddEdbInfoUpdateLog(req.EdbInfoId, 1, "", sysUser)
+		if edbInfo.EdbName != req.EdbName || edbInfo.Frequency != req.Frequency || edbInfo.Unit != req.Unit {
+			go data_stat.AddEdbInfoUpdateLog(req.EdbInfoId, 1, "", sysUser, 1)
+		}
 	}
 
 	br.Ret = 200

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

@@ -1264,6 +1264,15 @@ func (this *FutureGoodChartInfoController) ChartInfoDetail() {
 		}
 		chartType = chartInfo.ChartType
 	}
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
 
 	switch chartInfo.Source {
 	case utils.CHART_SOURCE_FUTURE_GOOD:
@@ -1395,6 +1404,11 @@ func getFutureGoodChartInfo(chartInfo *data_manage.ChartInfoView, chartType, dat
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
 	chartInfo.UnitEn = baseEdbInfo.UnitEn
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -1519,6 +1533,16 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	chartInfoId := chartInfo.ChartInfoId
 
 	startDate := chartInfo.StartDate
@@ -1629,6 +1653,11 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
 	chartInfo.UnitEn = baseEdbInfo.UnitEn
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.XEdbIdValue = xEdbIdValue

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

@@ -1080,6 +1080,11 @@ func getFutureGoodProfitChartInfo(chartInfo *data_manage.ChartInfoView, sysUser
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
 	chartInfo.UnitEn = baseEdbInfo.UnitEn
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp := new(data_manage.ChartInfoDetailResp)
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
@@ -1142,9 +1147,19 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 
 	chartInfoId := chartInfo.ChartInfoId
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	// 商品利润曲线图的一些配置
 	var extraConf request.ChartInfoReq
-	err := json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConf)
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &extraConf)
 	if err != nil {
 		msg = "商品利润曲线图配置异常"
 		errMsg = "商品利润曲线图配置异常,Err:" + err.Error()
@@ -1240,6 +1255,11 @@ func GetFutureGoodProfitChartInfoDetailFromUniqueCode(chartInfo *data_manage.Cha
 	chartInfo.IsEnChart = data.CheckIsEnChart(chartInfo.ChartNameEn, edbList[0:1], chartInfo.Source, chartInfo.ChartType)
 	chartInfo.UnitEn = baseEdbInfo.UnitEn
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.DataResp = response.ProfitFutureGoodChartResp{

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

@@ -751,6 +751,16 @@ func (this *LineEquationChartInfoController) Detail() {
 		return
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	if chartInfo.ExtraConfig == `` {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取配置信息失败,图表的额外配置信息为空"
@@ -1086,6 +1096,11 @@ func (this *LineEquationChartInfoController) Copy() {
 		LeftMin:         oldChartInfo.LeftMin,
 		LeftMax:         oldChartInfo.LeftMax,
 		ChartImage:      oldChartInfo.ChartImage,
+		ChartThemeId:    oldChartInfo.ChartThemeId,
+		SourcesFrom:     oldChartInfo.SourcesFrom,
+		Instructions:    oldChartInfo.Instructions,
+		MarkersLines:    oldChartInfo.MarkersLines,
+		MarkersAreas:    oldChartInfo.MarkersAreas,
 	})
 
 	resp := new(data_manage.AddChartInfoResp)
@@ -1172,13 +1187,23 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	if chartInfo.ExtraConfig == `` {
 		msg = "获取失败"
 		errMsg = "获取配置信息失败"
 		return
 	}
 	var lineChartInfoConfig request.LineChartInfoReq
-	err := json.Unmarshal([]byte(chartInfo.ExtraConfig), &lineChartInfoConfig)
+	err = json.Unmarshal([]byte(chartInfo.ExtraConfig), &lineChartInfoConfig)
 	if err != nil {
 		msg = "获取失败"
 		errMsg = "获取图表配置信息失败, Err:" + err.Error()
@@ -1253,6 +1278,11 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.DataResp = resultResp

+ 25 - 0
controllers/data_manage/line_feature/chart_info.go

@@ -1792,6 +1792,16 @@ func (this *LineFeaturesChartInfoController) Detail() {
 		return
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	// 获取图表关联指标
 	edbMappingList, err := data_manage.GetChartEdbMappingList(chartInfoId)
 	if err != nil {
@@ -2274,6 +2284,16 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		}
 	}
 
+	// 获取主题样式
+	chartTheme, err := data.GetChartThemeConfig(chartInfo.ChartThemeId, utils.CHART_SOURCE_DEFAULT, 1)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "获取主题信息失败,Err:" + err.Error()
+		return
+	}
+	chartInfo.ChartThemeStyle = chartTheme.Config
+	chartInfo.ChartThemeId = chartTheme.ChartThemeId
+
 	if chartInfo.ExtraConfig == `` {
 		msg = "获取失败"
 		errMsg = "获取配置信息失败,图表的额外配置信息为空"
@@ -2390,6 +2410,11 @@ func GetChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCa
 		IsSetName: chartInfo.IsSetName,
 	}
 
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := data.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList
 	resp.DataResp = resultResp

+ 1 - 1
controllers/data_manage/multiple_graph_config.go

@@ -1088,7 +1088,7 @@ func CopyMultipleGraphConfigSaveChart(req request.SaveMultipleGraphChartReq, thi
 				},
 			}
 
-			chartInfo, err, errMsg, isSendEmail = correlationServ.CopyChartInfo(multipleGraphConfigChartMapping.MultipleGraphConfigId, req.ClassifyId, req.ChartName, correlationChartInfoReq, sysUser)
+			chartInfo, err, errMsg, isSendEmail = correlationServ.CopyChartInfo(multipleGraphConfigChartMapping.MultipleGraphConfigId, req.ClassifyId, req.ChartName, correlationChartInfoReq, oldChartInfo, sysUser)
 		}
 	default:
 		br.Msg = "错误的来源"

+ 173 - 7
controllers/data_stat/edb_source_stat.go

@@ -317,6 +317,7 @@ func (this *EdbSourceStatController) EdbUpdateLog() {
 	sortParamReq := this.GetString("SortParam", "")
 	sortType := this.GetString("SortType", "desc")
 	createTime := this.GetString("CreateTime", "")
+	updateType, _ := this.GetInt("UpdateType", -1)
 
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
@@ -345,7 +346,10 @@ func (this *EdbSourceStatController) EdbUpdateLog() {
 		condition += " AND create_time >= ? AND create_time < ?"
 		pars = append(pars, createTime, endT.Format(utils.FormatDate))
 	}
-
+	if updateType >= 0 {
+		condition += " AND update_type =? "
+		pars = append(pars, updateType)
+	}
 	sortStr := ``
 	sortParam := ``
 	if sortParamReq != `` {
@@ -429,6 +433,7 @@ func (this *EdbSourceStatController) EdbUpdateLog() {
 		if v.ErDataUpdateDate != utils.EmptyDateStr {
 			tmp.ErDataUpdateDate = v.ErDataUpdateDate
 		}
+		tmp.UpdateType = v.UpdateType
 		list = append(list, tmp)
 	}
 
@@ -550,14 +555,14 @@ func (this *EdbSourceStatController) EdbUpdateStat() {
 	total, err := data_stat.GetEdbUpdateStatCount(condition, pars)
 	if err != nil {
 		if err.Error() != utils.ErrNoRow() {
-			br.Msg = "获取指标删除列表总数失败"
-			br.ErrMsg = "获取指标删除列表总数失败,Err:" + err.Error()
+			br.Msg = "获取指标列表总数失败"
+			br.ErrMsg = "获取指标列表总数失败,Err:" + err.Error()
 			return
 		} else {
 			err = nil
 		}
 	}
-	list := make([]*data_stat.EdbInfoUpdateStatItem, 0)
+	list := make([]*data_stat.EdbInfoUpdateStatItemMore, 0)
 	page := paging.GetPaging(currentIndex, pageSize, total)
 	if total == 0 {
 		resp := data_stat.GetEdbUpdateStatResp{
@@ -577,8 +582,28 @@ func (this *EdbSourceStatController) EdbUpdateStat() {
 		return
 	}
 
+	// 获取初始数据源名称
+	var edbCodeList []string
+	for _, v := range tmpList {
+		edbCodeList = append(edbCodeList, v.EdbCode)
+	}
+	initSourceMap := make(map[string]string)
+	if len(edbCodeList) > 0 {
+		baseCond := " and index_code in (" + utils.GetOrmInReplace(len(edbCodeList)) + ")"
+		var basePars []interface{}
+		basePars = append(basePars, edbCodeList)
+		indexList, tErr := data_manage.GetMysteelChemicalIndexList(baseCond, basePars, 0, 1000)
+		if tErr != nil {
+			br.Msg = "获取初始指标信息失败"
+			br.ErrMsg = "获取初始指标信息失败,Err:" + tErr.Error()
+			return
+		}
+		for _, v := range indexList {
+			initSourceMap[v.IndexCode] = v.Source
+		}
+	}
 	for _, v := range tmpList {
-		tmp := new(data_stat.EdbInfoUpdateStatItem)
+		tmp := new(data_stat.EdbInfoUpdateStatItemMore)
 		tmp.Id = v.Id
 		tmp.EdbInfoId = v.EdbInfoId
 		tmp.SourceName = v.SourceName
@@ -608,6 +633,7 @@ func (this *EdbSourceStatController) EdbUpdateStat() {
 		tmp.IsAdd = v.IsAdd
 		tmp.NeedRefresh = v.NeedRefresh
 		tmp.HasRefresh = v.HasRefresh
+		tmp.InitSourceName, _ = initSourceMap[v.EdbCode]
 		list = append(list, tmp)
 	}
 
@@ -757,8 +783,8 @@ func (this *EdbSourceStatController) EdbSourceStat() {
 }
 
 // EdbUpdateFailedList
-// @Title 查询指标更新失败详情列表
-// @Description 查询指标更新失败详情列表
+// @Title 查询指标更新失败详情汇总列表
+// @Description 查询指标更新失败详情汇总列表
 // @Success 200 {object} data_stat.GetEdbUpdateLogResp
 // @router /edb_update_stat/failed [get]
 func (this *EdbSourceStatController) EdbUpdateFailedList() {
@@ -840,3 +866,143 @@ func (this *EdbSourceStatController) EdbUpdateFailedList() {
 	br.Msg = "获取成功"
 	br.Data = resp
 }
+
+// EdbUpdateFailedDetailList
+// @Title 查询指标更新失败详情列表
+// @Description 查询指标更新失败详情列表
+// @Success 200 {object} data_stat.GetEdbUpdateLogResp
+// @router /edb_update_stat/failed/detail [get]
+func (this *EdbSourceStatController) EdbUpdateFailedDetailList() {
+	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)
+
+	terminalCode := this.GetString("TerminalCode", "")
+	createTime := this.GetString("CreateTime", "")
+	frequency := this.GetString("Frequency", "")
+	sourceUpdateFailedReason := this.GetString("SourceUpdateFailedReason", "")
+
+	if createTime == "" {
+		br.Msg = "请选择创建时间"
+		return
+	}
+
+	if terminalCode == "" {
+		br.Msg = "请选择对应的终端信息"
+		return
+	}
+	if frequency == "" {
+		br.Msg = "请选择对应的频度"
+		return
+	}
+
+	condition := " and source = ? and terminal_code = ? and frequency=? and data_update_failed_reason=? and data_update_result = 2"
+	var pars []interface{}
+	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, terminalCode, frequency, sourceUpdateFailedReason)
+
+	if createTime != "" {
+		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)
+		if err != nil {
+			br.Msg = "请求时间格式错误"
+			return
+		}
+		endT := startT.AddDate(0, 0, 1)
+		condition += " AND create_time >= ? AND create_time < ?"
+		pars = append(pars, createTime, endT.Format(utils.FormatDate))
+	}
+
+	total, err := data_stat.GetEdbUpdateStatCount(condition, pars)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取指标列表总数失败"
+			br.ErrMsg = "获取指标列表总数失败,Err:" + err.Error()
+			return
+		} else {
+			err = nil
+		}
+	}
+	list := make([]*data_stat.EdbInfoUpdateStatItemMore, 0)
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	if total == 0 {
+		resp := data_stat.GetEdbUpdateStatResp{
+			Paging: page,
+			List:   list,
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+	tmpList, err := data_stat.GetEdbUpdateStatByCondition(condition, pars, "", pageSize, startSize)
+	if err != nil {
+		br.Msg = "获取指标更新列表失败"
+		br.ErrMsg = "获取指标更新列表失败,Err:" + err.Error()
+		return
+	}
+
+	for _, v := range tmpList {
+		tmp := new(data_stat.EdbInfoUpdateStatItemMore)
+		tmp.Id = v.Id
+		tmp.EdbInfoId = v.EdbInfoId
+		tmp.SourceName = v.SourceName
+		tmp.Source = v.Source
+		tmp.EdbCode = v.EdbCode
+		tmp.EdbName = v.EdbName
+		tmp.EdbNameSource = v.EdbNameSource
+		tmp.Frequency = v.Frequency
+		tmp.Unit = v.Unit
+		tmp.StartDate = v.StartDate
+		tmp.SysUserId = v.SysUserId
+		tmp.SysUserRealName = v.SysUserRealName
+		tmp.LatestDate = v.LatestDate
+		tmp.LatestValue = v.LatestValue
+		tmp.TerminalCode = v.TerminalCode
+		tmp.CreateTime = v.CreateTime.Format(utils.FormatDateTime)
+		if v.DataUpdateTime != utils.EmptyDateTimeStr {
+			tmp.DataUpdateTime = v.DataUpdateTime
+		}
+		if v.ErDataUpdateDate != utils.EmptyDateStr {
+			tmp.ErDataUpdateDate = v.ErDataUpdateDate
+		}
+		tmp.DataUpdateResult = v.DataUpdateResult
+		tmp.DataUpdateFailedReason = v.DataUpdateFailedReason
+		if v.UpdateTime != utils.EmptyDateTimeStr {
+			tmp.UpdateTime = v.UpdateTime
+		}
+		tmp.IsAdd = v.IsAdd
+		tmp.NeedRefresh = v.NeedRefresh
+		tmp.HasRefresh = v.HasRefresh
+		list = append(list, tmp)
+	}
+
+	resp := data_stat.GetEdbUpdateStatResp{
+		Paging: page,
+		List:   list,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 18 - 0
controllers/english_report/english_classify.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
@@ -469,6 +470,23 @@ func (this *EnglishReportController) DelClassify() {
 		return
 	}
 
+	// 查询该分类是否关联了审批流
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowCond := fmt.Sprintf(` AND %s = ? AND (%s = ? Or %s = ?)`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+	flowPars := make([]interface{}, 0)
+	flowPars = append(flowPars, report_approve.FlowReportTypeEnglish, classifyId, classifyId)
+	flowCount, e := flowOb.GetCountByCondition(flowCond, flowPars)
+	if e != nil {
+		br.Msg = "检测失败"
+		br.ErrMsg = "获取关联审批流失败, Err: " + e.Error()
+		return
+	}
+	if flowCount > 0 {
+		br.Msg = "该分类关联审批流,不允许删除"
+		br.Ret = 403
+		return
+	}
+
 	if err = models.DeleteEnglishClassify(classifyId); err != nil {
 		br.Msg = "删除失败"
 		br.ErrMsg = "删除报告失败, Err: " + err.Error()

+ 274 - 20
controllers/english_report/report.go

@@ -5,6 +5,7 @@ import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
 	"eta/eta_api/models/company"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
@@ -85,6 +86,14 @@ func (this *EnglishReportController) Add() {
 		return
 	}
 
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
 	item := new(models.EnglishReport)
 	item.AddType = req.AddType
 	item.ClassifyIdFirst = req.ClassifyIdFirst
@@ -95,7 +104,7 @@ func (this *EnglishReportController) Add() {
 	item.Abstract = req.Abstract
 	item.Author = req.Author
 	item.Frequency = req.Frequency
-	item.State = req.State
+	item.State = state
 	item.Content = html.EscapeString(req.Content)
 	item.Stage = maxStage + 1
 	item.ContentSub = html.EscapeString(contentSub)
@@ -189,15 +198,27 @@ func (this *EnglishReportController) Edit() {
 	}
 
 	var stage int
-	report, _ := models.GetEnglishReportById(int(req.ReportId))
-	if report != nil {
-		if report.ClassifyNameFirst != req.ClassifyNameFirst || report.ClassifyNameSecond != req.ClassifyNameSecond {
-			maxStage, _ := models.GetEnglishReportStageEdit(req.ClassifyIdFirst, req.ClassifyIdSecond, int(req.ReportId))
-			maxStage = maxStage + 1
-			stage = maxStage
-		} else {
-			stage = report.Stage
+	report, e := models.GetEnglishReportById(int(req.ReportId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
 		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+	if report.State == models.ReportStatePublished || report.State == models.ReportStatePass {
+		br.Msg = "该报告已发布,不允许编辑"
+		br.ErrMsg = "该报告已发布,不允许编辑"
+		return
+	}
+	if report.ClassifyNameFirst != req.ClassifyNameFirst || report.ClassifyNameSecond != req.ClassifyNameSecond {
+		maxStage, _ := models.GetEnglishReportStageEdit(req.ClassifyIdFirst, req.ClassifyIdSecond, int(req.ReportId))
+		maxStage = maxStage + 1
+		stage = maxStage
+	} else {
+		stage = report.Stage
 	}
 
 	item := new(models.EnglishReport)
@@ -209,7 +230,7 @@ func (this *EnglishReportController) Edit() {
 	item.Abstract = req.Abstract
 	item.Author = req.Author
 	item.Frequency = req.Frequency
-	item.State = req.State
+	item.State = report.State // 编辑不变更状态
 	item.Stage = stage
 	item.Content = html.EscapeString(req.Content)
 	item.ContentSub = html.EscapeString(contentSub)
@@ -307,7 +328,7 @@ func (this *EnglishReportController) Detail() {
 // @Description 获取报告列表
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   TimeType     query string true  "筛选的时间类别:publish_time(发布时间),modify_time(更新时间)"
+// @Param   TimeType     query string true  "筛选的时间类别:publish_time(发布时间),modify_time(更新时间);approve_time(审批时间)"
 // @Param   StartDate   query   string  true       "开始时间"
 // @Param   EndDate   query   string  true       "结束时间"
 // @Param   Frequency   query   string  true       "频度"
@@ -369,7 +390,7 @@ func (this *EnglishReportController) ListReport() {
 	if timeType == "" {
 		timeType = "publish_time"
 	}
-	if timeType != "publish_time" && timeType != "modify_time" {
+	if timeType != "publish_time" && timeType != "modify_time" && timeType != "approve_time" {
 		br.Msg = "请选择正确的时间"
 		br.ErrMsg = "请选择正确的时间"
 		return
@@ -491,7 +512,7 @@ func (this *EnglishReportController) ListReport() {
 		fieldArr := []string{
 			"id", "add_type", "classify_id_first", "classify_name_first", "classify_id_second", "classify_name_second", "title", "abstract", "author",
 			"frequency", "create_time", "modify_time", "state", "publish_time", "pre_publish_time", "stage", "msg_is_send", "report_code", "pv", "share_url",
-			"pv_email", "email_state", "from_report_id", "key_takeaways", "admin_id", "admin_real_name",
+			"pv_email", "email_state", "from_report_id", "key_takeaways", "admin_id", "admin_real_name", "approve_time",
 		}
 		items, e := models.GetEnglishReportList(condition, pars, companyType, startSize, pageSize, fieldArr)
 		if e != nil {
@@ -713,14 +734,33 @@ func (this *EnglishReportController) PublishReport() {
 		} else {
 			publishTime = time.Now().Format(utils.FormatDateTime)
 		}
-		if tmpErr = models.PublishEnglishReportById(report.Id, publishTime); tmpErr != nil {
-			br.Msg = "报告发布失败"
-			br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+
+		// 根据审批开关及审批流判断当前报告状态
+		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, models.ReportOperatePublish)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
 			return
 		}
-		go func() {
-			_ = services.UpdateEnglishReportEs(report.Id, 2)
-		}()
+
+		if state == models.ReportStatePublished {
+			// 发布报告
+			if tmpErr = models.PublishEnglishReportById(report.Id, publishTime); tmpErr != nil {
+				br.Msg = "报告发布失败"
+				br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+				return
+			}
+			go func() {
+				_ = services.UpdateEnglishReportEs(report.Id, 2)
+			}()
+		} else {
+			// 从无审批切换为有审批, 状态重置
+			if e = models.ResetEnglishReportById(report.Id, state); tmpErr != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = fmt.Sprintf("重置英文报告状态失败, Err: %s, ReportId: %d", e.Error(), report.Id)
+				return
+			}
+		}
 	}
 
 	br.Ret = 200
@@ -793,6 +833,18 @@ func (this *EnglishReportController) PrePublishReport() {
 		return
 	}
 
+	// 校验是否开启了审批流
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
+		return
+	}
+	if opening {
+		br.Msg = "报告已开启审批流, 不可设置定时发布"
+		return
+	}
+
 	var tmpErr error
 	if tmpErr = models.SetPrePublishEnglishReportById(report.Id, req.PrePublishTime); tmpErr != nil {
 		br.Msg = "设置定时发布失败"
@@ -828,7 +880,22 @@ func (this *EnglishReportController) PublishCancleReport() {
 		br.ErrMsg = "参数错误,报告id不可为空"
 		return
 	}
-	err = models.PublishCancelEnglishReport(req.ReportIds)
+	reportInfo, err := models.GetEnglishReportById(req.ReportIds)
+	if err != nil {
+		br.Msg = "获取报告信息失败"
+		br.ErrMsg = "获取报告信息失败,Err:" + err.Error()
+		return
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, models.ReportOperateCancelPublish)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
+	err = models.PublishCancelEnglishReport(req.ReportIds, state)
 	if err != nil {
 		br.Msg = "取消发布失败"
 		br.ErrMsg = "取消发布失败,Err:" + err.Error()
@@ -1233,3 +1300,190 @@ func (this *EnglishReportController) EditPolicy() {
 	br.Msg = "保存成功"
 	br.Data = resp
 }
+
+// SubmitApprove
+// @Title 提交审批
+// @Description 提交审批接口
+// @Param	request	body models.ReportSubmitApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/submit [post]
+func (this *EnglishReportController) SubmitApprove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	var req models.ReportSubmitApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.EnglishReport)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待审批时, 仅更新状态
+	if state != models.ReportStateWaitApprove {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 提交审批
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "提交审批失败, Err: " + e.Error()
+		return
+	}
+	reportItem.ApproveId = approveId
+	reportItem.State = models.ReportStateWaitApprove
+	reportItem.ModifyTime = time.Now().Local()
+	e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// CancelApprove
+// @Title 撤销审批
+// @Description 撤销审批
+// @Param	request	body models.ReportCancelApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/cancel [post]
+func (this *EnglishReportController) CancelApprove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	var req models.ReportCancelApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.EnglishReport)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待提交时, 仅更新状态
+	if state != models.ReportStateWaitSubmit {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+	//if reportItem.ApproveId <= 0 {
+	//	br.Msg = "报告审批不存在"
+	//	br.ErrMsg = fmt.Sprintf("报告审批不存在, ApproveId: %d", reportItem.ApproveId)
+	//	return
+	//}
+
+	// 撤销审批
+	e = services.CancelReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.ApproveId, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "撤销审批失败, Err: " + e.Error()
+		return
+	}
+	//reportItem.ApproveId = 0
+	//reportItem.State = models.ReportStateWaitSubmit
+	//reportItem.ModifyTime = time.Now().Local()
+	//e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	//if e != nil {
+	//	br.Msg = "操作失败"
+	//	br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+	//	return
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 7 - 2
controllers/ppt_english.go

@@ -152,6 +152,7 @@ func (this *PptEnglishController) AddPpt() {
 			ReportType:    req.FirstPage.ReportType,
 			PptDate:       req.FirstPage.PptDate,
 			Content:       req.Content,
+			CoverContent:  req.CoverContent,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       this.SysUser.AdminId,
@@ -185,9 +186,10 @@ func (this *PptEnglishController) AddPpt() {
 		pptInfo.ReportType = req.FirstPage.ReportType
 		pptInfo.PptDate = req.FirstPage.PptDate
 		pptInfo.Content = req.Content
+		pptInfo.CoverContent = req.CoverContent
 		pptInfo.ModifyTime = time.Now()
 
-		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime"})
+		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent"})
 
 		msg = "保存成功"
 	}
@@ -264,8 +266,9 @@ func (this *PptEnglishController) EditPpt() {
 	pptInfo.ReportType = req.FirstPage.ReportType
 	pptInfo.PptDate = req.FirstPage.PptDate
 	pptInfo.Content = req.Content
+	pptInfo.CoverContent = req.CoverContent
 	pptInfo.ModifyTime = time.Now()
-	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime"})
+	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent"})
 	if err != nil {
 		br.Msg = "编辑失败"
 		br.ErrMsg = "编辑失败,Err:" + err.Error()
@@ -282,6 +285,7 @@ func (this *PptEnglishController) EditPpt() {
 			ReportType:    pptInfo.ReportType,
 			PptDate:       pptInfo.PptDate,
 			Content:       pptInfo.Content,
+			CoverContent:  pptInfo.CoverContent,
 			AdminId:       this.SysUser.AdminId,
 			AdminRealName: this.SysUser.RealName,
 			CreateTime:    time.Now(),
@@ -672,6 +676,7 @@ func (this *PptEnglishController) SaveLog() {
 		ReportType:    req.FirstPage.ReportType,
 		PptDate:       req.FirstPage.PptDate,
 		Content:       req.Content,
+		CoverContent:  req.CoverContent,
 		AdminId:       this.SysUser.AdminId,
 		AdminRealName: this.SysUser.RealName,
 		CreateTime:    time.Now(),

+ 9 - 2
controllers/ppt_v2.go

@@ -152,6 +152,7 @@ func (this *PptV2Controller) AddPpt() {
 			ReportType:    req.FirstPage.ReportType,
 			PptDate:       req.FirstPage.PptDate,
 			Content:       req.Content,
+			CoverContent:  req.CoverContent,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       this.SysUser.AdminId,
@@ -186,9 +187,10 @@ func (this *PptV2Controller) AddPpt() {
 		pptInfo.ReportType = req.FirstPage.ReportType
 		pptInfo.PptDate = req.FirstPage.PptDate
 		pptInfo.Content = req.Content
+		pptInfo.CoverContent = req.CoverContent
 		pptInfo.ModifyTime = time.Now()
 
-		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime"})
+		err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent"})
 
 		msg = "保存成功"
 	}
@@ -265,8 +267,9 @@ func (this *PptV2Controller) EditPpt() {
 	pptInfo.ReportType = req.FirstPage.ReportType
 	pptInfo.PptDate = req.FirstPage.PptDate
 	pptInfo.Content = req.Content
+	pptInfo.CoverContent = req.CoverContent
 	pptInfo.ModifyTime = time.Now()
-	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime"})
+	err = pptInfo.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "CoverContent"})
 	if err != nil {
 		br.Msg = "编辑失败"
 		br.ErrMsg = "编辑失败,Err:" + err.Error()
@@ -283,6 +286,7 @@ func (this *PptV2Controller) EditPpt() {
 			ReportType:    pptInfo.ReportType,
 			PptDate:       pptInfo.PptDate,
 			Content:       pptInfo.Content,
+			CoverContent:  pptInfo.CoverContent,
 			AdminId:       this.SysUser.AdminId,
 			AdminRealName: this.SysUser.RealName,
 			CreateTime:    time.Now(),
@@ -673,6 +677,7 @@ func (this *PptV2Controller) SaveLog() {
 		ReportType:    req.FirstPage.ReportType,
 		PptDate:       req.FirstPage.PptDate,
 		Content:       req.Content,
+		CoverContent:  req.CoverContent,
 		AdminId:       this.SysUser.AdminId,
 		AdminRealName: this.SysUser.RealName,
 		CreateTime:    time.Now(),
@@ -1019,6 +1024,7 @@ func (this *PptV2Controller) ToEn() {
 		ReportType:    origin.ReportType,
 		PptDate:       origin.PptDate,
 		Content:       origin.Content,
+		CoverContent:  origin.CoverContent,
 		CreateTime:    time.Now(),
 		ModifyTime:    time.Now(),
 		AdminId:       this.SysUser.AdminId,
@@ -1117,6 +1123,7 @@ func (this *PptV2Controller) BatchToEn() {
 			ReportType:    origin.ReportType,
 			PptDate:       origin.PptDate,
 			Content:       origin.Content,
+			CoverContent:  origin.CoverContent,
 			CreateTime:    nowTime,
 			ModifyTime:    nowTime,
 			AdminId:       this.SysUser.AdminId,

+ 301 - 57
controllers/report.go

@@ -3,7 +3,7 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_api/models"
-	"eta/eta_api/models/company"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
@@ -41,7 +41,7 @@ type ReportUploadCommonController struct {
 // @Description 获取报告列表
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   TimeType     query string true  "筛选的时间类别:publish_time(发布时间),modify_time(更新时间)"
+// @Param   TimeType     query string true  "筛选的时间类别:publish_time(发布时间),modify_time(更新时间);approve_time(审批时间)"
 // @Param   StartDate   query   string  true       "开始时间"
 // @Param   EndDate   query   string  true       "结束时间"
 // @Param   Frequency   query   string  true       "频度"
@@ -85,7 +85,7 @@ func (this *ReportController) ListReport() {
 	if timeType == "" {
 		timeType = "publish_time"
 	}
-	if timeType != "publish_time" && timeType != "modify_time" {
+	if timeType != "publish_time" && timeType != "modify_time" && timeType != "approve_time" {
 		br.Msg = "请选择正确的时间"
 		br.ErrMsg = "请选择正确的时间"
 		return
@@ -266,6 +266,8 @@ func (this *ReportController) PublishReport() {
 		br.ErrMsg = "参数错误,报告id不可为空"
 		return
 	}
+
+	// 这里实际上不会批量发布了...
 	reportArr := strings.Split(reportIds, ",")
 	tips := ""
 	for _, v := range reportArr {
@@ -314,28 +316,47 @@ func (this *ReportController) PublishReport() {
 				br.ErrMsg = "报告内容为空,不需要生成,report_id:" + strconv.Itoa(report.Id)
 				return
 			}
-			if tmpErr = models.PublishReportById(report.Id, publishTime); tmpErr != nil {
-				br.Msg = "报告发布失败"
-				br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+
+			// 根据审批开关及审批流判断当前报告状态
+			state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, report.ClassifyIdFirst, report.ClassifyIdSecond, models.ReportOperatePublish)
+			if e != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
 				return
 			}
 
-			go func() {
-				// 生成音频
-				if report.VideoUrl == "" {
-					_ = services.CreateVideo(report)
+			if state == models.ReportStatePublished {
+				// 发布报告
+				if tmpErr = models.PublishReportById(report.Id, publishTime); tmpErr != nil {
+					br.Msg = "报告发布失败"
+					br.ErrMsg = "报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(report.Id)
+					return
 				}
-				//// 推送找钢网
-				//if utils.RunMode == "release" && (report.ClassifyNameSecond == "知白守黑日评" || report.ClassifyNameSecond == "股债日评") {
-				//	_ = services.ZhaoGangSend(report)
-				//}
-				// 更新报告Es
-				_ = services.UpdateReportEs(report.Id, 2)
-			}()
+				go func() {
+					// 生成音频
+					if report.VideoUrl == "" {
+						_ = services.CreateVideo(report)
+					}
+					//// 推送找钢网
+					//if utils.RunMode == "release" && (report.ClassifyNameSecond == "知白守黑日评" || report.ClassifyNameSecond == "股债日评") {
+					//	_ = services.ZhaoGangSend(report)
+					//}
+					// 更新报告Es
+					_ = services.UpdateReportEs(report.Id, 2)
+				}()
+			} else {
+				// 从无审批切换为有审批, 状态重置
+				if e = models.ResetReportById(report.Id, state); tmpErr != nil {
+					br.Msg = "操作失败"
+					br.ErrMsg = fmt.Sprintf("重置报告状态失败, Err: %s, ReportId: %d", e.Error(), report.Id)
+					return
+				}
+			}
+
 			recordItem := &models.ReportStateRecord{
 				ReportId:   vint,
 				ReportType: 1,
-				State:      2,
+				State:      state,
 				AdminId:    this.SysUser.AdminId,
 				AdminName:  this.SysUser.AdminName,
 				CreateTime: time.Now(),
@@ -389,7 +410,16 @@ func (this *ReportController) PublishCancleReport() {
 	if reportInfo.MsgIsSend == 1 {
 		publishTimeNullFlag = false
 	}
-	err = models.PublishCancleReport(req.ReportIds, publishTimeNullFlag)
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, models.ReportOperateCancelPublish)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
+	err = models.PublishCancelReport(req.ReportIds, state, publishTimeNullFlag)
 	if err != nil {
 		br.Msg = "取消发布失败"
 		br.ErrMsg = "取消发布失败,Err:" + err.Error()
@@ -400,24 +430,24 @@ func (this *ReportController) PublishCancleReport() {
 		go services.UpdateReportEs(req.ReportIds, 1)
 	}
 
-	// 获取审批流设置
-	confKey := "approval_flow"
-	confTmp, e := company.GetConfigDetailByCode(confKey)
-	if e != nil {
-		br.Msg = "获取审批流配置失败"
-		br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
-		return
-	}
-	if confTmp.ConfigValue == "1" || confTmp.ConfigValue == "2" || confTmp.ConfigValue == "3" {
-		br.Msg = "撤销成功"
-	} else {
-		br.Msg = "取消发布成功"
-	}
+	//// 获取审批流设置
+	//confKey := "approval_flow"
+	//confTmp, e := company.GetConfigDetailByCode(confKey)
+	//if e != nil {
+	//	br.Msg = "获取审批流配置失败"
+	//	br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
+	//	return
+	//}
+	//if confTmp.ConfigValue == "1" || confTmp.ConfigValue == "2" || confTmp.ConfigValue == "3" {
+	//	br.Msg = "撤销成功"
+	//} else {
+	//	br.Msg = "取消发布成功"
+	//}
 
 	recordItem := &models.ReportStateRecord{
 		ReportId:   req.ReportIds,
 		ReportType: 1,
-		State:      1,
+		State:      state,
 		AdminId:    this.SysUser.AdminId,
 		AdminName:  this.SysUser.AdminName,
 		CreateTime: time.Now(),
@@ -514,6 +544,14 @@ func (this *ReportController) Add() {
 		return
 	}
 
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
 	item := new(models.Report)
 	item.AddType = req.AddType
 	item.ClassifyIdFirst = req.ClassifyIdFirst
@@ -524,7 +562,7 @@ func (this *ReportController) Add() {
 	item.Abstract = req.Abstract
 	item.Author = req.Author
 	item.Frequency = req.Frequency
-	item.State = req.State
+	item.State = state
 	item.Content = html.EscapeString(req.Content)
 	item.Stage = maxStage + 1
 	item.ContentSub = html.EscapeString(contentSub)
@@ -645,34 +683,41 @@ func (this *ReportController) Edit() {
 	}
 
 	var stage int
-	report, _ := models.GetReportById(int(req.ReportId))
-	if report != nil {
-		if report.ClassifyNameFirst != req.ClassifyNameFirst || report.ClassifyNameSecond != req.ClassifyNameSecond {
-			maxStage, _ := models.GetReportStageEdit(req.ClassifyIdFirst, req.ClassifyIdSecond, int(req.ReportId))
-			maxStage = maxStage + 1
-			stage = maxStage
-		} else {
-			stage = report.Stage
+	report, e := models.GetReportById(int(req.ReportId))
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
 		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
 	}
-	if report.State == 2 {
+	if report.State == models.ReportStatePublished || report.State == models.ReportStatePass {
 		br.Msg = "该报告已发布,不允许编辑"
 		br.ErrMsg = "该报告已发布,不允许编辑"
 		return
 	}
-	if req.State != report.State {
-		recordItem := &models.ReportStateRecord{
-			ReportId:   int(req.ReportId),
-			ReportType: 1,
-			State:      req.State,
-			AdminId:    this.SysUser.AdminId,
-			AdminName:  this.SysUser.AdminName,
-			CreateTime: time.Now(),
-		}
-		go func() {
-			_, _ = models.AddReportStateRecord(recordItem)
-		}()
-	}
+	if report.ClassifyNameFirst != req.ClassifyNameFirst || report.ClassifyNameSecond != req.ClassifyNameSecond {
+		maxStage, _ := models.GetReportStageEdit(req.ClassifyIdFirst, req.ClassifyIdSecond, int(req.ReportId))
+		maxStage = maxStage + 1
+		stage = maxStage
+	} else {
+		stage = report.Stage
+	}
+	//if req.State != report.State {
+	//	recordItem := &models.ReportStateRecord{
+	//		ReportId:   int(req.ReportId),
+	//		ReportType: 1,
+	//		State:      req.State,
+	//		AdminId:    this.SysUser.AdminId,
+	//		AdminName:  this.SysUser.AdminName,
+	//		CreateTime: time.Now(),
+	//	}
+	//	go func() {
+	//		_, _ = models.AddReportStateRecord(recordItem)
+	//	}()
+	//}
 
 	item := new(models.Report)
 	item.ClassifyIdFirst = req.ClassifyIdFirst
@@ -683,7 +728,7 @@ func (this *ReportController) Edit() {
 	item.Abstract = req.Abstract
 	item.Author = req.Author
 	item.Frequency = req.Frequency
-	item.State = req.State
+	item.State = report.State // 编辑不变更状态
 	item.Stage = stage
 	item.Content = html.EscapeString(req.Content)
 	item.ContentSub = html.EscapeString(contentSub)
@@ -3360,6 +3405,18 @@ func (this *ReportController) PrePublishReport() {
 		return
 	}
 
+	// 校验是否开启了审批流
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeChinese, report.ClassifyIdFirst, report.ClassifyIdSecond)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
+		return
+	}
+	if opening {
+		br.Msg = "报告已开启审批流, 不可设置定时发布"
+		return
+	}
+
 	var tmpErr error
 	if tmpErr = models.SetPrePublishReportById(report.Id, req.PrePublishTime, req.PreMsgSend); tmpErr != nil {
 		br.Msg = "设置定时发布失败"
@@ -3371,3 +3428,190 @@ func (this *ReportController) PrePublishReport() {
 	br.Success = true
 	br.Msg = "定时发布成功"
 }
+
+// SubmitApprove
+// @Title 提交审批
+// @Description 提交审批接口
+// @Param	request	body models.ReportSubmitApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/submit [post]
+func (this *ReportController) SubmitApprove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	var req models.ReportSubmitApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.Report)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待审批时, 仅更新状态
+	if state != models.ReportStateWaitApprove {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 提交审批
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeChinese, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "提交审批失败, Err: " + e.Error()
+		return
+	}
+	reportItem.ApproveId = approveId
+	reportItem.State = models.ReportStateWaitApprove
+	reportItem.ModifyTime = time.Now().Local()
+	e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// CancelApprove
+// @Title 撤销审批
+// @Description 撤销审批
+// @Param	request	body models.ReportCancelApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/cancel [post]
+func (this *ReportController) CancelApprove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	var req models.ReportCancelApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(models.Report)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeChinese, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待提交时, 仅更新状态
+	if state != models.ReportStateWaitSubmit {
+		reportItem.State = state
+		e = reportItem.UpdateReport([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+	//if reportItem.ApproveId <= 0 {
+	//	br.Msg = "报告审批不存在"
+	//	br.ErrMsg = fmt.Sprintf("报告审批不存在, ApproveId: %d", reportItem.ApproveId)
+	//	return
+	//}
+
+	// 撤销审批
+	e = services.CancelReportApprove(report_approve.FlowReportTypeChinese, reportItem.Id, reportItem.ApproveId, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "撤销审批失败, Err: " + e.Error()
+		return
+	}
+	//reportItem.ApproveId = 0
+	//reportItem.State = models.ReportStateWaitSubmit
+	//reportItem.ModifyTime = time.Now().Local()
+	//e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	//if e != nil {
+	//	br.Msg = "操作失败"
+	//	br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+	//	return
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 931 - 0
controllers/report_approve/report_approve.go

@@ -0,0 +1,931 @@
+package report_approve
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"sort"
+	"strings"
+	"time"
+)
+
+// ReportApproveController 报告审批
+type ReportApproveController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 审批列表
+// @Description 审批列表
+// @Param   PageSize			query	int		true	"每页数据条数"
+// @Param   CurrentIndex		query	int		true	"当前页页码"
+// @Param   ListType			query   int     true	"列表类型:1-待处理;2-已处理;3-我发起的"
+// @Param   ReportType			query   int     false	"报告类型:1-中文研报;2-英文研报;3-智能研报"
+// @Param   ClassifyIdFirst		query	int		false	"一级分类ID"
+// @Param   ClassifyIdSecond	query	int		false	"二级分类ID"
+// @Param   Keyword				query	string	false	"搜索关键词"
+// @Param   ApproveState		query	int		false	"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"
+// @Param   TimeType			query	int		false	"时间类型:1-提交时间;2-处理时间;3-审批时间"
+// @Param   StartTime			query	string	false	"开始时间"
+// @Param   EndTime				query	string	false	"结束时间"
+// @Param   SortField			query	int		false	"排序字段:1-提交时间;2-处理时间;3-审批时间"
+// @Param   SortRule			query	int		false	"排序方式: 1-正序; 2-倒序(默认)"
+// @Success 200 {object} report_approve.ReportApproveListResp
+// @router /list [get]
+func (this *ReportApproveController) 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
+	}
+	params := new(report_approve.ReportApproveListReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "入参解析失败, Err: " + e.Error()
+		return
+	}
+	if params.ListType != 1 && params.ListType != 2 && params.ListType != 3 {
+		br.Msg = "列表类型有误"
+		return
+	}
+
+	// 报告分类
+	cnClassifyIdName, enClassifyIdName := make(map[int]string), make(map[int]string)
+	cnClassify, e := models.GetAllClassify()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取中文分类失败, Err: " + e.Error()
+		return
+	}
+	for _, v := range cnClassify {
+		cnClassifyIdName[v.Id] = v.ClassifyName
+	}
+	enClassify, e := models.GetEnglishClassifies()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文分类失败, Err: " + e.Error()
+		return
+	}
+	enRootIdMap := make(map[int]int) // 英文分类的一级分类ID
+	for _, v := range enClassify {
+		enClassifyIdName[v.Id] = v.ClassifyName
+		enRootIdMap[v.Id] = v.RootId
+	}
+
+	// 分页
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+
+	resp := new(report_approve.ReportApproveListResp)
+	respTotal := 0
+	respList := make([]*report_approve.ReportApproveItem, 0)
+	timeField := map[int]string{1: fmt.Sprintf("a.%s", report_approve.ReportApproveCols.CreateTime), 2: fmt.Sprintf("a.%s", report_approve.ReportApproveRecordCols.ApproveTime), 3: fmt.Sprintf("b.%s", report_approve.ReportApproveCols.ApproveTime)}
+	mineTimeField := map[int]string{1: fmt.Sprintf("%s", report_approve.ReportApproveCols.CreateTime), 3: fmt.Sprintf("%s", report_approve.ReportApproveCols.ApproveTime)}
+	orderRules := map[int]string{1: "ASC", 2: "DESC"}
+	ormList := make([]*report_approve.ReportApproveItemOrm, 0)
+
+	// 待处理
+	if params.ListType == 1 {
+		cond := fmt.Sprintf(` AND a.%s = ? AND b.%s = ? AND a.%s = ?`, report_approve.ReportApproveRecordCols.State, report_approve.ReportApproveCols.State, report_approve.ReportApproveRecordCols.ApproveUserId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, report_approve.ReportApproveStateApproving, report_approve.ReportApproveStateApproving, sysUser.AdminId)
+		order := ""
+
+		// 筛选条件
+		if params.ReportType > 0 && params.ClassifySecondId > 0 {
+			cond += fmt.Sprintf(` AND b.%s = ? AND b.%s = ?`, report_approve.ReportApproveCols.ReportType, report_approve.ReportApproveCols.ClassifySecondId)
+			pars = append(pars, params.ReportType, params.ClassifySecondId)
+		}
+		if params.TimeType <= 0 {
+			params.TimeType = 1
+		}
+		if params.TimeType == 1 && params.StartTime != "" && params.EndTime != "" {
+			_, e := time.Parse(utils.FormatDate, params.StartTime)
+			if e != nil {
+				br.Msg = "开始时间格式有误"
+				return
+			}
+			_, e = time.Parse(utils.FormatDate, params.EndTime)
+			if e != nil {
+				br.Msg = "结束时间格式有误"
+				return
+			}
+			st := fmt.Sprintf("%s %s", params.StartTime, "00:00:00")
+			ed := fmt.Sprintf("%s %s", params.EndTime, "23:59:59")
+			cond += fmt.Sprintf(` AND (b.%s BETWEEN ? AND ?)`, report_approve.ReportApproveCols.CreateTime)
+			pars = append(pars, st, ed)
+		}
+		params.Keyword = strings.TrimSpace(params.Keyword)
+		if params.Keyword != "" {
+			kw := fmt.Sprint("%", params.Keyword, "%")
+			cond += fmt.Sprintf(` AND b.%s LIKE ?`, report_approve.ReportApproveCols.ReportTitle)
+			pars = append(pars, kw)
+		}
+		if params.SortField > 0 && params.SortRule > 0 {
+			orderField := timeField[params.SortField]
+			if orderField == "" {
+				br.Msg = "时间排序字段有误"
+				return
+			}
+			orderRule := orderRules[params.SortRule]
+			if orderRule == "" {
+				br.Msg = "时间排序方式有误"
+				return
+			}
+			order = fmt.Sprintf("%s %s", orderField, orderRule)
+		}
+		total, e := report_approve.GetApprovingReportApproveCount(cond, pars)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "GetApprovingReportApproveCount err: " + e.Error()
+			return
+		}
+		respTotal = total
+		list, e := report_approve.GetApprovingReportApprovePageList(cond, pars, order, startSize, params.PageSize)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "GetApprovingReportApprovePageList err: " + e.Error()
+			return
+		}
+		ormList = list
+	}
+
+	// 已处理
+	if params.ListType == 2 {
+		cond := fmt.Sprintf(` AND a.%s = ? AND a.%s IN (%s)`, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.State, utils.GetOrmInReplace(2))
+		pars := make([]interface{}, 0)
+		pars = append(pars, sysUser.AdminId, []int{report_approve.ReportApproveStatePass, report_approve.ReportApproveStateRefuse})
+		order := ""
+
+		// 筛选条件
+		if params.ReportType > 0 && params.ClassifySecondId > 0 {
+			cond += fmt.Sprintf(` AND b.%s = ? AND b.%s = ?`, report_approve.ReportApproveCols.ReportType, report_approve.ReportApproveCols.ClassifySecondId)
+			pars = append(pars, params.ReportType, params.ClassifySecondId)
+		}
+		if params.TimeType > 0 && params.StartTime != "" && params.EndTime != "" {
+			_, e := time.Parse(utils.FormatDate, params.StartTime)
+			if e != nil {
+				br.Msg = "开始时间格式有误"
+				return
+			}
+			_, e = time.Parse(utils.FormatDate, params.EndTime)
+			if e != nil {
+				br.Msg = "结束时间格式有误"
+				return
+			}
+			st := fmt.Sprintf("%s %s", params.StartTime, "00:00:00")
+			ed := fmt.Sprintf("%s %s", params.EndTime, "23:59:59")
+			cond += fmt.Sprintf(` AND (%s BETWEEN ? AND ?)`, timeField[params.TimeType])
+			pars = append(pars, st, ed)
+		}
+		params.Keyword = strings.TrimSpace(params.Keyword)
+		if params.Keyword != "" {
+			kw := fmt.Sprint("%", params.Keyword, "%")
+			cond += fmt.Sprintf(` AND b.%s LIKE ?`, report_approve.ReportApproveCols.ReportTitle)
+			pars = append(pars, kw)
+		}
+		if params.SortField > 0 && params.SortRule > 0 {
+			orderField := timeField[params.SortField]
+			if orderField == "" {
+				br.Msg = "时间排序字段有误"
+				return
+			}
+			orderRule := orderRules[params.SortRule]
+			if orderRule == "" {
+				br.Msg = "时间排序方式有误"
+				return
+			}
+			order = fmt.Sprintf("%s %s", orderField, orderRule)
+		}
+		if params.ApproveState > 0 {
+			cond += fmt.Sprintf(` AND a.%s = ?`, report_approve.ReportApproveRecordCols.State)
+			pars = append(pars, params.ApproveState)
+		}
+		total, e := report_approve.GetApprovedReportApproveCount(cond, pars)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "GetApprovedReportApproveCount err: " + e.Error()
+			return
+		}
+		respTotal = total
+		list, e := report_approve.GetApprovedReportApprovePageList(cond, pars, order, startSize, params.PageSize)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "GetApprovedReportApprovePageList err: " + e.Error()
+			return
+		}
+		ormList = list
+	}
+
+	// 我发起的
+	if params.ListType == 3 {
+		cond := fmt.Sprintf(` AND a.%s = ?`, report_approve.ReportApproveCols.ApplyUserId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, sysUser.AdminId)
+		order := ""
+
+		// 筛选条件
+		if params.ReportType > 0 && params.ClassifySecondId > 0 {
+			cond += fmt.Sprintf(` AND a.%s = ? AND a.%s = ?`, report_approve.ReportApproveCols.ReportType, report_approve.ReportApproveCols.ClassifySecondId)
+			pars = append(pars, params.ReportType, params.ClassifySecondId)
+		}
+		if params.TimeType > 0 && params.StartTime != "" && params.EndTime != "" {
+			_, e := time.Parse(utils.FormatDate, params.StartTime)
+			if e != nil {
+				br.Msg = "开始时间格式有误"
+				return
+			}
+			_, e = time.Parse(utils.FormatDate, params.EndTime)
+			if e != nil {
+				br.Msg = "结束时间格式有误"
+				return
+			}
+			st := fmt.Sprintf("%s %s", params.StartTime, "00:00:00")
+			ed := fmt.Sprintf("%s %s", params.EndTime, "23:59:59")
+			field := mineTimeField[params.TimeType]
+			if field == "" {
+				br.Msg = "筛选时间有误"
+				return
+			}
+			cond += fmt.Sprintf(` AND (%s BETWEEN ? AND ?)`, field)
+			pars = append(pars, st, ed)
+		}
+		params.Keyword = strings.TrimSpace(params.Keyword)
+		if params.Keyword != "" {
+			kw := fmt.Sprint("%", params.Keyword, "%")
+			cond += fmt.Sprintf(` AND a.%s LIKE ?`, report_approve.ReportApproveCols.ReportTitle)
+			pars = append(pars, kw)
+		}
+		if params.SortField > 0 && params.SortRule > 0 {
+			orderField := mineTimeField[params.SortField]
+			if orderField == "" {
+				br.Msg = "时间排序字段有误"
+				return
+			}
+			orderRule := orderRules[params.SortRule]
+			if orderRule == "" {
+				br.Msg = "时间排序方式有误"
+				return
+			}
+			order = fmt.Sprintf("%s %s", orderField, orderRule)
+		}
+		if params.ApproveState > 0 {
+			cond += fmt.Sprintf(` AND a.%s = ?`, report_approve.ReportApproveRecordCols.State)
+			pars = append(pars, params.ApproveState)
+		}
+		total, e := report_approve.GetApplyReportApproveCount(cond, pars)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "GetApplyReportApproveCount err: " + e.Error()
+			return
+		}
+		respTotal = total
+		list, e := report_approve.GetApplyReportApprovePageList(cond, pars, order, startSize, params.PageSize)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "GetApplyReportApprovePageList err: " + e.Error()
+			return
+		}
+		ormList = list
+	}
+
+	// 格式化列表
+	for _, v := range ormList {
+		t := report_approve.FormatReportApproveOrm2Item(v)
+		if v.ReportType == report_approve.FlowReportTypeEnglish {
+			t.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], enClassifyIdName[enRootIdMap[v.ClassifySecondId]], enClassifyIdName[v.ClassifyFirstId], enClassifyIdName[v.ClassifySecondId])
+		} else {
+			t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+		}
+		respList = append(respList, t)
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, respTotal)
+	resp.Paging = page
+	resp.List = respList
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Detail
+// @Title 审批详情
+// @Description 审批详情
+// @Param   ReportApproveId  query  int  true  "审批ID"
+// @Success 200 {object} report_approve.ReportApproveDetail
+// @router /detail [get]
+func (this *ReportApproveController) 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
+	}
+	approveId, _ := this.GetInt("ReportApproveId")
+	if approveId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveId: %d", approveId)
+		return
+	}
+
+	approveOb := new(report_approve.ReportApprove)
+	approveItem, e := approveOb.GetItemById(approveId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批失败, Err: " + e.Error()
+		return
+	}
+
+	// 审批信息
+	detail := new(report_approve.ReportApproveDetail)
+	detail.Approve = new(report_approve.ReportApproveDetailItem)
+	detail.Approve.ReportApproveId = approveItem.ReportApproveId
+	detail.Approve.State = approveItem.State
+	detail.Approve.FlowId = approveItem.FlowId
+	detail.Approve.FlowVersion = approveItem.FlowVersion
+	detail.Approve.StartNodeId = approveItem.StartNodeId
+	detail.Approve.CurrNodeId = approveItem.CurrNodeId
+	detail.Approve.ApplyUserId = approveItem.ApplyUserId
+	detail.Approve.ApplyUserName = approveItem.ApplyUserName
+	detail.Approve.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, approveItem.ApproveTime)
+	detail.Approve.CreateTime = utils.TimeTransferString(utils.FormatDateTime, approveItem.CreateTime)
+	detail.Approve.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, approveItem.ModifyTime)
+
+	// 审批流
+	//flowOb := new(report_approve.ReportApproveFlow)
+	//flowItem, e := flowOb.GetItemById(approveItem.FlowId)
+	//if e != nil {
+	//	if e.Error() == utils.ErrNoRow() {
+	//		br.Msg = "审批流已被删除, 请刷新页面"
+	//		return
+	//	}
+	//	br.Msg = "获取失败"
+	//	br.ErrMsg = "获取审批流失败, Err: " + e.Error()
+	//	return
+	//}
+	//detail.ApproveFlow = report_approve.FormatReportApproveFlow2Item(flowItem)
+
+	// 审批节点
+	nodeOb := new(report_approve.ReportApproveNode)
+	nodeCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveNodeCols.ReportApproveFlowId, report_approve.ReportApproveNodeCols.CurrVersion)
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, approveItem.FlowId, approveItem.FlowVersion)
+	nodeItems, e := nodeOb.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批节点失败, Err: " + e.Error()
+		return
+	}
+
+	// 审批记录
+	recordOb := new(report_approve.ReportApproveRecord)
+	recordCond := fmt.Sprintf(` AND %s = ?`, report_approve.ReportApproveRecordCols.ReportApproveId)
+	recordPars := make([]interface{}, 0)
+	recordPars = append(recordPars, approveItem.ReportApproveId)
+	recordItems, e := recordOb.GetItemsByCondition(recordCond, recordPars, []string{}, fmt.Sprintf("%s DESC", report_approve.ReportApproveRecordCols.ApproveTime))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批记录失败, Err: " + e.Error()
+		return
+	}
+	recordMap := make(map[string]*report_approve.ReportApproveRecord)
+	for _, v := range recordItems {
+		k := fmt.Sprintf("%d-%d", v.NodeId, v.ApproveUserId)
+		recordMap[k] = v
+	}
+
+	// 审批流节点详情
+	detail.ApproveFlowNodes = make([]*report_approve.ReportApproveDetailNodes, 0)
+	for _, v := range nodeItems {
+		t := new(report_approve.ReportApproveDetailNodes)
+		t.ReportApproveNodeId = v.ReportApproveNodeId
+		t.ReportApproveFlowId = v.ReportApproveFlowId
+		t.PrevNodeId = v.PrevNodeId
+		t.NextNodeId = v.NextNodeId
+		t.NodeType = v.NodeType
+		t.ApproveType = v.ApproveType
+		t.Users = make([]*report_approve.ReportApproveDetailNodeUser, 0)
+		us := make([]*report_approve.ReportApproveNodeUserReq, 0)
+		if v.Users != "" {
+			e = json.Unmarshal([]byte(v.Users), &us)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "节点用户解析失败, Err: " + e.Error()
+				return
+			}
+		}
+		for _, vu := range us {
+			u := new(report_approve.ReportApproveDetailNodeUser)
+			u.UserType = vu.UserType
+			u.UserId = vu.UserId
+			u.UserName = vu.UserName
+			u.Sort = vu.Sort
+			// 审批记录
+			k := fmt.Sprintf("%d-%d", v.ReportApproveNodeId, vu.UserId)
+			r := recordMap[k]
+			if r != nil {
+				u.ApproveRecord = new(report_approve.ReportApproveDetailNodeUserRecord)
+				u.ApproveRecord.ReportApproveRecordId = r.ReportApproveRecordId
+				u.ApproveRecord.State = r.State
+				u.ApproveRecord.ApproveUserId = r.ApproveUserId
+				u.ApproveRecord.ApproveUserName = r.ApproveUserName
+				u.ApproveRecord.ApproveRemark = r.ApproveRemark
+				u.ApproveRecord.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, r.ApproveTime)
+			}
+			t.Users = append(t.Users, u)
+		}
+		sort.Slice(t.Users, func(k, j int) bool {
+			return t.Users[k].Sort < t.Users[j].Sort
+		})
+		detail.ApproveFlowNodes = append(detail.ApproveFlowNodes, t)
+	}
+
+	// 报告信息
+	cnClassifyIdName, enClassifyIdName := make(map[int]string), make(map[int]string)
+	cnClassify, e := models.GetAllClassify()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取中文分类失败, Err: " + e.Error()
+		return
+	}
+	for _, v := range cnClassify {
+		cnClassifyIdName[v.Id] = v.ClassifyName
+	}
+	enClassify, e := models.GetEnglishClassifies()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文分类失败, Err: " + e.Error()
+		return
+	}
+	enRootIdMap := make(map[int]int) // 英文分类的一级分类ID
+	for _, v := range enClassify {
+		enClassifyIdName[v.Id] = v.ClassifyName
+		enRootIdMap[v.Id] = v.RootId
+	}
+	detail.Report = new(report_approve.ReportApproveDetailReport)
+	detail.Report.ReportType = approveItem.ReportType
+	detail.Report.ReportId = approveItem.ReportId
+	detail.Report.ReportTitle = approveItem.ReportTitle
+	// 报告分类路由
+	if approveItem.ReportType == report_approve.FlowReportTypeChinese {
+		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], cnClassifyIdName[approveItem.ClassifyFirstId], cnClassifyIdName[approveItem.ClassifySecondId])
+	}
+	if approveItem.ReportType == report_approve.FlowReportTypeEnglish {
+		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], enClassifyIdName[enRootIdMap[approveItem.ClassifySecondId]], enClassifyIdName[approveItem.ClassifyFirstId], enClassifyIdName[approveItem.ClassifySecondId])
+	}
+	if approveItem.ReportType == report_approve.FlowReportTypeSmart {
+		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], cnClassifyIdName[approveItem.ClassifyFirstId], cnClassifyIdName[approveItem.ClassifySecondId])
+	}
+
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Approve
+// @Title 通过审批
+// @Description 通过审批
+// @Param	request	body report_approve.ReportApprovePassReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /approve [post]
+func (this *ReportApproveController) Approve() {
+	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 report_approve.ReportApprovePassReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.ReportApproveId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveId: %d", req.ReportApproveId)
+		return
+	}
+
+	approveOb := new(report_approve.ReportApprove)
+	approveItem, e := approveOb.GetItemById(req.ReportApproveId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批信息失败, Err: " + e.Error()
+		return
+	}
+	if approveItem.State != report_approve.ReportApproveStateApproving {
+		br.Msg = "审批状态有误, 请刷新页面"
+		br.ErrMsg = fmt.Sprintf("审批状态有误, State: %d", approveItem.State)
+		return
+	}
+
+	// 校验审批记录和审批
+	recordOb := new(report_approve.ReportApproveRecord)
+	recordCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveRecordCols.ReportApproveId, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.State)
+	recordPars := make([]interface{}, 0)
+	recordPars = append(recordPars, approveItem.ReportApproveId, sysUser.AdminId, report_approve.ReportApproveStateApproving)
+	recordItem, e := recordOb.GetItemByCondition(recordCond, recordPars, "")
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "无权审批"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批记录失败, Err: " + e.Error()
+		return
+	}
+
+	// 通过审批
+	tips, e := services.PassReportApprove(approveItem, recordItem, sysUser.AdminId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "通过审批失败, Err: " + e.Error()
+		return
+	}
+	if tips != "" {
+		br.Msg = tips
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Refuse
+// @Title 驳回审批
+// @Description 驳回审批
+// @Param	request	body report_approve.ReportApproveRefuseReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /refuse [post]
+func (this *ReportApproveController) Refuse() {
+	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 report_approve.ReportApproveRefuseReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.ReportApproveId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveId: %d", req.ReportApproveId)
+		return
+	}
+
+	approveOb := new(report_approve.ReportApprove)
+	approveItem, e := approveOb.GetItemById(req.ReportApproveId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批信息失败, Err: " + e.Error()
+		return
+	}
+	if approveItem.State != report_approve.ReportApproveStateApproving {
+		br.Msg = "审批状态有误, 请刷新页面"
+		br.ErrMsg = fmt.Sprintf("审批状态有误, State: %d", approveItem.State)
+		return
+	}
+
+	// 校验审批记录和审批
+	recordOb := new(report_approve.ReportApproveRecord)
+	recordCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveRecordCols.ReportApproveId, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.State)
+	recordPars := make([]interface{}, 0)
+	recordPars = append(recordPars, approveItem.ReportApproveId, sysUser.AdminId, report_approve.ReportApproveStateApproving)
+	recordItem, e := recordOb.GetItemByCondition(recordCond, recordPars, "")
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "无权审批"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批记录失败, Err: " + e.Error()
+		return
+	}
+
+	// 驳回审批
+	if e = services.RefuseReportApprove(approveItem, recordItem, req.ApproveRemark, sysUser.AdminId); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "驳回审批失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Cancel
+// @Title 撤销审批
+// @Description 撤销审批
+// @Param	request	body report_approve.ReportApproveCancelReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /cancel [post]
+func (this *ReportApproveController) Cancel() {
+	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 report_approve.ReportApproveCancelReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.ReportApproveId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveId: %d", req.ReportApproveId)
+		return
+	}
+
+	approveOb := new(report_approve.ReportApprove)
+	approveItem, e := approveOb.GetItemById(req.ReportApproveId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批失败, Err: " + e.Error()
+		return
+	}
+	if approveItem.ApplyUserId != sysUser.AdminId {
+		br.Msg = "非申请人不可撤销"
+		return
+	}
+
+	// 撤销审批
+	e = services.CancelReportApprove(approveItem.ReportType, approveItem.ReportId, approveItem.ReportApproveId, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "撤销审批失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// MessageList
+// @Title 审批消息列表
+// @Description 审批消息列表
+// @Param   PageSize			query	int		true	"每页数据条数"
+// @Param   CurrentIndex		query	int		true	"当前页页码"
+// @Success 200 {object} report_approve.ReportApproveMessageListResp
+// @router /message/list [get]
+func (this *ReportApproveController) MessageList() {
+	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
+	}
+	params := new(report_approve.ReportApproveListReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "入参解析失败, Err: " + e.Error()
+		return
+	}
+
+	// 分页
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+
+	resp := new(report_approve.ReportApproveMessageListResp)
+	resp.List = make([]*report_approve.ReportApproveMessageItem, 0)
+	cond := fmt.Sprintf(` AND %s = ?`, report_approve.ReportApproveMessageCols.ReceiveUserId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, sysUser.AdminId)
+	order := fmt.Sprintf(`%s ASC, %s DESC`, report_approve.ReportApproveMessageCols.IsRead, report_approve.ReportApproveMessageCols.CreateTime)
+
+	messageOb := new(report_approve.ReportApproveMessage)
+	total, e := messageOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批消息列表总数失败, Err: " + e.Error()
+		return
+	}
+	list, e := messageOb.GetPageItemsByCondition(cond, pars, []string{}, order, startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批消息列表失败, Err: " + e.Error()
+		return
+	}
+	for _, v := range list {
+		t := report_approve.FormatReportApproveMessage2Item(v)
+		resp.List = append(resp.List, t)
+	}
+
+	// 未读消息数
+	cond += fmt.Sprintf(` AND %s = ?`, report_approve.ReportApproveMessageCols.IsRead)
+	pars = append(pars, 0)
+	unreadTotal, e := messageOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批消息列表总数失败, Err: " + e.Error()
+		return
+	}
+	resp.UnreadTotal = unreadTotal
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	resp.Paging = page
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// MessageRead
+// @Title 消息已读
+// @Description 消息已读
+// @Param	request	body report_approve.ReportApproveMessageReadReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /message/read [post]
+func (this *ReportApproveController) MessageRead() {
+	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 report_approve.ReportApproveMessageReadReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.MessageId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, MessageId: %d", req.MessageId)
+		return
+	}
+
+	messageOb := new(report_approve.ReportApproveMessage)
+	messageItem, e := messageOb.GetItemById(req.MessageId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "消息不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批消息失败, Err: " + e.Error()
+		return
+	}
+	messageItem.IsRead = 1
+	messageItem.ModifyTime = time.Now().Local()
+	cols := []string{"IsRead", "ModifyTime"}
+	if e = messageItem.Update(cols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新审批消息已读失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// CheckApproveOpen
+// @Title 校验分类是否开启审批
+// @Description 校验分类是否开启审批
+// @Param	request	body report_approve.ReportApproveCheckApproveOpenReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /classify/check_open [post]
+func (this *ReportApproveController) CheckApproveOpen() {
+	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 report_approve.ReportApproveCheckApproveOpenReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验是否开启了审批流
+	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
+		return
+	}
+
+	br.Data = opening
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 741 - 0
controllers/report_approve/report_approve_flow.go

@@ -0,0 +1,741 @@
+package report_approve
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"sync"
+	"time"
+)
+
+// ReportApproveFlowController 报告审批流
+type ReportApproveFlowController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 报告列表
+// @Description 报告列表
+// @Param   PageSize			query	int		true	"每页数据条数"
+// @Param   CurrentIndex		query	int		true	"当前页页码"
+// @Param   ReportType			query   int     false	"报告类型:1-中文研报;2-英文研报;3-智能研报"
+// @Param   ClassifyIdFirst		query	int		false	"一级分类ID"
+// @Param   ClassifyIdSecond	query	int		false	"二级分类ID"
+// @Param   Keyword				query	string	false	"搜索关键词"
+// @Param   SortRule			query	int		false	"排序方式: 1-正序; 2-倒序(默认)"
+// @Success 200 {object} report_approve.ReportApproveFlowListResp
+// @router /flow/list [get]
+func (this *ReportApproveFlowController) 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
+	}
+	params := new(report_approve.ReportApproveFlowListReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "入参解析失败, Err: " + e.Error()
+		return
+	}
+
+	var cond, orderRule string
+	var pars []interface{}
+	// 筛选项
+	{
+		keyword := strings.TrimSpace(params.Keyword)
+		if keyword != "" {
+			kw := fmt.Sprint("%", keyword, "%")
+			cond += fmt.Sprintf(` AND %s LIKE ?`, report_approve.ReportApproveFlowCols.FlowName)
+			pars = append(pars, kw)
+		}
+		if params.ReportType > 0 && params.ClassifySecondId > 0 {
+			cond += fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifySecondId)
+			pars = append(pars, params.ReportType, params.ClassifySecondId)
+		}
+		if params.SortRule > 0 {
+			orderMap := map[int]string{1: "ASC", 2: "DESC"}
+			orderRule = fmt.Sprintf("%s %s", report_approve.ReportApproveFlowCols.CreateTime, orderMap[params.SortRule])
+		}
+	}
+
+	resp := new(report_approve.ReportApproveFlowListResp)
+	flowOb := new(report_approve.ReportApproveFlow)
+	total, e := flowOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批流总数失败, Err:" + e.Error()
+		return
+	}
+	if total <= 0 {
+		page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+		resp.Paging = page
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
+		return
+	}
+
+	// 分页列表
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+
+	list, e := flowOb.GetPageItemsByCondition(cond, pars, []string{}, orderRule, startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批流分页列表失败, Err:" + e.Error()
+		return
+	}
+
+	// 指标分类
+	cnClassifyIdName, enClassifyIdName := make(map[int]string), make(map[int]string)
+	cnClassify, e := models.GetAllClassify()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取中文分类失败, Err: " + e.Error()
+		return
+	}
+	for _, v := range cnClassify {
+		cnClassifyIdName[v.Id] = v.ClassifyName
+	}
+	enClassify, e := models.GetEnglishClassifies()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文分类失败, Err: " + e.Error()
+		return
+	}
+	enRootIdMap := make(map[int]int) // 英文分类的一级分类ID
+	for _, v := range enClassify {
+		enClassifyIdName[v.Id] = v.ClassifyName
+		enRootIdMap[v.Id] = v.RootId
+	}
+
+	for _, v := range list {
+		t := report_approve.FormatReportApproveFlow2Item(v)
+		if v.ReportType == report_approve.FlowReportTypeEnglish {
+			t.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], enClassifyIdName[enRootIdMap[v.ClassifySecondId]], enClassifyIdName[v.ClassifyFirstId], enClassifyIdName[v.ClassifySecondId])
+		} else {
+			t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+		}
+		resp.List = append(resp.List, t)
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	resp.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增审批流
+// @Description 新增审批流
+// @Param	request	body report_approve.ReportApproveFlowAddReq true "type json string"
+// @Success 200 {object} report_approve.ReportApproveFlowDetailItem
+// @router /flow/add [post]
+func (this *ReportApproveFlowController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req report_approve.ReportApproveFlowAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	req.FlowName = strings.TrimSpace(req.FlowName)
+	if req.FlowName == "" {
+		br.Msg = "请输入审批流名称"
+		return
+	}
+	if len([]rune(req.FlowName)) > 20 {
+		br.Msg = "审批流名称最多输入20个字符"
+		return
+	}
+	reportTypes := []int{report_approve.FlowReportTypeChinese, report_approve.FlowReportTypeEnglish, report_approve.FlowReportTypeSmart}
+	if !utils.InArrayByInt(reportTypes, req.ReportType) {
+		br.Msg = "审批流报告类型有误"
+		br.ErrMsg = fmt.Sprintf("审批流报告类型有误, ReportType: %d", req.ReportType)
+		return
+	}
+	if req.ClassifyFirstId <= 0 || req.ClassifySecondId <= 0 {
+		br.Msg = "请选择报告分类"
+		return
+	}
+	if len(req.Nodes) <= 0 {
+		br.Msg = "请添加审批流程"
+		return
+	}
+	approveTypes := []int{report_approve.NodeApproveTypeRoll, report_approve.NodeApproveTypeAll, report_approve.NodeApproveTypeAny}
+	approveUserTypes := []string{report_approve.NodeUserTypeNormal, report_approve.NodeUserTypeRole}
+	for _, v := range req.Nodes {
+		if !utils.InArrayByInt(approveTypes, v.ApproveType) {
+			br.Msg = "审批流类型有误"
+			br.ErrMsg = fmt.Sprintf("审批流类型有误, ApproveType: %d", v.ApproveType)
+			return
+		}
+		for _, v2 := range v.Users {
+			if !utils.InArrayByStr(approveUserTypes, v2.UserType) {
+				br.Msg = "审批流用户类型有误"
+				br.ErrMsg = fmt.Sprintf("审批流用户类型有误, UserType: %d", v2.UserType)
+				return
+			}
+		}
+	}
+
+	// 审批流是否已存在
+	{
+		flowOb := new(report_approve.ReportApproveFlow)
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+		existPars := make([]interface{}, 0)
+		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+		exist, e := flowOb.GetItemByCondition(existCond, existPars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取审批流是否已存在失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "该分类已有审批流, 请勿重复添加"
+			return
+		}
+	}
+
+	flowItem := new(report_approve.ReportApproveFlow)
+	flowItem.FlowName = req.FlowName
+	flowItem.ReportType = req.ReportType
+	flowItem.ClassifyFirstId = req.ClassifyFirstId
+	flowItem.ClassifySecondId = req.ClassifySecondId
+	flowItem.CurrVersion = 1
+	flowItem.CreateTime = time.Now().Local()
+	flowItem.ModifyTime = time.Now().Local()
+
+	nodeItems := make([]*report_approve.ReportApproveNode, 0)
+	for _, v := range req.Nodes {
+		n := new(report_approve.ReportApproveNode)
+		n.ApproveType = v.ApproveType
+		n.CurrVersion = flowItem.CurrVersion
+		j, _ := json.Marshal(v.Users)
+		n.Users = string(j)
+		n.CreateTime = time.Now().Local()
+		nodeItems = append(nodeItems, n)
+	}
+
+	// 新增审批流和节点
+	if e := flowItem.CreateFlowAndNodes(flowItem, nodeItems); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "新增审批流和节点失败, Err: " + e.Error()
+		return
+	}
+
+	// 返回详情
+	detail, e := report_approve.FormatFlowAndNodesItem2Detail(flowItem, nodeItems)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批详情失败, Err: " + e.Error()
+		return
+	}
+
+	// 更新审批对应的报告状态:未发布->待提交
+	go func() {
+		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+	}()
+
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Edit
+// @Title 编辑审批流
+// @Description 编辑审批流
+// @Param	request	body report_approve.ReportApproveFlowEditReq true "type json string"
+// @Success 200 {object} report_approve.ReportApproveFlowDetailItem
+// @router /flow/edit [post]
+func (this *ReportApproveFlowController) 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 report_approve.ReportApproveFlowEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.ReportApproveFlowId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveFlowId: %d", req.ReportApproveFlowId)
+		return
+	}
+	req.FlowName = strings.TrimSpace(req.FlowName)
+	if req.FlowName == "" {
+		br.Msg = "请输入审批流名称"
+		return
+	}
+	if len([]rune(req.FlowName)) > 20 {
+		br.Msg = "审批流名称最多输入20个字符"
+		return
+	}
+	reportTypes := []int{report_approve.FlowReportTypeChinese, report_approve.FlowReportTypeEnglish, report_approve.FlowReportTypeSmart}
+	if !utils.InArrayByInt(reportTypes, req.ReportType) {
+		br.Msg = "审批流报告类型有误"
+		br.ErrMsg = fmt.Sprintf("审批流报告类型有误, ReportType: %d", req.ReportType)
+		return
+	}
+	if req.ClassifyFirstId <= 0 || req.ClassifySecondId <= 0 {
+		br.Msg = "请选择报告分类"
+		return
+	}
+	if len(req.Nodes) <= 0 {
+		br.Msg = "请添加审批流程"
+		return
+	}
+	approveTypes := []int{report_approve.NodeApproveTypeRoll, report_approve.NodeApproveTypeAll, report_approve.NodeApproveTypeAny}
+	approveUserTypes := []string{report_approve.NodeUserTypeNormal, report_approve.NodeUserTypeRole}
+	for _, v := range req.Nodes {
+		if !utils.InArrayByInt(approveTypes, v.ApproveType) {
+			br.Msg = "审批流类型有误"
+			br.ErrMsg = fmt.Sprintf("审批流类型有误, ApproveType: %d", v.ApproveType)
+			return
+		}
+		for _, v2 := range v.Users {
+			if !utils.InArrayByStr(approveUserTypes, v2.UserType) {
+				br.Msg = "审批流用户类型有误"
+				br.ErrMsg = fmt.Sprintf("审批流用户类型有误, UserType: %d", v2.UserType)
+				return
+			}
+		}
+	}
+
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowItem, e := flowOb.GetItemById(req.ReportApproveFlowId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批流已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批流信息失败, Err: " + e.Error()
+		return
+	}
+
+	// 审批流是否已存在
+	{
+		existCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s <> ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ReportApproveFlowId)
+		existPars := make([]interface{}, 0)
+		existPars = append(existPars, req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ReportApproveFlowId)
+		exist, e := flowOb.GetItemByCondition(existCond, existPars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取审批流是否已存在失败, Err: " + e.Error()
+			return
+		}
+		if exist != nil {
+			br.Msg = "该分类已有审批流, 请勿重复添加"
+			return
+		}
+	}
+
+	// 校验审批流是否关联了进行中的审批
+	{
+		approvingOb := new(report_approve.ReportApprove)
+		approvingCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveCols.FlowId, report_approve.ReportApproveCols.FlowVersion, report_approve.ReportApproveCols.State)
+		approvingPars := make([]interface{}, 0)
+		approvingPars = append(approvingPars, flowItem.ReportApproveFlowId, flowItem.CurrVersion, report_approve.ReportApproveStateApproving)
+		count, e := approvingOb.GetCountByCondition(approvingCond, approvingPars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "获取审批流关联进行中的审批数失败. Err: " + e.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "当前有未走完流程的报告,请走完流程后再做变更"
+			return
+		}
+	}
+
+	// 变更了报告分类时, 判断是否允许变更
+	if req.ReportType != flowItem.ReportType || req.ClassifyFirstId != flowItem.ClassifyFirstId || req.ClassifySecondId != flowItem.ClassifySecondId {
+		checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "校验审批流是否可变更失败, Err: " + e.Error()
+			return
+		}
+		if !checkOk {
+			br.Msg = "当前有未走完流程的报告, 请走完流程后再做变更!"
+			return
+		}
+	}
+
+	flowItem.FlowName = req.FlowName
+	flowItem.ReportType = req.ReportType
+	flowItem.ClassifyFirstId = req.ClassifyFirstId
+	flowItem.ClassifySecondId = req.ClassifySecondId
+	flowItem.CurrVersion += 1
+	flowItem.ModifyTime = time.Now().Local()
+
+	nodeItems := make([]*report_approve.ReportApproveNode, 0)
+	for _, v := range req.Nodes {
+		n := new(report_approve.ReportApproveNode)
+		n.ApproveType = v.ApproveType
+		n.CurrVersion = flowItem.CurrVersion
+		j, _ := json.Marshal(v.Users)
+		n.Users = string(j)
+		n.CreateTime = time.Now().Local()
+		nodeItems = append(nodeItems, n)
+	}
+
+	// 更新审批流和节点
+	if e := flowItem.UpdateFlowAndNodes(flowItem, nodeItems); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新审批流和节点失败, Err: " + e.Error()
+		return
+	}
+
+	// 返回详情
+	detail, e := report_approve.FormatFlowAndNodesItem2Detail(flowItem, nodeItems)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批详情失败, Err: " + e.Error()
+		return
+	}
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 审批流详情
+// @Description 审批流详情
+// @Param   ReportApproveFlowId  query  int  true  "审批流ID"
+// @Success 200 {object} report_approve.ReportApproveFlowDetailItem
+// @router /flow/detail [get]
+func (this *ReportApproveFlowController) 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
+	}
+	flowId, _ := this.GetInt("ReportApproveFlowId")
+	if flowId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveFlowId: %d", flowId)
+		return
+	}
+
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowItem, e := flowOb.GetItemById(flowId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批流已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批流信息失败, Err: " + e.Error()
+		return
+	}
+
+	// 审批节点
+	nodeOb := new(report_approve.ReportApproveNode)
+	nodeCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveNodeCols.ReportApproveFlowId, report_approve.ReportApproveNodeCols.CurrVersion)
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, flowItem.ReportApproveFlowId, flowItem.CurrVersion)
+	nodes, e := nodeOb.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批节点失败, Err: " + e.Error()
+		return
+	}
+
+	detail, e := report_approve.FormatFlowAndNodesItem2Detail(flowItem, nodes)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批详情失败, Err: " + e.Error()
+		return
+	}
+	br.Data = detail
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Remove
+// @Title 删除审批流
+// @Description 删除审批流
+// @Param	request	body report_approve.ReportApproveFlowRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /flow/remove [post]
+func (this *ReportApproveFlowController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req report_approve.ReportApproveFlowRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	if req.ReportApproveFlowId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportApproveFlowId: %d", req.ReportApproveFlowId)
+		return
+	}
+
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowItem, e := flowOb.GetItemById(req.ReportApproveFlowId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "审批流已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取审批流信息失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验是否允许删除
+	checkOk, e := services.CheckReportApproveFlowChange(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验审批流是否可变更失败, Err: " + e.Error()
+		return
+	}
+	if !checkOk {
+		br.Msg = "当前有未走完流程的报告, 请走完流程后再做删除!"
+		return
+	}
+
+	// 删除审批流, 保留审批节点, 历史审批回显会用得到
+	if e = flowItem.Del(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "删除审批流失败, Err: " + e.Error()
+		return
+	}
+
+	// 更新审批对应的报告状态:待提交->未发布
+	go func() {
+		_ = services.FlowOperateResetReportState(flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ReportClassifyTree
+// @Title 报告分类树
+// @Description 报告分类树
+// @Param   ReportApproveFlowId  query  int  false  "审批流ID"
+// @Success 200 {object} report_approve.ReportClassifyTreeItem
+// @router /report/classify_tree [get]
+func (this *ReportApproveFlowController) ReportClassifyTree() {
+	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
+	}
+	flowId, _ := this.GetInt("ReportApproveFlowId")
+
+	// 获取审批流信息, 用于查询分类是否可选
+	var flowKey string
+	if flowId > 0 {
+		flowOb := new(report_approve.ReportApproveFlow)
+		flowItem, e := flowOb.GetItemById(flowId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Msg = "审批流已被删除, 请刷新页面"
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取审批流信息失败, Err: " + e.Error()
+			return
+		}
+		flowKey = fmt.Sprintf("%d-%d-%d", flowItem.ReportType, flowItem.ClassifyFirstId, flowItem.ClassifySecondId)
+	}
+
+	// 获取审批流列表
+	flowOb := new(report_approve.ReportApproveFlow)
+	flows, e := flowOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取审批流列表失败, Err: " + e.Error()
+		return
+	}
+	hasFlowMap := make(map[string]bool)
+	for _, v := range flows {
+		k := fmt.Sprintf("%d-%d-%d", v.ReportType, v.ClassifyFirstId, v.ClassifySecondId)
+		if k == flowKey {
+			// 当前审批流对应的分类标记为可选状态
+			continue
+		}
+		hasFlowMap[k] = true
+	}
+
+	resp, cnTree, smartTree, enTree := make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0), make([]*report_approve.ReportClassifyTreeItem, 0)
+
+	var cnErr, enErr error
+	wg := sync.WaitGroup{}
+	wg.Add(2)
+
+	// 中文/智能研报分类
+	go func() {
+		defer func() {
+			wg.Done()
+		}()
+
+		classify, e := models.GetAllClassify()
+		if e != nil {
+			cnErr = fmt.Errorf("GetAllClassify err: %s", e.Error())
+			return
+		}
+		cnTree = services.GetReportClassifyTreeRecursive(classify, 0)
+		for _, v := range cnTree {
+			for _, v2 := range v.Children {
+				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeChinese, v.ClassifyId, v2.ClassifyId)
+				v2.HasFlow = hasFlowMap[k]
+			}
+		}
+
+		smartTree = services.GetReportClassifyTreeRecursive(classify, 0)
+		for _, v := range smartTree {
+			for _, v2 := range v.Children {
+				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeSmart, v.ClassifyId, v2.ClassifyId)
+				v2.HasFlow = hasFlowMap[k]
+			}
+		}
+	}()
+
+	// 英文研报分类
+	go func() {
+		defer func() {
+			wg.Done()
+		}()
+
+		classify, e := models.GetAllEnglishClassify()
+		if e != nil {
+			enErr = fmt.Errorf("GetAllEnglishClassify err: %s", e.Error())
+			return
+		}
+		enTree = services.GetReportClassifyTreeRecursive(classify, 0)
+		for _, v := range enTree {
+			for _, v2 := range v.Children {
+				k := fmt.Sprintf("%d-%d-%d", report_approve.FlowReportTypeEnglish, v.ClassifyId, v2.ClassifyId)
+				v2.HasFlow = hasFlowMap[k]
+			}
+		}
+	}()
+
+	wg.Wait()
+
+	if cnErr != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取中文分类失败, Err: " + cnErr.Error()
+		return
+	}
+	if enErr != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取英文分类失败, Err: " + enErr.Error()
+		return
+	}
+
+	resp = append(resp, &report_approve.ReportClassifyTreeItem{
+		ClassifyId:   report_approve.FlowReportTypeChinese,
+		ClassifyName: "研报列表",
+		Children:     cnTree,
+	}, &report_approve.ReportClassifyTreeItem{
+		ClassifyId:   report_approve.FlowReportTypeEnglish,
+		ClassifyName: "英文研报",
+		Children:     enTree,
+	}, &report_approve.ReportClassifyTreeItem{
+		ClassifyId:   report_approve.FlowReportTypeSmart,
+		ClassifyName: "智能研报",
+		Children:     smartTree,
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 257 - 25
controllers/smart_report/smart_report.go

@@ -4,11 +4,11 @@ import (
 	"encoding/json"
 	"eta/eta_api/controllers"
 	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/smart_report"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/services/data"
-	smartReportService "eta/eta_api/services/smart_report"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/kgiannakakis/mp3duration/src/mp3duration"
@@ -78,6 +78,14 @@ func (this *SmartReportController) Add() {
 	}
 	stageMax += 1
 
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
 	item := new(smart_report.SmartReport)
 	item.AddType = req.AddType
 	item.ClassifyIdFirst = req.ClassifyIdFirst
@@ -93,14 +101,14 @@ func (this *SmartReportController) Add() {
 	item.AdminRealName = sysUser.RealName
 	item.LastModifyAdminId = sysUser.AdminId
 	item.LastModifyAdminName = sysUser.RealName
-	item.State = smart_report.SmartReportStateWaitPublish
+	item.State = state
 	item.CreateTime = time.Now().Local()
 	item.ModifyTime = time.Now().Local()
 	item.ContentModifyTime = time.Now().Local()
 	item.HeadImg = req.HeadImg
 	item.EndImg = req.EndImg
 	item.CanvasColor = req.CanvasColor
-	
+
 	// 继承报告
 	if req.AddType == 2 {
 		ob := new(smart_report.SmartReport)
@@ -136,7 +144,7 @@ func (this *SmartReportController) Add() {
 	recordItem := &models.ReportStateRecord{
 		ReportId:   item.SmartReportId,
 		ReportType: 2,
-		State:      smart_report.SmartReportStateWaitPublish,
+		State:      state,
 		AdminId:    this.SysUser.AdminId,
 		AdminName:  this.SysUser.AdminName,
 		CreateTime: time.Now(),
@@ -219,7 +227,7 @@ func (this *SmartReportController) Edit() {
 		br.ErrMsg = "获取研报失败, Err: " + e.Error()
 		return
 	}
-	if item.State == 2 {
+	if item.State == models.ReportStatePublished || item.State == models.ReportStatePass {
 		br.Msg = "报告已发布, 请取消发布后编辑"
 		return
 	}
@@ -253,7 +261,6 @@ func (this *SmartReportController) Edit() {
 	item.EndImg = req.EndImg
 	item.CanvasColor = req.CanvasColor
 	if contentModify {
-		//fmt.Println(contentModify)
 		item.LastModifyAdminId = sysUser.AdminId
 		item.LastModifyAdminName = sysUser.RealName
 		item.ContentModifyTime = time.Now().Local()
@@ -328,7 +335,7 @@ func (this *SmartReportController) Remove() {
 
 	// ES更新报告为未发布
 	go func() {
-		_ = smartReportService.SmartReportElasticUpsert(item.SmartReportId, 1)
+		_ = services.SmartReportElasticUpsert(item.SmartReportId, 1)
 	}()
 
 	br.Ret = 200
@@ -422,6 +429,13 @@ func (this *SmartReportController) Publish() {
 		br.Msg = "参数有误"
 		return
 	}
+	operate := 0
+	if req.PublishState == smart_report.SmartReportStateWaitPublish {
+		operate = models.ReportOperateCancelPublish
+	}
+	if req.PublishState == smart_report.SmartReportStatePublished {
+		operate = models.ReportOperatePublish
+	}
 
 	ob := new(smart_report.SmartReport)
 	item, e := ob.GetItemById(req.SmartReportId)
@@ -444,10 +458,18 @@ func (this *SmartReportController) Publish() {
 		return
 	}
 
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond, operate)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
+		return
+	}
+
 	cols := []string{"State", "ModifyTime"}
-	item.State = req.PublishState
+	item.State = state
 	item.ModifyTime = time.Now().Local()
-	if req.PublishState == smart_report.SmartReportStatePublished {
+	if state == smart_report.SmartReportStatePublished {
 		cols = append(cols, "PublishTime")
 		item.PublishTime = time.Now().Local()
 
@@ -457,8 +479,9 @@ func (this *SmartReportController) Publish() {
 		queue.ReportCode = item.ReportCode
 		_ = utils.Rc.LPush(utils.CACHE_CREATE_REPORT_IMGPDF_QUEUE, queue)
 	}
+
 	// 取消发布时同时清除掉Img和PDF的文件地址
-	if req.PublishState == smart_report.SmartReportStateWaitPublish {
+	if state == smart_report.SmartReportStateWaitPublish {
 		cols = append(cols, "DetailImgUrl", "DetailPdfUrl")
 		item.DetailImgUrl = ""
 		item.DetailPdfUrl = ""
@@ -468,10 +491,11 @@ func (this *SmartReportController) Publish() {
 		br.ErrMsg = "更新研报失败, Err: " + e.Error()
 		return
 	}
+
 	recordItem := &models.ReportStateRecord{
 		ReportId:   req.SmartReportId,
 		ReportType: 2,
-		State:      req.PublishState,
+		State:      state,
 		AdminId:    this.SysUser.AdminId,
 		AdminName:  this.SysUser.AdminName,
 		CreateTime: time.Now(),
@@ -479,15 +503,24 @@ func (this *SmartReportController) Publish() {
 	go func() {
 		_, _ = models.AddReportStateRecord(recordItem)
 	}()
-	// 生成音频
-	if req.PublishState == smart_report.SmartReportStatePublished && item.VideoUrl == "" {
-		go smartReportService.SmartReportBuildVideoAndUpdate(item)
-	}
 
-	// ES更新报告
-	go func() {
-		_ = smartReportService.SmartReportElasticUpsert(item.SmartReportId, req.PublishState)
-	}()
+	if state == smart_report.SmartReportStatePublished {
+		// 生成音频
+		if item.VideoUrl == "" {
+			go services.SmartReportBuildVideoAndUpdate(item)
+		}
+
+		// ES更新报告
+		go func() {
+			_ = services.SmartReportElasticUpsert(item.SmartReportId, models.ReportStatePublished)
+		}()
+	}
+	if state == smart_report.SmartReportStateWaitPublish {
+		// ES更新报告
+		go func() {
+			_ = services.SmartReportElasticUpsert(item.SmartReportId, models.ReportStateUnpublished)
+		}()
+	}
 
 	br.Ret = 200
 	br.Success = true
@@ -573,6 +606,18 @@ func (this *SmartReportController) PrePublish() {
 		return
 	}
 
+	// 校验是否开启了审批流
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeSmart, item.ClassifyIdFirst, item.ClassifyIdSecond)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
+		return
+	}
+	if opening {
+		br.Msg = "报告已开启审批流, 不可设置定时发布"
+		return
+	}
+
 	item.PrePublishTime = preTime
 	item.PreMsgSend = req.PreMsgSend
 	cols := []string{"PrePublishTime", "PreMsgSend"}
@@ -733,7 +778,7 @@ func (this *SmartReportController) SaveContent() {
 	for _, ad := range admins {
 		adminIdName[ad.AdminId] = ad.RealName
 	}
-	editing, e := smartReportService.UpdateSmartReportEditing(req.SmartReportId, 1, sysUser.AdminId, sysUser.RealName, adminIdName)
+	editing, e := services.UpdateSmartReportEditing(req.SmartReportId, 1, sysUser.AdminId, sysUser.RealName, adminIdName)
 	if e != nil {
 		br.Msg = e.Error()
 		return
@@ -842,7 +887,7 @@ func (this *SmartReportController) MarkEditStatus() {
 		adminIdName[ad.AdminId] = ad.RealName
 	}
 
-	data, e := smartReportService.UpdateSmartReportEditing(req.SmartReportId, req.Status, sysUser.AdminId, sysUser.RealName, adminIdName)
+	data, e := services.UpdateSmartReportEditing(req.SmartReportId, req.Status, sysUser.AdminId, sysUser.RealName, adminIdName)
 	if e != nil {
 		br.Msg = e.Error()
 		return
@@ -859,7 +904,7 @@ func (this *SmartReportController) MarkEditStatus() {
 // @Description 报告列表
 // @Param   PageSize			query	int		true	"每页数据条数"
 // @Param   CurrentIndex		query	int		true	"当前页页码"
-// @Param   TimeType			query	string	false	"筛选的时间类别: publish_time-发布时间, modify_time-更新时间"
+// @Param   TimeType			query	string	false	"筛选的时间类别: publish_time-发布时间, modify_time-更新时间, approve_time-审批时间"
 // @Param   StartDate			query   string  false	"开始时间"
 // @Param   EndDate				query   string  false	"结束时间"
 // @Param   Frequency			query   string  false	"频度"
@@ -906,7 +951,7 @@ func (this *SmartReportController) List() {
 	if params.TimeType == "" {
 		params.TimeType = "publish_time"
 	}
-	if params.TimeType != "publish_time" && params.TimeType != "modify_time" {
+	if params.TimeType != "publish_time" && params.TimeType != "modify_time" && params.TimeType != "approve_time" {
 		br.Msg = "请选择正确的时间类型"
 		return
 	}
@@ -982,7 +1027,7 @@ func (this *SmartReportController) List() {
 		"smart_report_id", "report_code", "classify_id_first", "classify_name_first", "classify_id_second", "classify_name_second", "add_type",
 		"title", "abstract", "author", "frequency", "stage", "video_url", "video_name", "video_play_seconds", "video_size", "detail_img_url", "detail_pdf_url",
 		"admin_id", "admin_real_name", "state", "publish_time", "pre_publish_time", "pre_msg_send", "msg_is_send", "msg_send_time", "create_time", "modify_time",
-		"last_modify_admin_id", "last_modify_admin_name", "content_modify_time", "pv", "uv", "head_img", "end_img", "canvas_color",
+		"last_modify_admin_id", "last_modify_admin_name", "content_modify_time", "pv", "uv", "head_img", "end_img", "canvas_color", "approve_time",
 	}
 	list, e := reportOB.GetPageItemsByCondition(condition, pars, fields, "", startSize, params.PageSize)
 	if e != nil {
@@ -1005,7 +1050,7 @@ func (this *SmartReportController) List() {
 
 	for _, v := range list {
 		item := smart_report.FormatSmartReport2Item(v)
-		mark, e := smartReportService.UpdateSmartReportEditing(v.SmartReportId, 2, sysUser.AdminId, sysUser.RealName, adminIdName)
+		mark, e := services.UpdateSmartReportEditing(v.SmartReportId, 2, sysUser.AdminId, sysUser.RealName, adminIdName)
 		if e != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "查询编辑中标记失败, Err:" + e.Error()
@@ -1258,3 +1303,190 @@ func (this *SmartReportController) VoiceUpload() {
 	br.Data = resp
 	return
 }
+
+// SubmitApprove
+// @Title 提交审批
+// @Description 提交审批接口
+// @Param	request	body models.ReportSubmitApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/submit [post]
+func (this *SmartReportController) SubmitApprove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	var req models.ReportSubmitApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(smart_report.SmartReport)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待审批时, 仅更新状态
+	if state != models.ReportStateWaitApprove {
+		reportItem.State = state
+		e = reportItem.Update([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 提交审批
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeSmart, reportItem.SmartReportId, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "提交审批失败, Err: " + e.Error()
+		return
+	}
+	reportItem.ApproveId = approveId
+	reportItem.State = models.ReportStateWaitApprove
+	reportItem.ModifyTime = time.Now().Local()
+	e = reportItem.Update([]string{"ApproveId", "State", "ModifyTime"})
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// CancelApprove
+// @Title 撤销审批
+// @Description 撤销审批
+// @Param	request	body models.ReportCancelApproveReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /approve/cancel [post]
+func (this *SmartReportController) CancelApprove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	var req models.ReportCancelApproveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	reportId := req.ReportId
+	if reportId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, ReportId: %d", req.ReportId)
+		return
+	}
+
+	reportOb := new(smart_report.SmartReport)
+	reportItem, e := reportOb.GetItemById(reportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取报告失败, Err: " + e.Error()
+		return
+	}
+
+	// 校验当前审批配置, 返回下一个状态
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeSmart, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
+		return
+	}
+
+	// 下一个状态不为待提交时, 仅更新状态
+	if state != models.ReportStateWaitSubmit {
+		reportItem.State = state
+		e = reportItem.Update([]string{"State"})
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+	//if reportItem.ApproveId <= 0 {
+	//	br.Msg = "报告审批不存在"
+	//	br.ErrMsg = fmt.Sprintf("报告审批不存在, ApproveId: %d", reportItem.ApproveId)
+	//	return
+	//}
+
+	// 撤销审批
+	e = services.CancelReportApprove(report_approve.FlowReportTypeSmart, reportItem.SmartReportId, reportItem.ApproveId, sysUser.AdminId, sysUser.RealName)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "撤销审批失败, Err: " + e.Error()
+		return
+	}
+	//reportItem.ApproveId = 0
+	//reportItem.State = models.ReportStateWaitSubmit
+	//reportItem.ModifyTime = time.Now().Local()
+	//e = reportItem.UpdateReport([]string{"ApproveId", "State", "ModifyTime"})
+	//if e != nil {
+	//	br.Msg = "操作失败"
+	//	br.ErrMsg = "更新报告状态失败, Err: " + e.Error()
+	//	return
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 7 - 11
controllers/sys_admin.go

@@ -848,6 +848,9 @@ func (this *SysAdminController) EditEnabled() {
 		syncData.Source = utils.SOURCE_ETA_FLAG
 		syncData.AdminName = adminItem.AdminName
 		_ = utils.Rc.LPush(utils.CACHE_SYNC_ADMIN, syncData)
+
+		// 联动弘则研究公司下的联系人启禁用
+		go services.SwitchHzUserEnabledByMobile(req.Enabled, adminItem.Mobile)
 	}
 
 	//用户被禁用的情况下,需要将他对应的token给过期
@@ -855,11 +858,6 @@ func (this *SysAdminController) EditEnabled() {
 		logOutSystemUser(adminItem.AdminId)
 	}
 
-	// 联动弘则研究公司下的联系人启禁用
-	{
-		go services.SwitchHzUserEnabledByMobile(req.Enabled, adminItem.Mobile)
-	}
-
 	if err != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "操作失败,Err:" + err.Error()
@@ -912,16 +910,15 @@ func (this *SysAdminController) Delete() {
 		syncData.Source = utils.SOURCE_ETA_FLAG
 		syncData.AdminName = adminInfo.AdminName
 		_ = utils.Rc.LPush(utils.CACHE_SYNC_ADMIN, syncData)
+
+		// 删除弘则研究公司下的相同手机号联系人
+		go services.DeleteHzUserByMobile(mobile)
 	}
 
 	// 删除手工数据关联用户
 	{
 		go data_manage.DeleteManualUser(req.AdminId)
 	}
-	// 删除弘则研究公司下的相同手机号联系人
-	{
-		go services.DeleteHzUserByMobile(mobile)
-	}
 
 	// 清楚系统用户列表缓存key
 	utils.Rc.Delete(utils.CACHE_KEY_ADMIN)
@@ -1263,7 +1260,6 @@ func (this *SysAdminController) ResetPass() {
 	br.Msg = "重置密码成功"
 }
 
-
 // Add
 // @Title 用户详情信息
 // @Description 用户详情信息
@@ -1281,4 +1277,4 @@ func (this *SysAdminController) Detail() {
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"
-}
+}

+ 11 - 12
controllers/sys_role.go

@@ -3,7 +3,6 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_api/models"
-	"eta/eta_api/models/company"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
@@ -718,17 +717,17 @@ func (this *SysRoleController) SystemConfig() {
 	list = append(list, osc)
 
 	// 获取审批流设置
-	confKey := "approval_flow"
-	confTmp, e := company.GetConfigDetailByCode(confKey)
-	if e != nil {
-		br.Msg = "获取审批流配置失败"
-		br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
-		return
-	}
-	list = append(list, system.BusinessConf{
-		ConfKey: "ApprovalFlow",
-		ConfVal: confTmp.ConfigValue,
-	})
+	//confKey := "approval_flow"
+	//confTmp, e := company.GetConfigDetailByCode(confKey)
+	//if e != nil {
+	//	br.Msg = "获取审批流配置失败"
+	//	br.ErrMsg = "获取审批流配置失败, Err: " + e.Error()
+	//	return
+	//}
+	//list = append(list, system.BusinessConf{
+	//	ConfKey: "ApprovalFlow",
+	//	ConfVal: confTmp.ConfigValue,
+	//})
 
 	br.Data = list
 	br.Ret = 200

+ 5 - 0
models/aimod/ai.go

@@ -144,3 +144,8 @@ func EditTopic(topicId int, topicName string) (err error) {
 	_, err = o.Raw(sql, topicName, topicId).Exec()
 	return err
 }
+
+type HistoryChat struct {
+	Ask    string
+	Answer string
+}

+ 23 - 6
models/business_conf.go

@@ -9,12 +9,22 @@ import (
 )
 
 const (
-	BusinessConfUseXf          = "UseXf"
-	BusinessConfXfAppid        = "XfAppid"
-	BusinessConfXfApiKey       = "XfApiKey"
-	BusinessConfXfApiSecret    = "XfApiSecret"
-	BusinessConfXfVcn          = "XfVcn"
-	BusinessConfEnPptCoverImgs = "EnPptCoverImgs"
+	BusinessConfUseXf             = "UseXf"
+	BusinessConfXfAppid           = "XfAppid"
+	BusinessConfXfApiKey          = "XfApiKey"
+	BusinessConfXfApiSecret       = "XfApiSecret"
+	BusinessConfXfVcn             = "XfVcn"
+	BusinessConfEnPptCoverImgs    = "EnPptCoverImgs"
+	BusinessConfIsReportApprove   = "IsReportApprove"
+	BusinessConfReportApproveType = "ReportApproveType"
+	BusinessConfCompanyName      = "CompanyName"
+	BusinessConfCompanyWatermark = "CompanyWatermark"
+	BusinessConfWatermarkChart   = "WatermarkChart"
+)
+
+const (
+	BusinessConfReportApproveTypeEta   = "eta"
+	BusinessConfReportApproveTypeOther = "other"
 )
 
 // BusinessConf 商户配置表
@@ -165,3 +175,10 @@ func UpdateBusinessConfMulti(items []BusinessConfUpdate) (err error) {
 	}
 	return
 }
+
+func GetBusinessConfByKey(key string) (item *BusinessConf, err error) {
+	o := orm.NewOrm()
+	sql := fmt.Sprintf(`SELECT * FROM business_conf WHERE conf_key = ? LIMIT 1`)
+	err = o.Raw(sql, key).QueryRow(&item)
+	return
+}

+ 38 - 25
models/data_manage/base_from_trade_index.go

@@ -265,7 +265,7 @@ type BaseFromCoalmineFirmIndex struct {
 	ModifyTime                  string `description:"修改时间"`
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineFirmIndex(dataTime string) (items []*BaseFromCoalmineFirmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_firm_index WHERE data_time LIKE  `
@@ -290,7 +290,7 @@ type BaseFromCoalmineCoastalIndex struct {
 	ModifyTime                     string `description:"修改时间"`
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineCoastalIndex(startDate, endDate string) (items []*BaseFromCoalmineCoastalIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_coastal_index WHERE data_time between ? and ? `
@@ -313,7 +313,7 @@ type BaseFromCoalmineInlandIndex struct {
 	ModifyTime                    string `description:"修改时间"`
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineInlandIndex(startDate, endDate string) (items []*BaseFromCoalmineInlandIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_inland_index WHERE data_time between ? and ? `
@@ -321,7 +321,7 @@ func GetBaseFromCoalmineInlandIndex(startDate, endDate string) (items []*BaseFro
 	return
 }
 
-type BaseFromCoalmineClassify struct {
+type BaseFromCoalmineClassifyItem struct {
 	ClassifyId   int
 	ClassifyName string
 	Child        []CoalChild
@@ -381,7 +381,7 @@ func GetFrequencyFromCoal(suffix string) (list *string, err error) {
 	return
 }
 
-//查询数据
+// 查询数据
 func GetBaseFromCoalmineIndexByFrequency(frequency, groupName string) (items []*BaseFromCoalmineJsmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_jsm_index WHERE frequency=? AND province=?`
@@ -389,7 +389,7 @@ func GetBaseFromCoalmineIndexByFrequency(frequency, groupName string) (items []*
 	return
 }
 
-//查询数据
+// 查询数据
 func GetGroupNameFromCoalmineIndex(suffix string) (items []*string, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT DISTINCT group_name FROM base_from_coalmine_%s `
@@ -398,7 +398,7 @@ func GetGroupNameFromCoalmineIndex(suffix string) (items []*string, err error) {
 	return
 }
 
-//查询数据
+// 查询数据
 func GetProvinceFromCoalmineIndex(suffix string) (items []*string, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT DISTINCT province FROM base_from_coalmine_%s `
@@ -407,7 +407,7 @@ func GetProvinceFromCoalmineIndex(suffix string) (items []*string, err error) {
 	return
 }
 
-//查询数据
+// 查询数据
 func GetClassifyCoalmineIndexByGroupName(groupName string) (items []*string, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT DISTINCT index_code FROM base_from_coalmine_jsm_index WHERE province=? `
@@ -415,7 +415,7 @@ func GetClassifyCoalmineIndexByGroupName(groupName string) (items []*string, err
 	return
 }
 
-//查询数据
+// 查询数据
 func GetPageFromCoalmineIndexByFrequency(frequency, classify string, startSize, pageSize int) (items []*BaseFromCoalmineJsmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_jsm_index WHERE frequency=? AND index_code=? ORDER BY data_time DESC LIMIT ?,?  `
@@ -423,7 +423,7 @@ func GetPageFromCoalmineIndexByFrequency(frequency, classify string, startSize,
 	return
 }
 
-//查询数据
+// 查询数据
 func GetCountFromJsm(indexCode string) (item int, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT COUNT(1) FROM base_from_coalmine_jsm_index WHERE index_code=? `
@@ -445,7 +445,7 @@ func GetClassifyJsmByGroupName(groupName string) (items []*string, err error) {
 	return
 }
 
-//查询公司指标
+// 查询公司指标
 func GetPageFromCoalmineCompanyIndexByFrequency(frequency, classify string, startSize, pageSize int) (items []*BaseFromCoalmineCompanyIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_company_index WHERE frequency=? AND index_code=? ORDER BY data_time DESC LIMIT ?,? `
@@ -474,7 +474,7 @@ func GetClassifyFirmByGroupName(groupName string) (items []*string, err error) {
 	return
 }
 
-//查询指标
+// 查询指标
 func GetPageFromCoalmineFirmIndexByFrequency(frequency, classify string, startSize, pageSize int) (items []*BaseFromCoalmineFirmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_firm_index WHERE frequency=? AND index_code=? ORDER BY data_time DESC LIMIT ?,?  `
@@ -496,7 +496,7 @@ func GetClassifyCoastalByGroupName(groupName string) (items []*string, err error
 	return
 }
 
-//查询指标
+// 查询指标
 func GetPageFromCoalmineCoastalIndexByFrequency(frequency, classify string, startSize, pageSize int) (items []*BaseFromCoalmineCoastalIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_coastal_index WHERE frequency=? AND index_code=? ORDER BY data_time DESC LIMIT ?,? `
@@ -518,7 +518,7 @@ func GetClassifyInlandByGroupName(groupName string) (items []*string, err error)
 	return
 }
 
-//查询指标
+// 查询指标
 func GetPageFromCoalmineInlandIndexByFrequency(frequency, classify string, startSize, pageSize int) (items []*BaseFromCoalmineInlandIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_inland_index WHERE frequency=? AND index_code=? ORDER BY data_time DESC LIMIT ?,? `
@@ -526,7 +526,7 @@ func GetPageFromCoalmineInlandIndexByFrequency(frequency, classify string, start
 	return
 }
 
-//查询公司指标
+// 查询公司指标
 func GetBaseFromCoalmineCompanyIndexByFrequency(frequency, groupName string) (items []*BaseFromCoalmineCompanyIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_company_index WHERE frequency=? AND group_name=? `
@@ -534,7 +534,7 @@ func GetBaseFromCoalmineCompanyIndexByFrequency(frequency, groupName string) (it
 	return
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineFirmIndexByFrequency(frequency, groupName string) (items []*BaseFromCoalmineFirmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_firm_index WHERE frequency=? AND group_name=? `
@@ -542,7 +542,7 @@ func GetBaseFromCoalmineFirmIndexByFrequency(frequency, groupName string) (items
 	return
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineCoastalIndexByFrequency(frequency, groupName string) (items []*BaseFromCoalmineCoastalIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_coastal_index WHERE frequency=? AND group_name=? `
@@ -550,7 +550,7 @@ func GetBaseFromCoalmineCoastalIndexByFrequency(frequency, groupName string) (it
 	return
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineInlandIndexByFrequency(frequency, groupName string) (items []*BaseFromCoalmineInlandIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_inland_index WHERE frequency=? AND group_name=? `
@@ -558,7 +558,7 @@ func GetBaseFromCoalmineInlandIndexByFrequency(frequency, groupName string) (ite
 	return
 }
 
-//查询数据
+// 查询数据
 func GetBaseFromCoalmineIndexByCode(indexCode string) (items []*BaseFromCoalmineJsmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_jsm_index WHERE index_code=?`
@@ -566,7 +566,7 @@ func GetBaseFromCoalmineIndexByCode(indexCode string) (items []*BaseFromCoalmine
 	return
 }
 
-//查询公司指标
+// 查询公司指标
 func GetBaseFromCoalmineCompanyIndexByCode(indexCode string) (items []*BaseFromCoalmineCompanyIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_company_index WHERE index_code=? `
@@ -574,7 +574,7 @@ func GetBaseFromCoalmineCompanyIndexByCode(indexCode string) (items []*BaseFromC
 	return
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineFirmIndexByCode(indexCode string) (items []*BaseFromCoalmineFirmIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_firm_index WHERE index_code=? `
@@ -582,7 +582,7 @@ func GetBaseFromCoalmineFirmIndexByCode(indexCode string) (items []*BaseFromCoal
 	return
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineCoastalIndexByCode(indexCode string) (items []*BaseFromCoalmineCoastalIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_coastal_index WHERE index_code=? `
@@ -590,7 +590,7 @@ func GetBaseFromCoalmineCoastalIndexByCode(indexCode string) (items []*BaseFromC
 	return
 }
 
-//查询指标
+// 查询指标
 func GetBaseFromCoalmineInlandIndexByCode(indexCode string) (items []*BaseFromCoalmineInlandIndex, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT * FROM base_from_coalmine_inland_index WHERE index_code=? `
@@ -598,7 +598,6 @@ func GetBaseFromCoalmineInlandIndexByCode(indexCode string) (items []*BaseFromCo
 	return
 }
 
-
 type BaseFromTradeEicIndexV2 struct {
 	BaseFromEicIndexId     int `orm:"column(base_from_eic_index_id);pk"`
 	Type                   string
@@ -687,4 +686,18 @@ func GetFirstBaseFromTradeIndexByDate(exchange string) (item *BaseFromTradeShang
 	sql := "SELECT * FROM base_from_trade_" + exchange + "_index where rank < 50 order by data_time asc"
 	err = o.Raw(sql).QueryRow(&item)
 	return
-}
+}
+
+type BaseFromCoalmineClassify struct {
+	BaseFromCoalmineClassifyId int    `orm:"column(base_from_coalmine_classify_id);pk"`
+	ClassifyName               string // 分类名称
+	Suffix                     string // 表名后缀
+	CreateTime                 time.Time
+}
+
+func GetCoalmineClassifyList() (list []*BaseFromCoalmineClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM base_from_coalmine_classify"
+	_,err = o.Raw(sql).QueryRows(&list)
+	return
+}

+ 1 - 0
models/data_manage/chart_edb_mapping.go

@@ -29,6 +29,7 @@ type ChartEdbMapping struct {
 	PredictChartColor string    `description:"预测数据的颜色"`
 	ChartWidth        float64   `description:"线条大小"`
 	Source            int       `description:"1:ETA图库;2:商品价格曲线"`
+	EdbAliasName      string    `description:"中文别名"`
 }
 
 func AddChartEdbMapping(items []*ChartEdbMapping) (err error) {

+ 38 - 2
models/data_manage/chart_info.go

@@ -42,6 +42,11 @@ type ChartInfo struct {
 	ExtraConfig       string `description:"图表额外配置,json数据"`
 	SeasonExtraConfig string `description:"季节性图表中的配置,json数据"`
 	StartYear         int    `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	ChartThemeId      int    `description:"图表应用主题ID"`
+	SourcesFrom       string `description:"图表来源"`
+	Instructions      string `description:"图表说明"`
+	MarkersLines      string `description:"标识线"`
+	MarkersAreas      string `description:"标识区"`
 }
 
 type ChartInfoMore struct {
@@ -169,6 +174,7 @@ type ChartSaveItem struct {
 	PredictChartColor string  `description:"预测数据的颜色"`
 	ChartWidth        float64 `description:"线条大小"`
 	Source            int     `description:"1:ETA图库;2:商品价格曲线"`
+	EdbAliasName      string  `description:"中文别名"`
 }
 
 func DeleteChartInfoAndData(chartInfoId int) (err error) {
@@ -224,6 +230,11 @@ type EditChartInfoReq struct {
 	ExtraConfig          string                  `description:"图表额外配置信息,json字符串"`
 	SeasonExtraConfig    SeasonExtraItem         `description:"季节性图表中的配置,json数据"`
 	StartYear            int                     `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	ChartThemeId         int                     `description:"图表应用主题ID"`
+	SourcesFrom          string                  `description:"图表来源"`
+	Instructions         string                  `description:"图表说明"`
+	MarkersLines         string                  `description:"标识线"`
+	MarkersAreas         string                  `description:"标识区"`
 }
 
 type EditChartEnInfoReq struct {
@@ -685,6 +696,11 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 	pars = append(pars, req.ExtraConfig)
 	pars = append(pars, seasonExtra)
 	pars = append(pars, req.StartYear)
+	pars = append(pars, req.ChartThemeId)
+	pars = append(pars, req.SourcesFrom)
+	pars = append(pars, req.Instructions)
+	pars = append(pars, req.MarkersLines)
+	pars = append(pars, req.MarkersAreas)
 
 	sql := ` UPDATE  chart_info
 			SET
@@ -697,7 +713,12 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
               bar_config = ?,
               extra_config = ?, 
               season_extra_config = ?,
- 			  start_year = ?
+ 			  start_year = ?,
+ 			  chart_theme_id = ?,
+ 			  sources_from = ?,
+ 			  instructions = ?,
+ 			  markers_lines = ?,
+ 			  markers_areas = ?
 			`
 	if calendar != "" {
 		sql += `,calendar = ? `
@@ -765,7 +786,8 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 			tmpChartEdbMapping.ChartColor = v.ChartColor
 			tmpChartEdbMapping.PredictChartColor = v.PredictChartColor
 			tmpChartEdbMapping.ChartWidth = v.ChartWidth
-			_, err = to.Update(tmpChartEdbMapping, "ModifyTime", "MaxData", "MinData", "IsOrder", "IsAxis", "EdbInfoType", "LeadValue", "LeadUnit", "ChartStyle", "ChartColor", "PredictChartColor", "ChartWidth")
+			tmpChartEdbMapping.EdbAliasName = v.EdbAliasName
+			_, err = to.Update(tmpChartEdbMapping, "ModifyTime", "MaxData", "MinData", "IsOrder", "IsAxis", "EdbInfoType", "LeadValue", "LeadUnit", "ChartStyle", "ChartColor", "PredictChartColor", "ChartWidth", "EdbAliasName")
 			if err != nil {
 				fmt.Println("chart_edb_mapping Err:" + err.Error())
 				return err
@@ -790,6 +812,7 @@ func EditChartInfoAndMapping(req *EditChartInfoReq, edbInfoIdStr string, calenda
 			mapItem.PredictChartColor = v.PredictChartColor
 			mapItem.ChartWidth = v.ChartWidth
 			mapItem.Source = utils.CHART_SOURCE_DEFAULT
+			mapItem.EdbAliasName = v.EdbAliasName
 			tmpId, err := to.Insert(mapItem)
 			if err != nil {
 				fmt.Println("AddChartEdbMapping Err:" + err.Error())
@@ -1041,6 +1064,11 @@ type AddChartInfoReq struct {
 	ChartImage           string                  `description:"封面图" json:"-"`
 	SeasonExtraConfig    SeasonExtraItem         `description:"季节性图表中的配置,json数据"`
 	StartYear            int                     `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	ChartThemeId         int                     `description:"图表应用主题ID"`
+	SourcesFrom          string                  `description:"图表来源"`
+	Instructions         string                  `description:"图表说明"`
+	MarkersLines         string                  `description:"标识线"`
+	MarkersAreas         string                  `description:"标识区"`
 }
 
 type PreviewChartInfoReq struct {
@@ -1342,9 +1370,17 @@ type ChartInfoView struct {
 	Source            int    `description:"1:ETA图库;2:商品价格曲线;3:相关性图表"`
 	//CorrelationLeadUnit string `description:"相关性图表-领先单位"`
 	ExtraConfig       string          `description:"图表额外配置,json数据"`
+	ChartSource       string          `description:"图表来源str"`
+	ChartSourceEn     string          `description:"图表来源(英文)"`
 	Button            ChartViewButton `description:"操作按钮"`
 	SeasonExtraConfig string          `description:"季节性图表中的配置,json数据"`
 	StartYear         int             `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	ChartThemeId      int             `description:"图表应用主题ID"`
+	ChartThemeStyle   string          `description:"图表应用主题样式"`
+	SourcesFrom       string          `description:"图表来源"`
+	Instructions      string          `description:"图表说明"`
+	MarkersLines      string          `description:"标识线"`
+	MarkersAreas      string          `description:"标识区"`
 }
 
 type ChartViewButton struct {

+ 151 - 0
models/data_manage/chart_theme/chart_theme.go

@@ -0,0 +1,151 @@
+package chart_theme
+
+import (
+	"errors"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ChartTheme
+// @Description: 图表主题表
+type ChartTheme struct {
+	ChartThemeId     int       `description:"图表主题类型ID" orm:"column(chart_theme_id);pk"`
+	ChartThemeName   string    `description:"图表主题名称"`
+	ChartThemeTypeId int       `description:"图表主题类型ID"`
+	ChartImage       string    `description:"缩略图"`
+	Config           string    `description:"配置"`
+	IsDelete         int       `description:"是否删除,0:未删除;1:已删除"`
+	SysUserId        int       `description:"操作人"`
+	SysUserRealName  string    `description:"操作人的真实名称"`
+	IsSystemTheme    int       `description:"是否是系统主题,0:不是;1:是"`
+	ModifyTime       time.Time `description:"修改时间"`
+	CreateTime       time.Time `description:"创建时间"`
+}
+
+// GetChartThemeId
+// @Description: 根据id获取主题
+// @author: Roc
+// @datetime 2023-12-14 16:05:36
+// @param chartThemeId int
+// @return item *ChartTheme
+// @return err error
+func GetChartThemeId(chartThemeId int) (item *ChartTheme, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme where chart_theme_id = ? AND is_delete = 0`
+	err = o.Raw(sql, chartThemeId).QueryRow(&item)
+
+	return
+}
+
+// Add
+// @Description: 添加
+// @author: Roc
+// @receiver m
+// @datetime 2023-12-14 16:11:10
+// @param cols []string
+// @return err error
+func (m *ChartTheme) Add() (err error) {
+	if m.ChartThemeId > 0 {
+		err = errors.New("该配置已存在")
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	lastId, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ChartThemeId = int(lastId)
+
+	return
+}
+
+// Update
+// @Description: 更新
+// @author: Roc
+// @receiver m
+// @datetime 2023-12-14 16:11:10
+// @param cols []string
+// @return err error
+func (m *ChartTheme) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+// GetChartThemeListByTypeId
+// @Description: 根据图表类型获取关联的图表主题列表
+// @author: Roc
+// @datetime 2023-12-13 17:39:48
+// @return list []*ChartTheme
+// @return err error
+func GetChartThemeListByTypeId(chartThemeTypeId int) (list []*ChartTheme, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme WHERE chart_theme_type_id = ? AND  is_delete=0 ORDER BY chart_theme_id ASC `
+	_, err = o.Raw(sql, chartThemeTypeId).QueryRows(&list)
+
+	return
+}
+
+// GetAllChartThemeList
+// @Description: 获取所有图表主题列表
+// @author: Roc
+// @datetime 2023-12-13 17:39:48
+// @return list []*ChartTheme
+// @return err error
+func GetAllChartThemeList() (list []*ChartTheme, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme WHERE  is_delete=0 ORDER BY chart_theme_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// ChartThemeItem
+// @Description: 图表主题配置
+type ChartThemeItem struct {
+	ChartThemeId     int    `description:"图表主题类型ID" orm:"column(chart_theme_id);pk"`
+	ChartThemeName   string `description:"图表主题名称"`
+	ChartThemeTypeId int    `description:"图表主题类型ID"`
+	Config           string `description:"配置"`
+	ChartImage       string `description:"缩略图"`
+	//IsDelete            int       `description:"是否删除,0:未删除;1:已删除"`
+	IsSystemTheme       int       `description:"是否是系统主题,0:不是;1:是"`
+	SysUserId           int       `description:"操作人"`
+	SysUserRealName     string    `description:"操作人的真实名称"`
+	ModifyTime          time.Time `description:"修改时间"`
+	CreateTime          time.Time `description:"创建时间"`
+	DefaultChartThemeId int       `description:"默认使用的图表主题ID"`
+}
+
+// GetChartThemeItemList
+// @Description: 根据图表类型id获取配置列表
+// @author: Roc
+// @datetime 2023-12-14 14:26:35
+// @param chartThemeTypeId int
+// @return list []*ChartThemeConfig
+// @return err error
+func GetChartThemeItemList(chartThemeTypeId int) (list []*ChartThemeItem, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.*,b.default_chart_theme_id FROM chart_theme a 
+         JOIN chart_theme_type b on a.chart_theme_type_id =b.chart_theme_type_id 
+         WHERE a.chart_theme_type_id = ? AND a.is_delete=0 ORDER BY a.chart_theme_id ASC `
+	_, err = o.Raw(sql, chartThemeTypeId).QueryRows(&list)
+
+	return
+}
+
+// GetSystemChartTheme
+// @Description: 根据图表类型id获取系统配置
+// @author: Roc
+// @datetime 2023-12-14 14:26:35
+// @param chartThemeTypeId int
+// @return item *ChartTheme
+// @return err error
+func GetSystemChartTheme(chartThemeTypeId int) (item *ChartTheme, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT a.* FROM chart_theme a
+         WHERE a.chart_theme_type_id = ? AND a.is_system_theme=1 ORDER BY a.chart_theme_id ASC `
+	err = o.Raw(sql, chartThemeTypeId).QueryRow(&item)
+
+	return
+}

+ 92 - 0
models/data_manage/chart_theme/chart_theme_default_data.go

@@ -0,0 +1,92 @@
+package chart_theme
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ChartThemeDefaultData
+// @Description: 图表默认数据
+type ChartThemeDefaultData struct {
+	EdbDataId     int       `description:"指标数据ID" orm:"column(edb_data_id);pk"`
+	EdbInfoId     int       `description:"指标ID"`
+	EdbCode       string    `description:"指标编码"`
+	DataTime      string    //`json:"-" description:"数据日期"`
+	DataTimestamp int64     `description:"数据日期"`
+	Value         float64   `description:"数据值"`
+	ModifyTime    time.Time `description:"修改时间"`
+	CreateTime    time.Time `description:"创建时间"`
+}
+
+// GetChartThemeDefaultDataList
+// @Description: 获取指标的数据(日期正序返回)
+// @author: Roc
+// @datetime 2023-12-13 16:40:14
+// @param endInfoId int
+// @return list []*ChartThemeDefaultData
+// @return err error
+func GetChartThemeDefaultDataList(endInfoId int) (list []*ChartThemeDefaultData, err error) {
+	sql := `SELECT * FROM chart_theme_default_data WHERE edb_info_id=? `
+
+	sql += ` ORDER BY data_time ASC `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, endInfoId).QueryRows(&list)
+	return
+}
+
+// Update
+// @Description: 更新
+// @author: Roc
+// @receiver m
+// @datetime 2023-12-13 16:40:04
+// @param cols []string
+// @return err error
+func (m *ChartThemeDefaultData) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+
+	return
+}
+
+// GetChartThemeDefaultDataItemList
+// @Description: 获取指标的数据(日期正序返回)
+// @author: Roc
+// @datetime 2023-12-13 16:40:22
+// @param endInfoId int
+// @return list []*ChartThemeDefaultDataItem
+// @return err error
+func GetChartThemeDefaultDataItemList(endInfoId int, startDate string) (list []*data_manage.EdbDataList, err error) {
+	sql := `SELECT edb_data_id,edb_info_id,data_time,value,data_timestamp FROM chart_theme_default_data WHERE edb_info_id=? `
+	var pars []interface{}
+	if startDate != "" {
+		sql += ` AND data_time>=? `
+		pars = append(pars, startDate)
+	}
+
+	sql += ` ORDER BY data_time ASC `
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw(sql, endInfoId, pars).QueryRows(&list)
+	return
+}
+
+// GetChartEdbMappingListByEdbInfoId
+// @Description: 返回模拟的mapping假数据
+// @author: Roc
+// @datetime 2023-12-14 10:03:20
+// @param edbInfoIdList []int
+// @return list []*data_manage.ChartEdbInfoMapping
+// @return err error
+func GetChartEdbMappingListByEdbInfoId(edbInfoIdList []int) (list []*data_manage.ChartEdbInfoMapping, err error) {
+	num := len(edbInfoIdList)
+	if num <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_theme_default_edb_info WHERE edb_info_id IN(` + utils.GetOrmInReplace(num) + `)
+              `
+	_, err = o.Raw(sql, edbInfoIdList).QueryRows(&list)
+
+	return
+}

+ 90 - 0
models/data_manage/chart_theme/chart_theme_type.go

@@ -0,0 +1,90 @@
+package chart_theme
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// ChartThemeType
+// @Description: 图表主题类型表
+type ChartThemeType struct {
+	ChartThemeTypeId    int       `description:"图表主题类型ID" orm:"column(chart_theme_type_id);pk"`
+	ChartTypeName       string    `description:"类型名称"`
+	ChartType           int       `description:"图表类型"`
+	ChartSource         int       `description:"图表来源"`
+	DefaultChartThemeId int       `description:"默认使用的主题id"`
+	ModifyTime          time.Time `description:"修改时间"`
+	CreateTime          time.Time `description:"创建时间"`
+}
+
+// Update
+// @Description: 更新
+// @author: Roc
+// @receiver m
+// @datetime 2023-12-14 16:11:10
+// @param cols []string
+// @return err error
+func (m *ChartThemeType) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+// GetAllChartThemeTypeList
+// @Description: 获取所有图表主题类型列表
+// @author: Roc
+// @datetime 2023-12-13 17:31:03
+// @return list []*ChartThemeType
+// @return err error
+func GetAllChartThemeTypeList() (list []*ChartThemeType, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme_type ORDER BY chart_theme_type_id ASC `
+	_, err = o.Raw(sql).QueryRows(&list)
+
+	return
+}
+
+// GetChartThemeTypeListBySource
+// @Description: 根据来源获取图表主题类型列表
+// @author: Roc
+// @datetime 2023-12-13 17:31:03
+// @param source int
+// @return list []*ChartThemeType
+// @return err error
+func GetChartThemeTypeListBySource(source int) (list []*ChartThemeType, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme_type WHERE chart_source = ?  ORDER BY chart_theme_type_id ASC `
+	_, err = o.Raw(sql, source).QueryRows(&list)
+
+	return
+}
+
+// GetChartThemeTypeById
+// @Description: 通过类型id获取类型
+// @author: Roc
+// @datetime 2023-12-14 09:53:58
+// @param chartThemeTypeId int
+// @return item *ChartThemeType
+// @return err error
+func GetChartThemeTypeById(chartThemeTypeId int) (item *ChartThemeType, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme_type where chart_theme_type_id = ? `
+	err = o.Raw(sql, chartThemeTypeId).QueryRow(&item)
+
+	return
+}
+
+// GetChartThemeTypeByChartTypeAndSource
+// @Description: 通过图表类型和来源获取类型
+// @author: Roc
+// @datetime 2023-12-14 09:53:58
+// @param chartThemeTypeId int
+// @return item *ChartThemeType
+// @return err error
+func GetChartThemeTypeByChartTypeAndSource(chartType, source int) (item *ChartThemeType, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM chart_theme_type where chart_type = ? AND chart_source = ? `
+	err = o.Raw(sql, chartType, source).QueryRow(&item)
+
+	return
+}

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

@@ -0,0 +1,30 @@
+package request
+
+// AddThemeReq
+// @Description: 新增主题请求参数
+type AddThemeReq struct {
+	ChartThemeName   string `description:"主题名称"`
+	ChartThemeTypeId int    `description:"图表类型id"`
+}
+
+// EditThemeReq
+// @Description: 编辑主题请求参数
+type EditThemeReq struct {
+	ChartThemeId   int    `description:"主题id"`
+	ChartThemeName string `description:"主题名称"`
+	Config         string `description:"配置的值"`
+	ChartImage     string `description:"缩略图"`
+}
+
+// DeleteThemeReq
+// @Description: 删除主题请求参数
+type DeleteThemeReq struct {
+	ChartThemeId int `description:"配置id"`
+}
+
+// SetDefaultThemeReq
+// @Description: 配置默认主题请求参数
+type SetDefaultThemeReq struct {
+	ChartThemeId     int `description:"主题id"`
+	ChartThemeTypeId int `description:"主题类型id"`
+}

+ 3 - 1
models/data_manage/cross_variety/chart_tag_variety.go

@@ -232,7 +232,9 @@ func SaveVarietyEdb(chartTagId int, list []request.VarietyEdbReq, sysUserId int,
 func GetCountByEdbInfoId(edbInfoId int) (total int, err error) {
 	o := orm.NewOrmUsingDB("data")
 	sql := `SELECT count(1) total FROM chart_tag_variety a 
-         join edb_info b on a.edb_info_id=b.edb_info_id WHERE a.edb_info_id = ?`
+            INNER JOIN edb_info b ON a.edb_info_id=b.edb_info_id 
+            INNER JOIN chart_tag AS c ON a.chart_tag_id=c.chart_tag_id
+            WHERE a.edb_info_id = ?`
 	err = o.Raw(sql, edbInfoId).QueryRow(&total)
 
 	return

+ 7 - 0
models/data_manage/cross_variety/request/chart.go

@@ -31,6 +31,13 @@ type AddChartReq struct {
 	CalculateUnit  string            `description:"计算频度"`
 	DateConfigList []ChartConfigDate `description:"日期配置列表"`
 	VarietyList    []int
+
+	// 主题相关
+	ChartThemeId int    `description:"图表应用主题ID"`
+	SourcesFrom  string `description:"图表来源"`
+	Instructions string `description:"图表说明"`
+	MarkersLines string `description:"标识线"`
+	MarkersAreas string `description:"标识区"`
 }
 
 // EditChartReq

+ 6 - 0
models/data_manage/line_equation/request/line_equation.go

@@ -23,6 +23,12 @@ type AddChart struct {
 	LeftMin         string `description:"图表左侧最小值"`
 	LeftMax         string `description:"图表左侧最大值"`
 	ChartImage      string `description:"图表截图,复制的时候才用到" json:"-"`
+
+	ChartThemeId int    `description:"图表应用主题ID"`
+	SourcesFrom  string `description:"图表来源"`
+	Instructions string `description:"图表说明"`
+	MarkersLines string `description:"标识线"`
+	MarkersAreas string `description:"标识区"`
 }
 
 // EditChartEnInfoReq 编辑图表英文信息

+ 2 - 0
models/data_stat/edb_info_update_log.go

@@ -37,6 +37,7 @@ type EdbInfoUpdateLog struct {
 	DataUpdateFailedReason string    `description:"数据未正常更新原因"`
 	DataUpdateTime         string    `description:"数据更新时间"`
 	IsSourceRefresh        int       `description:"是否为终端刷新到数据源的刷新操作:0否,1是"`
+	UpdateType             int       `description:"变更类型,0:数据明细变更,1:基础信息变更, 2:新增指标"`
 }
 
 type EdbInfoUpdateLogItem struct {
@@ -62,6 +63,7 @@ type EdbInfoUpdateLogItem struct {
 	TerminalCode          string  `description:"终端编码,用于配置在机器上"`
 	DataUpdateTime        string  `description:"最近一次数据发生变化的时间"`
 	ErDataUpdateDate      string  `description:"本次更新,数据发生变化的最早日期"`
+	UpdateType            int     `description:"变更类型,0:数据明细变更,1:基础信息变更, 2:新增指标"`
 }
 
 func AddEdbUpdateLog(item *EdbInfoUpdateLog) (lastId int64, err error) {

+ 6 - 1
models/data_stat/edb_info_update_stat.go

@@ -70,8 +70,13 @@ type EdbInfoUpdateStatItem struct {
 	HasRefresh             int8    `description:"今日是否已刷新,1是,0否"`
 }
 
+type EdbInfoUpdateStatItemMore struct {
+	EdbInfoUpdateStatItem
+	InitSourceName string `description:"初始数据源"`
+}
+
 type GetEdbUpdateStatResp struct {
-	List   []*EdbInfoUpdateStatItem
+	List   []*EdbInfoUpdateStatItemMore
 	Paging *paging.PagingItem
 }
 

+ 30 - 2
models/db.go

@@ -4,6 +4,7 @@ import (
 	"eta/eta_api/models/aimod"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/chart_theme"
 	"eta/eta_api/models/data_manage/cross_variety"
 	"eta/eta_api/models/data_manage/excel"
 	future_good2 "eta/eta_api/models/data_manage/future_good"
@@ -11,6 +12,7 @@ import (
 	"eta/eta_api/models/data_stat"
 	"eta/eta_api/models/eta_trial"
 	"eta/eta_api/models/ppt_english"
+	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/semantic_analysis"
 	"eta/eta_api/models/smart_report"
@@ -174,6 +176,12 @@ func init() {
 	//初始化AI
 	initAi()
 
+	// 报告审批
+	initReportApprove()
+
+	// 初始化图表主题
+	initChartTheme()
+
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
 	data_manage.InitEdbSourceVar()
 }
@@ -483,8 +491,8 @@ func initExcel() {
 // initSmartReport 智能研报相关表
 func initSmartReport() {
 	orm.RegisterModel(
-		new(smart_report.SmartReport),        // 智能研报主表
-		new(smart_report.SmartReportSaveLog), // 智能研报-保存记录表
+		new(smart_report.SmartReport),         // 智能研报主表
+		new(smart_report.SmartReportSaveLog),  // 智能研报-保存记录表
 		new(smart_report.SmartReportResource), // 智能研报-资源表
 	)
 }
@@ -506,3 +514,23 @@ func initAi() {
 		new(aimod.AiChat),
 	)
 }
+
+// initReportApprove 报告审批相关表
+func initReportApprove() {
+	orm.RegisterModel(
+		new(report_approve.ReportApprove),        // 审批表
+		new(report_approve.ReportApproveFlow),    // 审批流表
+		new(report_approve.ReportApproveNode),    // 审批节点表
+		new(report_approve.ReportApproveRecord),  // 审批记录表
+		new(report_approve.ReportApproveMessage), // 审批消息表
+	)
+}
+
+// initChartTheme 初始化图表主题
+func initChartTheme() {
+	orm.RegisterModel(
+		new(chart_theme.ChartTheme),            // 图表主题
+		new(chart_theme.ChartThemeType),        // 图表主题类型
+		new(chart_theme.ChartThemeDefaultData), //默认数据
+	)
+}

+ 76 - 8
models/english_report.go

@@ -23,7 +23,7 @@ type EnglishReport struct {
 	Frequency          string    `description:"频度"`
 	CreateTime         string    `description:"创建时间"`
 	ModifyTime         time.Time `description:"修改时间"`
-	State              int       `description:"1:未发布,2:已发布"`
+	State              int       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	PublishTime        time.Time `description:"发布时间"`
 	PrePublishTime     time.Time `description:"预发布时间"`
 	Stage              int       `description:"期数"`
@@ -42,6 +42,8 @@ type EnglishReport struct {
 	FromReportId       int       `description:"继承的报告ID(英文策略报告ID)"`
 	AdminId            int       `description:"创建者账号"`
 	AdminRealName      string    `description:"创建者姓名"`
+	ApproveTime        time.Time `description:"审批时间"`
+	ApproveId          int       `description:"审批ID"`
 }
 
 func GetEnglishReportStage(classifyIdFirst, classifyIdSecond int) (count int, err error) {
@@ -240,7 +242,7 @@ type EnglishReportList struct {
 	Frequency          string    `description:"频度"`
 	CreateTime         string    `description:"创建时间"`
 	ModifyTime         time.Time `description:"修改时间"`
-	State              int       `description:"1:未发布,2:已发布"`
+	State              int       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	PublishTime        string    `description:"发布时间"`
 	PrePublishTime     string    `description:"预发布时间"`
 	Stage              int       `description:"期数"`
@@ -264,6 +266,7 @@ type EnglishReportList struct {
 	FullClassifyName   string    `description:"顶级分类名/父级分类名/当前分类名"`
 	ClassifyIdRoot     int       `description:"顶级分类id"`
 	ClassifyNameRoot   string    `description:"顶级分类名称"`
+	ApproveTime        string    `description:"审批时间"`
 }
 
 type EnglishReportListResp struct {
@@ -308,7 +311,8 @@ func GetEnglishReportList(condition string, pars []interface{}, companyType stri
 	if condition != "" {
 		sql += condition
 	}
-	sql += `ORDER BY state ASC, modify_time DESC LIMIT ?,?`
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
 	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
 	return
 }
@@ -335,7 +339,7 @@ func GetEnglishReportCountByCondition(condition string, pars []interface{}) (cou
 	return
 }
 
-// 发布报告
+// PublishEnglishReportById 发布报告
 func PublishEnglishReportById(reportId int, publishTime string) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `UPDATE english_report SET state=2,publish_time=?,pre_publish_time=null,modify_time=NOW() WHERE id = ? `
@@ -343,11 +347,19 @@ func PublishEnglishReportById(reportId int, publishTime string) (err error) {
 	return
 }
 
-// 取消发布报告
-func PublishCancelEnglishReport(reportIds int) (err error) {
+// ResetEnglishReportById 重置报告状态
+func ResetEnglishReportById(reportId, state int) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
-	sql := ` UPDATE english_report SET state=1,pre_publish_time=null WHERE id =?  `
-	_, err = o.Raw(sql, reportIds).Exec()
+	sql := `UPDATE english_report SET state = ?, pre_publish_time = null, modify_time = NOW() WHERE id = ?`
+	_, err = o.Raw(sql, state, reportId).Exec()
+	return
+}
+
+// PublishCancelEnglishReport 取消发布报告
+func PublishCancelEnglishReport(reportIds, state int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` UPDATE english_report SET state=?, pre_publish_time=null WHERE id =?  `
+	_, err = o.Raw(sql, state, reportIds).Exec()
 	return
 }
 
@@ -827,3 +839,59 @@ where a.id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
 	_, err = o.Raw(sql, classifyIds).QueryRows(&list)
 	return
 }
+
+func (m *EnglishReport) GetItemById(id int) (item *EnglishReport, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM english_report WHERE id = ? LIMIT 1`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+// GetEnglishClassifies 获取所有英文分类
+func GetEnglishClassifies() (list []*EnglishClassify, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM english_classify `
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+// GetEnglishReportStateCount 获取指定状态的报告数量
+func GetEnglishReportStateCount(state int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM english_report WHERE state = ?`
+	err = o.Raw(sql, state).QueryRow(&count)
+	return
+}
+
+// UpdateEnglishReportsStateByCond 批量更新报告状态
+func UpdateEnglishReportsStateByCond(classifyFirstId, classifySecondId, oldState, newState int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	cond := ``
+	if classifyFirstId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_first = %d`, classifyFirstId)
+	}
+	if classifySecondId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_second = %d`, classifySecondId)
+	}
+	sql := fmt.Sprintf(`UPDATE english_report SET state = ?, pre_publish_time = NULL WHERE state = ? %s`, cond)
+	_, err = o.Raw(sql, newState, oldState).Exec()
+	return
+}
+
+// UpdateEnglishReportsStateBySecondIds 批量更新二级分类报告状态
+func UpdateEnglishReportsStateBySecondIds(oldState, newState int, secondIds []int) (err error) {
+	if len(secondIds) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	// (有审批流的)未发布->待提交
+	sql := fmt.Sprintf(`UPDATE english_report SET state = ?, pre_publish_time = NULL WHERE state = ? AND classify_id_second IN (%s)`, utils.GetOrmInReplace(len(secondIds)))
+	_, err = o.Raw(sql, newState, oldState, secondIds).Exec()
+	if err != nil {
+		return
+	}
+	// (无审批流的)待提交->未发布
+	sql = fmt.Sprintf(`UPDATE english_report SET state = ?, pre_publish_time = NULL WHERE state = ? AND classify_id_second NOT IN (%s)`, utils.GetOrmInReplace(len(secondIds)))
+	_, err = o.Raw(sql, oldState, newState, secondIds).Exec()
+	return
+}

+ 6 - 2
models/ppt_english/ppt_english.go

@@ -24,6 +24,7 @@ type PptEnglish struct {
 	ReportCode    string    `description:"关联的报告code"`
 	IsShare       int8      `description:"是否分享,0:不分享,1:分享"`
 	PublishTime   time.Time `description:"发布时间"`
+	CoverContent  string    `description:"PPT内容-JSON"`
 }
 
 type PptEnglishItem struct {
@@ -44,6 +45,7 @@ type PptEnglishItem struct {
 	ReportCode    string    `description:"关联的报告code"`
 	IsShare       int8      `description:"是否分享,0:不分享,1:分享"`
 	PublishTime   time.Time `description:"发布时间"`
+	CoverContent  string    `description:"PPT内容-JSON"`
 }
 
 func GetPptEnglishList(condition string, pars []interface{}, startSize, pageSize int) (items []*PptEnglishItem, err error) {
@@ -112,8 +114,9 @@ type AddPptEnglishReq struct {
 		BackIndex    int    `description:"背景图片下标"`
 		TemplateType int    `description:"模版id"`
 	} `description:"首页"`
-	Content string `description:"ppt的json数据"`
-	GroupId int64  `description:"目录id"`
+	Content      string `description:"ppt的json数据"`
+	GroupId      int64  `description:"目录id"`
+	CoverContent string `description:"PPT内容-JSON"`
 }
 
 type AddPptEnglishResp struct {
@@ -249,6 +252,7 @@ type PptEnglishSaveLog struct {
 	AdminId       int       `description:"系统用户id"`
 	AdminRealName string    `description:"系统用户名称"`
 	CreateTime    time.Time `description:"创建时间"`
+	CoverContent  string    `description:"PPT内容-JSON"`
 }
 
 // AddPptEnglishSaveLog 新增PPT日志

+ 6 - 2
models/ppt_v2.go

@@ -27,6 +27,7 @@ type PptV2 struct {
 	ReportCode    string    `description:"关联的报告code"`
 	IsShare       int8      `description:"是否分享,0:不分享,1:分享"`
 	PublishTime   time.Time `description:"发布时间"`
+	CoverContent  string    `description:"PPT内容-JSON"`
 }
 
 type PptV2Item struct {
@@ -49,6 +50,7 @@ type PptV2Item struct {
 	ReportCode    string    `description:"关联的报告code"`
 	IsShare       int8      `description:"是否分享,0:不分享,1:分享"`
 	PublishTime   time.Time `description:"发布时间"`
+	CoverContent  string    `description:"PPT内容-JSON"`
 }
 
 func GetPptV2List(condition string, pars []interface{}, startSize, pageSize int) (items []*PptV2Item, err error) {
@@ -117,8 +119,9 @@ type AddPptV2Req struct {
 		BackIndex    int    `description:"背景图片下标"`
 		TemplateType int    `description:"模版id"`
 	} `description:"首页"`
-	Content string `description:"ppt的json数据"`
-	GroupId int64  `description:"目录id"`
+	Content      string `description:"ppt的json数据"`
+	GroupId      int64  `description:"目录id"`
+	CoverContent string `description:"封面图内容-JSON数据"`
 }
 
 type AddPptResp struct {
@@ -200,6 +203,7 @@ type PptV2SaveLog struct {
 	AdminId       int       `description:"系统用户id"`
 	AdminRealName string    `description:"系统用户名称"`
 	CreateTime    time.Time `description:"创建时间"`
+	CoverContent  string    `description:"PPT内容-JSON"`
 }
 
 // AddPptV2SaveLog 新增PPT日志

+ 101 - 10
models/report.go

@@ -2,12 +2,33 @@ package models
 
 import (
 	"eta/eta_api/utils"
+	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"strings"
 	"time"
 )
 
+// 报告状态
+const (
+	ReportStateUnpublished = 1 // 未发布
+	ReportStatePublished   = 2 // 已发布
+	ReportStateWaitSubmit  = 3 // 待提交
+	ReportStateWaitApprove = 4 // 审批中
+	ReportStateRefused     = 5 // 已驳回
+	ReportStatePass        = 6 // 已通过
+)
+
+// 报告操作
+const (
+	ReportOperateAdd           = 1 // 新增报告
+	ReportOperateEdit          = 2 // 编辑报告
+	ReportOperatePublish       = 3 // 发布报告
+	ReportOperateCancelPublish = 4 // 取消发布报告
+	ReportOperateSubmitApprove = 5 // 提交审批
+	ReportOperateCancelApprove = 6 // 撤回审批
+)
+
 type Report struct {
 	Id                 int       `orm:"column(id)" description:"报告Id"`
 	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
@@ -21,7 +42,7 @@ type Report struct {
 	Frequency          string    `description:"频度"`
 	CreateTime         string    `description:"创建时间"`
 	ModifyTime         time.Time `description:"修改时间"`
-	State              int       `description:"1:未发布,2:已发布"`
+	State              int       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	PublishTime        time.Time `description:"发布时间"`
 	Stage              int       `description:"期数"`
 	MsgIsSend          int       `description:"消息是否已发送,0:否,1:是"`
@@ -40,6 +61,8 @@ type Report struct {
 	MsgSendTime        time.Time `description:"模版消息发送时间"`
 	AdminId            int       `description:"创建者账号"`
 	AdminRealName      string    `description:"创建者姓名"`
+	ApproveTime        time.Time `description:"审批时间"`
+	ApproveId          int       `description:"审批ID"`
 }
 
 type ReportList struct {
@@ -55,7 +78,7 @@ type ReportList struct {
 	Frequency          string                    `description:"频度"`
 	CreateTime         string                    `description:"创建时间"`
 	ModifyTime         time.Time                 `description:"修改时间"`
-	State              int                       `description:"1:未发布,2:已发布 3:已驳回 4:已审批"`
+	State              int                       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	PublishTime        string                    `description:"发布时间"`
 	PrePublishTime     string                    `description:"预发布时间"`
 	Stage              int                       `description:"期数"`
@@ -80,6 +103,7 @@ type ReportList struct {
 	Editor             string                    `description:"编辑人"`
 	AdminId            int                       `description:"创建者账号"`
 	AdminRealName      string                    `description:"创建者姓名"`
+	ApproveTime        string                    `description:"审批时间"`
 }
 
 type ReportListResp struct {
@@ -122,7 +146,8 @@ func GetReportList(condition string, pars []interface{}, companyType string, sta
 	if condition != "" {
 		sql += condition
 	}
-	sql += `ORDER BY FIELD(state,3,1,2,4), modify_time DESC LIMIT ?,?`
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	sql += `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC LIMIT ?,?`
 	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
 	return
 }
@@ -138,16 +163,16 @@ func PublishReport(reportIds []int) (err error) {
 	return
 }
 
-// PublishCancleReport 取消发布报告
-func PublishCancleReport(reportIds int, publishTimeNullFlag bool) (err error) {
+// PublishCancelReport 取消发布报告
+func PublishCancelReport(reportId, state int, publishTimeNullFlag bool) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	var sql string
 	if publishTimeNullFlag {
-		sql = ` UPDATE report SET state=1, publish_time=null, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
+		sql = ` UPDATE report SET state=?, publish_time=null, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
 	} else {
-		sql = ` UPDATE report SET state=1, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
+		sql = ` UPDATE report SET state=?, pre_publish_time=null, pre_msg_send=0 WHERE id =?`
 	}
-	_, err = o.Raw(sql, reportIds).Exec()
+	_, err = o.Raw(sql, state, reportId).Exec()
 	return
 }
 
@@ -674,7 +699,7 @@ SELECT DISTINCT report_id FROM report_chapter WHERE publish_state = 2 AND (video
 	return
 }
 
-// 发布报告
+// PublishReportById 发布报告
 func PublishReportById(reportId int, publishTime time.Time) (err error) {
 	o := orm.NewOrmUsingDB("rddp")
 	sql := `UPDATE report SET state = 2, publish_time = ?, pre_publish_time=null, pre_msg_send=0, modify_time = NOW() WHERE id = ? `
@@ -682,6 +707,14 @@ func PublishReportById(reportId int, publishTime time.Time) (err error) {
 	return
 }
 
+// ResetReportById 重置报告状态
+func ResetReportById(reportId, state int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `UPDATE report SET state = ?, pre_publish_time = null, pre_msg_send = 0, modify_time = NOW() WHERE id = ?`
+	_, err = o.Raw(sql, state, reportId).Exec()
+	return
+}
+
 // GetCommentReportByReportId 查询有留言的报告列表
 func GetCommentReportByReportId(condition string, pars []interface{}, startSize, pageSize int) (list []*Report, err error) {
 	o := orm.NewOrmUsingDB("rddp")
@@ -1049,4 +1082,62 @@ func SetPrePublishReportById(reportId int, prePublishTime string, preMsgSend int
 	sql := `UPDATE report SET pre_publish_time=?, pre_msg_send=? WHERE id = ? and state = 1 `
 	_, err = o.Raw(sql, prePublishTime, preMsgSend, reportId).Exec()
 	return
-}
+}
+
+// ReportSubmitApproveReq 提交审批请求体
+type ReportSubmitApproveReq struct {
+	ReportId int `description:"报告ID"`
+}
+
+// ReportCancelApproveReq 撤回审批请求体
+type ReportCancelApproveReq struct {
+	ReportId int `description:"报告ID"`
+}
+
+func (m *Report) GetItemById(id int) (item *Report, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id = ? LIMIT 1`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+// GetReportStateCount 获取指定状态的报告数量
+func GetReportStateCount(state int) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT COUNT(1) AS count FROM report WHERE state = ?`
+	err = o.Raw(sql, state).QueryRow(&count)
+	return
+}
+
+// UpdateReportsStateByCond 批量更新报告状态
+func UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, newState int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	cond := ``
+	if classifyFirstId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_first = %d`, classifyFirstId)
+	}
+	if classifySecondId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_second = %d`, classifySecondId)
+	}
+	sql := fmt.Sprintf(`UPDATE report SET state = ?, pre_publish_time = NULL WHERE state = ? %s`, cond)
+	_, err = o.Raw(sql, newState, oldState).Exec()
+	return
+}
+
+// UpdateReportsStateBySecondIds 批量更新二级分类报告状态
+func UpdateReportsStateBySecondIds(oldState, newState int, secondIds []int) (err error) {
+	if len(secondIds) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	// (有审批流的)未发布->待提交
+	sql := fmt.Sprintf(`UPDATE report SET state = ?, pre_publish_time = NULL WHERE state = ? AND classify_id_second IN (%s)`, utils.GetOrmInReplace(len(secondIds)))
+	_, err = o.Raw(sql, newState, oldState, secondIds).Exec()
+	if err != nil {
+		return
+	}
+	// (无审批流的)待提交->未发布
+	sql = fmt.Sprintf(`UPDATE report SET state = ?, pre_publish_time = NULL WHERE state = ? AND classify_id_second NOT IN (%s)`, utils.GetOrmInReplace(len(secondIds)))
+	_, err = o.Raw(sql, oldState, newState, secondIds).Exec()
+	return
+}

+ 35 - 0
models/report_approve/constant.go

@@ -0,0 +1,35 @@
+package report_approve
+
+// 报告类型
+const (
+	FlowReportTypeChinese = 1 // 中文研报
+	FlowReportTypeEnglish = 2 // 英文研报
+	FlowReportTypeSmart   = 3 // 智能研报
+)
+
+var FlowReportTypeMap = map[int]string{
+	FlowReportTypeChinese: "中文研报",
+	FlowReportTypeEnglish: "英文研报",
+	FlowReportTypeSmart:   "智能研报",
+}
+
+// 节点审批方式
+const (
+	NodeApproveTypeRoll = 1 // 依次审批
+	NodeApproveTypeAll  = 2 // 会签
+	NodeApproveTypeAny  = 3 // 或签
+)
+
+// 节点审批人类型
+const (
+	NodeUserTypeNormal = "user" // 用户
+	NodeUserTypeRole   = "role" // 角色
+)
+
+// 报告审批状态
+const (
+	ReportApproveStateApproving = 1 // 待审批
+	ReportApproveStatePass      = 2 // 已审批
+	ReportApproveStateRefuse    = 3 // 已驳回
+	ReportApproveStateCancel    = 4 // 已撤销
+)

+ 448 - 0
models/report_approve/report_approve.go

@@ -0,0 +1,448 @@
+package report_approve
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// ReportApprove 报告审批表
+type ReportApprove struct {
+	ReportApproveId  int       `orm:"column(report_approve_id);pk" description:"审批ID"`
+	ReportType       int       `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ReportId         int       `description:"报告ID"`
+	ReportTitle      string    `description:"报告标题"`
+	ClassifyFirstId  int       `description:"一级分类ID"`
+	ClassifySecondId int       `description:"二级分类ID"`
+	State            int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	FlowId           int       `description:"审批流ID"`
+	FlowVersion      int       `description:"审批流版本"`
+	StartNodeId      int       `description:"开始节点ID"`
+	CurrNodeId       int       `description:"当前节点ID"`
+	ApplyUserId      int       `description:"申请人ID"`
+	ApplyUserName    string    `description:"申请人姓名"`
+	ApproveRemark    string    `description:"审批备注"`
+	ApproveTime      time.Time `description:"审批时间"`
+	CreateTime       time.Time `description:"创建时间"`
+	ModifyTime       time.Time `description:"修改时间"`
+}
+
+var ReportApproveCols = struct {
+	ReportApproveId  string
+	ReportType       string
+	ReportId         string
+	ReportTitle      string `description:"报告标题"`
+	ClassifyFirstId  string `description:"一级分类ID"`
+	ClassifySecondId string `description:"二级分类ID"`
+	State            string
+	FlowId           string
+	FlowVersion      string
+	StartNodeId      string
+	CurrNodeId       string
+	ApplyUserId      string `description:"申请人ID"`
+	ApplyUserName    string `description:"申请人姓名"`
+	ApproveRemark    string `description:"审批备注"`
+	ApproveTime      string `description:"审批时间"`
+	CreateTime       string
+	ModifyTime       string
+}{
+	ReportApproveId:  "report_approve_id",
+	ReportType:       "report_type",
+	ReportId:         "report_id",
+	ReportTitle:      "report_title",
+	ClassifyFirstId:  "classify_first_id",
+	ClassifySecondId: "classify_second_id",
+	State:            "state",
+	FlowId:           "flow_id",
+	FlowVersion:      "flow_version",
+	StartNodeId:      "start_node_id",
+	CurrNodeId:       "curr_node_id",
+	ApplyUserId:      "apply_user_id",
+	ApplyUserName:    "apply_user_name",
+	ApproveRemark:    "approve_remark",
+	ApproveTime:      "approve_time",
+	CreateTime:       "create_time",
+	ModifyTime:       "modify_time",
+}
+
+func (m *ReportApprove) TableName() string {
+	return "report_approve"
+}
+
+func (m *ReportApprove) PrimaryId() string {
+	return ReportApproveCols.ReportApproveId
+}
+
+func (m *ReportApprove) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ReportApproveId = int(id)
+	return
+}
+
+func (m *ReportApprove) CreateMulti(items []*ReportApprove) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ReportApprove) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ReportApprove) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ReportApproveId).Exec()
+	return
+}
+
+func (m *ReportApprove) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *ReportApprove) GetItemById(id int) (item *ReportApprove, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ReportApprove) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *ReportApprove, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApprove) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApprove) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ReportApprove, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *ReportApprove) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ReportApprove, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ReportApproveItem 报告审批信息
+type ReportApproveItem struct {
+	ReportApproveId       int    `description:"审批ID"`
+	ReportApproveRecordId int    `description:"审批记录ID"`
+	ReportType            int    `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ReportId              int    `description:"报告ID"`
+	ReportTitle           string `description:"报告标题"`
+	ReportClassify        string `description:"报告分类"`
+	ClassifyFirstId       int    `description:"一级分类ID"`
+	ClassifySecondId      int    `description:"二级分类ID"`
+	State                 int    `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	RecordState           int    `description:"审批记录状态:1-待审批;2-已通过;3-已驳回"`
+	FlowId                int    `description:"审批流ID"`
+	FlowVersion           int    `description:"审批流版本"`
+	StartNodeId           int    `description:"开始节点ID"`
+	CurrNodeId            int    `description:"当前节点ID"`
+	ApplyUserId           int    `description:"申请人ID"`
+	ApplyUserName         string `description:"申请人姓名"`
+	ApproveRemark         string `description:"审批备注"`
+	ApproveTime           string `description:"审批时间"`
+	HandleTime            string `description:"处理时间"`
+	CreateTime            string `description:"创建时间"`
+	ModifyTime            string `description:"修改时间"`
+}
+
+// FormatReportApproveOrm2Item 格式化报告审批
+func FormatReportApproveOrm2Item(origin *ReportApproveItemOrm) (item *ReportApproveItem) {
+	item = new(ReportApproveItem)
+	if origin == nil {
+		return
+	}
+	item.ReportApproveId = origin.ReportApproveId
+	item.ReportApproveRecordId = origin.ReportApproveRecordId
+	item.ReportType = origin.ReportType
+	item.ReportId = origin.ReportId
+	item.ReportTitle = origin.ReportTitle
+	item.ClassifyFirstId = origin.ClassifyFirstId
+	item.ClassifySecondId = origin.ClassifySecondId
+	item.State = origin.State
+	item.RecordState = origin.RecordState
+	item.FlowId = origin.FlowId
+	item.FlowVersion = origin.FlowVersion
+	item.StartNodeId = origin.StartNodeId
+	item.CurrNodeId = origin.CurrNodeId
+	item.ApplyUserId = origin.ApplyUserId
+	item.ApplyUserName = origin.ApplyUserName
+	item.ApproveRemark = origin.ApproveRemark
+	item.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, origin.ApproveTime)
+	item.HandleTime = utils.TimeTransferString(utils.FormatDateTime, origin.HandleTime)
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// ReportApproveListReq 审批列表请求体
+type ReportApproveListReq struct {
+	PageSize         int    `form:"PageSize"`
+	CurrentIndex     int    `form:"CurrentIndex"`
+	ListType         int    `form:"ListType" description:"列表类型:1-待处理;2-已处理;3-我发起的"`
+	ReportType       int    `form:"ReportType" description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ClassifyFirstId  int    `form:"ClassifyFirstId" description:"一级分类ID"`
+	ClassifySecondId int    `form:"ClassifySecondId" description:"二级分类ID"`
+	Keyword          string `form:"Keyword" description:"关键词:报告标题"`
+	ApproveState     int    `form:"ApproveState" description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	TimeType         int    `form:"TimeType" description:"时间类型:1-提交时间;2-处理时间;3-审批时间"`
+	StartTime        string `form:"StartTime" description:"开始时间"`
+	EndTime          string `form:"EndTime" description:"结束时间"`
+	SortField        int    `form:"SortField" description:"排序字段:1-提交时间;2-处理时间;3-审批时间"`
+	SortRule         int    `form:"SortRule" description:"排序方式: 1-正序; 2-倒序(默认)"`
+}
+
+// ReportApproveListResp 审批列表响应体
+type ReportApproveListResp struct {
+	List   []*ReportApproveItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type ReportApproveItemOrm struct {
+	ReportApproveId       int       `description:"审批ID"`
+	ReportApproveRecordId int       `description:"审批记录ID"`
+	ReportType            int       `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ReportId              int       `description:"报告ID"`
+	ReportTitle           string    `description:"报告标题"`
+	ClassifyFirstId       int       `description:"一级分类ID"`
+	ClassifySecondId      int       `description:"二级分类ID"`
+	State                 int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	RecordState           int       `description:"审批记录状态:1-待审批;2-已通过;3-已驳回"`
+	FlowId                int       `description:"审批流ID"`
+	FlowVersion           int       `description:"审批流版本"`
+	StartNodeId           int       `description:"开始节点ID"`
+	CurrNodeId            int       `description:"当前节点ID"`
+	ApplyUserId           int       `description:"申请人ID"`
+	ApplyUserName         string    `description:"申请人姓名"`
+	ApproveRemark         string    `description:"审批备注"`
+	ApproveTime           time.Time `description:"审批时间"`
+	HandleTime            time.Time `description:"处理时间"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+// GetApprovingReportApproveCount 获取待处理的审批分页列表总数
+func GetApprovingReportApproveCount(cond string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	base := fmt.Sprintf(`SELECT a.report_approve_record_id
+		FROM report_approve_record AS a
+		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id AND a.node_id = b.curr_node_id
+		WHERE 1 = 1 %s`, cond)
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM (%s) t`, base)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetApprovingReportApprovePageList 获取待处理的审批列表-分页
+func GetApprovingReportApprovePageList(cond string, pars []interface{}, orderRule string, startSize, pageSize int) (items []*ReportApproveItemOrm, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	order := `ORDER BY a.create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.state AS record_state, b.*
+		FROM report_approve_record AS a
+		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id AND a.node_id = b.curr_node_id
+		WHERE 1 = 1 %s %s
+		LIMIT ?,?`, cond, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetApprovedReportApproveCount 获取已处理的审批分页列表总数
+func GetApprovedReportApproveCount(cond string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	base := fmt.Sprintf(`SELECT a.report_approve_record_id
+		FROM report_approve_record AS a
+		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id
+		WHERE 1 = 1 %s`, cond)
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM (%s) t`, base)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetApprovedReportApprovePageList 获取已处理的审批列表-分页
+func GetApprovedReportApprovePageList(cond string, pars []interface{}, orderRule string, startSize, pageSize int) (items []*ReportApproveItemOrm, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	order := `ORDER BY a.create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT a.report_approve_record_id, a.state AS record_state, a.approve_time AS handle_time, b.*
+		FROM report_approve_record AS a
+		JOIN report_approve AS b ON a.report_approve_id = b.report_approve_id
+		WHERE 1 = 1 %s %s
+		LIMIT ?,?`, cond, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetApplyReportApproveCount 获取我发起的审批分页列表总数
+func GetApplyReportApproveCount(cond string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	base := fmt.Sprintf(`SELECT a.* FROM report_approve AS a WHERE 1 = 1 %s`, cond)
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM (%s) t`, base)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+// GetApplyReportApprovePageList 获取我发起的审批列表-分页
+func GetApplyReportApprovePageList(cond string, pars []interface{}, orderRule string, startSize, pageSize int) (items []*ReportApproveItemOrm, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	order := `ORDER BY a.create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT a.* FROM report_approve AS a WHERE 1 = 1 %s %s LIMIT ?,?`, cond, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ReportApproveDetail 审批详情信息
+type ReportApproveDetail struct {
+	Report           *ReportApproveDetailReport  `description:"报告信息"`
+	Approve          *ReportApproveDetailItem    `description:"审批信息"`
+	ApproveFlowNodes []*ReportApproveDetailNodes `description:"审批节点信息"`
+	//ApproveFlow      *ReportApproveFlowItem      `description:"审批流信息"`
+}
+
+// ReportApproveDetailItem 审批详情-审批信息
+type ReportApproveDetailItem struct {
+	ReportApproveId int    `description:"审批ID"`
+	State           int    `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	FlowId          int    `description:"审批流ID"`
+	FlowVersion     int    `description:"审批流版本"`
+	StartNodeId     int    `description:"开始节点ID"`
+	CurrNodeId      int    `description:"当前节点ID"`
+	ApplyUserId     int    `description:"申请人ID"`
+	ApplyUserName   string `description:"申请人姓名"`
+	ApproveTime     string `description:"审批时间"`
+	CreateTime      string `description:"创建时间"`
+	ModifyTime      string `description:"修改时间"`
+}
+
+// ReportApproveDetailReport 审批详情-报告信息
+type ReportApproveDetailReport struct {
+	ReportType     int    `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ReportId       int    `description:"报告ID"`
+	ReportTitle    string `description:"报告标题"`
+	ReportClassify string `description:"报告分类"`
+	//ClassifyFirstId  int    `description:"一级分类ID"`
+	//ClassifySecondId int    `description:"二级分类ID"`
+	//Content          string `description:"报告内容"`
+}
+
+// CreateApproveAndRecord 新增审批和记录
+func (m *ReportApprove) CreateApproveAndRecord(approveItem *ReportApprove, recordItems []*ReportApproveRecord) (err error) {
+	if approveItem == nil {
+		err = fmt.Errorf("approve is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	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()
+	}()
+
+	lastId, e := tx.Insert(approveItem)
+	if e != nil {
+		err = fmt.Errorf("insert approve err: %s", e.Error())
+		return
+	}
+	approveItem.ReportApproveId = int(lastId)
+
+	if len(recordItems) > 0 {
+		for _, v := range recordItems {
+			v.ReportApproveId = approveItem.ReportApproveId
+		}
+		_, e = tx.InsertMulti(len(recordItems), recordItems)
+		if e != nil {
+			err = fmt.Errorf("insert records err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+// ReportApprovePassReq 审批通过请求体
+type ReportApprovePassReq struct {
+	ReportApproveId int `description:"审批ID"`
+}
+
+// ReportApproveRefuseReq 审批驳回请求体
+type ReportApproveRefuseReq struct {
+	ReportApproveId int    `description:"审批ID"`
+	ApproveRemark   string `description:"驳回理由"`
+}
+
+// ReportApproveCancelReq 撤销审批请求体
+type ReportApproveCancelReq struct {
+	ReportApproveId int `description:"审批ID"`
+}
+
+// ReportApproveCheckApproveOpenReq 校验分类是否打开审批请求体
+type ReportApproveCheckApproveOpenReq struct {
+	ReportType       int `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ClassifyFirstId  int `description:"一级分类ID"`
+	ClassifySecondId int `description:"二级分类ID"`
+}

+ 387 - 0
models/report_approve/report_approve_flow.go

@@ -0,0 +1,387 @@
+package report_approve
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// ReportApproveFlow 报告审批流表
+type ReportApproveFlow struct {
+	ReportApproveFlowId int       `orm:"column(report_approve_flow_id);pk" description:"审批流ID"`
+	FlowName            string    `description:"审批流名称"`
+	ReportType          int       `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ClassifyFirstId     int       `description:"一级分类ID"`
+	ClassifySecondId    int       `description:"二级分类ID"`
+	CurrVersion         int       `description:"当前版本号"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+}
+
+var ReportApproveFlowCols = struct {
+	ReportApproveFlowId string
+	FlowName            string
+	ReportType          string
+	ClassifyFirstId     string
+	ClassifySecondId    string
+	CurrVersion         string
+	CreateTime          string
+	ModifyTime          string
+}{
+	ReportApproveFlowId: "report_approve_flow_id",
+	FlowName:            "flow_name",
+	ReportType:          "report_type",
+	ClassifyFirstId:     "classify_first_id",
+	ClassifySecondId:    "classify_second_id",
+	CurrVersion:         "curr_version",
+	CreateTime:          "create_time",
+	ModifyTime:          "modify_time",
+}
+
+func (m *ReportApproveFlow) TableName() string {
+	return "report_approve_flow"
+}
+
+func (m *ReportApproveFlow) PrimaryId() string {
+	return ReportApproveFlowCols.ReportApproveFlowId
+}
+
+func (m *ReportApproveFlow) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ReportApproveFlowId = int(id)
+	return
+}
+
+func (m *ReportApproveFlow) CreateMulti(items []*ReportApproveFlow) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ReportApproveFlow) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ReportApproveFlow) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ReportApproveFlowId).Exec()
+	return
+}
+
+func (m *ReportApproveFlow) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *ReportApproveFlow) GetItemById(id int) (item *ReportApproveFlow, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ReportApproveFlow) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *ReportApproveFlow, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveFlow) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveFlow) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ReportApproveFlow, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *ReportApproveFlow) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ReportApproveFlow, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ReportApproveFlowItem 报告审批流信息
+type ReportApproveFlowItem struct {
+	ReportApproveFlowId int    `description:"审批流ID"`
+	FlowName            string `description:"审批流名称"`
+	ReportType          int    `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ClassifyFirstId     int    `description:"一级分类ID"`
+	ClassifySecondId    int    `description:"二级分类ID"`
+	ReportClassify      string `description:"报告类型: XX研报/一级分类/二级分类"`
+	CreateTime          string `description:"创建时间"`
+	ModifyTime          string `description:"修改时间"`
+}
+
+// FormatReportApproveFlow2Item 格式化报告审批流
+func FormatReportApproveFlow2Item(origin *ReportApproveFlow) (item *ReportApproveFlowItem) {
+	item = new(ReportApproveFlowItem)
+	if origin == nil {
+		return
+	}
+	item.ReportApproveFlowId = origin.ReportApproveFlowId
+	item.FlowName = origin.FlowName
+	item.ReportType = origin.ReportType
+	item.ClassifyFirstId = origin.ClassifyFirstId
+	item.ClassifySecondId = origin.ClassifySecondId
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// ReportApproveFlowAddReq 新增报告审批流请求体
+type ReportApproveFlowAddReq struct {
+	FlowName         string                     `description:"审批流名称"`
+	ReportType       int                        `description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ClassifyFirstId  int                        `description:"一级分类ID"`
+	ClassifySecondId int                        `description:"二级分类ID"`
+	Nodes            []ReportApproveNodeSaveReq `description:"审批节点信息"`
+}
+
+// ReportApproveFlowEditReq 编辑报告审批流请求体
+type ReportApproveFlowEditReq struct {
+	ReportApproveFlowId int `description:"审批流ID"`
+	ReportApproveFlowAddReq
+}
+
+// ReportApproveFlowRemoveReq 删除报告审批流请求体
+type ReportApproveFlowRemoveReq struct {
+	ReportApproveFlowId int `description:"报告审批流ID"`
+}
+
+type ReportClassifyTreeItem struct {
+	ClassifyId   int                       `description:"分类ID"`
+	ClassifyName string                    `description:"分类名称"`
+	ParentId     int                       `description:"父级ID"`
+	HasFlow      bool                      `description:"是否已配置审批流"`
+	Children     []*ReportClassifyTreeItem `description:"子分类"`
+}
+
+// CreateFlowAndNodes 新增审批流和节点
+func (m *ReportApproveFlow) CreateFlowAndNodes(flowItem *ReportApproveFlow, nodeItems []*ReportApproveNode) (err error) {
+	if flowItem == nil {
+		err = fmt.Errorf("flow is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %s", e.Error())
+		return
+	}
+	prevNodes := make([]*ReportApproveNode, 0)
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+		// 更新每个节点的下一个节点信息, 放在事务中会更新失败
+		if e = m.UpdateNextNodes(prevNodes); e != nil {
+			err = fmt.Errorf("UpdatePrevNodes err: %s", e.Error())
+			return
+		}
+	}()
+
+	lastId, e := tx.Insert(flowItem)
+	if e != nil {
+		err = fmt.Errorf("insert flow err: %s", e.Error())
+		return
+	}
+	flowItem.ReportApproveFlowId = int(lastId)
+
+	nodesLen := len(nodeItems)
+	if nodesLen == 0 {
+		return
+	}
+	prevId := 0
+	prevNode := new(ReportApproveNode)
+	for k, v := range nodeItems {
+		v.ReportApproveFlowId = flowItem.ReportApproveFlowId
+		v.PrevNodeId = prevId
+		id, e := tx.Insert(v)
+		if e != nil {
+			err = fmt.Errorf("insert node err: %s", e.Error())
+			return
+		}
+		v.ReportApproveNodeId = int(id)
+		prevId = v.ReportApproveNodeId
+
+		// 下一个节点
+		if prevNode != nil && k > 0 && k < nodesLen {
+			prevNode.NextNodeId = int(id)
+			prevNodes = append(prevNodes, prevNode)
+		}
+		prevNode = v
+	}
+	return
+}
+
+func (m *ReportApproveFlow) UpdateNextNodes(nodes []*ReportApproveNode) (err error) {
+	if len(nodes) == 0 {
+		return
+	}
+	updateCols := []string{"NextNodeId"}
+	for _, v := range nodes {
+		e := v.Update(updateCols)
+		if e != nil {
+			err = fmt.Errorf("prev node update err: %s", e.Error())
+			return
+		}
+	}
+	return
+}
+
+// UpdateFlowAndNodes 更新审批流和节点
+func (m *ReportApproveFlow) UpdateFlowAndNodes(flowItem *ReportApproveFlow, nodeItems []*ReportApproveNode) (err error) {
+	if flowItem == nil {
+		err = fmt.Errorf("flow is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %s", e.Error())
+		return
+	}
+	prevNodes := make([]*ReportApproveNode, 0)
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+		// 更新每个节点的下一个节点信息, 放在事务中会更新失败
+		if e = m.UpdateNextNodes(prevNodes); e != nil {
+			err = fmt.Errorf("UpdatePrevNodes err: %s", e.Error())
+			return
+		}
+	}()
+
+	// 更新审批流
+	updateCols := []string{"FlowName", "ReportType", "ClassifyFirstId", "ClassifySecondId", "CurrVersion", "ModifyTime"}
+	if e = flowItem.Update(updateCols); e != nil {
+		err = fmt.Errorf("update flow err: %s", e.Error())
+		return
+	}
+
+	// 新增节点(旧版的节点不删除, 根据版本号区分)
+	nodesLen := len(nodeItems)
+	if nodesLen == 0 {
+		return
+	}
+	prevId := 0
+	prevNode := new(ReportApproveNode)
+	for k, v := range nodeItems {
+		v.ReportApproveFlowId = flowItem.ReportApproveFlowId
+		v.PrevNodeId = prevId
+		id, e := tx.Insert(v)
+		if e != nil {
+			err = fmt.Errorf("insert node err: %s", e.Error())
+			return
+		}
+		v.ReportApproveNodeId = int(id)
+		prevId = v.ReportApproveNodeId
+
+		// 下一个节点
+		if prevNode != nil && k > 0 && k < nodesLen {
+			prevNode.NextNodeId = int(id)
+			prevNodes = append(prevNodes, prevNode)
+		}
+		prevNode = v
+	}
+	return
+}
+
+// ReportApproveFlowDetailItem 报告审批流详情信息
+type ReportApproveFlowDetailItem struct {
+	ReportApproveFlowItem `description:"审批流信息"`
+	Nodes                 []*ReportApproveNodeItem `description:"节点信息"`
+}
+
+// FormatFlowAndNodesItem2Detail 格式化审批流详情
+func FormatFlowAndNodesItem2Detail(flowItem *ReportApproveFlow, nodeItems []*ReportApproveNode) (detail *ReportApproveFlowDetailItem, err error) {
+	if flowItem == nil {
+		return
+	}
+	detail = new(ReportApproveFlowDetailItem)
+	detail.ReportApproveFlowId = flowItem.ReportApproveFlowId
+	detail.FlowName = flowItem.FlowName
+	detail.ReportType = flowItem.ReportType
+	detail.ClassifyFirstId = flowItem.ClassifyFirstId
+	detail.ClassifySecondId = flowItem.ClassifySecondId
+	detail.CreateTime = utils.TimeTransferString(utils.FormatDateTime, flowItem.CreateTime)
+	detail.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, flowItem.ModifyTime)
+	detail.Nodes = make([]*ReportApproveNodeItem, 0)
+	for _, v := range nodeItems {
+		t, e := FormatReportApproveNode2Item(v)
+		if e != nil {
+			err = fmt.Errorf("format node err: %s", e.Error())
+			return
+		}
+		detail.Nodes = append(detail.Nodes, t)
+	}
+	return
+}
+
+// ReportApproveFlowListReq 审批流列表请求体
+type ReportApproveFlowListReq struct {
+	PageSize         int    `form:"PageSize"`
+	CurrentIndex     int    `form:"CurrentIndex"`
+	ReportType       int    `form:"ReportType" description:"报告类型:1-中文研报;2-英文研报;3-智能研报"`
+	ClassifyFirstId  int    `form:"ClassifyFirstId" description:"一级分类ID"`
+	ClassifySecondId int    `form:"ClassifySecondId" description:"二级分类ID"`
+	Keyword          string `form:"Keyword" description:"关键词"`
+	SortRule         int    `form:"SortRule" description:"排序方式: 1-正序; 2-倒序(默认)"`
+}
+
+// ReportApproveFlowListResp 审批流列表响应体
+type ReportApproveFlowListResp struct {
+	List   []*ReportApproveFlowItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 204 - 0
models/report_approve/report_approve_message.go

@@ -0,0 +1,204 @@
+package report_approve
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// ReportApproveMessage 报告审批消息表
+type ReportApproveMessage struct {
+	Id              int       `orm:"column(id);pk"`
+	SendUserId      int       `description:"发送人ID"`
+	ReceiveUserId   int       `description:"接收者ID"`
+	Content         string    `description:"消息内容"`
+	Remark          string    `description:"备注信息"`
+	ReportApproveId int       `description:"审批ID"`
+	ApproveState    int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	IsRead          int       `description:"是否已读:0-未读;1-已读"`
+	CreateTime      time.Time `description:"创建时间"`
+	ModifyTime      time.Time `description:"修改时间"`
+}
+
+var ReportApproveMessageCols = struct {
+	Id              string
+	SendUserId      string
+	ReceiveUserId   string
+	Content         string
+	Remark          string
+	ReportApproveId string
+	ApproveState    string
+	IsRead          string
+	CreateTime      string
+	ModifyTime      string
+}{
+	Id:              "id",
+	SendUserId:      "send_user_id",
+	ReceiveUserId:   "receive_user_id",
+	Content:         "content",
+	Remark:          "remark",
+	ReportApproveId: "report_approve_id",
+	ApproveState:    "approve_state",
+	IsRead:          "is_read",
+	CreateTime:      "create_time",
+	ModifyTime:      "modify_time",
+}
+
+func (m *ReportApproveMessage) TableName() string {
+	return "report_approve_message"
+}
+
+func (m *ReportApproveMessage) PrimaryId() string {
+	return ReportApproveMessageCols.Id
+}
+
+func (m *ReportApproveMessage) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.Id = int(id)
+	return
+}
+
+func (m *ReportApproveMessage) CreateMulti(items []*ReportApproveMessage) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ReportApproveMessage) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ReportApproveMessage) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.Id).Exec()
+	return
+}
+
+func (m *ReportApproveMessage) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *ReportApproveMessage) GetItemById(id int) (item *ReportApproveMessage, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ReportApproveMessage) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *ReportApproveMessage, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveMessage) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveMessage) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ReportApproveMessage, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *ReportApproveMessage) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ReportApproveMessage, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ReportApproveMessageItem 报告审批消息信息
+type ReportApproveMessageItem struct {
+	Id              int
+	SendUserId      int    `description:"发送人ID"`
+	ReceiveUserId   int    `description:"接收者ID"`
+	Content         string `description:"消息内容"`
+	Remark          string `description:"备注信息"`
+	ReportApproveId int    `description:"审批ID"`
+	ApproveState    int    `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	IsRead          int    `description:"是否已读:0-未读;1-已读"`
+	CreateTime      string `description:"创建时间"`
+	ModifyTime      string `description:"修改时间"`
+}
+
+// FormatReportApproveMessage2Item 格式化报告审批消息
+func FormatReportApproveMessage2Item(origin *ReportApproveMessage) (item *ReportApproveMessageItem) {
+	item = new(ReportApproveMessageItem)
+	if origin == nil {
+		return
+	}
+	item.Id = origin.Id
+	item.SendUserId = origin.SendUserId
+	item.ReceiveUserId = origin.ReceiveUserId
+	item.Content = origin.Content
+	item.Remark = origin.Remark
+	item.ReportApproveId = origin.ReportApproveId
+	item.ApproveState = origin.ApproveState
+	item.IsRead = origin.IsRead
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// ReportApproveMessageListReq 审批消息列表请求参数
+type ReportApproveMessageListReq struct {
+	PageSize     int `form:"PageSize"`
+	CurrentIndex int `form:"CurrentIndex"`
+}
+
+// ReportApproveMessageListResp 审批消息列表响应体
+type ReportApproveMessageListResp struct {
+	List        []*ReportApproveMessageItem
+	Paging      *paging.PagingItem `description:"分页数据"`
+	UnreadTotal int                `description:"消息未读数"`
+}
+
+// ReportApproveMessageReadReq 审批消息已读请求体
+type ReportApproveMessageReadReq struct {
+	MessageId int `description:"审批消息ID"`
+}

+ 215 - 0
models/report_approve/report_approve_node.go

@@ -0,0 +1,215 @@
+package report_approve
+
+import (
+	"encoding/json"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// ReportApproveNode 报告审批节点表
+type ReportApproveNode struct {
+	ReportApproveNodeId int       `orm:"column(report_approve_node_id);pk" description:"报告审批节点ID"`
+	ReportApproveFlowId int       `description:"报告审批流ID"`
+	PrevNodeId          int       `description:"上一个节点ID(0为开始节点)"`
+	NextNodeId          int       `description:"下一个节点ID(0为结束节点)"`
+	NodeType            int       `description:"节点类型:0-审批;1-抄送"`
+	ApproveType         int       `description:"审批类型:1-依次审批;2-会签;3-或签"`
+	Users               string    `description:"审批人信息-JSON,user_type:user-用户;role-角色,user_id:用户/角色ID"`
+	CurrVersion         int       `description:"当前版本号"`
+	CreateTime          time.Time `description:"创建时间"`
+}
+
+var ReportApproveNodeCols = struct {
+	ReportApproveNodeId string
+	ReportApproveFlowId string
+	PrevNodeId          string
+	NextNodeId          string
+	NodeType            string
+	ApproveType         string
+	Users               string
+	CurrVersion         string
+	CreateTime          string
+}{
+	ReportApproveNodeId: "report_approve_node_id",
+	ReportApproveFlowId: "report_approve_flow_id",
+	PrevNodeId:          "prev_node_id",
+	NextNodeId:          "next_node_id",
+	NodeType:            "node_type",
+	ApproveType:         "approve_type",
+	Users:               "users",
+	CurrVersion:         "curr_version",
+	CreateTime:          "create_time",
+}
+
+func (m *ReportApproveNode) TableName() string {
+	return "report_approve_node"
+}
+
+func (m *ReportApproveNode) PrimaryId() string {
+	return "report_approve_node_id"
+}
+
+func (m *ReportApproveNode) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ReportApproveNodeId = int(id)
+	return
+}
+
+func (m *ReportApproveNode) CreateMulti(items []*ReportApproveNode) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ReportApproveNode) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ReportApproveNode) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ReportApproveNodeId).Exec()
+	return
+}
+
+func (m *ReportApproveNode) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *ReportApproveNode) GetItemById(id int) (item *ReportApproveNode, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ReportApproveNode) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *ReportApproveNode, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveNode) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveNode) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ReportApproveNode, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC, report_approve_node_id ASC`
+	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 *ReportApproveNode) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ReportApproveNode, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ReportApproveNodeSaveReq 报告审批节点保存请求体
+type ReportApproveNodeSaveReq struct {
+	ApproveType int                        `description:"审批类型:1-依次审批;2-会签;3-或签"`
+	Users       []ReportApproveNodeUserReq `description:"审批人信息"`
+}
+
+// ReportApproveNodeUserReq 报告审批节点用户请求体
+type ReportApproveNodeUserReq struct {
+	UserType string `description:"审批人类型: user-用户; role-角色"`
+	UserId   int    `description:"用户/角色ID"`
+	UserName string `description:"用户/角色姓名"`
+	Sort     int    `description:"排序"`
+}
+
+// ReportApproveNodeItem 报告审批节点详情信息
+type ReportApproveNodeItem struct {
+	ReportApproveNodeId int                         `description:"报告审批节点ID"`
+	ReportApproveFlowId int                         `description:"报告审批流ID"`
+	PrevNodeId          int                         `description:"上一个节点ID(0为开始节点)"`
+	NextNodeId          int                         `description:"下一个节点ID(0为结束节点)"`
+	NodeType            int                         `description:"节点类型:0-审批;1-抄送"`
+	ApproveType         int                         `description:"审批类型:1-依次审批;2-会签;3-或签"`
+	Users               []*ReportApproveNodeUserReq `description:"审批人信息"`
+}
+
+// ReportApproveDetailNodes 审批详情-节点信息
+type ReportApproveDetailNodes struct {
+	ReportApproveNodeId int                            `description:"报告审批节点ID"`
+	ReportApproveFlowId int                            `description:"报告审批流ID"`
+	PrevNodeId          int                            `description:"上一个节点ID(0为开始节点)"`
+	NextNodeId          int                            `description:"下一个节点ID(0为结束节点)"`
+	NodeType            int                            `description:"节点类型:0-审批;1-抄送"`
+	ApproveType         int                            `description:"审批类型:1-依次审批;2-会签;3-或签"`
+	Users               []*ReportApproveDetailNodeUser `description:"审批人信息"`
+}
+
+// ReportApproveDetailNodeUser 审批详情-节点用户信息
+type ReportApproveDetailNodeUser struct {
+	ReportApproveNodeUserReq
+	ApproveRecord *ReportApproveDetailNodeUserRecord `description:"用户审批记录"`
+}
+
+// FormatReportApproveNode2Item 格式化报告审批节点信息
+func FormatReportApproveNode2Item(origin *ReportApproveNode) (item *ReportApproveNodeItem, err error) {
+	if origin == nil {
+		return
+	}
+	item = new(ReportApproveNodeItem)
+	item.ReportApproveNodeId = origin.ReportApproveNodeId
+	item.ReportApproveFlowId = origin.ReportApproveFlowId
+	item.PrevNodeId = origin.PrevNodeId
+	item.NextNodeId = origin.NextNodeId
+	item.NodeType = origin.NodeType
+	item.ApproveType = origin.ApproveType
+	item.Users = make([]*ReportApproveNodeUserReq, 0)
+	if origin.Users != "" {
+		e := json.Unmarshal([]byte(origin.Users), &item.Users)
+		if e != nil {
+			err = fmt.Errorf("node users unmarshal err: %s", e.Error())
+			return
+		}
+	}
+	return
+}

+ 220 - 0
models/report_approve/report_approve_record.go

@@ -0,0 +1,220 @@
+package report_approve
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// ReportApproveRecord 报告审批记录表
+type ReportApproveRecord struct {
+	ReportApproveRecordId int       `orm:"column(report_approve_record_id);pk" description:"审批记录ID"`
+	ReportApproveId       int       `description:"审批ID"`
+	State                 int       `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	NodeId                int       `description:"节点ID"`
+	NodeType              int       `description:"节点类型:0-审批;1-抄送"`
+	PrevNodeId            int       `description:"上级节点ID"`
+	NextNodeId            int       `description:"下级节点ID"`
+	ApproveType           int       `description:"审批类型:1-依次审批;2-会签;3-或签"`
+	ApproveUserId         int       `description:"审批人ID"`
+	ApproveUserName       string    `description:"审批人姓名"`
+	ApproveUserSort       int       `description:"依次审批:用户的审批顺序"`
+	ApproveRemark         string    `description:"审批备注(驳回备注等)"`
+	ApproveTime           time.Time `description:"审批时间"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+var ReportApproveRecordCols = struct {
+	ReportApproveRecordId string
+	ReportApproveId       string
+	State                 string
+	NodeId                string
+	NodeType              string
+	PrevNodeId            string
+	NextNodeId            string
+	ApproveType           string
+	ApproveRemark         string
+	ApproveUserId         string
+	ApproveUserName       string
+	ApproveUserSort       string
+	ApproveTime           string
+	CreateTime            string
+	ModifyTime            string
+}{
+	ReportApproveRecordId: "report_approve_record_id",
+	ReportApproveId:       "report_approve_id",
+	State:                 "state",
+	NodeId:                "node_id",
+	NodeType:              "node_type",
+	PrevNodeId:            "prev_node_id",
+	NextNodeId:            "next_node_id",
+	ApproveType:           "approve_type",
+	ApproveUserId:         "approve_user_id",
+	ApproveUserName:       "approve_user_name",
+	ApproveUserSort:       "approve_user_sort",
+	ApproveRemark:         "approve_remark",
+	ApproveTime:           "approve_time",
+	CreateTime:            "create_time",
+	ModifyTime:            "modify_time",
+}
+
+func (m *ReportApproveRecord) TableName() string {
+	return "report_approve_record"
+}
+
+func (m *ReportApproveRecord) PrimaryId() string {
+	return ReportApproveRecordCols.ReportApproveRecordId
+}
+
+func (m *ReportApproveRecord) Create() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.ReportApproveRecordId = int(id)
+	return
+}
+
+func (m *ReportApproveRecord) CreateMulti(items []*ReportApproveRecord) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *ReportApproveRecord) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *ReportApproveRecord) Del() (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	_, err = o.Raw(sql, m.ReportApproveRecordId).Exec()
+	return
+}
+
+func (m *ReportApproveRecord) MultiDel(menuIds []int) (err error) {
+	if len(menuIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.PrimaryId(), utils.GetOrmInReplace(len(menuIds)))
+	_, err = o.Raw(sql, menuIds).Exec()
+	return
+}
+
+func (m *ReportApproveRecord) GetItemById(id int) (item *ReportApproveRecord, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.PrimaryId())
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *ReportApproveRecord) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *ReportApproveRecord, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveRecord) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	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 *ReportApproveRecord) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*ReportApproveRecord, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *ReportApproveRecord) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*ReportApproveRecord, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// ReportApproveRecordItem 报告审批记录信息
+type ReportApproveRecordItem struct {
+	ReportApproveRecordId int    `description:"审批记录ID"`
+	ReportApproveId       int    `description:"审批ID"`
+	State                 int    `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	NodeId                int    `description:"节点ID"`
+	NodeType              int    `description:"节点类型:0-审批;1-抄送"`
+	PrevNodeId            int    `description:"上级节点ID"`
+	NextNodeId            int    `description:"下级节点ID"`
+	ApproveType           int    `description:"审批类型:1-依次审批;2-会签;3-或签"`
+	ApproveUserId         int    `description:"审批人ID"`
+	ApproveUserName       string `description:"审批人姓名"`
+	ApproveUserSort       int    `description:"依次审批:用户的审批顺序"`
+	ApproveRemark         string `description:"审批备注(驳回备注等)"`
+	ApproveTime           string `description:"审批时间"`
+	CreateTime            string `description:"创建时间"`
+	ModifyTime            string `description:"修改时间"`
+}
+
+// FormatReportApproveRecord2Item 格式化报告审批记录
+func FormatReportApproveRecord2Item(origin *ReportApproveRecord) (item *ReportApproveRecordItem) {
+	item = new(ReportApproveRecordItem)
+	if origin == nil {
+		return
+	}
+	item.ReportApproveRecordId = origin.ReportApproveRecordId
+	item.ReportApproveId = origin.ReportApproveId
+	item.State = origin.State
+	item.NodeId = origin.NodeId
+	item.NodeType = origin.NodeType
+	item.PrevNodeId = origin.PrevNodeId
+	item.NextNodeId = origin.NextNodeId
+	item.ApproveType = origin.ApproveType
+	item.ApproveUserId = origin.ApproveUserId
+	item.ApproveUserName = origin.ApproveUserName
+	item.ApproveUserSort = origin.ApproveUserSort
+	item.ApproveRemark = origin.ApproveRemark
+	item.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, origin.ApproveTime)
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	return
+}
+
+// ReportApproveDetailNodeUserRecord 审批详情-节点用户审批记录
+type ReportApproveDetailNodeUserRecord struct {
+	ReportApproveRecordId int    `description:"审批记录ID"`
+	State                 int    `description:"审批状态:1-待审批;2-已审批;3-已驳回;4-已撤回"`
+	ApproveUserId         int    `description:"审批人ID"`
+	ApproveUserName       string `description:"审批人姓名"`
+	ApproveRemark         string `description:"审批备注"`
+	ApproveTime           string `description:"审批时间"`
+}

+ 41 - 3
models/smart_report/smart_report.go

@@ -38,7 +38,7 @@ type SmartReport struct {
 	VideoSize           string    `description:"音频文件大小,单位M"`
 	AdminId             int       `description:"创建者ID"`
 	AdminRealName       string    `description:"创建者姓名"`
-	State               int       `description:"发布状态:1-待发布;2-已发布"`
+	State               int       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	LastModifyAdminId   int       `description:"最后更新人ID"`
 	LastModifyAdminName string    `description:"最后更新人姓名"`
 	ContentModifyTime   time.Time `description:"内容更新时间"`
@@ -56,6 +56,8 @@ type SmartReport struct {
 	HeadImg             string    `description:"报告头图地址"`
 	EndImg              string    `description:"报告尾图地址"`
 	CanvasColor         string    `description:"画布颜色"`
+	ApproveTime         time.Time `description:"审批时间"`
+	ApproveId           int       `description:"审批ID"`
 }
 
 func (m *SmartReport) TableName() string {
@@ -154,7 +156,8 @@ func (m *SmartReport) GetPageItemsByCondition(condition string, pars []interface
 	if len(fieldArr) == 0 {
 		fields = `*`
 	}
-	order := `ORDER BY FIELD(state,3,1,2,4), create_time DESC`
+	// 排序:1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过
+	order := `ORDER BY FIELD(state,3,1,4,5,6,2), modify_time DESC`
 	if orderRule != "" {
 		order = ` ORDER BY ` + orderRule
 	}
@@ -198,7 +201,7 @@ type SmartReportItem struct {
 	ContentModifyTime   string  `description:"内容更新时间"`
 	Pv                  int     `description:"pv"`
 	Uv                  int     `description:"uv"`
-	State               int     `description:"发布状态:1-待发布;2-已发布"`
+	State               int     `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
 	PublishTime         string  `description:"发布时间"`
 	PrePublishTime      string  `description:"预发布时间"`
 	MsgIsSend           int     `description:"消息是否已发送:0-否;1-是"`
@@ -207,6 +210,7 @@ type SmartReportItem struct {
 	DetailPdfUrl        string  `description:"报告详情PDF地址"`
 	CreateTime          string  `description:"创建时间"`
 	ModifyTime          string  `description:"修改时间"`
+	ApproveTime         string  `description:"审批时间"`
 	CanEdit             bool    `description:"是否可编辑"`
 	Editor              string  `description:"当前编辑人"`
 	HeadImg             string  `description:"报告头图地址"`
@@ -258,6 +262,7 @@ func FormatSmartReport2Item(origin *SmartReport) (item *SmartReportItem) {
 	item.HeadImg = origin.HeadImg
 	item.EndImg = origin.EndImg
 	item.CanvasColor = origin.CanvasColor
+	item.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, origin.ApproveTime)
 	return
 }
 
@@ -361,3 +366,36 @@ type Report2ImgQueueReq struct {
 	ReportType int    `description:"报告类型: 1-研报; 2-智能研报"`
 	ReportCode string `description:"报告唯一编码"`
 }
+
+// UpdateSmartReportsStateByCond 批量更新报告状态
+func UpdateSmartReportsStateByCond(classifyFirstId, classifySecondId, oldState, newState int) (err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	cond := ``
+	if classifyFirstId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_first = %d`, classifyFirstId)
+	}
+	if classifySecondId > 0 {
+		cond += fmt.Sprintf(` AND classify_id_second = %d`, classifySecondId)
+	}
+	sql := fmt.Sprintf(`UPDATE smart_report SET state = ?, pre_publish_time = NULL WHERE state = ? %s`, cond)
+	_, err = o.Raw(sql, newState, oldState).Exec()
+	return
+}
+
+// UpdateSmartReportsStateBySecondIds 批量更新二级分类报告状态
+func UpdateSmartReportsStateBySecondIds(oldState, newState int, secondIds []int) (err error) {
+	if len(secondIds) <= 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	// (有审批流的)未发布->待提交
+	sql := fmt.Sprintf(`UPDATE smart_report SET state = ?, pre_publish_time = NULL WHERE state = ? AND classify_id_second IN (%s)`, utils.GetOrmInReplace(len(secondIds)))
+	_, err = o.Raw(sql, newState, oldState, secondIds).Exec()
+	if err != nil {
+		return
+	}
+	// (无审批流的)待提交->未发布
+	sql = fmt.Sprintf(`UPDATE smart_report SET state = ?, pre_publish_time = NULL WHERE state = ? AND classify_id_second NOT IN (%s)`, utils.GetOrmInReplace(len(secondIds)))
+	_, err = o.Raw(sql, oldState, newState, secondIds).Exec()
+	return
+}

+ 270 - 0
routers/commentsRouter.go

@@ -2266,6 +2266,87 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/chart/theme/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/chart/theme/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/chart/theme/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/chart/theme/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "ListBySource",
+            Router: `/chart/theme/list_by_source`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "GetThemePreviewData",
+            Router: `/chart/theme/preview_data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "SetDefaultTheme",
+            Router: `/chart/theme/set_default`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "TypeList",
+            Router: `/chart/theme/type/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartThemeController"],
+        beego.ControllerComments{
+            Method: "TypeListBySource",
+            Router: `/chart/theme/type/list_by_source`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbClassifyController"],
         beego.ControllerComments{
             Method: "AddEdbClassify",
@@ -4156,6 +4237,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbSourceStatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbSourceStatController"],
+        beego.ControllerComments{
+            Method: "EdbUpdateFailedDetailList",
+            Router: `/edb_update_stat/failed/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbSourceStatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbSourceStatController"],
         beego.ControllerComments{
             Method: "Column",
@@ -4498,6 +4588,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "CancelApprove",
+            Router: `/approve/cancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "SubmitApprove",
+            Router: `/approve/submit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
         beego.ControllerComments{
             Method: "Author",
@@ -4894,6 +5002,132 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "Approve",
+            Router: `/approve`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "Cancel",
+            Router: `/cancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "CheckApproveOpen",
+            Router: `/classify/check_open`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "MessageList",
+            Router: `/message/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "MessageRead",
+            Router: `/message/read`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveController"],
+        beego.ControllerComments{
+            Method: "Refuse",
+            Router: `/refuse`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/flow/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/flow/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/flow/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/flow/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/flow/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/report_approve:ReportApproveFlowController"],
+        beego.ControllerComments{
+            Method: "ReportClassifyTree",
+            Router: `/report/classify_tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/roadshow:CalendarController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/roadshow:CalendarController"],
         beego.ControllerComments{
             Method: "ResearcherList",
@@ -5344,6 +5578,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportController"],
+        beego.ControllerComments{
+            Method: "CancelApprove",
+            Router: `/approve/cancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportController"],
+        beego.ControllerComments{
+            Method: "SubmitApprove",
+            Router: `/approve/submit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/smart_report:SmartReportController"],
         beego.ControllerComments{
             Method: "Detail",
@@ -6541,6 +6793,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "CancelApprove",
+            Router: `/approve/cancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "SubmitApprove",
+            Router: `/approve/submit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "Author",

+ 8 - 0
routers/router.go

@@ -21,6 +21,7 @@ import (
 	"eta/eta_api/controllers/data_stat"
 	"eta/eta_api/controllers/english_report"
 	"eta/eta_api/controllers/eta_trial"
+	"eta/eta_api/controllers/report_approve"
 	"eta/eta_api/controllers/roadshow"
 	"eta/eta_api/controllers/sandbox"
 	"eta/eta_api/controllers/semantic_analysis"
@@ -160,6 +161,7 @@ func init() {
 				&data_manage.PredictEdbInfoController{},
 				&data_manage.BaseFromNationalStatisticsController{},
 				&data_manage.JiaYueEdbSourceController{},
+				&data_manage.ChartThemeController{},
 			),
 		),
 		web.NSNamespace("/my_chart",
@@ -328,6 +330,12 @@ func init() {
 				&cross_variety.TagController{},
 			),
 		),
+		web.NSNamespace("/report_approve",
+			web.NSInclude(
+				&report_approve.ReportApproveController{},
+				&report_approve.ReportApproveFlowController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 5 - 1
services/aiser/ai.go

@@ -3,6 +3,7 @@ package aiser
 import (
 	"encoding/json"
 	"errors"
+	"eta/eta_api/models/aimod"
 	"eta/eta_api/utils"
 	"io/ioutil"
 	"net/http"
@@ -10,12 +11,15 @@ import (
 	"time"
 )
 
-func ChatAutoMsg(prompt string) (result string, err error) {
+func ChatAutoMsg(prompt string, historyChatList []aimod.HistoryChat) (result string, err error) {
 	chatUrl := utils.EtaAiUrl + `chat/auto_msg`
 
 	param := make(map[string]interface{})
 	param["Prompt"] = prompt
+	param["HistoryChatList"] = historyChatList
+
 	postData, err := json.Marshal(param)
+
 	if err != nil {
 		return result, err
 	}

+ 91 - 0
services/data/chart_info.go

@@ -3,6 +3,7 @@ package data
 import (
 	"encoding/json"
 	"errors"
+	"eta/eta_api/models"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
@@ -448,6 +449,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		item.LatestDate = v.LatestDate
 		item.UniqueCode = v.UniqueCode
 		item.MoveLatestDate = v.LatestDate
+		item.EdbAliasName = v.EdbAliasName
 
 		var startDateReal string
 		var diffSeconds int64
@@ -1821,6 +1823,24 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 		isSendEmail = false
 		return
 	}
+	//if req.ChartThemeId <= 0 {
+	//	errMsg = "请选择主题"
+	//	err = errors.New(errMsg)
+	//	isSendEmail = false
+	//	return
+	//}
+	//
+	//// 判断主题是否存在
+	//_, err = chart_theme.GetChartThemeId(req.ChartThemeId)
+	//if err != nil {
+	//	if err.Error() == utils.ErrNoRow() {
+	//		errMsg = "主题不存在"
+	//		err = errors.New(errMsg)
+	//		return
+	//	}
+	//	errMsg = "获取主题信息失败"
+	//	return
+	//}
 
 	chartClassify, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
 	if err != nil {
@@ -2083,6 +2103,11 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 	chartInfo.SeasonExtraConfig = seasonExtraConfig
 	chartInfo.StartYear = req.StartYear
 	chartInfo.Source = utils.CHART_SOURCE_DEFAULT
+	chartInfo.ChartThemeId = req.ChartThemeId
+	chartInfo.SourcesFrom = req.SourcesFrom
+	chartInfo.Instructions = req.Instructions
+	chartInfo.MarkersLines = req.MarkersLines
+	chartInfo.MarkersAreas = req.MarkersAreas
 	newId, err := data_manage.AddChartInfo(chartInfo)
 	if err != nil {
 		errMsg = `保存失败`
@@ -2112,6 +2137,7 @@ func AddChartInfo(req data_manage.AddChartInfoReq, sysUserId int, sysUserRealNam
 		mapItem.PredictChartColor = v.PredictChartColor
 		mapItem.ChartWidth = v.ChartWidth
 		mapItem.Source = utils.CHART_SOURCE_DEFAULT
+		mapItem.EdbAliasName = v.EdbAliasName
 		mapList = append(mapList, mapItem)
 	}
 	err = data_manage.AddChartEdbMapping(mapList)
@@ -2144,6 +2170,24 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin) (cha
 		isSendEmail = false
 		return
 	}
+	//if req.ChartThemeId <= 0 {
+	//	errMsg = "请选择主题!"
+	//	err = errors.New(errMsg)
+	//	isSendEmail = false
+	//	return
+	//}
+	//
+	//// 判断主题是否存在
+	//_, err = chart_theme.GetChartThemeId(req.ChartThemeId)
+	//if err != nil {
+	//	if err.Error() == utils.ErrNoRow() {
+	//		errMsg = "主题不存在"
+	//		err = errors.New(errMsg)
+	//		return
+	//	}
+	//	errMsg = "获取主题信息失败"
+	//	return
+	//}
 
 	chartClassify, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
 	if err != nil {
@@ -2423,3 +2467,50 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin) (cha
 
 	return
 }
+
+// GetEdbSourceByEdbInfoIdList 获取关联指标的来源
+func GetEdbSourceByEdbInfoIdList(chartEdbInfoMappingList []*data_manage.ChartEdbInfoMapping) (sourceNameList, sourceNameEnList []string) {
+	sourceNameList = make([]string, 0)
+	sourceNameEnList = make([]string, 0)
+	sourceMap := make(map[int]string)
+	for _, v := range chartEdbInfoMappingList {
+		// 指标类型:1:基础指标,2:计算指标
+		if v.EdbType == 2 || v.EdbInfoCategoryType == 1 {
+			//sourceMap[0] = "弘则研究"
+			baseEdbInfoArr, _, _ := data_manage.GetRefreshEdbInfoFromBase(v.EdbInfoId, v.Source)
+			for _, baseEdbInfo := range baseEdbInfoArr {
+				if baseEdbInfo.EdbInfoType == 0 { //普通指标才参与,预测指标不参与
+					sourceMap[baseEdbInfo.Source] = baseEdbInfo.SourceName
+				}
+			}
+		} else {
+			sourceMap[v.Source] = v.SourceName
+		}
+	}
+
+	for source, sourceName := range sourceMap {
+		if utils.InArrayByInt([]int{utils.DATA_SOURCE_MANUAL, utils.DATA_SOURCE_MYSTEEL_CHEMICAL}, source) {
+			continue
+		}
+		sourceNameList = append(sourceNameList, sourceName)
+
+		sourceNameEn, ok := utils.DataSourceEnMap[source]
+		if !ok {
+			sourceNameEn = sourceName
+		}
+		sourceNameEnList = append(sourceNameEnList, sourceNameEn)
+	}
+	//sourceNameList = append(sourceNameList, utils.ChartDefaultNameCn)
+	//sourceNameEnList = append(sourceNameEnList, utils.ChartDefaultNameEn)
+
+	// 图表来源
+	conf, e := models.GetBusinessConf()
+	if e != nil {
+		return
+	}
+	if conf[models.BusinessConfCompanyName] != "" {
+		sourceNameList = append(sourceNameList, conf[models.BusinessConfCompanyName])
+		sourceNameEnList = append(sourceNameEnList, conf[models.BusinessConfCompanyName])
+	}
+	return
+}

+ 595 - 0
services/data/chart_theme.go

@@ -0,0 +1,595 @@
+package data
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/data_manage/chart_theme"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
+	"time"
+)
+
+// GetThemePreviewChartEdbData 获取图表的指标数据
+func GetThemePreviewChartEdbData(chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string) (edbList []*data_manage.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []data_manage.YData, dataResp interface{}, err error, errMsg string) {
+	edbList = make([]*data_manage.ChartEdbInfoMapping, 0)
+	xEdbIdValue = make([]int, 0)
+	yDataList = make([]data_manage.YData, 0)
+
+	var extraConfig interface{}
+	switch chartType {
+	case 7: // 柱形图
+		var barConfig data_manage.BarChartInfoReq
+		if extraConfigStr == `` {
+			errMsg = "柱方图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &barConfig)
+		if err != nil {
+			errMsg = "柱方图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		extraConfig = barConfig
+	case 10: // 截面散点图
+		var tmpExtraConfig data_manage.SectionScatterReq
+		if extraConfigStr == `` {
+			errMsg = "截面散点图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpExtraConfig)
+		if err != nil {
+			errMsg = "截面散点配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+
+		extraConfig = tmpExtraConfig
+
+	default:
+		xEdbIdValue = make([]int, 0)
+		yDataList = make([]data_manage.YData, 0)
+	}
+
+	// 指标对应的所有数据
+	edbDataListMap, edbList, err := getThemePreviewEdbDataMapList(chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig)
+	if err != nil {
+		return
+	}
+
+	// 特殊图形数据处理
+	switch chartType {
+	case 7: // 柱形图
+		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
+		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
+
+		for k := range yDataList {
+			yDataList[k].Unit = barChartConf.Unit
+			yDataList[k].UnitEn = barChartConf.UnitEn
+		}
+
+		for _, v := range edbList {
+			// 指标别名
+			if barChartConf.EdbInfoIdList != nil && len(barChartConf.EdbInfoIdList) > 0 {
+				for _, reqEdb := range barChartConf.EdbInfoIdList {
+					if v.EdbInfoId == reqEdb.EdbInfoId {
+						v.EdbAliasName = reqEdb.Name
+					}
+				}
+			}
+		}
+	case 10: // 截面散点图
+		sectionScatterConf := extraConfig.(data_manage.SectionScatterReq)
+		xEdbIdValue, dataResp, err = getThemePreviewSectionScatterChartData(mappingList, edbDataListMap, sectionScatterConf)
+
+		var tmpExtraConfig data_manage.SectionScatterReq
+		if extraConfigStr == `` {
+			errMsg = "截面散点图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpExtraConfig)
+		if err != nil {
+			errMsg = "截面散点配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 这个数据没有必要返回给前端
+		for _, v := range edbList {
+			v.DataList = nil
+		}
+	}
+
+	return
+}
+
+func getThemePreviewEdbDataMapList(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
+		item.IsAxis = 1
+		item.EdbInfoType = 1
+		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 = 0
+		item.IsOrder = false
+		item.MaxData = v.MaxValue
+		item.MinData = v.MinValue
+		item.LatestValue = v.LatestValue
+		item.LatestDate = v.LatestDate
+		item.UniqueCode = v.UniqueCode
+		item.MoveLatestDate = v.LatestDate
+
+		var startDateReal string
+		var diffSeconds int64
+		if chartType == 2 { //季节性图
+			startDateReal = startDate
+		} 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
+			}
+		}
+
+		dataList := make([]*data_manage.EdbDataList, 0)
+		dataList, err = chart_theme.GetChartThemeDefaultDataItemList(v.EdbInfoId, startDateReal)
+		if err != nil {
+			return
+		}
+		edbDataListMap[v.EdbInfoId] = dataList
+
+		if diffSeconds != 0 && v.EdbInfoType == 0 {
+			dataListLen := len(dataList)
+			for i := 0; i < dataListLen; i++ {
+				dataList[i].DataTimestamp = dataList[i].DataTimestamp - diffSeconds
+			}
+		}
+
+		if chartType == 2 {
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+			}
+
+			quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+			if tErr != nil {
+				err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+				return
+			}
+			item.DataList = quarterDataList
+		} else if chartType == 7 { //柱方图
+			//item.DataList = dataList
+		} else {
+			item.DataList = dataList
+		}
+		edbList = append(edbList, item)
+	}
+
+	return
+}
+
+func getThemePreviewSectionScatterChartData(mappingList []*data_manage.ChartEdbInfoMapping, edbDataListMap map[int][]*data_manage.EdbDataList, extraConfig data_manage.SectionScatterReq) (edbIdList []int, chartDataResp data_manage.SectionScatterInfoResp, err error) {
+	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
+	edbDataMap := make(map[int]map[string]float64)
+	for edbInfoId, edbDataList := range edbDataListMap {
+		edbDateData := make(map[string]float64)
+		for _, edbData := range edbDataList {
+			edbDateData[edbData.DataTime] = edbData.Value
+		}
+		edbDataMap[edbInfoId] = edbDateData
+	}
+
+	// edbIdList 指标展示顺序;x轴的指标顺序
+	edbIdList = make([]int, 0)
+	edbMappingMap := make(map[int]*data_manage.ChartEdbInfoMapping)
+	for _, v := range mappingList {
+		edbIdList = append(edbIdList, v.EdbInfoId)
+		edbMappingMap[v.EdbInfoId] = v
+	}
+	//SectionScatterSeriesInfoResp
+
+	dataListResp := make([]data_manage.SectionScatterSeriesItemResp, 0) //y轴的数据列表
+
+	for _, seriesItem := range extraConfig.SeriesList {
+		var maxDate time.Time
+		// 系列中的指标数据
+		tmpSeriesEdbInfoList := make([]data_manage.SectionScatterEdbItemResp, 0)
+
+		var minXVal, maxXVal, minYVal, maxYVal float64
+		for _, edbConf := range seriesItem.EdbInfoList {
+			tmpItem := data_manage.SectionScatterEdbItemResp{
+				IsShow: edbConf.IsShow,
+				Name:   edbConf.Name,
+				NameEn: edbConf.NameEn,
+			} //单个坐标点的数据
+
+			//X轴的数据
+			{
+				edbInfoId := edbConf.XEdbInfoId //X轴的指标
+				edbMappingInfo, ok := edbMappingMap[edbInfoId]
+				if !ok {
+					continue
+				}
+				findDate := edbConf.XDate             //需要的日期值
+				dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+				if len(dataList) <= 0 {
+					// 没有数据的指标id
+					//findDataList = append(findDataList, 0)
+					continue
+				}
+
+				tmpItem.XEdbInfoId = edbInfoId
+				tmpItem.XName = edbMappingInfo.EdbName
+				tmpItem.XNameEn = edbMappingInfo.EdbNameEn
+
+				switch edbConf.XDateType {
+				case 1: //最新值
+					findDate = dataList[len(dataList)-1].DataTime
+				case 2: //近期几天
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					findDateTime = findDateTime.AddDate(0, 0, -edbConf.XDateValue)
+
+					lenData := len(dataList) - 1
+					for i := lenData; i >= 0; i-- {
+						currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+						if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+							findDate = dataList[i].DataTime
+							break
+						}
+					}
+				case 3: // 固定日期
+					//最早的日期
+					minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//寻找固定日期的数据
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, edbConf.XDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+						tmpDate := tmpDateTime.Format(utils.FormatDate)
+						if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+							findDate = tmpDate
+							break
+						}
+					}
+				default:
+					err = errors.New(fmt.Sprint("日期类型异常,Type:", edbConf.XDate))
+					return
+				}
+				findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+				if maxDate.IsZero() {
+					maxDate = findDateTime
+				} else {
+					if findDateTime.After(maxDate) {
+						maxDate = findDateTime
+					}
+				}
+				if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+					tmpItem.XDate = findDate
+					tmpItem.XValue = tmpValue
+				} else {
+					continue
+				}
+			}
+
+			//Y轴的数据
+			{
+				edbInfoId := edbConf.YEdbInfoId //Y轴的指标
+				edbMappingInfo, ok := edbMappingMap[edbInfoId]
+				if !ok {
+					continue
+				}
+				findDate := edbConf.YDate             //需要的日期值
+				dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+				if len(dataList) <= 0 {
+					// 没有数据的指标id
+					//findDataList = append(findDataList, 0)
+					continue
+				}
+
+				tmpItem.YEdbInfoId = edbInfoId
+				tmpItem.YName = edbMappingInfo.EdbName
+				tmpItem.YNameEn = edbMappingInfo.EdbNameEn
+
+				switch edbConf.YDateType {
+				case 1: //最新值
+					findDate = dataList[len(dataList)-1].DataTime
+				case 2: //近期几天
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					findDateTime = findDateTime.AddDate(0, 0, -edbConf.YDateValue)
+
+					lenData := len(dataList) - 1
+					for i := lenData; i >= 0; i-- {
+						currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+						if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+							findDate = dataList[i].DataTime
+							break
+						}
+					}
+				case 3: // 固定日期
+					//最早的日期
+					minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//寻找固定日期的数据
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, edbConf.YDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+						tmpDate := tmpDateTime.Format(utils.FormatDate)
+						if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+							findDate = tmpDate
+							break
+						}
+					}
+				default:
+					err = errors.New(fmt.Sprint("日期类型异常,Type:", edbConf.YDate))
+					return
+				}
+				findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+				if maxDate.IsZero() {
+					maxDate = findDateTime
+				} else {
+					if findDateTime.After(maxDate) {
+						maxDate = findDateTime
+					}
+				}
+				if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+					tmpItem.YDate = findDate
+					tmpItem.YValue = tmpValue
+				} else {
+					continue
+				}
+			}
+
+			// 获取当前系列的X轴的最大最小值
+			{
+				if tmpItem.XValue < minXVal {
+					minXVal = tmpItem.XValue
+				}
+				if tmpItem.XValue > maxXVal {
+					maxXVal = tmpItem.XValue
+				}
+				if tmpItem.YValue < minYVal {
+					minYVal = tmpItem.YValue
+				}
+				if tmpItem.YValue > maxYVal {
+					maxYVal = tmpItem.YValue
+				}
+			}
+			tmpSeriesEdbInfoList = append(tmpSeriesEdbInfoList, tmpItem)
+		}
+
+		trendLimitData := make([]data_manage.CoordinatePoint, 0) //趋势线的前后坐标点
+		var trendLine, rSquare string
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			for _, tmpSeriesEdbInfo := range tmpSeriesEdbInfoList {
+				tmpCoordinate1 := utils.Coordinate{
+					X: tmpSeriesEdbInfo.XValue,
+					Y: tmpSeriesEdbInfo.YValue,
+				}
+				coordinateData = append(coordinateData, tmpCoordinate1)
+			}
+
+			// 只有存在两个坐标点的时候,才能去计算线性方程和R平方
+			if len(coordinateData) >= 2 {
+				a, b = utils.GetLinearResult(coordinateData)
+				if !math.IsNaN(a) && !math.IsNaN(b) {
+					if b > 0 {
+						trendLine = fmt.Sprintf("y=%sx+%s", utils.SubFloatToString(a, 4), utils.SubFloatToString(b, 4))
+					} else {
+						trendLine = fmt.Sprintf("y=%sx%s", utils.SubFloatToString(a, 4), utils.SubFloatToString(b, 4))
+					}
+
+					minYVal, _ = decimal.NewFromFloat(a).Mul(decimal.NewFromFloat(minXVal)).Add(decimal.NewFromFloat(b)).Round(4).Float64()
+					maxYVal, _ = decimal.NewFromFloat(a).Mul(decimal.NewFromFloat(maxXVal)).Add(decimal.NewFromFloat(b)).Round(4).Float64()
+				}
+
+				// 计算R平方
+				rSquare = fmt.Sprint(utils.CalculationDecisive(coordinateData))
+			}
+
+			trendLimitData = append(trendLimitData, data_manage.CoordinatePoint{
+				X: minXVal,
+				Y: minYVal,
+			}, data_manage.CoordinatePoint{
+				X: maxXVal,
+				Y: maxYVal,
+			})
+		}
+
+		dataListResp = append(dataListResp, data_manage.SectionScatterSeriesItemResp{
+			Name:            seriesItem.Name,
+			NameEn:          seriesItem.NameEn,
+			IsNameDefault:   seriesItem.IsNameDefault,
+			Color:           seriesItem.Color,
+			EdbInfoList:     tmpSeriesEdbInfoList,
+			ShowTrendLine:   seriesItem.ShowTrendLine,
+			ShowFitEquation: seriesItem.ShowFitEquation,
+			ShowRSquare:     seriesItem.ShowRSquare,
+			TrendLine:       trendLine,
+			RSquare:         rSquare,
+			TrendLimitData:  trendLimitData,
+		})
+	}
+
+	// 截面散点图点击详情时自动更新系列名
+	if len(extraConfig.SeriesList) > 0 {
+		// 默认名字的时候才自动更新
+		if extraConfig.SeriesList[0].IsNameDefault {
+			firstXEdbInfoId := extraConfig.SeriesList[0].EdbInfoList[0].XEdbInfoId
+			if v, ok := edbMappingMap[firstXEdbInfoId]; ok {
+				extraConfig.SeriesList[0].Name = v.LatestDate
+				extraConfig.SeriesList[0].NameEn = v.LatestDate
+				dataListResp[0].Name = v.LatestDate
+				dataListResp[0].NameEn = v.LatestDate
+			}
+
+		}
+	}
+
+	chartDataResp = data_manage.SectionScatterInfoResp{
+		XName:       extraConfig.XName,
+		XNameEn:     extraConfig.XNameEn,
+		XUnitName:   extraConfig.XUnitName,
+		XUnitNameEn: extraConfig.XUnitNameEn,
+		YName:       extraConfig.YName,
+		YNameEn:     extraConfig.YNameEn,
+		YUnitName:   extraConfig.YUnitName,
+		YUnitNameEn: extraConfig.YUnitNameEn,
+		XMinValue:   extraConfig.XMinValue,
+		XMaxValue:   extraConfig.XMaxValue,
+		YMinValue:   extraConfig.YMinValue,
+		YMaxValue:   extraConfig.YMaxValue,
+		DataList:    dataListResp,
+	}
+	return
+}
+
+// GetChartThemeConfig
+// @Description: 根据主题id获取主题信息,如果获取不到的话,那么就获取默认的主题
+// @author: Roc
+// @datetime 2023-12-19 14:31:17
+// @param chartThemeId int
+// @param chartType int
+// @param source int
+// @return chartTheme *chart_theme.ChartTheme
+// @return err error
+func GetChartThemeConfig(chartThemeId, source, chartType int) (chartTheme *chart_theme.ChartTheme, err error) {
+	chartTheme, err = chart_theme.GetChartThemeId(chartThemeId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+
+	err = nil
+
+	// 如果找到了,那么就返回
+	if chartTheme != nil {
+		return
+	}
+
+	// 没有找到的话,那么就找默认的主题
+
+	// 查找主题类型id
+	chartThemeType, err := chart_theme.GetChartThemeTypeByChartTypeAndSource(chartType, source)
+	if err != nil {
+		return
+	}
+
+	// 寻找默认的主题id
+	chartTheme, err = chart_theme.GetChartThemeId(chartThemeType.DefaultChartThemeId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+
+	err = nil
+
+	// 如果找到了,那么就返回
+	if chartTheme != nil {
+		return
+	}
+
+	// 如果还是没找到,那就系统的主题id
+	chartTheme, err = chart_theme.GetSystemChartTheme(chartThemeType.ChartThemeTypeId)
+
+	return
+}

+ 11 - 1
services/data/correlation/chart_info.go

@@ -806,6 +806,11 @@ func AddChartInfo(req data_manage.AddChartInfoReq, source int, sysUser *system.A
 	chartInfo.RightMax = req.RightMax
 	chartInfo.Disabled = disableVal
 	chartInfo.Source = source
+	chartInfo.ChartThemeId = req.ChartThemeId
+	chartInfo.SourcesFrom = req.SourcesFrom
+	chartInfo.Instructions = req.Instructions
+	chartInfo.MarkersLines = req.MarkersLines
+	chartInfo.MarkersAreas = req.MarkersAreas
 
 	// 指标信息
 	mapList := make([]*data_manage.ChartEdbMapping, 0)
@@ -1140,7 +1145,7 @@ func EditChartInfo(req data_manage.EditChartInfoReq, sysUser *system.Admin) (cha
 }
 
 // CopyChartInfo 复制图表
-func CopyChartInfo(configId, classifyId int, chartName string, correlationChartInfoReq data_manage.CorrelationChartInfoReq, sysUser *system.Admin) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
+func CopyChartInfo(configId, classifyId int, chartName string, correlationChartInfoReq data_manage.CorrelationChartInfoReq, oldChartInfo *data_manage.ChartInfo, sysUser *system.Admin) (chartInfo *data_manage.ChartInfo, err error, errMsg string, isSendEmail bool) {
 	configSource := 2
 	isSendEmail = true
 	// 获取相关性图的配置
@@ -1165,6 +1170,11 @@ func CopyChartInfo(configId, classifyId int, chartName string, correlationChartI
 		ChartType:            utils.CHART_TYPE_CURVE,
 		Calendar:             "公历",
 		CorrelationChartInfo: correlationChartInfoReq,
+		ChartThemeId:         oldChartInfo.ChartThemeId,
+		SourcesFrom:          oldChartInfo.SourcesFrom,
+		Instructions:         oldChartInfo.Instructions,
+		MarkersLines:         oldChartInfo.MarkersLines,
+		MarkersAreas:         oldChartInfo.MarkersAreas,
 	}
 	chartSource := utils.CHART_SOURCE_CORRELATION // 默认是相关性图
 	chartInfo, err, errMsg, isSendEmail = AddChartInfo(addChartReq, chartSource, sysUser)

+ 6 - 0
services/data/cross_variety/chart.go

@@ -608,6 +608,12 @@ func AddChartInfo(req request.AddChartReq, sysUser *system.Admin) (chartInfo *da
 	//chartInfo.Disabled = disableVal
 	chartInfo.Source = source
 	chartInfo.ExtraConfig = string(extraConfigByte)
+	chartInfo.ChartImage = req.ChartImage
+	chartInfo.ChartThemeId = req.ChartThemeId
+	chartInfo.SourcesFrom = req.SourcesFrom
+	chartInfo.Instructions = req.Instructions
+	chartInfo.MarkersLines = req.MarkersLines
+	chartInfo.MarkersAreas = req.MarkersAreas
 
 	// 图表品种
 	chartVarietyMappingList := make([]*cross_varietyModel.ChartVarietyMapping, 0)

+ 8 - 5
services/data/edb_classify.go

@@ -913,17 +913,20 @@ func moveEdbClassify(parentEdbClassifyInfo, edbClassifyInfo, prevClassify, nextC
 		}
 		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
 		if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+			if edbClassifyInfo.Level != parentEdbClassifyInfo.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
 			edbClassifyInfo.ParentId = parentEdbClassifyInfo.ClassifyId
 			edbClassifyInfo.RootId = parentEdbClassifyInfo.RootId
 			edbClassifyInfo.Level = parentEdbClassifyInfo.Level + 1
 			edbClassifyInfo.ModifyTime = time.Now()
 			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime")
 		} else if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId == 0 {
-			edbClassifyInfo.ParentId = 0
-			edbClassifyInfo.RootId = edbClassifyInfo.ClassifyId
-			edbClassifyInfo.Level = 1
-			edbClassifyInfo.ModifyTime = time.Now()
-			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime")
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
 		}
 
 		if prevSort > 0 {

+ 1 - 1
services/data/edb_info.go

@@ -2922,7 +2922,7 @@ func GetBaseIndexTableName(source int) (tableName string) {
 	return
 }
 
-// EdbInfoAdd 添加指标到指标库
+// EdbInfoWsdAdd 添加指标到指标库
 func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err error, errMsg string, isSendEmail bool) {
 	isSendEmail = true
 	//判断指标名称是否存在

+ 240 - 178
services/data/excel/custom_analysis_edb.go

@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"github.com/araddon/dateparse"
 	"github.com/shopspring/decimal"
+	"github.com/tealeg/xlsx"
 	"github.com/xuri/excelize/v2"
 	"strings"
 )
@@ -88,16 +89,17 @@ func GenerateExcelCustomAnalysisExcel(excelInfo *excel.ExcelInfo) (downloadFileP
 	return
 }
 
+// 数据集
+type dataStruct struct {
+	Value float64
+	Ok    bool
+}
+
 // HandleEdbSequenceVal 处理日期集和数据集(获取可用的日期、数据集)
 func HandleEdbSequenceVal(dateSequenceVal, dataSequenceVal []string) (newDateList []string, newDataList []float64, err error, errMsg string) {
 	newDateList = make([]string, 0)
 	newDataList = make([]float64, 0)
 
-	// 数据集
-	type dataStruct struct {
-		Value float64
-		Ok    bool
-	}
 	dataList := make([]dataStruct, 0)
 	{
 		for _, v := range dataSequenceVal {
@@ -202,6 +204,13 @@ func HandleEdbSequenceVal(dateSequenceVal, dataSequenceVal []string) (newDateLis
 	return
 }
 
+// ResetCustomAnalysisData 数据重置的结构体
+type ResetCustomAnalysisData struct {
+	EdbInfoId int
+	DateList  []string
+	DataList  []float64
+}
+
 // Refresh  刷新表格关联的指标信息
 func Refresh(excelInfo *excel.ExcelInfo) (err error, errMsg string, isSendEmail bool) {
 	isSendEmail = true
@@ -243,224 +252,277 @@ func Refresh(excelInfo *excel.ExcelInfo) (err error, errMsg string, isSendEmail
 
 	//fmt.Println(xlsxFile)
 
-	// ResetCustomAnalysisData 数据重置的结构体
-	type ResetCustomAnalysisData struct {
-		EdbInfoId int
-		DateList  []string
-		DataList  []float64
-	}
-
 	edbInfoIdList := make([]int, 0)
 
 	for _, v := range list {
 		edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
 
-		dateList := make([]string, 0)
-		dataList := make([]string, 0)
+		// 获取对应的日期和数据列表
+		relDateList, relDataList, tmpErr, tmpErrMsg := getDateAndDataList(v, xlsxFile)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = tmpErrMsg
+			return
+		}
+		//fmt.Println(v)
+		req2 := &ResetCustomAnalysisData{
+			EdbInfoId: v.EdbInfoId,
+			DateList:  relDateList,
+			DataList:  relDataList,
+		}
 
-		// 日期序列
-		{
-			sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(v.DateSequenceStr)
-			if tmpErr != nil {
-				err = tmpErr
-				return
-			}
+		// 调用指标库去更新
+		reqJson, tmpErr := json.Marshal(req2)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		respItem, tmpErr := data.ResetCustomAnalysisData(string(reqJson))
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		if respItem.Ret != 200 {
+			errMsg = respItem.Msg
+			err = errors.New(respItem.ErrMsg)
+			return
+		}
+	}
 
-			// 查找sheet页
-			sheetInfo, ok := xlsxFile.Sheet[sheetName]
-			if !ok {
-				errMsg = "找不到" + sheetName
-				err = errors.New(errMsg)
-				return
-			}
+	if len(edbInfoIdList) > 0 {
+		err, _ = data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, true, true)
+	}
 
-			startNum = startNum - 1
-			endNum = endNum - 1
-			// 选择行的数据
-			if isRow {
-				// 因为是选择一行的数据,所以开始行和结束行时一样的
-				//endNum = startNum - 1
-
-				// 开始列名、结束列
-				var startColumn, endColumn int
-				if isAll {
-					// 结束列(其实也就是整列的个数)
-					endColumn = len(sheetInfo.Cols) - 1
-				} else {
-					tmpStartColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
-					if tmpErr != nil {
-						errMsg = "列名异常:" + startColumnName
-						err = errors.New(errMsg)
-						return
-					}
-
-					tmpEndColumn, tmpErr := excelize.ColumnNameToNumber(endColumnName)
-					if tmpErr != nil {
-						errMsg = "列名异常:" + endColumnName
-						err = errors.New(errMsg)
-						return
-					}
-					startColumn = tmpStartColumn - 1
-					endColumn = tmpEndColumn - 1
-				}
+	//xlsxFile.Sheet[]
 
-				for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
-					currCell := sheetInfo.Cell(startNum, currColumn)
-					if currCell == nil {
-						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
-						err = errors.New(errMsg)
-						return
-					}
-					dateList = append(dateList, currCell.Value)
-				}
+	return
+}
 
-			} else if isColumn { // 选择列的数据
-				if isAll {
-					// 选择一整列的话,结束行得根据实际情况调整(其实也就是整个sheet有多少行)
-					endNum = len(sheetInfo.Rows) - 1
-				}
+// getDateAndDataList
+// @Description: 获取待刷新的日期和数据
+// @author: Roc
+// @datetime 2023-12-21 15:21:14
+// @param excelEdbMappingItem *excel.ExcelEdbMappingItem
+// @param xlsxFile *xlsx.File
+// @return newDateList []string
+// @return newDataList []float64
+// @return err error
+// @return errMsg string
+func getDateAndDataList(excelEdbMappingItem *excel.ExcelEdbMappingItem, xlsxFile *xlsx.File) (newDateList []string, newDataList []float64, err error, errMsg string) {
+	var dateList, dataList []string
+
+	// 日期序列
+	{
+		sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(excelEdbMappingItem.DateSequenceStr)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
 
-				startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+		// 查找sheet页
+		sheetInfo, ok := xlsxFile.Sheet[sheetName]
+		if !ok {
+			errMsg = "找不到" + sheetName
+			err = errors.New(errMsg)
+			return
+		}
+
+		startNum = startNum - 1
+		endNum = endNum - 1
+		// 选择行的数据
+		if isRow {
+			// 因为是选择一行的数据,所以开始行和结束行时一样的
+			//endNum = startNum - 1
+
+			// 开始列名、结束列
+			var startColumn, endColumn int
+			if isAll {
+				// 结束列(其实也就是整列的个数)
+				endColumn = len(sheetInfo.Cols) - 1
+			} else {
+				tmpStartColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
 				if tmpErr != nil {
 					errMsg = "列名异常:" + startColumnName
 					err = errors.New(errMsg)
 					return
 				}
-				startColumn = startColumn - 1
-
-				for currRow := startNum; currRow <= endNum; currRow++ {
-					currCell := sheetInfo.Cell(currRow, startColumn)
-					if currCell == nil {
-						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
-						err = errors.New(errMsg)
-						return
-					}
-					dateList = append(dateList, currCell.Value)
+
+				tmpEndColumn, tmpErr := excelize.ColumnNameToNumber(endColumnName)
+				if tmpErr != nil {
+					errMsg = "列名异常:" + endColumnName
+					err = errors.New(errMsg)
+					return
 				}
+				startColumn = tmpStartColumn - 1
+				endColumn = tmpEndColumn - 1
 			}
 
-		}
+			// 最大列数,如果设置的超过了最大列数,那么结束列就是最大列数
+			maxCol := len(sheetInfo.Cols)
+			if endColumn > maxCol {
+				endColumn = maxCol - 1
+			}
 
-		// 数据序列
-		{
-			sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(v.DataSequenceStr)
-			if tmpErr != nil {
-				err = tmpErr
-				return
+			// 长度固定,避免一直申请内存空间
+			dateList = make([]string, endColumn-startColumn+1)
+
+			i := 0
+			for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
+				currCell := sheetInfo.Cell(startNum, currColumn)
+				if currCell == nil {
+					errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+					err = errors.New(errMsg)
+					return
+				}
+				//dateList = append(dateList, currCell.Value)
+				dateList[i] = currCell.Value
+				i++
 			}
 
-			// 查找sheet页
-			sheetInfo, ok := xlsxFile.Sheet[sheetName]
-			if !ok {
-				errMsg = "找不到" + sheetName
+		} else if isColumn { // 选择列的数据
+			if isAll {
+				// 选择一整列的话,结束行得根据实际情况调整(其实也就是整个sheet有多少行)
+				endNum = len(sheetInfo.Rows) - 1
+			}
+
+			startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+			if tmpErr != nil {
+				errMsg = "列名异常:" + startColumnName
 				err = errors.New(errMsg)
 				return
 			}
+			startColumn = startColumn - 1
 
-			startNum = startNum - 1
-			endNum = endNum - 1
-			// 选择行的数据
-			if isRow {
-				// 开始列名、结束列
-				var startColumn, endColumn int
-				if isAll {
-					// 结束列(其实也就是整列的个数)
-					endColumn = len(sheetInfo.Cols) - 1
-				} else {
-
-					tmpStartColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
-					if tmpErr != nil {
-						errMsg = "列名异常:" + startColumnName
-						err = errors.New(errMsg)
-						return
-					}
-
-					tmpEndColumn, tmpErr := excelize.ColumnNameToNumber(endColumnName)
-					if tmpErr != nil {
-						errMsg = "列名异常:" + endColumnName
-						err = errors.New(errMsg)
-						return
-					}
-					startColumn = tmpStartColumn - 1
-					endColumn = tmpEndColumn - 1
+			// 最大行数,如果设置的超过了最大行数,那么结束行就是最大行数
+			maxRow := len(sheetInfo.Rows)
+			if endNum > maxRow {
+				endNum = maxRow - 1
+			}
+			// 长度固定,避免一直申请内存空间
+			dateList = make([]string, endNum-startNum+1)
+			i := 0
+			for currRow := startNum; currRow <= endNum; currRow++ {
+				currCell := sheetInfo.Cell(currRow, startColumn)
+				if currCell == nil {
+					errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+					err = errors.New(errMsg)
+					return
 				}
+				//dateList = append(dateList, currCell.Value)
+				dateList[i] = currCell.Value
+				i++
+			}
+		}
 
-				for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
-					currCell := sheetInfo.Cell(startNum, currColumn)
-					if currCell == nil {
-						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
-						err = errors.New(errMsg)
-						return
-					}
-					dataList = append(dataList, currCell.Value)
-				}
+	}
 
-			} else if isColumn { // 选择列的数据
-				if isAll {
-					// 选择一整列的话,结束行得根据实际情况调整(其实也就是整个sheet有多少行)
-					endNum = len(sheetInfo.Rows) - 1
-				}
+	// 数据序列
+	{
+		sheetName, startColumnName, endColumnName, startNum, endNum, isAll, isRow, isColumn, tmpErr := GetSheetStr(excelEdbMappingItem.DataSequenceStr)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 查找sheet页
+		sheetInfo, ok := xlsxFile.Sheet[sheetName]
+		if !ok {
+			errMsg = "找不到" + sheetName
+			err = errors.New(errMsg)
+			return
+		}
+
+		startNum = startNum - 1
+		endNum = endNum - 1
+		// 选择行的数据
+		if isRow {
+			// 开始列名、结束列
+			var startColumn, endColumn int
+			if isAll {
+				// 结束列(其实也就是整列的个数)
+				endColumn = len(sheetInfo.Cols) - 1
+			} else {
 
-				startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+				tmpStartColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
 				if tmpErr != nil {
 					errMsg = "列名异常:" + startColumnName
 					err = errors.New(errMsg)
 					return
 				}
-				startColumn = startColumn - 1
-
-				for currRow := startNum; currRow <= endNum; currRow++ {
-					currCell := sheetInfo.Cell(currRow, startColumn)
-					if currCell == nil {
-						errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
-						err = errors.New(errMsg)
-						return
-					}
-					dataList = append(dataList, currCell.Value)
+
+				tmpEndColumn, tmpErr := excelize.ColumnNameToNumber(endColumnName)
+				if tmpErr != nil {
+					errMsg = "列名异常:" + endColumnName
+					err = errors.New(errMsg)
+					return
 				}
+				startColumn = tmpStartColumn - 1
+				endColumn = tmpEndColumn - 1
 			}
 
-		}
+			// 最大列数,如果设置的超过了最大列数,那么结束列就是最大列数
+			maxCol := len(sheetInfo.Cols)
+			if endColumn > maxCol {
+				endColumn = maxCol - 1
+			}
+			// 长度固定,避免一直申请内存空间
+			dataList = make([]string, endColumn-startColumn+1)
+			i := 0
+			for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
+				currCell := sheetInfo.Cell(startNum, currColumn)
+				if currCell == nil {
+					errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+					err = errors.New(errMsg)
+					return
+				}
+				//dataList = append(dataList, currCell.Value)
+				dataList[i] = currCell.Value
+				i++
+			}
 
-		//fmt.Println("日期序列结束")
+		} else if isColumn { // 选择列的数据
+			if isAll {
+				// 选择一整列的话,结束行得根据实际情况调整(其实也就是整个sheet有多少行)
+				endNum = len(sheetInfo.Rows) - 1
+			}
 
-		// 将excel中的日期、数据系列处理
-		relDateList, relDataList, tmpErr, tmpErrMsg := HandleEdbSequenceVal(dateList, dataList)
-		if tmpErr != nil {
-			err = tmpErr
-			errMsg = tmpErrMsg
-			return
-		}
-		req2 := &ResetCustomAnalysisData{
-			EdbInfoId: v.EdbInfoId,
-			DateList:  relDateList,
-			DataList:  relDataList,
-		}
+			startColumn, tmpErr := excelize.ColumnNameToNumber(startColumnName)
+			if tmpErr != nil {
+				errMsg = "列名异常:" + startColumnName
+				err = errors.New(errMsg)
+				return
+			}
+			startColumn = startColumn - 1
 
-		// 调用指标库去更新
-		reqJson, tmpErr := json.Marshal(req2)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		respItem, tmpErr := data.ResetCustomAnalysisData(string(reqJson))
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		if respItem.Ret != 200 {
-			errMsg = respItem.Msg
-			err = errors.New(respItem.ErrMsg)
-			return
+			// 最大行数,如果设置的超过了最大行数,那么结束行就是最大行数
+			maxRow := len(sheetInfo.Rows)
+			if endNum > maxRow {
+				endNum = maxRow - 1
+			}
+
+			// 长度固定,避免一直申请内存空间
+			dataList = make([]string, endNum-startNum+1)
+			i := 0
+			for currRow := startNum; currRow <= endNum; currRow++ {
+				currCell := sheetInfo.Cell(currRow, startColumn)
+				if currCell == nil {
+					errMsg = fmt.Sprintf("第%d列,第%d行数据异常", startColumn, startNum)
+					err = errors.New(errMsg)
+					return
+				}
+				//dataList = append(dataList, currCell.Value)
+				dataList[i] = currCell.Value
+				i++
+			}
 		}
-		//sheetInfo.Cell()
-	}
 
-	if len(edbInfoIdList) > 0 {
-		err, _ = data.EdbInfoRefreshAllFromBaseV3(edbInfoIdList, false, true, true)
 	}
 
-	//xlsxFile.Sheet[]
+	//fmt.Println(dateList, dataList)
+	//fmt.Println("日期序列结束")
+
+	// 将excel中的日期、数据系列处理
+	newDateList, newDataList, err, errMsg = HandleEdbSequenceVal(dateList, dataList)
 
 	return
 }

+ 5 - 0
services/data/line_equation/chart_info.go

@@ -493,6 +493,11 @@ func BatchAddChartInfo(batchAddChartReq []request.AddChart, lineChartInfoConfig
 			BarConfig:       "",
 			Source:          source,
 			ExtraConfig:     string(tmpLineChartInfoConfigByte),
+			ChartThemeId:    v.ChartThemeId,
+			SourcesFrom:     v.SourcesFrom,
+			Instructions:    v.Instructions,
+			MarkersLines:    v.MarkersLines,
+			MarkersAreas:    v.MarkersAreas,
 		}
 
 		// 指标信息

+ 5 - 0
services/data/line_feature/chart_info.go

@@ -606,6 +606,11 @@ func CopyChartInfo(configId, configSource, classifyId int, chartName string, edb
 		Calendar:        "公历",
 		ExtraConfig:     oldChartInfo.ExtraConfig,
 		ChartImage:      oldChartInfo.ChartImage,
+		ChartThemeId:    oldChartInfo.ChartThemeId,
+		SourcesFrom:     oldChartInfo.SourcesFrom,
+		Instructions:    oldChartInfo.Instructions,
+		MarkersLines:    oldChartInfo.MarkersLines,
+		MarkersAreas:    oldChartInfo.MarkersAreas,
 	}
 	chartSource := oldChartInfo.Source // 默认是相关性图
 	chartInfo, err, errMsg, isSendEmail = AddChartInfo(addChartReq, edbInfoMapping, chartSource, sysUser)

+ 2 - 1
services/data_stat/edb_info_stat.go

@@ -9,7 +9,7 @@ import (
 )
 
 // AddEdbInfoUpdateLog 添加指标编辑/刷新日志
-func AddEdbInfoUpdateLog(edbInfoId int, updateResult int, updateFailedReason string, sysUser *system.Admin) (err error) {
+func AddEdbInfoUpdateLog(edbInfoId int, updateResult int, updateFailedReason string, sysUser *system.Admin, updateType int) (err error) {
 	var edbInfo *data_manage.EdbInfo
 	if edbInfoId > 0 {
 		// 获取指标详情
@@ -45,6 +45,7 @@ func AddEdbInfoUpdateLog(edbInfoId int, updateResult int, updateFailedReason str
 		log.UpdateFailedReason = updateFailedReason
 		log.DataUpdateTime = edbInfo.DataUpdateTime
 		log.ErDataUpdateDate = edbInfo.ErDataUpdateDate
+		log.UpdateType = updateType
 		_, err = data_stat.AddEdbUpdateLog(log)
 		if err != nil {
 			err = fmt.Errorf("新增指标更新日志失败,Err: %s", err)

+ 1 - 1
services/english_report.go

@@ -509,7 +509,7 @@ func UpdateAllPublishedEnglishReportToEs() (err error) {
 	// 获取所有已发布的报告
 	var condition string
 	var pars []interface{}
-	condition = ` AND state = 2 `
+	condition = ` AND state IN (2, 6) `
 	reportList, err := models.GetEnglishReportByCondition(condition, pars)
 	count := 0
 	failCount := 0

+ 2 - 0
services/ppt/ppt_english_group.go

@@ -209,6 +209,7 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 			ReportType:    v.ReportType,
 			PptDate:       v.PptDate,
 			Content:       v.Content,
+			CoverContent:  v.CoverContent,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       adminId,
@@ -734,6 +735,7 @@ func CopyPptEnglish(pptId int, groupId int64, adminId int, adminRealName string)
 		ReportType:    pptInfo.ReportType,
 		PptDate:       pptInfo.PptDate,
 		Content:       pptInfo.Content,
+		CoverContent:  pptInfo.CoverContent,
 		CreateTime:    time.Now(),
 		ModifyTime:    time.Now(),
 		AdminId:       adminId,

+ 2 - 0
services/ppt/ppt_group.go

@@ -376,6 +376,7 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 			ReportType:    v.ReportType,
 			PptDate:       v.PptDate,
 			Content:       v.Content,
+			CoverContent:  v.CoverContent,
 			PptUrl:        v.PptUrl,
 			PptxUrl:       v.PptxUrl,
 			CreateTime:    time.Now(),
@@ -926,6 +927,7 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		ReportType:    pptInfo.ReportType,
 		PptDate:       pptInfo.PptDate,
 		Content:       pptInfo.Content,
+		CoverContent:  pptInfo.CoverContent,
 		PptUrl:        pptInfo.PptUrl,
 		PptxUrl:       pptInfo.PptxUrl,
 		CreateTime:    time.Now(),

+ 1 - 1
services/report.go

@@ -564,7 +564,7 @@ func UpdatePublishedReportToEs() (err error) {
 	// 获取所有已发布的报告
 	var condition string
 	var pars []interface{}
-	condition = ` AND state = 2 `
+	condition = ` AND state IN (2, 6) `
 	reportList, err := models.GetReportList(condition, pars, "ficc", 1, 5000)
 	count := 0
 	failCount := 0

+ 1013 - 0
services/report_approve.go

@@ -0,0 +1,1013 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/report_approve"
+	"eta/eta_api/models/smart_report"
+	"eta/eta_api/services/alarm_msg"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"time"
+)
+
+// GetReportClassifyTreeRecursive 递归获取报告分类树
+func GetReportClassifyTreeRecursive(list []*models.Classify, parentId int) []*report_approve.ReportClassifyTreeItem {
+	res := make([]*report_approve.ReportClassifyTreeItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			t := new(report_approve.ReportClassifyTreeItem)
+			t.ClassifyId = v.Id
+			t.ClassifyName = v.ClassifyName
+			t.ParentId = v.ParentId
+			t.Children = GetReportClassifyTreeRecursive(list, v.Id)
+			res = append(res, t)
+		}
+	}
+	return res
+}
+
+// CheckReportApproveFlowChange 校验是否可变更分类
+func CheckReportApproveFlowChange(reportType, classifyFirstId, classifySecondId int) (ok bool, err error) {
+	var count int
+	cond := ` AND classify_id_first = ? AND classify_id_second = ? AND state = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, classifyFirstId, classifySecondId, models.ReportStateWaitApprove)
+	switch reportType {
+	case report_approve.FlowReportTypeChinese:
+		ct, e := models.GetReportListCount(cond, pars, "")
+		if e != nil {
+			err = fmt.Errorf("GetReportListCount err: %s", e.Error())
+			return
+		}
+		count = ct
+	case report_approve.FlowReportTypeEnglish:
+		ct, e := models.GetEnglishReportListCount(cond, pars, "")
+		if e != nil {
+			err = fmt.Errorf("GetEnglishReportListCount err: %s", e.Error())
+			return
+		}
+		count = ct
+	case report_approve.FlowReportTypeSmart:
+		ob := new(smart_report.SmartReport)
+		ct, e := ob.GetCountByCondition(cond, pars)
+		if e != nil {
+			err = fmt.Errorf("SmartReport GetCountByCondition err: %s", e.Error())
+			return
+		}
+		count = ct
+	default:
+		err = fmt.Errorf("报告类型有误")
+		return
+	}
+	if count <= 0 {
+		ok = true
+	}
+	return
+}
+
+// CheckReportOpenApprove 校验报告是否开启了审批流
+func CheckReportOpenApprove(reportType, firstId, secondId int) (opening bool, err error) {
+	// 获取审批配置
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	openMap := map[string]bool{"false": false, "true": true}
+	openApprove := openMap[confMap[models.BusinessConfIsReportApprove]]
+
+	// 查询对应分类是否有审批流
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+	flowPars := make([]interface{}, 0)
+	flowPars = append(flowPars, reportType, firstId, secondId)
+	flowItem, e := flowOb.GetItemByCondition(flowCond, flowPars, "")
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("ApproveFlow GetItemByCondition err: %s", e.Error())
+		return
+	}
+
+	// 开启审批/有审批流
+	if openApprove && (flowItem != nil || confMap[models.BusinessConfReportApproveType] == models.BusinessConfReportApproveTypeOther) {
+		opening = true
+		return
+	}
+	return
+}
+
+// CheckReportCurrState 校验报告当前应有的状态
+func CheckReportCurrState(reportType, firstId, secondId, operate int) (state int, err error) {
+	// 获取审批配置
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	openMap := map[string]bool{"false": false, "true": true}
+	openApprove := openMap[confMap[models.BusinessConfIsReportApprove]]
+
+	// 查询对应分类是否有审批流
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+	flowPars := make([]interface{}, 0)
+	flowPars = append(flowPars, reportType, firstId, secondId)
+	flowItem, e := flowOb.GetItemByCondition(flowCond, flowPars, "")
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("ApproveFlow GetItemByCondition err: %s", e.Error())
+		return
+	}
+
+	// 开启审批/有审批流
+	if openApprove && (flowItem != nil || confMap[models.BusinessConfReportApproveType] == models.BusinessConfReportApproveTypeOther) {
+		// 操作为无审批的操作时, 会转为有审批的初始状态-待提交
+		stateMap := map[int]int{
+			models.ReportOperateAdd:           models.ReportStateWaitSubmit,  // 新增
+			models.ReportOperateEdit:          models.ReportStateWaitSubmit,  // 编辑
+			models.ReportOperatePublish:       models.ReportStateWaitSubmit,  // 发布
+			models.ReportOperateCancelPublish: models.ReportStateWaitSubmit,  // 取消发布
+			models.ReportOperateSubmitApprove: models.ReportStateWaitApprove, // 提审
+			models.ReportOperateCancelApprove: models.ReportStateWaitSubmit,  // 撤回
+		}
+		state = stateMap[operate]
+		return
+	}
+
+	// 关闭审批/分类无审批
+	// 操作为有审批的操作时, 会转为无审批的初始状态-未发布
+	stateMap := map[int]int{
+		models.ReportOperateAdd:           models.ReportStateUnpublished, // 新增
+		models.ReportOperateEdit:          models.ReportStateUnpublished, // 编辑
+		models.ReportOperatePublish:       models.ReportStatePublished,   // 发布
+		models.ReportOperateCancelPublish: models.ReportStateUnpublished, // 取消发布
+		models.ReportOperateSubmitApprove: models.ReportStateUnpublished, // 提审
+		models.ReportOperateCancelApprove: models.ReportStateUnpublished, // 撤回
+	}
+	state = stateMap[operate]
+	return
+}
+
+// SubmitReportApprove 提交审批
+func SubmitReportApprove(reportType, reportId int, reportTitle string, firstId, secondId int, sysAdminId int, sysAdminName string) (approveId int, err error) {
+	// 默认内部审批, 如果是走的第三方审批, 那么仅修改状态
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	openMap := map[string]bool{"false": false, "true": true}
+	openApprove := openMap[confMap[models.BusinessConfIsReportApprove]]
+	if !openApprove {
+		err = fmt.Errorf("未开启审批")
+		return
+	}
+	confApproveType := confMap[models.BusinessConfReportApproveType]
+	if confApproveType == "" {
+		confApproveType = models.BusinessConfReportApproveTypeEta
+	}
+	if confApproveType == models.BusinessConfReportApproveTypeOther {
+		return
+	}
+
+	// 查询审批流
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId)
+	flowPars := make([]interface{}, 0)
+	flowPars = append(flowPars, reportType, firstId, secondId)
+	flowItem, e := flowOb.GetItemByCondition(flowCond, flowPars, "")
+	if e != nil {
+		err = fmt.Errorf("ApproveFlow GetItemByCondition err: %s", e.Error())
+		return
+	}
+
+	// 查询审批节点
+	nodeOb := new(report_approve.ReportApproveNode)
+	nodeCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveNodeCols.ReportApproveFlowId, report_approve.ReportApproveNodeCols.CurrVersion)
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, flowItem.ReportApproveFlowId, flowItem.CurrVersion)
+	nodeItems, e := nodeOb.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("ApproveNodes GetItemsByCondition err: %s", e.Error())
+		return
+	}
+	if len(nodeItems) == 0 {
+		err = fmt.Errorf("无审批节点")
+		return
+	}
+
+	// 取出首个节点
+	firstNodeItem := new(report_approve.ReportApproveNode)
+	for _, v := range nodeItems {
+		if v.PrevNodeId == 0 {
+			firstNodeItem = v
+			continue
+		}
+	}
+	if firstNodeItem == nil {
+		err = fmt.Errorf("首个审批节点有误")
+		return
+	}
+
+	// 审批信息
+	now := time.Now().Local()
+	newApprove := new(report_approve.ReportApprove)
+	newApprove.ReportType = reportType
+	newApprove.ReportId = reportId
+	newApprove.ReportTitle = reportTitle
+	newApprove.ClassifyFirstId = firstId
+	newApprove.ClassifySecondId = secondId
+	newApprove.State = report_approve.ReportApproveStateApproving
+	newApprove.FlowId = flowItem.ReportApproveFlowId
+	newApprove.FlowVersion = flowItem.CurrVersion
+	newApprove.StartNodeId = firstNodeItem.ReportApproveNodeId
+	newApprove.CurrNodeId = firstNodeItem.ReportApproveNodeId
+	newApprove.ApplyUserId = sysAdminId
+	newApprove.ApplyUserName = sysAdminName
+	newApprove.CreateTime = now
+	newApprove.ModifyTime = now
+	if e = newApprove.Create(); e != nil {
+		err = fmt.Errorf("生成审批信息失败, Err: %s", e.Error())
+		return
+	}
+	approveId = newApprove.ReportApproveId
+
+	// 生成节点审批记录
+	err = BuildNextNodeRecordAndMsg(firstNodeItem, newApprove.ReportApproveId, sysAdminId, sysAdminName)
+	return
+}
+
+// CancelReportApprove 撤回审批
+func CancelReportApprove(reportType, reportId, approveId, sysAdminId int, sysAdminName string) (err error) {
+	// 默认内部审批, 如果是走的第三方审批, 那么仅修改状态
+	confMap, e := models.GetBusinessConf()
+	if e != nil {
+		err = fmt.Errorf("GetBusinessConf err: %s", e.Error())
+		return
+	}
+	openMap := map[string]bool{"false": false, "true": true}
+	openApprove := openMap[confMap[models.BusinessConfIsReportApprove]]
+	if !openApprove {
+		//err = fmt.Errorf("未开启审批")
+		return
+	}
+	confApproveType := confMap[models.BusinessConfReportApproveType]
+	if confApproveType == "" {
+		confApproveType = models.BusinessConfReportApproveTypeEta
+	}
+	// 第三方审批仅修改报告状态
+	if confApproveType == models.BusinessConfReportApproveTypeOther {
+		e = updateReportApproveState(reportType, reportId, 0, models.ReportStateWaitSubmit)
+		if e != nil {
+			err = fmt.Errorf("更新报告审批撤回失败, Err: %s", e.Error())
+			return
+		}
+		return
+	}
+
+	// 修改审批信息状态
+	approveOb := new(report_approve.ReportApprove)
+	approveItem, e := approveOb.GetItemById(approveId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			return
+		}
+		err = fmt.Errorf("approve GetItemById err: %s", e.Error())
+		return
+	}
+	if approveItem.State == report_approve.ReportApproveStateCancel {
+		return
+	}
+	approveItem.State = report_approve.ReportApproveStateCancel
+	cols := []string{"State", "ModifyTime"}
+	if e = approveItem.Update(cols); e != nil {
+		err = fmt.Errorf("approve Update err: %s", e.Error())
+		return
+	}
+
+	// 修改报告状态
+	e = updateReportApproveState(reportType, reportId, 0, models.ReportStateWaitSubmit)
+	if e != nil {
+		err = fmt.Errorf("更新报告审批撤回失败, Err: %s", e.Error())
+		return
+	}
+
+	// 推送撤回消息
+	go func() {
+		recordOb := new(report_approve.ReportApproveRecord)
+		recordCond := fmt.Sprintf(` AND %s = ?`, report_approve.ReportApproveRecordCols.ReportApproveId)
+		recordPars := make([]interface{}, 0)
+		recordPars = append(recordPars, approveId)
+		recordItems, e := recordOb.GetItemsByCondition(recordCond, recordPars, []string{}, "")
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("approve record GetItemsByCondition err: %s", e.Error()))
+			return
+		}
+
+		messageOb := new(report_approve.ReportApproveMessage)
+		messages := make([]*report_approve.ReportApproveMessage, 0)
+		for _, v := range recordItems {
+			m := new(report_approve.ReportApproveMessage)
+			m.SendUserId = sysAdminId
+			m.ReceiveUserId = v.ApproveUserId
+			m.Content = fmt.Sprintf("%s提交的【研报审批】已撤回", sysAdminName)
+			m.ReportApproveId = approveId
+			m.ApproveState = report_approve.ReportApproveStateCancel
+			m.CreateTime = time.Now().Local()
+			m.ModifyTime = time.Now().Local()
+			messages = append(messages, m)
+		}
+		e = messageOb.CreateMulti(messages)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("CancelReportApprove messages err: %s", e.Error()))
+			return
+		}
+	}()
+	return
+}
+
+// updateReportApproveState 更新报告审批状态
+func updateReportApproveState(reportType, reportId, approveId, state int) (err error) {
+	updateCols := []string{"ApproveId", "State", "ModifyTime"}
+	if reportType == report_approve.FlowReportTypeChinese {
+		reportOb := new(models.Report)
+		reportItem, e := reportOb.GetItemById(reportId)
+		// 报告可能会被删除, 这里查不到就忽略掉
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取中文研报失败, Err: %s", e.Error())
+			return
+		}
+		if reportItem != nil {
+			reportItem.ApproveId = approveId
+			reportItem.State = state
+			reportItem.ModifyTime = time.Now().Local()
+			if state == models.ReportStatePass || state == models.ReportStateRefused {
+				updateCols = append(updateCols, "ApproveTime")
+				reportItem.ApproveTime = time.Now().Local()
+			}
+			if state == models.ReportStatePass {
+				updateCols = append(updateCols, "PublishTime")
+				reportItem.PublishTime = time.Now().Local()
+			}
+			if e = reportItem.UpdateReport(updateCols); e != nil {
+				err = fmt.Errorf("更新中文研报审批状态失败, Err: %s", e.Error())
+				return
+			}
+		}
+	}
+	if reportType == report_approve.FlowReportTypeEnglish {
+		reportItem, e := models.GetEnglishReportItemById(reportId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取英文研报失败, Err: %s", e.Error())
+			return
+		}
+		if reportItem != nil {
+			reportItem.ApproveId = approveId
+			reportItem.State = state
+			reportItem.ModifyTime = time.Now().Local()
+			if state == models.ReportStatePass || state == models.ReportStateRefused {
+				updateCols = append(updateCols, "ApproveTime")
+				reportItem.ApproveTime = time.Now().Local()
+			}
+			if state == models.ReportStatePass {
+				updateCols = append(updateCols, "PublishTime")
+				reportItem.PublishTime = time.Now().Local()
+			}
+			if e = reportItem.UpdateReport(updateCols); e != nil {
+				err = fmt.Errorf("更新英文研报审批状态失败, Err: %s", e.Error())
+				return
+			}
+		}
+	}
+	if reportType == report_approve.FlowReportTypeSmart {
+		reportOb := new(smart_report.SmartReport)
+		reportItem, e := reportOb.GetItemById(reportId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			err = fmt.Errorf("获取智能研报失败, Err: %s", e.Error())
+			return
+		}
+		if reportItem != nil {
+			reportItem.ApproveId = approveId
+			reportItem.State = state
+			reportItem.ModifyTime = time.Now().Local()
+			if state == models.ReportStatePass || state == models.ReportStateRefused {
+				updateCols = append(updateCols, "ApproveTime")
+				reportItem.ApproveTime = time.Now().Local()
+			}
+			if state == models.ReportStatePass {
+				updateCols = append(updateCols, "PublishTime")
+				reportItem.PublishTime = time.Now().Local()
+			}
+			if e = reportItem.Update(updateCols); e != nil {
+				err = fmt.Errorf("更新智能研报审批状态失败, Err: %s", e.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+// PassReportApprove 通过审批
+func PassReportApprove(approveItem *report_approve.ReportApprove, recordItem *report_approve.ReportApproveRecord, sysAdminId int) (tips string, err error) {
+	if approveItem == nil {
+		err = fmt.Errorf("审批信息有误")
+		return
+	}
+	if recordItem == nil {
+		err = fmt.Errorf("审批记录有误")
+		return
+	}
+
+	// 查询审批流和审批流节点
+	flowOb := new(report_approve.ReportApproveFlow)
+	flowItem, e := flowOb.GetItemById(approveItem.FlowId)
+	if e != nil {
+		err = fmt.Errorf("获取审批流失败, Err: %s", e.Error())
+		return
+	}
+	nodeOb := new(report_approve.ReportApproveNode)
+	nodeCond := fmt.Sprintf(` AND %s = ? AND %s = ?`, report_approve.ReportApproveNodeCols.ReportApproveFlowId, report_approve.ReportApproveNodeCols.CurrVersion)
+	nodePars := make([]interface{}, 0)
+	nodePars = append(nodePars, flowItem.ReportApproveFlowId, flowItem.CurrVersion)
+	nodeItems, e := nodeOb.GetItemsByCondition(nodeCond, nodePars, []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("ApproveNodes GetItemsByCondition err: %s", e.Error())
+		return
+	}
+	if len(nodeItems) == 0 {
+		err = fmt.Errorf("无审批节点")
+		return
+	}
+	nodeMap := make(map[int]*report_approve.ReportApproveNode)
+	for _, v := range nodeItems {
+		nodeMap[v.ReportApproveNodeId] = v
+	}
+
+	// 取出审批记录的节点
+	currNodeItem := nodeMap[recordItem.NodeId]
+	if currNodeItem == nil {
+		err = fmt.Errorf("当前节点信息有误")
+		return
+	}
+	currNode, e := report_approve.FormatReportApproveNode2Item(currNodeItem)
+	if e != nil {
+		err = fmt.Errorf("当前节点信息有误, Err: %s", e.Error())
+		return
+	}
+	now := time.Now().Local()
+	recordItem.State = report_approve.ReportApproveStatePass
+	recordItem.ApproveTime = now
+	recordItem.ModifyTime = now
+	recordCols := []string{"State", "ApproveTime", "ModifyTime"}
+	lastApprove := false
+
+	// 依次审批
+	if currNode.ApproveType == report_approve.NodeApproveTypeRoll {
+		if e = recordItem.Update(recordCols); e != nil {
+			err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
+			return
+		}
+
+		// 检查依次审批情况
+		sort.Slice(currNode.Users, func(k, j int) bool {
+			return currNode.Users[k].Sort < currNode.Users[j].Sort
+		})
+		userLen := len(currNode.Users)
+		//lastRoll := false
+		nextUser := new(report_approve.ReportApproveNodeUserReq) // 下一个审批人, 为nil则表示当前审批人即为最后
+		for k, v := range currNode.Users {
+			// 当前审批人
+			if v.UserId == sysAdminId && recordItem.ApproveUserSort == v.Sort {
+				//if k == (userLen - 1) {
+				//	lastRoll = true
+				//	break
+				//}
+				if (k + 1) < userLen {
+					nextUser = currNode.Users[k+1]
+				}
+			}
+		}
+		//if lastRoll && currNode.NextNodeId == 0 {
+		//	lastApprove = true
+		//}
+
+		// 当前节点下一个审批人, 生成下一个审批记录且return
+		if nextUser.UserId > 0 {
+			newRecord := new(report_approve.ReportApproveRecord)
+			newRecord.ReportApproveId = recordItem.ReportApproveId
+			newRecord.State = report_approve.ReportApproveStateApproving
+			newRecord.NodeId = currNode.ReportApproveNodeId
+			newRecord.PrevNodeId = currNode.PrevNodeId
+			newRecord.NextNodeId = currNode.NextNodeId
+			newRecord.ApproveType = currNode.ApproveType
+			newRecord.ApproveUserId = nextUser.UserId
+			newRecord.ApproveUserName = nextUser.UserName
+			newRecord.ApproveUserSort = nextUser.Sort
+			newRecord.CreateTime = now
+			newRecord.ModifyTime = now
+			if e = newRecord.Create(); e != nil {
+				err = fmt.Errorf("生成审批记录失败, Err: %s", e.Error())
+				return
+			}
+
+			// 推送审批消息
+			go func() {
+				messageItem := new(report_approve.ReportApproveMessage)
+				messageItem.SendUserId = approveItem.ApplyUserId
+				messageItem.ReceiveUserId = nextUser.UserId
+				messageItem.Content = "您有新的待办任务"
+				messageItem.Remark = fmt.Sprintf("%s提交的【研报审批】需要您审批,请及时处理", approveItem.ApplyUserName)
+				messageItem.ReportApproveId = approveItem.ReportApproveId
+				messageItem.ApproveState = report_approve.ReportApproveStateApproving
+				messageItem.CreateTime = now
+				messageItem.ModifyTime = now
+				if e = messageItem.Create(); e != nil {
+					utils.FileLog.Info(fmt.Sprintf("PassReportApprove message err: %s", e.Error()))
+					return
+				}
+			}()
+			return
+		}
+
+		// 更新审批当前节点并进入下一个节点
+		if currNode.NextNodeId > 0 {
+			nextNode := nodeMap[currNode.NextNodeId]
+			approveItem.CurrNodeId = currNode.NextNodeId
+			approveItem.ModifyTime = now
+			if e = approveItem.Update([]string{"CurrNodeId", "ModifyTime"}); e != nil {
+				err = fmt.Errorf("更新审批当前节点失败, Err: %s", e.Error())
+				return
+			}
+			err = BuildNextNodeRecordAndMsg(nextNode, approveItem.ReportApproveId, approveItem.ApplyUserId, approveItem.ApplyUserName)
+			return
+		} else {
+			// 最后一个节点
+			lastApprove = true
+		}
+	}
+
+	// 会签
+	if currNode.ApproveType == report_approve.NodeApproveTypeAll {
+		// 查询其他审批人是否已审批
+		otherOb := new(report_approve.ReportApproveRecord)
+		otherCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s <> ?`, report_approve.ReportApproveRecordCols.ReportApproveId, report_approve.ReportApproveRecordCols.NodeId, report_approve.ReportApproveRecordCols.ApproveUserId)
+		otherPars := make([]interface{}, 0)
+		otherPars = append(otherPars, approveItem.ReportApproveId, recordItem.NodeId, sysAdminId)
+		otherRecords, e := otherOb.GetItemsByCondition(otherCond, otherPars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取节点审批记录失败, Err: %s", e.Error())
+			return
+		}
+		otherPass := true
+		for _, v := range otherRecords {
+			if v.State != report_approve.ReportApproveStatePass {
+				otherPass = false
+			}
+		}
+
+		// 其他人未审批, 仅更新当前审批记录
+		if e = recordItem.Update(recordCols); e != nil {
+			err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
+			return
+		}
+
+		// 其他人已审批且为最后节点
+		if otherPass && currNode.NextNodeId == 0 {
+			lastApprove = true
+		}
+
+		// 其他人已审批且不为最后节点, 进入下一节点
+		if otherPass && currNode.NextNodeId > 0 {
+			nextNode := nodeMap[currNode.NextNodeId]
+			approveItem.CurrNodeId = currNode.NextNodeId
+			approveItem.ModifyTime = now
+			if e = approveItem.Update([]string{"CurrNodeId", "ModifyTime"}); e != nil {
+				err = fmt.Errorf("更新审批当前节点失败, Err: %s", e.Error())
+				return
+			}
+			err = BuildNextNodeRecordAndMsg(nextNode, approveItem.ReportApproveId, approveItem.ApplyUserId, approveItem.ApplyUserName)
+			return
+		}
+	}
+
+	// 或签
+	if currNode.ApproveType == report_approve.NodeApproveTypeAny {
+		// 需检查一下审批的当前节点和记录的节点是否匹配, 不匹配可能是因为另外的审批人已通过, 所以此处应给提示
+		// 前端也有做相应的判断,但是两个人同时进入审批详情页时就可能出现这种情况
+		if approveItem.CurrNodeId != recordItem.NodeId {
+			tips = "该节点已完成审批, 请刷新页面"
+			return
+		}
+
+		if e = recordItem.Update(recordCols); e != nil {
+			err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
+			return
+		}
+		if currNode.NextNodeId == 0 {
+			lastApprove = true
+		}
+		if currNode.NextNodeId > 0 {
+			nextNode := nodeMap[currNode.NextNodeId]
+			approveItem.CurrNodeId = currNode.NextNodeId
+			approveItem.ModifyTime = now
+			if e = approveItem.Update([]string{"CurrNodeId", "ModifyTime"}); e != nil {
+				err = fmt.Errorf("更新审批当前节点失败, Err: %s", e.Error())
+				return
+			}
+			err = BuildNextNodeRecordAndMsg(nextNode, approveItem.ReportApproveId, approveItem.ApplyUserId, approveItem.ApplyUserName)
+			return
+		}
+	}
+
+	// 最后一个审批, 更新审批记录、审批、报告状态、推送消息给申请人
+	if lastApprove {
+		if e = recordItem.Update(recordCols); e != nil {
+			err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
+			return
+		}
+		approveItem.State = report_approve.ReportApproveStatePass
+		approveItem.ApproveTime = now
+		approveItem.ModifyTime = now
+		approveCols := []string{"State", "ApproveTime", "ModifyTime"}
+		if e = approveItem.Update(approveCols); e != nil {
+			err = fmt.Errorf("更新审批信息失败, Err: %s", e.Error())
+			return
+		}
+		if e = updateReportApproveState(approveItem.ReportType, approveItem.ReportId, approveItem.ReportApproveId, models.ReportStatePass); e != nil {
+			err = fmt.Errorf("更新报告审批状态失败, Err: %s", e.Error())
+			return
+		}
+
+		go func() {
+			messageItem := new(report_approve.ReportApproveMessage)
+			messageItem.SendUserId = sysAdminId
+			messageItem.ReceiveUserId = approveItem.ApplyUserId
+			messageItem.Content = "您提交的审批已通过"
+			messageItem.Remark = "您提交的【研报审批】已通过"
+			messageItem.ReportApproveId = approveItem.ReportApproveId
+			messageItem.ApproveState = report_approve.ReportApproveStatePass
+			messageItem.CreateTime = now
+			messageItem.ModifyTime = now
+			if e = messageItem.Create(); e != nil {
+				utils.FileLog.Info(fmt.Sprintf("PassReportApprove message err: %s", e.Error()))
+				return
+			}
+		}()
+
+		// 审批通过之后的处理
+		go func() {
+			if e = AfterReportApprovePass(approveItem.ReportType, approveItem.ReportId); e != nil {
+				utils.FileLog.Info(fmt.Sprintf("AfterReportApprovePass err: %s, ReportType: %d, ReportId: %d", e.Error(), approveItem.ReportType, approveItem.ReportId))
+				return
+			}
+		}()
+	}
+	return
+}
+
+// RefuseReportApprove 驳回审批
+func RefuseReportApprove(approveItem *report_approve.ReportApprove, recordItem *report_approve.ReportApproveRecord, approveRemark string, sysAdminId int) (err error) {
+	if approveItem == nil {
+		err = fmt.Errorf("审批信息有误")
+		return
+	}
+	if recordItem == nil {
+		err = fmt.Errorf("审批记录有误")
+		return
+	}
+
+	// 更新审批记录
+	now := time.Now().Local()
+	recordItem.State = report_approve.ReportApproveStateRefuse
+	recordItem.ApproveRemark = approveRemark
+	recordItem.ApproveTime = now
+	recordItem.ModifyTime = now
+	recordCols := []string{"State", "ApproveRemark", "ApproveTime", "ModifyTime"}
+	if e := recordItem.Update(recordCols); e != nil {
+		err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
+		return
+	}
+
+	// 驳回-更新审批, 报告状态, 推送消息
+	approveItem.State = report_approve.ReportApproveStateRefuse
+	approveItem.ApproveRemark = approveRemark
+	approveItem.ApproveTime = now
+	approveItem.ModifyTime = now
+	approveCols := []string{"State", "ApproveRemark", "ApproveTime", "ModifyTime"}
+	if e := approveItem.Update(approveCols); e != nil {
+		err = fmt.Errorf("更新审批状态失败, Err: %s", e.Error())
+		return
+	}
+	if e := updateReportApproveState(approveItem.ReportType, approveItem.ReportId, approveItem.ReportApproveId, models.ReportStateRefused); e != nil {
+		err = fmt.Errorf("更新报告状态失败, Err: %s", e.Error())
+		return
+	}
+
+	// 推送驳回消息给申请人
+	go func() {
+		messageItem := new(report_approve.ReportApproveMessage)
+		messageItem.SendUserId = sysAdminId
+		messageItem.ReceiveUserId = approveItem.ApplyUserId
+		messageItem.Content = "您提交的审批被驳回"
+		messageItem.Remark = "您提交的【研报审批】已被驳回"
+		messageItem.ReportApproveId = approveItem.ReportApproveId
+		messageItem.ApproveState = report_approve.ReportApproveStateRefuse
+		messageItem.CreateTime = now
+		messageItem.ModifyTime = now
+		if e := messageItem.Create(); e != nil {
+			utils.FileLog.Info(fmt.Sprintf("ApproveReport message err: %s", e.Error()))
+			return
+		}
+	}()
+	return
+}
+
+// BuildNextNodeRecordAndMsg 生成下一个节点的审批记录并推送消息
+func BuildNextNodeRecordAndMsg(approveNodeItem *report_approve.ReportApproveNode, approveId, sysAdminId int, sysAdminName string) (err error) {
+	if approveNodeItem == nil {
+		err = fmt.Errorf("approve node nil")
+		return
+	}
+
+	// 根据节点审批方式生成审批记录
+	now := time.Now().Local()
+	approveNode, e := report_approve.FormatReportApproveNode2Item(approveNodeItem)
+	if e != nil {
+		err = fmt.Errorf("FormatReportApproveNode2Item err: %s", e.Error())
+		return
+	}
+	if len(approveNode.Users) == 0 {
+		err = fmt.Errorf("审批节点用户有误")
+		return
+	}
+	newRecords := make([]*report_approve.ReportApproveRecord, 0)
+	sort.Slice(approveNode.Users, func(k, j int) bool {
+		return approveNode.Users[k].Sort < approveNode.Users[j].Sort
+	})
+	for _, u := range approveNode.Users {
+		r := new(report_approve.ReportApproveRecord)
+		r.ReportApproveId = approveId
+		r.State = report_approve.ReportApproveStateApproving
+		r.NodeId = approveNode.ReportApproveNodeId
+		r.PrevNodeId = approveNode.PrevNodeId
+		r.NextNodeId = approveNode.NextNodeId
+		r.ApproveType = approveNode.ApproveType
+		r.ApproveUserId = u.UserId
+		r.ApproveUserName = u.UserName
+		r.ApproveUserSort = u.Sort
+		r.CreateTime = now
+		r.ModifyTime = now
+		newRecords = append(newRecords, r)
+		// 依次审批仅生成一条记录
+		if approveNode.ApproveType == report_approve.NodeApproveTypeRoll {
+			break
+		}
+	}
+
+	recordOb := new(report_approve.ReportApproveRecord)
+	if e = recordOb.CreateMulti(newRecords); e != nil {
+		err = fmt.Errorf("生成节点审批记录失败, Err: %s", e.Error())
+		return
+	}
+
+	// 推送审批消息
+	go func() {
+		messageOb := new(report_approve.ReportApproveMessage)
+		messages := make([]*report_approve.ReportApproveMessage, 0)
+		for _, v := range newRecords {
+			m := new(report_approve.ReportApproveMessage)
+			m.SendUserId = sysAdminId
+			m.ReceiveUserId = v.ApproveUserId
+			m.Content = "您有新的待办任务"
+			m.Remark = fmt.Sprintf("%s提交的【研报审批】需要您审批,请及时处理", sysAdminName)
+			m.ReportApproveId = approveId
+			m.ApproveState = report_approve.ReportApproveStateApproving
+			m.CreateTime = now
+			m.ModifyTime = now
+			messages = append(messages, m)
+		}
+		e = messageOb.CreateMulti(messages)
+		if e != nil {
+			utils.FileLog.Info(fmt.Sprintf("BuildNextNodeRecordAndMsg messages err: %s", e.Error()))
+			return
+		}
+	}()
+	return
+}
+
+// AfterReportApprovePass 报告审批通过后的处理
+func AfterReportApprovePass(reportType, reportId int) (err error) {
+	// 中文研报
+	if reportType == report_approve.FlowReportTypeChinese {
+		report, e := models.GetReportById(reportId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				return
+			}
+			err = fmt.Errorf("获取研报信息失败, Err: %s", e.Error())
+			return
+		}
+		_ = CreateVideo(report)
+		_ = UpdateReportEs(report.Id, models.ReportStatePublished)
+		return
+	}
+
+	// 英文研报
+	if reportType == report_approve.FlowReportTypeEnglish {
+		_ = UpdateEnglishReportEs(reportId, models.ReportStatePublished)
+		return
+	}
+
+	// 智能研报
+	if reportType == report_approve.FlowReportTypeSmart {
+		ob := new(smart_report.SmartReport)
+		item, e := ob.GetItemById(reportId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				return
+			}
+			err = fmt.Errorf("获取智能研报信息失败, Err: %s", e.Error())
+			return
+		}
+
+		// 写入队列
+		var queue smart_report.Report2ImgQueueReq
+		queue.ReportType = 2
+		queue.ReportCode = item.ReportCode
+		_ = utils.Rc.LPush(utils.CACHE_CREATE_REPORT_IMGPDF_QUEUE, queue)
+
+		// 生成音频
+		if item.VideoUrl == "" {
+			SmartReportBuildVideoAndUpdate(item)
+		}
+
+		// ES更新报告
+		_ = SmartReportElasticUpsert(item.SmartReportId, models.ReportStatePublished)
+	}
+	return
+}
+
+// CheckCloseReportApproveConf 校验是否可以关闭报告审批
+func CheckCloseReportApproveConf() (yes bool, err error) {
+	// 查询待审批中的报告数量
+	count, e := models.GetReportStateCount(models.ReportStateWaitApprove)
+	if e != nil {
+		err = fmt.Errorf("查询审批中的报告数量失败, Err: %s", e.Error())
+		return
+	}
+	if count > 0 {
+		return
+	}
+	count, e = models.GetEnglishReportStateCount(models.ReportStateWaitApprove)
+	if e != nil {
+		err = fmt.Errorf("查询审批中的英文报告数量失败, Err: %s", e.Error())
+		return
+	}
+	if count > 0 {
+		return
+	}
+	smartOb := new(smart_report.SmartReport)
+	cond := ` AND state = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, models.ReportStateWaitApprove)
+	count, e = smartOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		err = fmt.Errorf("查询审批中的智能报告数量失败, Err: %s", e.Error())
+		return
+	}
+	if count > 0 {
+		return
+	}
+	yes = true
+	return
+}
+
+// FlowOperateResetReportState 审批流变化-重置报告的初始状态
+func FlowOperateResetReportState(reportType, classifyFirstId, classifySecondId, oldState, State int) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("审批流变化-重置报告初始状态失败, ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+
+	// 中文研报
+	if reportType == report_approve.FlowReportTypeChinese {
+		e := models.UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, State)
+		if e != nil {
+			err = fmt.Errorf("UpdateReportsStateByCond err: %s", e.Error())
+		}
+		return
+	}
+
+	// 英文研报
+	if reportType == report_approve.FlowReportTypeEnglish {
+		e := models.UpdateEnglishReportsStateByCond(classifyFirstId, classifySecondId, oldState, State)
+		if e != nil {
+			err = fmt.Errorf("UpdateEnglishReportsStateByCond err: %s", e.Error())
+		}
+		return
+	}
+
+	// 智能研报
+	if reportType == report_approve.FlowReportTypeSmart {
+		e := smart_report.UpdateSmartReportsStateByCond(classifyFirstId, classifySecondId, oldState, State)
+		if e != nil {
+			err = fmt.Errorf("UpdateSmartReportsStateByCond err: %s", e.Error())
+		}
+		return
+	}
+	return
+}
+
+// ConfigChangeResetReportState 审批配置变化-重置报告的初始状态
+func ConfigChangeResetReportState(changeType string) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("审批配置变化-重置报告初始状态失败, ErrMsg: %s", err.Error())
+			utils.FileLog.Info(tips)
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+
+	// 关闭审批-待提交->未发布
+	if changeType == "" {
+		e := models.UpdateReportsStateByCond(0, 0, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+		if e != nil {
+			err = fmt.Errorf("UpdateReportsStateByCond err: %s", e.Error())
+		}
+		e = models.UpdateEnglishReportsStateByCond(0, 0, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+		if e != nil {
+			err = fmt.Errorf("UpdateEnglishReportsStateByCond err: %s", e.Error())
+		}
+		e = smart_report.UpdateSmartReportsStateByCond(0, 0, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+		if e != nil {
+			err = fmt.Errorf("UpdateSmartReportsStateByCond err: %s", e.Error())
+		}
+		return
+	}
+
+	// 开启内部审批-未发布->部分待提交
+	if changeType == models.BusinessConfReportApproveTypeEta {
+		flowOb := new(report_approve.ReportApproveFlow)
+		flows, e := flowOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("GetApproveFlows err: %s", e.Error())
+			return
+		}
+		reportSecondIds, englishSecondIds, smartSecondIds := make([]int, 0), make([]int, 0), make([]int, 0)
+		for _, v := range flows {
+			if v.ReportType == report_approve.FlowReportTypeChinese {
+				reportSecondIds = append(reportSecondIds, v.ClassifySecondId)
+				continue
+			}
+			if v.ReportType == report_approve.FlowReportTypeEnglish {
+				englishSecondIds = append(englishSecondIds, v.ClassifySecondId)
+				continue
+			}
+			if v.ReportType == report_approve.FlowReportTypeSmart {
+				smartSecondIds = append(smartSecondIds, v.ClassifySecondId)
+			}
+		}
+		if len(reportSecondIds) > 0 {
+			e = models.UpdateReportsStateBySecondIds(models.ReportStateUnpublished, models.ReportStateWaitSubmit, reportSecondIds)
+			if e != nil {
+				err = fmt.Errorf("UpdateReportsStateBySecondIds err: %s", e.Error())
+				return
+			}
+		}
+		if len(englishSecondIds) > 0 {
+			e = models.UpdateEnglishReportsStateBySecondIds(models.ReportStateUnpublished, models.ReportStateWaitSubmit, englishSecondIds)
+			if e != nil {
+				err = fmt.Errorf("UpdateEnglishReportsStateBySecondIds err: %s", e.Error())
+				return
+			}
+		}
+		if len(smartSecondIds) > 0 {
+			e = smart_report.UpdateSmartReportsStateBySecondIds(models.ReportStateUnpublished, models.ReportStateWaitSubmit, smartSecondIds)
+			if e != nil {
+				err = fmt.Errorf("UpdateSmartReportsStateBySecondIds err: %s", e.Error())
+				return
+			}
+		}
+		return
+	}
+
+	// 开启第三方审批->未发布->待提交
+	if changeType == models.BusinessConfReportApproveTypeOther {
+		e := models.UpdateReportsStateByCond(0, 0, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+		if e != nil {
+			err = fmt.Errorf("UpdateReportsStateByCond err: %s", e.Error())
+		}
+		e = models.UpdateEnglishReportsStateByCond(0, 0, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+		if e != nil {
+			err = fmt.Errorf("UpdateEnglishReportsStateByCond err: %s", e.Error())
+		}
+		e = smart_report.UpdateSmartReportsStateByCond(0, 0, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+		if e != nil {
+			err = fmt.Errorf("UpdateSmartReportsStateByCond err: %s", e.Error())
+		}
+		return
+	}
+	return
+}

+ 3 - 4
services/smart_report/smart_report.go → services/smart_report.go

@@ -1,10 +1,9 @@
-package smart_report
+package services
 
 import (
 	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/smart_report"
-	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/utils"
 	"fmt"
@@ -26,7 +25,7 @@ func SmartReportBuildVideoAndUpdate(item *smart_report.SmartReport) {
 		}
 	}()
 
-	videoUrl, videoName, videoSize, videoPlaySeconds, e := services.CreateReportVideo(item.Title, item.Content, time.Now().Local().Format(utils.FormatDateTime))
+	videoUrl, videoName, videoSize, videoPlaySeconds, e := CreateReportVideo(item.Title, item.Content, time.Now().Local().Format(utils.FormatDateTime))
 	if e != nil {
 		err = fmt.Errorf("create audio err: %s", e.Error())
 		return
@@ -124,7 +123,7 @@ func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
 	esReport.ClassifyNameSecond = item.ClassifyNameSecond
 	esReport.StageStr = strconv.Itoa(item.Stage)
 	esReport.Frequency = item.Frequency
-	if err = services.EsAddOrEditSmartReport(utils.SmartReportIndexName, strconv.Itoa(item.SmartReportId), esReport); err != nil {
+	if err = EsAddOrEditSmartReport(utils.SmartReportIndexName, strconv.Itoa(item.SmartReportId), esReport); err != nil {
 		return
 	}
 	return

+ 3 - 0
services/user.go

@@ -8,6 +8,9 @@ import (
 
 // SwitchHzUserEnabledByMobile 根据手机号启用/禁用弘则研究下的联系人
 func SwitchHzUserEnabledByMobile(opEnabled int, mobile string) (err error) {
+	if utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeSandbox {
+		return
+	}
 	if mobile == "" {
 		return
 	}

+ 28 - 1
utils/constants.go

@@ -362,10 +362,37 @@ const (
 
 const (
 	WindDbWsd = "wsd"
-	ThsDs = "thsds"
+	ThsDs     = "thsds"
 )
 
 const (
 	UserLoginSalt = "MiQM9yusNA9T2uIH"         // 用户登录盐值
 	DesKeySalt    = "JMCqSoUrTAmyNNIRb0TtlrPk" // DesKey盐值
 )
+
+// DataSourceEnMap 指标来源的英文名称
+var DataSourceEnMap = map[int]string{
+	DATA_SOURCE_WIND:             "Wind",
+	DATA_SOURCE_THS:              "iFind",
+	DATA_SOURCE_PB:               "Bloomberg",
+	DATA_SOURCE_PB_FINANCE:       "Bloomberg Finance",
+	DATA_SOURCE_LT:               "Reuters",
+	DATA_SOURCE_MANUAL:           "Horizon Insights",
+	DATA_SOURCE_LZ:               "OilChem",
+	DATA_SOURCE_YS:               "SMM",
+	DATA_SOURCE_GL:               "MySteel",
+	DATA_SOURCE_ZZ:               "Zhengzhou Commodity Exchange",
+	DATA_SOURCE_DL:               "Dalian Commodity Exchange",
+	DATA_SOURCE_SH:               "Shanghai Futures Exchange",
+	DATA_SOURCE_CFFEX:            "China Financial Futures Exchange",
+	DATA_SOURCE_SHFE:             "Shanghai International Energy Exchange",
+	DATA_SOURCE_GIE:              "Eurostat",
+	DATA_SOURCE_COAL:             "China Coal Transport & Distribution Association",
+	DATA_SOURCE_GOOGLE_TRAVEL:    "Our World in Data",
+	DATA_SOURCE_EIA_STEO:         "Energy Information Administration",
+	DATA_SOURCE_COM_TRADE:        "United Nations",
+	DATA_SOURCE_SCI:              "Sublime China Information",
+	DATA_SOURCE_BAIINFO:          "BAIINFO",
+	DATA_SOURCE_MYSTEEL_CHEMICAL: "Horizon Insights",
+	DATA_SOURCE_FUBAO:            "FuBao",
+}