瀏覽代碼

Merge remote-tracking branch 'origin/master' into custom

Roc 8 月之前
父節點
當前提交
617548f8d9
共有 66 個文件被更改,包括 11951 次插入2046 次删除
  1. 188 411
      controllers/classify.go
  2. 283 1
      controllers/data_manage/chart_info.go
  3. 1 1
      controllers/data_manage/data_manage_permission/data_manage_permission.go
  4. 77 0
      controllers/data_manage/edb_info.go
  5. 12 0
      controllers/data_manage/edb_info_calculate.go
  6. 59 0
      controllers/data_manage/future_good/future_good_chart_info.go
  7. 78 0
      controllers/data_manage/predict_edb_info.go
  8. 7 7
      controllers/english_report/report.go
  9. 1 1
      controllers/ppt_v2.go
  10. 434 499
      controllers/report.go
  11. 77 4
      controllers/report_approve/report_approve.go
  12. 40 34
      controllers/report_approve/report_approve_flow.go
  13. 1 1
      controllers/report_author.go
  14. 1629 0
      controllers/report_chapter.go
  15. 50 63
      controllers/report_chapter_type.go
  16. 1956 0
      controllers/report_v2.go
  17. 8 9
      controllers/smart_report/smart_report.go
  18. 90 28
      controllers/voice.go
  19. 21 0
      models/chart_permission.go
  20. 153 68
      models/classify.go
  21. 46 0
      models/company/company_config.go
  22. 189 4
      models/data_manage/chart_info.go
  23. 1 1
      models/data_manage/data_manage_permission/req_and_resp.go
  24. 10 6
      models/db.go
  25. 16 0
      models/english_report.go
  26. 25 0
      models/permission.go
  27. 3 3
      models/ppt_v2.go
  28. 426 95
      models/report.go
  29. 110 0
      models/report/report_chapter_grant.go
  30. 152 0
      models/report/report_chapter_permission_mapping.go
  31. 126 0
      models/report/report_grant.go
  32. 1 1
      models/report_approve/constant.go
  33. 11 1
      models/report_approve/report_approve.go
  34. 12 1
      models/report_approve/report_approve_flow.go
  35. 39 0
      models/report_approve/report_approve_record.go
  36. 343 43
      models/report_chapter.go
  37. 71 24
      models/report_chapter_type.go
  38. 60 22
      models/report_chapter_type_permission.go
  39. 66 0
      models/report_grant.go
  40. 453 0
      models/report_v2.go
  41. 23 0
      models/smart_report/smart_resource.go
  42. 19 0
      models/system/sys_admin.go
  43. 3 3
      models/wechat_send_msg.go
  44. 144 9
      routers/commentsRouter.go
  45. 671 0
      services/classify.go
  46. 1233 5
      services/data/chart_info.go
  47. 58 1
      services/data/chart_info_excel_balance.go
  48. 10 0
      services/data/chart_theme.go
  49. 106 0
      services/data/edb_data.go
  50. 140 0
      services/data/edb_info_calculate.go
  51. 2 2
      services/data/excel/mixed_table.go
  52. 2 0
      services/elastic.go
  53. 99 0
      services/file.go
  54. 52 10
      services/ppt.go
  55. 194 473
      services/report.go
  56. 59 23
      services/report_approve.go
  57. 225 0
      services/report_chapter.go
  58. 40 30
      services/report_classify.go
  59. 0 84
      services/report_push.go
  60. 1447 0
      services/report_v2.go
  61. 11 0
      services/smart_report.go
  62. 21 22
      services/task.go
  63. 31 34
      services/video.go
  64. 34 21
      services/wechat_send_msg.go
  65. 1 1
      utils/common.go
  66. 1 0
      utils/constants.go

+ 188 - 411
controllers/classify.go

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

+ 283 - 1
controllers/data_manage/chart_info.go

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

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

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

+ 77 - 0
controllers/data_manage/edb_info.go

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

+ 12 - 0
controllers/data_manage/edb_info_calculate.go

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

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

@@ -3227,3 +3227,62 @@ func (this *FutureGoodChartInfoController) BaseInfoEdit() {
 	br.Msg = "编辑成功"
 	br.IsAddLog = true
 }
+
+// ChartInfoImgSetBySvg
+// @Title 图表图片上传
+// @Param   Img   query   string  true       "图片"
+// @Param   ChartInfoId   query   int  true       "图表ID"
+// @Success 200 {object} models.ResourceResp
+// @router /chart_info/image/set_by_svg [post]
+func (this *FutureGoodChartInfoController) ChartInfoImgSetBySvg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	imgData := this.GetString("Img")
+	if imgData == "" {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,Img Is Empty"
+		return
+	}
+	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
+	if chartInfoId <= 0 {
+		br.Msg = "图片参数错误"
+		br.ErrMsg = "图片参数错误,ChartInfoId Is Empty"
+		return
+	}
+	resp := new(models.ResourceResp)
+
+	// 通过svg图片生成图片资源地址
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	// 修改图表的缩略图信息
+	if chartInfoId > 0 && resourceUrl != "" {
+		err = data_manage.EditChartInfoImageV2(chartInfoId, resourceUrl)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+
+		//修改es数据
+		go data.EsAddOrEditChartInfo(chartInfoId)
+		//修改my eta es数据
+		go data.EsAddOrEditMyChartInfoByChartInfoId(chartInfoId)
+	}
+
+	resp.ResourceUrl = resourceUrl
+	resp.Source = "convert"
+	//resp.CacheKey = imgDataKey
+	br.Msg = "上传成功"
+	br.Ret = 200
+	br.Success = true
+	br.Data = resp
+	return
+}

+ 78 - 0
controllers/data_manage/predict_edb_info.go

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

+ 7 - 7
controllers/english_report/report.go

@@ -92,7 +92,7 @@ func (this *EnglishReportController) Add() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, models.ReportOperateAdd)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird, models.ReportOperateAdd)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -676,7 +676,7 @@ func (this *EnglishReportController) PublishReport() {
 		}
 
 		// 根据审批开关及审批流判断当前报告状态
-		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, models.ReportOperatePublish)
+		state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, 0, models.ReportOperatePublish)
 		if e != nil {
 			br.Msg = "操作失败"
 			br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -779,7 +779,7 @@ func (this *EnglishReportController) PrePublishReport() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond)
+	opening, e := services.CheckReportOpenApprove(report_approve.FlowReportTypeEnglish, report.ClassifyIdFirst, report.ClassifyIdSecond, 0)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()
@@ -838,7 +838,7 @@ func (this *EnglishReportController) PublishCancleReport() {
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, models.ReportOperateCancelPublish)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, 0, models.ReportOperateCancelPublish)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告当前状态失败, Err: " + e.Error()
@@ -1301,7 +1301,7 @@ func (this *EnglishReportController) SubmitApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateSubmitApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateSubmitApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()
@@ -1324,7 +1324,7 @@ func (this *EnglishReportController) SubmitApprove() {
 	}
 
 	// 提交审批
-	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, sysUser.AdminId, sysUser.RealName)
+	approveId, e := services.SubmitReportApprove(report_approve.FlowReportTypeEnglish, reportItem.Id, reportItem.Title, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, sysUser.AdminId, sysUser.RealName)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "提交审批失败, Err: " + e.Error()
@@ -1392,7 +1392,7 @@ func (this *EnglishReportController) CancelApprove() {
 	}
 
 	// 校验当前审批配置, 返回下一个状态
-	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, models.ReportOperateCancelApprove)
+	state, e := services.CheckReportCurrState(report_approve.FlowReportTypeEnglish, reportItem.ClassifyIdFirst, reportItem.ClassifyIdSecond, 0, models.ReportOperateCancelApprove)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告状态失败, Err: " + e.Error()

+ 1 - 1
controllers/ppt_v2.go

@@ -813,7 +813,7 @@ func (this *PptV2Controller) ToReport() {
 		br.Msg = "参数有误"
 		return
 	}
-	reportId, reportCode, msg, e := services.SavePPTReport(req.PptId, req.ClassifyIdSecond, req.Title, sysUser)
+	reportId, reportCode, msg, e := services.SavePPTReport(req.PptId, req.ClassifyId, req.Title, sysUser)
 	if e != nil {
 		br.Msg = msg
 		br.ErrMsg = "PPT转报告失败, Err: " + e.Error()

文件差異過大導致無法顯示
+ 434 - 499
controllers/report.go


+ 77 - 4
controllers/report_approve/report_approve.go

@@ -174,7 +174,7 @@ func (this *ReportApproveController) List() {
 
 	// 已处理
 	if params.ListType == 2 {
-		cond := fmt.Sprintf(` AND a.%s = ? AND a.%s IN (%s)`, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.State, utils.GetOrmInReplace(2))
+		cond := fmt.Sprintf(` AND a.%s = ? AND a.%s IN (%s)`, report_approve.ReportApproveRecordCols.ApproveUserId, report_approve.ReportApproveRecordCols.NodeState, utils.GetOrmInReplace(2))
 		pars := make([]interface{}, 0)
 		pars = append(pars, sysUser.AdminId, []int{report_approve.ReportApproveStatePass, report_approve.ReportApproveStateRefuse})
 		order := ""
@@ -236,6 +236,12 @@ func (this *ReportApproveController) List() {
 			br.ErrMsg = "GetApprovedReportApprovePageList err: " + e.Error()
 			return
 		}
+
+		for _, v := range list {
+			// 这个时候的状态,用审批状态
+			v.RecordState = v.NodeState
+			v.ApproveTime = v.NodeApproveTime
+		}
 		ormList = list
 	}
 
@@ -311,14 +317,71 @@ func (this *ReportApproveController) List() {
 		ormList = list
 	}
 
+	// 审批通过的报告显示下载按钮
+	reportImg := make(map[string]string)
+	reportPdf := make(map[string]string)
+	{
+		var reportIds, enReportIds []int
+		for _, v := range ormList {
+			if v.State != report_approve.ReportApproveStatePass {
+				continue
+			}
+			if v.ReportType == report_approve.FlowReportTypeChinese {
+				reportIds = append(reportIds, v.ReportId)
+			}
+			if v.ReportType == report_approve.FlowReportTypeEnglish {
+				enReportIds = append(enReportIds, v.ReportId)
+			}
+		}
+		if len(reportIds) > 0 {
+			reports, e := models.GetReportFieldsByIds(reportIds, []string{"id", "detail_img_url", "detail_pdf_url"})
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("GetReportFieldsByIds, %v", e)
+				return
+			}
+			for _, r := range reports {
+				k := fmt.Sprintf("%d-%d", report_approve.FlowReportTypeChinese, r.Id)
+				reportImg[k] = r.DetailImgUrl
+				reportPdf[k] = r.DetailPdfUrl
+			}
+		}
+		if len(enReportIds) > 0 {
+			enReports, e := models.GetEnglishReportFieldsByIds(enReportIds, []string{"id", "detail_img_url", "detail_pdf_url"})
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("GetEnglishReportFieldsByIds, %v", e)
+				return
+			}
+			for _, r := range enReports {
+				k := fmt.Sprintf("%d-%d", report_approve.FlowReportTypeEnglish, r.Id)
+				reportImg[k] = r.DetailImgUrl
+				reportPdf[k] = r.DetailPdfUrl
+			}
+		}
+	}
+
 	// 格式化列表
 	for _, v := range ormList {
 		t := report_approve.FormatReportApproveOrm2Item(v)
 		if v.ReportType == report_approve.FlowReportTypeEnglish {
 			t.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], enClassifyIdName[enRootIdMap[v.ClassifySecondId]], enClassifyIdName[v.ClassifyFirstId], enClassifyIdName[v.ClassifySecondId])
 		} else {
-			t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+			//t.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId], cnClassifyIdName[v.ClassifySecondId])
+			reportClassify := fmt.Sprintf("%s/%s", report_approve.FlowReportTypeMap[v.ReportType], cnClassifyIdName[v.ClassifyFirstId])
+			if v.ClassifySecondId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifySecondId])
+			}
+			if v.ClassifyThirdId > 0 {
+				reportClassify = fmt.Sprintf("%s/%s", reportClassify, cnClassifyIdName[v.ClassifyThirdId])
+			}
+			t.ReportClassify = reportClassify
 		}
+
+		k := fmt.Sprintf("%d-%d", t.ReportType, t.ReportId)
+		t.DetailImgUrl = reportImg[k]
+		t.DetailPdfUrl = reportPdf[k]
+
 		respList = append(respList, t)
 	}
 
@@ -502,9 +565,18 @@ func (this *ReportApproveController) Detail() {
 	detail.Report.ReportType = approveItem.ReportType
 	detail.Report.ReportId = approveItem.ReportId
 	detail.Report.ReportTitle = approveItem.ReportTitle
+	detail.Report.ReportLayout = 1
 
 	// 报告分类路由
 	if approveItem.ReportType == report_approve.FlowReportTypeChinese {
+		reportInfo, e := models.GetReportLayoutByReportId(approveItem.ReportId)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取报告失败, Err: " + e.Error()
+			return
+		}
+		detail.Report.ReportLayout = reportInfo.ReportLayout
+
 		detail.Report.ReportCode = utils.MD5(strconv.Itoa(approveItem.ReportId))
 		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], cnClassifyIdName[approveItem.ClassifyFirstId], cnClassifyIdName[approveItem.ClassifySecondId])
 	}
@@ -513,7 +585,8 @@ func (this *ReportApproveController) Detail() {
 		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], enClassifyIdName[enRootIdMap[approveItem.ClassifySecondId]], enClassifyIdName[approveItem.ClassifyFirstId], enClassifyIdName[approveItem.ClassifySecondId])
 	}
 	if approveItem.ReportType == report_approve.FlowReportTypeSmart {
-		detail.Report.ReportCode = 	utils.MD5(fmt.Sprint("smart_", approveItem.ReportId))
+		detail.Report.ReportLayout = 2
+		detail.Report.ReportCode = utils.MD5(fmt.Sprint("smart_", approveItem.ReportId))
 		detail.Report.ReportClassify = fmt.Sprintf("%s/%s/%s", report_approve.FlowReportTypeMap[approveItem.ReportType], cnClassifyIdName[approveItem.ClassifyFirstId], cnClassifyIdName[approveItem.ClassifySecondId])
 	}
 
@@ -922,7 +995,7 @@ func (this *ReportApproveController) CheckApproveOpen() {
 	}
 
 	// 校验是否开启了审批流
-	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId)
+	opening, e := services.CheckReportOpenApprove(req.ReportType, req.ClassifyFirstId, req.ClassifySecondId, req.ClassifyThirdId)
 	if e != nil {
 		br.Msg = "操作失败"
 		br.ErrMsg = "校验报告是否开启审批流失败, Err: " + e.Error()

+ 40 - 34
controllers/report_approve/report_approve_flow.go

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

+ 1 - 1
controllers/report_author.go

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

+ 1629 - 0
controllers/report_chapter.go

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

+ 50 - 63
controllers/report_chapter_type.go

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

+ 1956 - 0
controllers/report_v2.go

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

+ 8 - 9
controllers/smart_report/smart_report.go

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

+ 90 - 28
controllers/voice.go

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

+ 21 - 0
models/chart_permission.go

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

+ 153 - 68
models/classify.go

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

+ 46 - 0
models/company/company_config.go

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

+ 189 - 4
models/data_manage/chart_info.go

@@ -263,6 +263,54 @@ type EditChartInfoReq struct {
 	UnitEn               string                  `description:"英文单位名称"`
 }
 
+type MarkersLine struct {
+	Axis             int             `json:"axis" description:"1左轴 2右轴 3横轴"`
+	AxisName         string          `json:"axisName" description:"轴的名称,例如'左轴'"`
+	MarkerType       string          `json:"markerType" description:"标识线或标识区"`
+	MarkLineType     int             `json:"markLineType" description:"1:固定 2:指标计算"`
+	Value            string          `json:"value" description:"连线指向的数值,例如'4000'"`
+	FromValue        string          `json:"fromValue" description:"连线的起始点,可以为空"`
+	ToValue          string          `json:"toValue" description:"连线的结束点,可以为空"`
+	LineWidth        float64         `json:"lineWidth" description:"连线的宽度"`
+	DashStyle        string          `json:"dashStyle" description:"连线的虚线样式,例如'ShortDashDot'"`
+	Color            string          `json:"color" description:"连线的颜色"`
+	Text             string          `json:"text" description:"连线旁边显示的文本"`
+	TextPosition     string          `json:"textPosition" description:"文本的显示位置,例如'bottom'"`
+	TextColor        string          `json:"textColor" description:"文本颜色"`
+	TextFontSize     int             `json:"textFontSize" description:"文本的字号大小"`
+	IsShow           bool            `json:"isShow" description:"是否显示连线及文本"`
+	EdbType          int             `json:"edbType" description:"指标类型 0图中第一个指标 1其他指标 前端回显用"`
+	EdbInfoId        int             `json:"edbInfoId" description:"指标id"`
+	Calculation      int             `json:"calculation" description:"计算方式 1区间均值 2区间均值加N倍标准差 3区间个数分位 4区间数值分位"`
+	CalculationValue float64         `json:"calculationValue" description:"计算方式对应的值 2就是几倍标准差 3就是分位值 4就是数值值·"`
+	TimeIntervalType int             `json:"timeInterval" description:"时间区间 0跟随图表 1自定义"`
+	StartDate        MarkersLineTime `json:"startTime" description:"开始时间"`
+	EndDate          MarkersLineTime `json:"endTime" description:"结束时间"`
+}
+
+type MarkersLineTime struct {
+	TimeType int               `json:"timeType" description:"时间类型 1固定 2动态 3至今(仅结束时间)"`
+	Date     string            `json:"date" description:"日期"`
+	Conf     EdbDateChangeConf `json:"conf" description:"动态时间配置"`
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `json:"moveForward" description:"前移的期数"`
+	BaseDate    int `json:"baseDate" description:"基准日期 0系统日期 1指标最新日期"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+type EdbDateConfDateChange struct {
+	Year         int    `json:"year" description:"前移的期数"`
+	Month        int    `json:"month" description:"前移的期数"`
+	Day          int    `json:"day" description:"前移的期数"`
+	Frequency    string `json:"moveForward" description:"频度变换"`
+	FrequencyDay string `json:"frequencyDay" description:"频度的固定日期"`
+	ChangeType   int    `json:"changeType" description:"日期变换类型1日期位移,2指定频率"`
+}
+
 type EditChartEnInfoReq struct {
 	ChartInfoId      int                       `description:"图表ID"`
 	ChartNameEn      string                    `description:"英文图表名称"`
@@ -1429,19 +1477,129 @@ type PreviewChartInfoReq struct {
 	SeasonExtraConfig SeasonExtraItem  `description:"季节性图表中的配置,json数据"`
 	StartYear         int              `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
 	ChartSource       int
+	MarkersLines      string `description:"标识线"`
 }
 
 type SeasonExtraItem struct {
-	ChartLegend []SeasonChartLegend `description:"自定义的图例名称"`
-	XStartDate  string              `description:"横坐标显示的起始日"`
-	XEndDate    string              `description:"横坐标显示的截止日"`
-	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	ChartLegend                 []SeasonChartLegend         `description:"自定义的图例名称"`
+	XStartDate                  string                      `description:"横坐标显示的起始日"`
+	XEndDate                    string                      `description:"横坐标显示的截止日"`
+	JumpYear                    int                         `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
 }
 
 type SeasonChartLegend struct {
 	Name  string
 	Value string
 }
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int     `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string  `description:"生成样式"`
+	Shape         string  `description:"形状"`
+	ChartColor    string  `description:"图表颜色"`
+	Size          float64 `description:"大小"`
+	Legend        string  `description:"图例名称"`
+	NumFormat     int     `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int     `description:"是否连接 0不连接 1连接"`
+	LineColor     string  `description:"线条颜色"`
+	LineWidth     float64 `description:"线条宽度"`
+	LineStyle     string  `description:"线条样式"`
+	IsShow        bool    `description:"是否显示"`
+	IsAdd         bool    `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string  `description:"颜色"`
+	Year      int     `description:"均线取值范围"`
+	Legend    string  `description:"图例名称"`
+	LineType  string  `description:"线型"`
+	LineWidth float64 `description:"线宽"`
+	IsShow    bool    `description:"是否显示"`
+	IsAdd     bool    `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth float64                  `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*ChartEdbInfoMapping
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
+}
+
 type AddChartInfoResp struct {
 	ChartInfoId int    `description:"图表id"`
 	UniqueCode  string `description:"图表唯一编码"`
@@ -1940,6 +2098,7 @@ type BarChartInfoReq struct {
 	YEdbList      []BarChartInfoEdbItemReq `description:"Y轴选择的指标列表"`
 	Unit          string                   `description:"中文单位"`
 	UnitEn        string                   `description:"英文单位"`
+	MarkersLines  string                   `description:"标识线"`
 }
 
 // BarChartInfoEdbItemReq 柱方图预览请求数据(指标相关)
@@ -2416,6 +2575,32 @@ func GetChartInfoBySourceAndParentId(source, parentId, adminId int) (items []*Ch
 	return
 }
 
+// PreviewSeasonChartReq 预览季节性图的请求入参
+type PreviewSeasonChartReq struct {
+	ChartEdbInfoList  []*ChartSaveItem `description:"指标及配置信息"`
+	SeasonExtraConfig SeasonExtraItem  `description:"季节图额外配置信息,json字符串"`
+}
+
+// EditChartInfoImageV2
+// @Description: 修改图表的缩略图
+// @author: Roc
+// @datetime 2024-07-09 10:52:34
+// @param chartInfoId int
+// @param imageUrl string
+// @return err error
+func EditChartInfoImageV2(chartInfoId int, imageUrl string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	sql := ` UPDATE  chart_info SET chart_image=?, modify_time = NOW() WHERE chart_info_id = ? `
+	_, err = o.Raw(sql, imageUrl, chartInfoId).Exec()
+	if err != nil {
+		fmt.Println("EditChartInfoImageV2 Err:", err.Error())
+		return err
+	}
+
+	return
+}
+
 func getThsHfEdbDataListByMongo(source, subSource, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
 	list = make([]*EdbDataList, 0)
 

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

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

+ 10 - 6
models/db.go

@@ -15,6 +15,7 @@ import (
 	"eta/eta_api/models/eta_trial"
 	"eta/eta_api/models/fe_calendar"
 	"eta/eta_api/models/ppt_english"
+	"eta/eta_api/models/report"
 	"eta/eta_api/models/report_approve"
 	"eta/eta_api/models/sandbox"
 	"eta/eta_api/models/semantic_analysis"
@@ -253,12 +254,15 @@ func initReport() {
 		new(ChartPermission),                     // 权限表
 		new(YbPcSuncode),
 		new(YbSuncodePars),
-		new(ReportAuthor),                  //报告作者
-		new(ClassifyMenu),                  // 报告分类-子目录表
-		new(ClassifyMenuRelation),          // 报告分类-子目录关联表
-		new(ChartPermissionChapterMapping), // 权限mapping表
-		new(ReportChapterType),             // 报告章节类型表
-		new(ReportStateRecord),             // 研报状态修改记录表
+		new(ReportAuthor),                          //报告作者
+		new(ClassifyMenu),                          // 报告分类-子目录表
+		new(ClassifyMenuRelation),                  // 报告分类-子目录关联表
+		new(ChartPermissionChapterMapping),         // 权限mapping表
+		new(ReportChapterType),                     // 报告章节类型表
+		new(ReportStateRecord),                     // 研报状态修改记录表
+		new(report.ReportGrant),                    // 报告授权用户表
+		new(report.ReportChapterGrant),             // 报告章节授权用户表
+		new(report.ReportChapterPermissionMapping), // 报告章节的权限关系表
 	)
 }
 

+ 16 - 0
models/english_report.go

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

+ 25 - 0
models/permission.go

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

+ 3 - 3
models/ppt_v2.go

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

+ 426 - 95
models/report.go

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

+ 110 - 0
models/report/report_chapter_grant.go

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

+ 152 - 0
models/report/report_chapter_permission_mapping.go

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

+ 126 - 0
models/report/report_grant.go

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

+ 1 - 1
models/report_approve/constant.go

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

+ 11 - 1
models/report_approve/report_approve.go

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

+ 12 - 1
models/report_approve/report_approve_flow.go

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

+ 39 - 0
models/report_approve/report_approve_record.go

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

+ 343 - 43
models/report_chapter.go

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

+ 71 - 24
models/report_chapter_type.go

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

+ 60 - 22
models/report_chapter_type_permission.go

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

+ 66 - 0
models/report_grant.go

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

+ 453 - 0
models/report_v2.go

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

+ 23 - 0
models/smart_report/smart_resource.go

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

+ 19 - 0
models/system/sys_admin.go

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

+ 3 - 3
models/wechat_send_msg.go

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

+ 144 - 9
routers/commentsRouter.go

@@ -1348,6 +1348,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoImgSetBySvg",
+            Router: `/chart_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/future_good:FutureGoodChartInfoController"],
         beego.ControllerComments{
             Method: "ChartList",
@@ -2617,6 +2626,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "ChartInfoImgSetBySvg",
+            Router: `/chart_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "ChartInfoMove",
@@ -2644,6 +2662,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "PreviewSeasonChartInfo",
+            Router: `/chart_info/preview/season`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "PreviewSectionScatterChartInfo",
@@ -3643,6 +3670,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
+        beego.ControllerComments{
+            Method: "ChartImageSetBySvg",
+            Router: `/edb_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInfoController"],
         beego.ControllerComments{
             Method: "SetEdbDataInsertConfig",
@@ -5146,6 +5182,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"],
+        beego.ControllerComments{
+            Method: "ChartImageSetBySvg",
+            Router: `/predict_edb_info/image/set_by_svg`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:PredictEdbInfoController"],
         beego.ControllerComments{
             Method: "ClassifyEdbInfoItems",
@@ -7423,6 +7468,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
+        beego.ControllerComments{
+            Method: "ClassifyPermissionV2",
+            Router: `/permission/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ClassifyController"],
         beego.ControllerComments{
             Method: "ClassifyPermission",
@@ -8370,8 +8424,8 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "AddDayWeekReport",
-            Router: `/addDayWeekReport`,
+            Method: "CancelApprove",
+            Router: `/approve/cancel`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -8379,8 +8433,8 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "CancelApprove",
-            Router: `/approve/cancel`,
+            Method: "SubmitApprove",
+            Router: `/approve/submit`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -8388,8 +8442,17 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "SubmitApprove",
-            Router: `/approve/submit`,
+            Method: "Author",
+            Router: `/author`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "AddChapter",
+            Router: `/chapter/add`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -8397,13 +8460,58 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "Author",
-            Router: `/author`,
+            Method: "EditChapterBaseInfoAndPermission",
+            Router: `/chapter/base_info/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "DelChapter",
+            Router: `/chapter/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "ChapterMove",
+            Router: `/chapter/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditChapterTitle",
+            Router: `/chapter/title/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "GetUnPublishReportChapterList",
+            Router: `/chapter/un_publish/list`,
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "VoiceUpload",
+            Router: `/chapter/voice/upload`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "ClassifyIdDetail",
@@ -8431,6 +8539,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "BaseDetail",
+            Router: `/detail/base`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "Edit",
@@ -8566,6 +8683,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "EditLayoutImg",
+            Router: `/layout_img/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "ListReport",
@@ -8575,6 +8701,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "AuthorizedListReport",
+            Router: `/list/authorized`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "MarkEditStatus",
@@ -8604,7 +8739,7 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
-            Method: "PublishCancleReport",
+            Method: "PublishCancelReport",
             Router: `/publish/cancle`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),

+ 671 - 0
services/classify.go

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

+ 1233 - 5
services/data/chart_info.go

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

+ 58 - 1
services/data/chart_info_excel_balance.go

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

+ 10 - 0
services/data/chart_theme.go

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

+ 106 - 0
services/data/edb_data.go

@@ -519,6 +519,112 @@ func getPageDataByMongo(edbInfoId, source, subSource int, endDataTime string, st
 	return
 }
 
+// GetEdbDataTbzForSeason 获取指标的同比值数据
+func GetEdbDataTbzForSeason(frequency string, tmpDataList []*data_manage.EdbDataList, startDateTime time.Time) (dataList []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
+	dataList = make([]*data_manage.EdbDataList, 0)
+
+	// 数据处理
+	var dateArr []string
+	dataMap := make(map[string]*data_manage.EdbDataList)
+	for _, v := range tmpDataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	for _, av := range dateArr {
+		currentItem, ok := dataMap[av]
+		// 如果找不到当前日期的数据,那么终止当前循环,进入下一循环
+		if !ok {
+			continue
+		}
+		tmpItem := *currentItem
+		var isOk bool //是否计算出来结果
+
+		//当前日期
+		currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 如果存在开始日期,同时,当前日期早于开始日期,那么终止当前循环,进入下一循环
+		if !startDateTime.IsZero() && currentDate.Before(startDateTime) {
+			continue
+		}
+		//上一年的日期
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+			tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+			isOk = true
+		} else {
+			if frequency == "月度" { //向上和向下,各找一个月
+				for i := 0; i <= 35; i++ {
+					nextDateDay := preDate.AddDate(0, 0, i)
+					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						preDateDay := preDate.AddDate(0, 0, -i)
+						preDateDayStr := preDateDay.Format(utils.FormatDate)
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			} else if frequency == "季度" || frequency == "年度" {
+				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期->下一个月找到
+					tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+					isOk = true
+					break
+				}
+			} else {
+				nextDateDay := preDate.AddDate(0, 0, 1)
+				nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+
+				preDateDay := preDate.AddDate(0, 0, -1)
+				preDateDayStr := preDateDay.Format(utils.FormatDate)
+
+				for i := 0; i < 35; i++ {
+					if i >= 1 {
+						nextDateDay = nextDateDay.AddDate(0, 0, i)
+						nextDateDayStr = nextDateDay.Format(utils.FormatDate)
+					}
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						if i >= 1 {
+							preDateDay = preDate.AddDate(0, 0, -i)
+							preDateDayStr = nextDateDay.Format(utils.FormatDate)
+						}
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			}
+		}
+
+		if isOk {
+			if tmpItem.Value > maxValue {
+				maxValue = tmpItem.Value
+			}
+			if tmpItem.Value < minValue {
+				minValue = tmpItem.Value
+			}
+			dataList = append(dataList, &tmpItem)
+		}
+	}
+
+	return
+}
+
 func getThsHfPageDataByMongo(edbInfoId, source, subSource int, endDataTime string, startSize, pageSize int) (dataCount int, dataList []*data_manage.EdbData, err error) {
 	dataList = make([]*data_manage.EdbData, 0)
 

+ 140 - 0
services/data/edb_info_calculate.go

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

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

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

+ 2 - 0
services/elastic.go

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

+ 99 - 0
services/file.go

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

+ 52 - 10
services/ppt.go

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

+ 194 - 473
services/report.go

@@ -5,13 +5,13 @@ import (
 	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/models/company"
+	"eta/eta_api/models/report"
 	"eta/eta_api/models/system"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/public_api"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/PuerkitoBio/goquery"
-	"github.com/rdlucklib/rdluck_tools/http"
 	"html"
 	"os"
 	"regexp"
@@ -45,246 +45,17 @@ func GetReportContentSub(content string) (contentSub string, err error) {
 	return
 }
 
-type ZgParam struct {
-}
-
-// 找钢网
-func ZhaoGangSend(report *models.ReportDetail) (err error) {
-	defer func() {
-		if err != nil {
-			go alarm_msg.SendAlarmMsg("发送报告至找刚网失败,Err"+err.Error(), 3)
-			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "发送报告至找刚网失败 ErrMsg:"+err.Error(), utils.EmailSendToUsers)
-		}
-	}()
-	reportIdStr := strconv.Itoa(report.Id)
-	articleId := utils.MD5(reportIdStr)
-
-	var reportType int
-	if report.ClassifyNameSecond == "知白守黑日评" {
-		reportType = 1
-	} else {
-		reportType = 2
-	}
-	contentHtml := html.UnescapeString(report.Content)
-
-	doc, err := goquery.NewDocumentFromReader(strings.NewReader(contentHtml))
-	if err != nil {
-		fmt.Println("Create Doc Err:" + err.Error())
-		err = errors.New("Create Doc Err:" + err.Error())
-		return
-	}
-	doc.Find("p").Each(func(i int, p *goquery.Selection) {
-		phtml, err := p.Html()
-		if err != nil {
-			fmt.Println("Err:" + err.Error())
-			return
-		}
-		if phtml == "<br/>" {
-			if i == 0 {
-				p.Remove()
-			} else {
-				p.ReplaceWithHtml("<br/>")
-			}
-		} else {
-			p.SetAttr("style", "line-height:30px")
-		}
-	})
-	doc.Find("img").Each(func(i int, img *goquery.Selection) {
-		img.SetAttr("style", "width:100%;")
-	})
-	contentHtml, _ = doc.Find("body").Html()
-
-	createDate, _ := time.Parse(utils.FormatDateTime, report.CreateTime)
-	createDay := createDate.Format("0102")
-	title := "【第 " + strconv.Itoa(report.Stage) + "期|FICC" + "】 " + report.Title + "(" + createDay + ")"
-	contentSummary := report.Abstract
-
-	postUrl := `http://appserver.index.zhaogang.com/double.index.appserver.service/api/v1/wechat/article/hongze/push`
-	signStr := "zhaogang_data_vip" + "articleId" + articleId + "type" + strconv.Itoa(reportType) + "title" + title + "contentSummary" + contentSummary + "zhaogang_data_vip"
-	fmt.Println("signStr:", signStr)
-	sign := utils.MD5(signStr)
-	disclaimers := `<p>1、本报告仅供弘则弥道(上海)投资咨询有限公司正式签约的机构客户使用,不会仅因接收人/接受机构收到本报告而将其视为客户。</p >         <p>2、本报告根据国际和行业通行的准则,以合法渠道获得这些信息,尽可能保证可靠、准确和完整,但并不保证报告所述信息的准确性和完整性,也不保证本报告所包含的信息或建议在本报告发出后不会发生任何变更。本报告中所提供的信息仅供参考。</p >         <p>3、报告中的内容不对投资者做出的最终操作建议做任何的担保,也没有任何形式的分享投资收益或者分担投资损失的书面或口头承诺。不作为客户在投资、法律、会计或税务等方面的最终操作建议,也不作为道义的、责任的和法律的依据或者凭证,无论是否已经明示或者暗示。</p >         <p>4、在任何情况下,本公司不对客户/接受人/接受机构因使用报告中内容所引致的一切损失负责任,客户/接受人/接受机构需自行承担全部风险。</p >`
-	param := make(map[string]interface{})
-	dataMap := make(map[string]interface{})
-	contentMap := make(map[string]interface{})
-
-	dataMap["articleId"] = articleId
-	dataMap["type"] = reportType
-	dataMap["title"] = title
-	dataMap["contentSummary"] = contentSummary
-
-	contentMap["contentHtml"] = contentHtml
-	contentMap["articleAuthor"] = report.Author
-	//contentMap["publishedTime"] = report.PublishTime.Format(utils.FormatDateTime)
-	contentMap["publishedTime"] = report.PublishTime
-	contentMap["audioName"] = report.VideoName
-	contentMap["audioUrl"] = report.VideoUrl
-
-	videoPlaySeconds := report.VideoPlaySeconds
-	f, _ := strconv.ParseFloat(videoPlaySeconds, 64)
-	contentMap["audioLength"] = f * 1000
-	contentMap["disclaimers"] = disclaimers
-
-	param["cardData"] = dataMap
-	param["contentData"] = contentMap
-	param["sign"] = strings.ToUpper(sign)
-
-	paramJson, err := json.Marshal(param)
-	if err != nil {
-		fmt.Println("param json.Marshal Err:" + err.Error())
-		err = errors.New("param json.Marshal Err:" + err.Error())
-		return
-	}
-
-	utils.FileLog.Info("ZhaoGangSend parms:%s", string(paramJson))
-	result, err := http.Post(postUrl, string(paramJson), "application/json")
-	if err != nil {
-		fmt.Println("post err:" + err.Error())
-		err = errors.New("post Err:" + err.Error())
-		return
-	}
-	utils.FileLog.Info("ZhaoGangSend Result:%s", string(result))
-	//返回数据校验
-	mapResult := make(map[string]interface{})
-	err = json.Unmarshal(result, &mapResult)
-	if err != nil {
-		fmt.Println("找钢网返回数据转json失败: err:", err.Error(), ";返回数据:", string(result))
-		err = errors.New(fmt.Sprint("找钢网返回数据转json失败: err:", err.Error(), ";返回数据:", string(result)))
-		return
-	}
-	if resultCode, ok := mapResult["code"]; ok {
-		tmpResultCode := resultCode.(float64)
-		if tmpResultCode != 200 {
-			fmt.Println("找钢网返回数据异常,code返回参异常;返回数据:", string(result))
-			err = errors.New(fmt.Sprint("找钢网返回数据异常,code返回参异常;返回数据:", string(result)))
-			return
-		}
-	} else {
-		fmt.Println("找钢网返回数据异常,缺少code返回参;返回数据:", string(result))
-		err = errors.New(fmt.Sprint("找钢网返回数据异常,缺少code返回参;返回数据:", string(result)))
-		return
-	}
-
-	utils.FileLog.Info("%s", string(result))
-	return
-}
-
-/*func init() {
-	fmt.Println("start")
-	vint := 845
-	report, err := models.GetReportById(vint)
-	if err != nil {
-		fmt.Println("GetReportById Err", err.Error())
-		return
-	}
-	ZhaoGangSend(report)
-	fmt.Println("end")
-	return
-}*/
-
-// PublishDayWeekReport 发布晨周报
-func PublishDayWeekReport(reportId int) (tips string, err error) {
-	report, err := models.GetReportByReportId(reportId)
-	if err != nil {
-		return
-	}
-	if report.State == 2 {
-		return
-	}
-	chapters, err := models.GetChapterListByReportId(reportId)
-	if err != nil {
-		return
-	}
-	chapterLen := len(chapters)
-	if chapterLen <= 0 {
-		err = errors.New("报告章节为空,不可发布")
-		return
-	}
-	reportType := chapters[0].ReportType
-	// 校验章节
-	publishReport, tips, publishIdArr, unPublishIdArr, err := checkDayWeekChapterWrite(chapters, reportType)
-	if err != nil {
-		return
-	}
-	publishLen := len(publishIdArr)
-	if publishLen <= 0 {
-		err = errors.New("报告章节均不可发布")
-		return
-	}
-
-	// 需发布整期
-	updateCols := make([]string, 0)
-	if publishReport {
-		updateCols = append(updateCols, "Title", "State", "ModifyTime")
-
-		// 发布后标题调整
-		title := report.Title
-		title = strings.ReplaceAll(title, "【弘则FICC晨报】", "")
-		title = strings.ReplaceAll(title, "【弘则FICC周报】", "")
-		if title == "" {
-			// 取第一个需发布章节的标题
-			firstId := publishIdArr[0]
-			firstTitle := ""
-			for i := 0; i < chapterLen; i++ {
-				if chapters[i].ReportChapterId == firstId {
-					firstTitle = chapters[i].Title
-					break
-				}
-			}
-			title = firstTitle
-		}
-		report.Title = title
-		report.State = 2
-
-		// 研报后台4.4 只在没有发布过时更新发布时间,其余均按模版消息发送时间当作发布时间
-		if report.MsgIsSend == 0 || report.PublishTime.IsZero() {
-			report.PublishTime = time.Now().Local()
-			updateCols = append(updateCols, "PublishTime")
-
-		}
-		report.ModifyTime = time.Now().Local()
-	}
-	publishIdStr := utils.IntArr2joinString(publishIdArr, ",")
-	//unPublishIdStr := utils.IntArr2joinString(unPublishIdArr, ",")
-
-	if e := models.PublishReportAndChapter(report, publishIdArr, unPublishIdArr, publishReport, updateCols); e != nil {
-		err = errors.New("发布报告及章节失败")
-		return
-	}
-	// 生成章节音频
-	go func() {
-		_ = UpdateChaptersVideo(publishIdStr)
-	}()
-	// 更新报告ES
-	go func() {
-		_ = UpdateReportEs(report.Id, 2)
-	}()
-
-	// 发布时备份内容
-	go SaveReportLogs(report, chapters, report.AdminId, report.AdminRealName)
-	return
-}
-
 // UpdateChaptersVideo 更新章节音频
-func UpdateChaptersVideo(chapterIds string) (err error) {
+func UpdateChaptersVideo(ids []int) (err error) {
 	defer func() {
 		if err != nil {
-			utils.FileLog.Error("UpdateChaptersVideo, chapterIds:%s, Err:%s", chapterIds, err.Error())
-			go alarm_msg.SendAlarmMsg("更新章节音频失败, 章节ID: "+chapterIds+", Err: "+err.Error(), 3)
+			utils.FileLog.Error("UpdateChaptersVideo, chapterIds:%v, Err:%s", ids, err.Error())
+			go alarm_msg.SendAlarmMsg(fmt.Sprintf("更新章节音频失败, 章节ID: %v; Err: "+err.Error(), ids), 3)
 		}
 	}()
-	if chapterIds == "" {
+	if len(ids) <= 0 {
 		return
 	}
-	ids := make([]int, 0)
-	chapterIdArr := strings.Split(chapterIds, ",")
-	for _, v := range chapterIdArr {
-		id, e := strconv.Atoi(v)
-		if e != nil {
-			return
-		}
-		ids = append(ids, id)
-	}
 
 	chapterList, err := models.GetChapterListByChapterIds(ids)
 	if err != nil {
@@ -317,41 +88,41 @@ func UpdateChaptersVideo(chapterIds string) (err error) {
 }
 
 // PublishTodayDayReport 发布今日晨报
-func PublishTodayDayReport() (err error) {
-	nowTime := time.Now()
-	startTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.Local)
-	endTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 23, 59, 59, 0, time.Local)
-	todayReport, err := models.GetUnPublishDayReport(startTime, endTime)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() { //如果是找不到待发送的晨报,那么需要将err置空
-			err = nil
-		}
-		return
-	}
-	if todayReport != nil {
-		if _, tmpErr := PublishDayWeekReport(todayReport.Id); tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		// 定时发布的晨报自动推送客群
-		reportDetail, tmpErr := models.GetReportById(todayReport.Id)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		// 推送模板消息
-		if tmpErr = SendMiniProgramReportWxMsg(todayReport.Id); tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		if tmpErr = models.ModifyReportThsMsgIsSend(reportDetail); tmpErr != nil {
-			err = tmpErr
-			return
-		}
-	}
-
-	return
-}
+//func PublishTodayDayReport() (err error) {
+//	nowTime := time.Now()
+//	startTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.Local)
+//	endTime := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 23, 59, 59, 0, time.Local)
+//	todayReport, err := models.GetUnPublishDayReport(startTime, endTime)
+//	if err != nil {
+//		if err.Error() == utils.ErrNoRow() { //如果是找不到待发送的晨报,那么需要将err置空
+//			err = nil
+//		}
+//		return
+//	}
+//	if todayReport != nil {
+//		if _, tmpErr, _ := PublishChapterReport(todayReport, "", nil); tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//		// 定时发布的晨报自动推送客群
+//		reportDetail, tmpErr := models.GetReportById(todayReport.Id)
+//		if tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//		// 推送模板消息
+//		if tmpErr = SendMiniProgramReportWxMsg(todayReport.Id); tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//		if tmpErr = models.ModifyReportThsMsgIsSend(reportDetail); tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//	}
+//
+//	return
+//}
 
 // UpdateReportEs 更新报告/章节Es
 func UpdateReportEs(reportId int, publishState int) (err error) {
@@ -370,46 +141,21 @@ func UpdateReportEs(reportId int, publishState int) (err error) {
 			return
 		}
 		if len(chapterList) > 0 {
-			for i := 0; i < len(chapterList); i++ {
-				// 章节对应的品种
-				permissionList, tmpErr := models.GetChapterTypePermissionByTypeIdAndResearchType(chapterList[i].TypeId, chapterList[i].ReportType)
-				if tmpErr != nil {
-					return
-				}
-				categoryArr := make([]string, 0)
-				if len(permissionList) > 0 {
-					for ii := 0; ii < len(permissionList); ii++ {
-						categoryArr = append(categoryArr, permissionList[ii].PermissionName)
-					}
-				}
-				aliasArr, _ := addCategoryAliasToArr(categoryArr)
-				chapterCategories := strings.Join(aliasArr, ",")
-
-				esChapter := &models.ElasticReportDetail{
-					ReportId:           chapterList[i].ReportId,
-					ReportChapterId:    chapterList[i].ReportChapterId,
-					Title:              chapterList[i].Title,
-					Abstract:           chapterList[i].Abstract,
-					BodyContent:        utils.TrimHtml(html.UnescapeString(chapterList[i].Content)),
-					PublishTime:        chapterList[i].PublishTime.Format(utils.FormatDateTime),
-					PublishState:       chapterList[i].PublishState,
-					Author:             chapterList[i].Author,
-					ClassifyIdFirst:    chapterList[i].ClassifyIdFirst,
-					ClassifyNameFirst:  chapterList[i].ClassifyNameFirst,
-					ClassifyIdSecond:   0,
-					ClassifyNameSecond: "",
-					Categories:         chapterCategories,
-					StageStr:           strconv.Itoa(chapterList[i].Stage),
-				}
-				chapterDocId := fmt.Sprintf("%d-%d", reportInfo.Id, chapterList[i].ReportChapterId)
-				if err = EsAddOrEditReport(utils.EsReportIndexName, chapterDocId, esChapter); err != nil {
+			// 更新章节的es数据
+			for _, chapterInfo := range chapterList {
+				err = updateReportChapterEsByChapter(chapterInfo)
+				if err != nil {
 					return
 				}
 			}
 		}
 	} else {
-		//if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
-		permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword("rddp", reportInfo.ClassifyIdSecond)
+		// 获取最小分类的id
+		minClassifyId, _, tmpErr := getMinClassify(reportInfo)
+		if tmpErr != nil {
+			return
+		}
+		permissionList, tmpErr := models.GetChartPermissionNameFromMappingByKeyword("rddp", minClassifyId)
 		if tmpErr != nil {
 			return
 		}
@@ -422,6 +168,12 @@ func UpdateReportEs(reportId int, publishState int) (err error) {
 		//}
 	}
 
+	// 最小单位的分类id
+	minClassifyId, minClassifyName, err := getMinClassify(reportInfo)
+	if err != nil {
+		return
+	}
+
 	// 新增报告ES
 	esReport := &models.ElasticReportDetail{
 		ReportId:           reportInfo.Id,
@@ -436,6 +188,8 @@ func UpdateReportEs(reportId int, publishState int) (err error) {
 		ClassifyNameFirst:  reportInfo.ClassifyNameFirst,
 		ClassifyIdSecond:   reportInfo.ClassifyIdSecond,
 		ClassifyNameSecond: reportInfo.ClassifyNameSecond,
+		ClassifyId:         minClassifyId,
+		ClassifyName:       minClassifyName,
 		Categories:         categories,
 		StageStr:           strconv.Itoa(reportInfo.Stage),
 	}
@@ -481,7 +235,12 @@ func addCategoryAliasToArr(categoryArr []string) (aliasArr []string, err error)
 	return
 }
 
-// UpdateReportChapterEs 更新报告章节ES
+// UpdateReportChapterEs
+// @Description: 通过章节id更新报告章节ES
+// @author: Roc
+// @datetime 2024-06-20 13:16:22
+// @param reportChapterId int
+// @return err error
 func UpdateReportChapterEs(reportChapterId int) (err error) {
 	if reportChapterId <= 0 {
 		return
@@ -490,26 +249,44 @@ func UpdateReportChapterEs(reportChapterId int) (err error) {
 	if err != nil {
 		return
 	}
+
+	err = updateReportChapterEsByChapter(chapterInfo)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// updateReportChapterEsByChapter
+// @Description: 通过章节详情更新报告章节ES
+// @author: Roc
+// @datetime 2024-06-20 13:16:11
+// @param chapterInfo *models.ReportChapter
+// @return err error
+func updateReportChapterEsByChapter(chapterInfo *models.ReportChapter) (err error) {
 	// 章节对应的品种
-	permissionList, tmpErr := models.GetChapterTypePermissionByTypeIdAndResearchType(chapterInfo.TypeId, chapterInfo.ReportType)
+	obj := report.ReportChapterPermissionMapping{}
+	permissionList, tmpErr := obj.GetPermissionItemListById(chapterInfo.ReportChapterId)
 	if tmpErr != nil {
 		return
 	}
 	categoryArr := make([]string, 0)
 	if len(permissionList) > 0 {
 		for ii := 0; ii < len(permissionList); ii++ {
-			categoryArr = append(categoryArr, permissionList[ii].PermissionName)
+			categoryArr = append(categoryArr, permissionList[ii].ChartPermissionName)
 		}
 	}
 	aliasArr, _ := addCategoryAliasToArr(categoryArr)
 	categories := strings.Join(aliasArr, ",")
 	// 新增/编辑ES
+
 	esChapter := &models.ElasticReportDetail{
 		ReportId:           chapterInfo.ReportId,
 		ReportChapterId:    chapterInfo.ReportChapterId,
 		Title:              chapterInfo.Title,
 		Abstract:           chapterInfo.Abstract,
-		BodyContent:        utils.TrimHtml(html.EscapeString(chapterInfo.Content)),
+		BodyContent:        utils.TrimHtml(html.UnescapeString(chapterInfo.Content)),
 		PublishTime:        chapterInfo.PublishTime.Format(utils.FormatDateTime),
 		PublishState:       chapterInfo.PublishState,
 		Author:             chapterInfo.Author,
@@ -517,6 +294,8 @@ func UpdateReportChapterEs(reportChapterId int) (err error) {
 		ClassifyNameFirst:  chapterInfo.ClassifyNameFirst,
 		ClassifyIdSecond:   0,
 		ClassifyNameSecond: "",
+		ClassifyId:         chapterInfo.ClassifyIdFirst,
+		ClassifyName:       chapterInfo.ClassifyNameFirst,
 		Categories:         categories,
 		StageStr:           strconv.Itoa(chapterInfo.Stage),
 	}
@@ -528,59 +307,6 @@ func UpdateReportChapterEs(reportChapterId int) (err error) {
 	return
 }
 
-// DeleteReportAndChapter 删除报告及章节
-func DeleteReportAndChapter(reportId int) (err error) {
-	reportInfo, err := models.GetReportByReportId(reportId)
-	if err != nil {
-		err = errors.New("报告信息有误, Err: " + err.Error())
-		return
-	}
-	if reportInfo.State == 2 {
-		err = errors.New("报告已发布,不可删除")
-		return
-	}
-	// 更新ES
-	_ = UpdateReportEs(reportId, 1)
-	// 删除
-	if reportInfo.HasChapter == 1 && (reportInfo.ChapterType == utils.REPORT_TYPE_DAY || reportInfo.ChapterType == utils.REPORT_TYPE_WEEK) {
-		err = models.DeleteDayWeekReportAndChapter(reportId)
-	} else {
-		err = models.DeleteReport(reportId)
-	}
-	if err != nil {
-		err = errors.New("删除失败, Err: " + err.Error())
-		return
-	}
-	// 重置PPT关联报告
-	go func() {
-		_ = ResetPPTReport(reportId, false)
-	}()
-
-	return
-}
-
-// UpdatePublishedReportToEs 更新已发布的报告ES
-func UpdatePublishedReportToEs() (err error) {
-	// 获取所有已发布的报告
-	var condition string
-	var pars []interface{}
-	condition = ` AND state IN (2, 6) `
-	reportList, err := models.GetReportList(condition, pars, "ficc", 1, 5000)
-	count := 0
-	failCount := 0
-	for i := 0; i < len(reportList); i++ {
-		if err = UpdateReportEs(reportList[i].Id, 2); err != nil {
-			fmt.Printf("更新失败, report_id: %d, Err: %s\n", reportList[i].Id, err.Error())
-			failCount += 1
-		} else {
-			count += 1
-		}
-	}
-	fmt.Printf("报告总数:%d, 更新成功数: %d, 更新失败数: %d", len(reportList), count, failCount)
-
-	return
-}
-
 // 替换报告内容中的base64图片
 func replaceReportBase64ToImg(content string) (newContent string, err error) {
 	if content == "" {
@@ -677,79 +403,78 @@ func reportBase64ToImg(imageBase64 string) (resourceUrl string, err error) {
 	return
 }
 
-// UpdateReportVideo 更新报告及其章节音频
-func UpdateReportVideo(reportId int) (err error) {
-	defer func() {
-		if err != nil {
-			utils.FileLog.Error("UpdateReportVideo, reportId:%s, Err:%s", strconv.Itoa(reportId), err.Error())
-			go alarm_msg.SendAlarmMsg("更新报告音频失败, 报告ID: "+strconv.Itoa(reportId)+", Err: "+err.Error(), 3)
-			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "更新报告音频失败, 报告ID: " + reportIdStr + ", Err: "+err.Error(), utils.EmailSendToUsers)
-		}
-	}()
-	if reportId == 0 {
-		return
-	}
-	reportInfo, err := models.GetReportByReportId(reportId)
-	if err != nil {
-		return
-	}
-	if reportInfo.HasChapter == 1 {
-		// 更新章节音频
-		chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		chapterIdArr := make([]string, 0)
-		for i := 0; i < len(chapterList); i++ {
-			chapterIdArr = append(chapterIdArr, strconv.Itoa(chapterList[i].ReportChapterId))
-		}
-		chapterIds := strings.Join(chapterIdArr, ",")
-		//go UpdateChaptersVideo(chapterIds)
-		err = UpdateChaptersVideo(chapterIds)
-	} else {
-		// 更新报告音频
-		if reportInfo.VideoUrl != "" {
-			return
-		}
-		nowTime := time.Now()
-		updateCols := make([]string, 0)
-		updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds")
-		videoUrl, videoName, videoSize, videoPlaySeconds, tmpErr := CreateReportVideo(reportInfo.Title, html.UnescapeString(reportInfo.Content), nowTime.Format(utils.FormatDateTime))
-		reportInfo.VideoUrl = videoUrl
-		reportInfo.VideoName = videoName
-		reportInfo.VideoSize = videoSize
-		reportInfo.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
-		tmpErr = reportInfo.UpdateReport(updateCols)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-	}
-	return
-}
-
-func UpdateEmptyVideoReportVideo() (err error) {
-	list, err := models.GetSyncEmptyVideoReport()
-	if err != nil {
-		return
-	}
-	listLen := len(list)
-	if listLen <= 0 {
-		fmt.Println("无报告需要更新音频")
-		return
-	}
-	fmt.Println("Start 待更新报告音频数: ", listLen)
-	for i := 0; i < listLen; i++ {
-		if err = UpdateReportVideo(list[i].Id); err != nil {
-			fmt.Printf("更新音频失败")
-			fmt.Println(err.Error())
-			return
-		}
-	}
-	fmt.Println("End 报告音频更新完毕")
-	return
-}
+//// UpdateReportVideo 更新报告及其章节音频
+//func UpdateReportVideo(reportId int) (err error) {
+//	defer func() {
+//		if err != nil {
+//			utils.FileLog.Error("UpdateReportVideo, reportId:%s, Err:%s", strconv.Itoa(reportId), err.Error())
+//			go alarm_msg.SendAlarmMsg("更新报告音频失败, 报告ID: "+strconv.Itoa(reportId)+", Err: "+err.Error(), 3)
+//			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "更新报告音频失败, 报告ID: " + reportIdStr + ", Err: "+err.Error(), utils.EmailSendToUsers)
+//		}
+//	}()
+//	if reportId == 0 {
+//		return
+//	}
+//	reportInfo, err := models.GetReportByReportId(reportId)
+//	if err != nil {
+//		return
+//	}
+//	if reportInfo.HasChapter == 1 {
+//		// 更新章节音频
+//		chapterList, tmpErr := models.GetPublishedChapterListByReportId(reportInfo.Id)
+//		if tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//		chapterIdArr := make([]int, 0)
+//		for i := 0; i < len(chapterList); i++ {
+//			chapterIdArr = append(chapterIdArr, chapterList[i].ReportChapterId)
+//		}
+//		//go UpdateChaptersVideo(chapterIds)
+//		err = UpdateChaptersVideo(chapterIdArr)
+//	} else {
+//		// 更新报告音频
+//		if reportInfo.VideoUrl != "" {
+//			return
+//		}
+//		nowTime := time.Now()
+//		updateCols := make([]string, 0)
+//		updateCols = append(updateCols, "VideoUrl", "VideoName", "VideoSize", "VideoPlaySeconds")
+//		videoUrl, videoName, videoSize, videoPlaySeconds, tmpErr := CreateReportVideo(reportInfo.Title, html.UnescapeString(reportInfo.Content), nowTime.Format(utils.FormatDateTime))
+//		reportInfo.VideoUrl = videoUrl
+//		reportInfo.VideoName = videoName
+//		reportInfo.VideoSize = videoSize
+//		reportInfo.VideoPlaySeconds = fmt.Sprintf("%.2f", videoPlaySeconds)
+//		tmpErr = reportInfo.UpdateReport(updateCols)
+//		if tmpErr != nil {
+//			err = tmpErr
+//			return
+//		}
+//	}
+//	return
+//}
+//
+//func UpdateEmptyVideoReportVideo() (err error) {
+//	list, err := models.GetSyncEmptyVideoReport()
+//	if err != nil {
+//		return
+//	}
+//	listLen := len(list)
+//	if listLen <= 0 {
+//		fmt.Println("无报告需要更新音频")
+//		return
+//	}
+//	fmt.Println("Start 待更新报告音频数: ", listLen)
+//	for i := 0; i < listLen; i++ {
+//		if err = UpdateReportVideo(list[i].Id); err != nil {
+//			fmt.Printf("更新音频失败")
+//			fmt.Println(err.Error())
+//			return
+//		}
+//	}
+//	fmt.Println("End 报告音频更新完毕")
+//	return
+//}
 
 // checkDayWeekChapterWrite 校验晨周报已写章节与本期应写章节
 func checkDayWeekChapterWrite(chapters []*models.ReportChapter, reportType string) (publishReport bool, tips string, publishIdArr, unPublishIdArr []int, err error) {
@@ -957,7 +682,7 @@ func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId in
 		}
 		contentSub = sub
 	}
-	maxStage, e := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond)
+	maxStage, e := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
 	if e != nil {
 		errMsg = "期数获取失败!"
 		err = errors.New("期数获取失败,Err:" + e.Error())
@@ -983,36 +708,31 @@ func CreateNewReport(req models.AddReq, adminInfo *system.Admin) (newReportId in
 	item.ReportVersion = req.ReportVersion
 	item.AdminId = adminInfo.AdminId
 	item.AdminRealName = adminInfo.RealName
-	newReportId, e = models.AddReport(item)
-	if e != nil {
-		errMsg = "保存失败"
-		err = errors.New("保存失败,Err:" + e.Error())
-		return
-	}
 
-	// 处理权限
-	//if utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox {
-	go func() {
-		permissionItems, e := models.GetPermission(req.ClassifyIdSecond)
-		if e != nil {
-			alarm_msg.SendAlarmMsg("获取权限失败,Err:"+err.Error(), 3)
-		}
-		for _, v := range permissionItems {
-			e = models.AddChartPermissionChapterMapping(v.ChartPermissionId, newReportId)
-			if e != nil {
-				alarm_msg.SendAlarmMsg("新增权限失败,Err:"+err.Error(), 3)
-			}
-		}
-		// 同步crm权限
-		_ = EditReportPermissionSync(newReportId, req.ClassifyIdSecond)
-	}()
-	//}
+	item.ClassifyIdThird = req.ClassifyIdThird
+	item.ClassifyNameThird = req.ClassifyNameThird
+	// 产品要求,如果是多人协作,那么就是章节类型的报告
+	if req.CollaborateType == 2 {
+		item.HasChapter = 1
+		item.ChapterType = ""
+	}
+	item.LastModifyAdminId = adminInfo.AdminId
+	item.LastModifyAdminName = adminInfo.RealName
+	item.ContentModifyTime = time.Now()
+	item.NeedSplice = 1
+	item.ContentStruct = html.EscapeString(req.ContentStruct)
+	item.HeadImg = req.HeadImg
+	item.EndImg = req.EndImg
+	item.CanvasColor = req.CanvasColor
+	item.HeadResourceId = req.HeadResourceId
+	item.EndResourceId = req.EndResourceId
+	item.CollaborateType = req.CollaborateType
+	item.ReportLayout = req.ReportLayout
+	item.IsPublicPublish = req.IsPublicPublish
+	item.ReportCreateTime = time.Now()
+
+	err, errMsg = AddReportAndChapter(item, 0, req.GrantAdminIdList)
 
-	reportCode = utils.MD5(strconv.Itoa(int(newReportId)))
-	//修改唯一编码
-	{
-		go models.ModifyReportCode(newReportId, reportCode)
-	}
 	return
 }
 
@@ -1196,10 +916,14 @@ func GetReportContentSubWithoutIframe(content string) (contentSub string, err er
 }
 
 // UpdateReportEditMark 更新研报当前更新状态
-// status 枚举值 1:编辑中,0:完成编辑, 2:只做查询
-func UpdateReportEditMark(reportId, nowUserId, status int, nowUserName, lang string) (ret models.MarkReportResp, err error) {
+// status 枚举值 1:编辑中, 2:只做查询,3:完成编辑
+func UpdateReportEditMark(reportId, reportChapterId, nowUserId, status int, nowUserName, lang string) (ret models.MarkReportResp, err error) {
 	//更新标记key
 	key := fmt.Sprint(`crm:report:edit:`, reportId)
+	//  章节id不为0则加上章节id
+	if reportChapterId > 0 {
+		key = fmt.Sprint(key, ":", reportChapterId)
+	}
 	ret.Status = 0
 	ret.Msg = "无人编辑"
 
@@ -1228,9 +952,6 @@ func UpdateReportEditMark(reportId, nowUserId, status int, nowUserName, lang str
 		classifyNameFirst = reportInfo.ClassifyNameFirst
 	}
 
-	if classifyNameFirst == "晨报" || classifyNameFirst == "周报" {
-		return
-	}
 	if opUserId > 0 && opUserId != nowUserId {
 		editor := opUser.Editor
 		if editor == "" {
@@ -1260,12 +981,12 @@ func UpdateReportEditMark(reportId, nowUserId, status int, nowUserName, lang str
 			return
 		}
 		if opUserId > 0 {
-			utils.Rc.Do("SETEX", key, int64(180), string(bt)) //3分钟缓存
+			utils.Rc.Do("SETEX", key, int64(60), string(bt)) //3分钟缓存
 		} else {
-			utils.Rc.SetNX(key, string(bt), time.Second*60*3) //3分钟缓存
+			utils.Rc.SetNX(key, string(bt), time.Second*60*1) //3分钟缓存
 		}
-	} else if status == 0 {
-		//清除编辑缓存
+	} else if status == 3 {
+		//完成编辑,开始清除编辑缓存
 		_ = utils.Rc.Delete(key)
 	}
 	return
@@ -1292,7 +1013,7 @@ func SaveReportLogs(item *models.Report, chapters []*models.ReportChapter, admin
 	}()
 
 	if item != nil {
-		e := models.AddReportSaveLog(item.Id, item.AdminId, item.Content, item.ContentSub, item.AdminRealName)
+		e := models.AddReportSaveLog(item.Id, item.AdminId, item.Content, item.ContentSub, item.ContentStruct, item.CanvasColor, item.AdminRealName, item.HeadResourceId, item.EndResourceId)
 		if e != nil {
 			err = fmt.Errorf("AddReportSaveLog: %s", e.Error())
 			return

+ 59 - 23
services/report_approve.go

@@ -28,14 +28,16 @@ func GetReportClassifyTreeRecursive(list []*models.Classify, parentId int) []*re
 }
 
 // CheckReportApproveFlowChange 校验是否可变更分类
-func CheckReportApproveFlowChange(reportType, classifyFirstId, classifySecondId int) (ok bool, err error) {
+func CheckReportApproveFlowChange(reportType, classifyFirstId, classifySecondId, classifyThirdId 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, "")
+		cond += ` AND classify_id_third = ?  `
+		pars = append(pars, classifyThirdId)
+		ct, e := models.GetReportListCount(cond, pars)
 		if e != nil {
 			err = fmt.Errorf("GetReportListCount err: %s", e.Error())
 			return
@@ -67,7 +69,7 @@ func CheckReportApproveFlowChange(reportType, classifyFirstId, classifySecondId
 }
 
 // CheckReportOpenApprove 校验报告是否开启了审批流
-func CheckReportOpenApprove(reportType, firstId, secondId int) (opening bool, err error) {
+func CheckReportOpenApprove(reportType, firstId, secondId, thirdId int) (opening bool, err error) {
 	// 获取审批配置
 	confMap, e := models.GetBusinessConf()
 	if e != nil {
@@ -79,9 +81,9 @@ func CheckReportOpenApprove(reportType, firstId, secondId int) (opening bool, er
 
 	// 查询对应分类是否有审批流
 	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)
+	flowCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId)
 	flowPars := make([]interface{}, 0)
-	flowPars = append(flowPars, reportType, firstId, secondId)
+	flowPars = append(flowPars, reportType, firstId, secondId, thirdId)
 	flowItem, e := flowOb.GetItemByCondition(flowCond, flowPars, "")
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		err = fmt.Errorf("ApproveFlow GetItemByCondition err: %s", e.Error())
@@ -97,7 +99,7 @@ func CheckReportOpenApprove(reportType, firstId, secondId int) (opening bool, er
 }
 
 // CheckReportCurrState 校验报告当前应有的状态
-func CheckReportCurrState(reportType, firstId, secondId, operate int) (state int, err error) {
+func CheckReportCurrState(reportType, firstId, secondId, thirdId, operate int) (state int, err error) {
 	// 获取审批配置
 	confMap, e := models.GetBusinessConf()
 	if e != nil {
@@ -109,9 +111,9 @@ func CheckReportCurrState(reportType, firstId, secondId, operate int) (state int
 
 	// 查询对应分类是否有审批流
 	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)
+	flowCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId)
 	flowPars := make([]interface{}, 0)
-	flowPars = append(flowPars, reportType, firstId, secondId)
+	flowPars = append(flowPars, reportType, firstId, secondId, thirdId)
 	flowItem, e := flowOb.GetItemByCondition(flowCond, flowPars, "")
 	if e != nil && e.Error() != utils.ErrNoRow() {
 		err = fmt.Errorf("ApproveFlow GetItemByCondition err: %s", e.Error())
@@ -148,7 +150,7 @@ func CheckReportCurrState(reportType, firstId, secondId, operate int) (state int
 }
 
 // SubmitReportApprove 提交审批
-func SubmitReportApprove(reportType, reportId int, reportTitle string, firstId, secondId int, sysAdminId int, sysAdminName string) (approveId int, err error) {
+func SubmitReportApprove(reportType, reportId int, reportTitle string, firstId, secondId, thirdId int, sysAdminId int, sysAdminName string) (approveId int, err error) {
 	// 默认内部审批, 如果是走的第三方审批, 那么仅修改状态
 	confMap, e := models.GetBusinessConf()
 	if e != nil {
@@ -171,9 +173,9 @@ func SubmitReportApprove(reportType, reportId int, reportTitle string, firstId,
 
 	// 查询审批流
 	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)
+	flowCond := fmt.Sprintf(` AND %s = ? AND %s = ? AND %s = ? AND %s = ?`, report_approve.ReportApproveFlowCols.ReportType, report_approve.ReportApproveFlowCols.ClassifyFirstId, report_approve.ReportApproveFlowCols.ClassifySecondId, report_approve.ReportApproveFlowCols.ClassifyThirdId)
 	flowPars := make([]interface{}, 0)
-	flowPars = append(flowPars, reportType, firstId, secondId)
+	flowPars = append(flowPars, reportType, firstId, secondId, thirdId)
 	flowItem, e := flowOb.GetItemByCondition(flowCond, flowPars, "")
 	if e != nil {
 		err = fmt.Errorf("ApproveFlow GetItemByCondition err: %s", e.Error())
@@ -216,6 +218,7 @@ func SubmitReportApprove(reportType, reportId int, reportTitle string, firstId,
 	newApprove.ReportTitle = reportTitle
 	newApprove.ClassifyFirstId = firstId
 	newApprove.ClassifySecondId = secondId
+	newApprove.ClassifyThirdId = thirdId
 	newApprove.State = report_approve.ReportApproveStateApproving
 	newApprove.FlowId = flowItem.ReportApproveFlowId
 	newApprove.FlowVersion = flowItem.CurrVersion
@@ -457,7 +460,12 @@ func PassReportApprove(approveItem *report_approve.ReportApprove, recordItem *re
 	recordItem.State = report_approve.ReportApproveStatePass
 	recordItem.ApproveTime = now
 	recordItem.ModifyTime = now
-	recordCols := []string{"State", "ApproveTime", "ModifyTime"}
+	recordItem.NodeState = report_approve.ReportApproveStatePass
+	recordItem.NodeApproveUserId = recordItem.ApproveUserId
+	recordItem.NodeApproveUserName = recordItem.ApproveUserName
+	recordItem.NodeApproveTime = now
+
+	recordCols := []string{"State", "ApproveTime", "ModifyTime", "NodeState", "NodeApproveUserId", "NodeApproveUserName", "NodeApproveTime"}
 	lastApprove := false
 
 	// 依次审批
@@ -504,6 +512,7 @@ func PassReportApprove(approveItem *report_approve.ReportApprove, recordItem *re
 			newRecord.ApproveUserSort = nextUser.Sort
 			newRecord.CreateTime = now
 			newRecord.ModifyTime = now
+			newRecord.NodeState = report_approve.ReportApproveStateApproving
 			if e = newRecord.Create(); e != nil {
 				err = fmt.Errorf("生成审批记录失败, Err: %s", e.Error())
 				return
@@ -602,6 +611,13 @@ func PassReportApprove(approveItem *report_approve.ReportApprove, recordItem *re
 			err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
 			return
 		}
+
+		// 将该审批的同一个节点的记录标记为已审批
+		if e = recordItem.UpdateNodeState(recordItem.ReportApproveId, recordItem.NodeId, recordItem.NodeState, recordItem.NodeApproveUserId, recordItem.NodeApproveUserName, recordItem.NodeApproveTime); e != nil {
+			err = fmt.Errorf("更新同一节点的其他审批记录状态失败, Err: %s", e.Error())
+			return
+		}
+
 		if currNode.NextNodeId == 0 {
 			lastApprove = true
 		}
@@ -660,9 +676,6 @@ func PassReportApprove(approveItem *report_approve.ReportApprove, recordItem *re
 				return
 			}
 		}()
-
-		// 生成报告pdf和长图
-		go Report2pdfAndJpeg(reportUrl, approveItem.ReportId, approveItem.ReportType)
 	}
 	return
 }
@@ -684,12 +697,24 @@ func RefuseReportApprove(approveItem *report_approve.ReportApprove, recordItem *
 	recordItem.ApproveRemark = approveRemark
 	recordItem.ApproveTime = now
 	recordItem.ModifyTime = now
-	recordCols := []string{"State", "ApproveRemark", "ApproveTime", "ModifyTime"}
+
+	recordItem.NodeState = report_approve.ReportApproveStateRefuse
+	recordItem.NodeApproveUserId = recordItem.ApproveUserId
+	recordItem.NodeApproveUserName = recordItem.ApproveUserName
+	recordItem.NodeApproveTime = now
+
+	recordCols := []string{"State", "ApproveRemark", "ApproveTime", "ModifyTime", "NodeState", "NodeApproveUserId", "NodeApproveUserName", "NodeApproveTime"}
 	if e := recordItem.Update(recordCols); e != nil {
 		err = fmt.Errorf("更新审批记录状态失败, Err: %s", e.Error())
 		return
 	}
 
+	// 将该审批的同一个节点的记录标记为已审批
+	if e := recordItem.UpdateNodeState(recordItem.ReportApproveId, recordItem.NodeId, recordItem.NodeState, recordItem.NodeApproveUserId, recordItem.NodeApproveUserName, recordItem.NodeApproveTime); e != nil {
+		err = fmt.Errorf("更新同一节点的其他审批记录状态失败, Err: %s", e.Error())
+		return
+	}
+
 	// 驳回-更新审批, 报告状态, 推送消息
 	approveItem.State = report_approve.ReportApproveStateRefuse
 	approveItem.ApproveRemark = approveRemark
@@ -700,6 +725,7 @@ func RefuseReportApprove(approveItem *report_approve.ReportApprove, recordItem *
 		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
@@ -759,6 +785,7 @@ func BuildNextNodeRecordAndMsg(approveNodeItem *report_approve.ReportApproveNode
 		r.ApproveUserSort = u.Sort
 		r.CreateTime = now
 		r.ModifyTime = now
+		r.NodeState = report_approve.ReportApproveStateApproving // 当前节点审批状态
 		newRecords = append(newRecords, r)
 		// 依次审批仅生成一条记录
 		if approveNode.ApproveType == report_approve.NodeApproveTypeRoll {
@@ -801,7 +828,7 @@ func BuildNextNodeRecordAndMsg(approveNodeItem *report_approve.ReportApproveNode
 func AfterReportApprovePass(reportType, reportId int) (err error) {
 	// 中文研报
 	if reportType == report_approve.FlowReportTypeChinese {
-		report, e := models.GetReportById(reportId)
+		reportInfo, e := models.GetReportByReportId(reportId)
 		if e != nil {
 			if e.Error() == utils.ErrNoRow() {
 				return
@@ -809,8 +836,17 @@ func AfterReportApprovePass(reportType, reportId int) (err error) {
 			err = fmt.Errorf("获取研报信息失败, Err: %s", e.Error())
 			return
 		}
-		_ = CreateVideo(report)
-		_ = UpdateReportEs(report.Id, models.ReportStatePublished)
+		// 重新生成音频,里面还涉及到章节类型的报告
+		go UpdateReportVideo(reportInfo)
+
+		// 生成报告pdf和长图
+		{
+			reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ReportLayout)
+			go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
+		}
+
+		//_ = CreateVideo(reportInfo)
+		_ = UpdateReportEs(reportInfo.Id, models.ReportStatePublished)
 
 		return
 	}
@@ -886,7 +922,7 @@ func CheckCloseReportApproveConf() (yes bool, err error) {
 }
 
 // FlowOperateResetReportState 审批流变化-重置报告的初始状态
-func FlowOperateResetReportState(reportType, classifyFirstId, classifySecondId, oldState, State int) (err error) {
+func FlowOperateResetReportState(reportType, classifyFirstId, classifySecondId, classifyThirdId, oldState, State int) (err error) {
 	defer func() {
 		if err != nil {
 			tips := fmt.Sprintf("审批流变化-重置报告初始状态失败, ErrMsg: %s", err.Error())
@@ -897,7 +933,7 @@ func FlowOperateResetReportState(reportType, classifyFirstId, classifySecondId,
 
 	// 中文研报
 	if reportType == report_approve.FlowReportTypeChinese {
-		e := models.UpdateReportsStateByCond(classifyFirstId, classifySecondId, oldState, State)
+		e := models.UpdateReportsStateByCond(classifyFirstId, classifySecondId, classifyThirdId, oldState, State)
 		if e != nil {
 			err = fmt.Errorf("UpdateReportsStateByCond err: %s", e.Error())
 		}
@@ -936,7 +972,7 @@ func ConfigChangeResetReportState(changeType string) (err error) {
 
 	// 关闭审批-待提交->未发布
 	if changeType == "" {
-		e := models.UpdateReportsStateByCond(0, 0, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
+		e := models.UpdateReportsStateByCond(0, 0, 0, models.ReportStateWaitSubmit, models.ReportStateUnpublished)
 		if e != nil {
 			err = fmt.Errorf("UpdateReportsStateByCond err: %s", e.Error())
 		}
@@ -999,7 +1035,7 @@ func ConfigChangeResetReportState(changeType string) (err error) {
 
 	// 开启第三方审批->未发布->待提交
 	if changeType == models.BusinessConfReportApproveTypeOther {
-		e := models.UpdateReportsStateByCond(0, 0, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
+		e := models.UpdateReportsStateByCond(0, 0, 0, models.ReportStateUnpublished, models.ReportStateWaitSubmit)
 		if e != nil {
 			err = fmt.Errorf("UpdateReportsStateByCond err: %s", e.Error())
 		}

+ 225 - 0
services/report_chapter.go

@@ -0,0 +1,225 @@
+package services
+
+import (
+	"eta/eta_api/models"
+	"eta/eta_api/models/report"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// MoveReportChapter
+// @Description: 移动报告章节
+// @author: Roc
+// @datetime 2024-06-06 09:48:52
+// @param req *models.ReportChapterMoveReq
+// @return err error
+// @return errMsg string
+func MoveReportChapter(req *models.ReportChapterMoveReq) (err error, errMsg string) {
+	ob := new(models.ReportChapter)
+	reportChapterId := req.ReportChapterId
+	prevReportChapterId := req.PrevReportChapterId
+	nextReportChapterId := req.NextReportChapterId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		reportChapter     *models.ReportChapter
+		prevReportChapter *models.ReportChapter
+		nextReportChapter *models.ReportChapter
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	reportChapter, err = ob.GetReportChapterById(reportChapterId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前报告章节不存在"
+			err = fmt.Errorf("获取报告章节信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取章节信息失败,Err:" + err.Error())
+		return
+	} else if reportChapter.ReportChapterId == 0 {
+		errMsg = "当前报告章节不存在"
+		err = fmt.Errorf("获取报告章节信息失败,Err:" + err.Error())
+		return
+	}
+
+	if prevReportChapterId > 0 {
+		prevReportChapter, err = ob.GetReportChapterById(prevReportChapterId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevReportChapter.Sort
+	}
+
+	if nextReportChapterId > 0 {
+		//下一个兄弟节点
+		nextReportChapter, err = ob.GetReportChapterById(nextReportChapterId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextReportChapter.Sort
+	}
+
+	err, errMsg = moveReportChapter(reportChapter, prevReportChapter, nextReportChapter, reportChapter.ReportId, prevSort, nextSort)
+	return
+}
+
+// moveReportChapter
+// @Description: 移动章节分类
+// @author: Roc
+// @datetime 2024-06-06 09:48:46
+// @param reportChapter *models.ReportChapter
+// @param prevReportChapter *models.ReportChapter
+// @param nextReportChapter *models.ReportChapter
+// @param reportId int
+// @param prevSort int
+// @param nextSort int
+// @return err error
+// @return errMsg string
+func moveReportChapter(reportChapter, prevReportChapter, nextReportChapter *models.ReportChapter, reportId int, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(models.ReportChapter)
+	updateCol := make([]string, 0)
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == reportChapter.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevReportChapter != nil {
+					_ = ob.UpdateReportChapterSortByReportId(reportId, prevReportChapter.ReportChapterId, prevReportChapter.Sort, updateSortStr)
+				} else {
+					_ = ob.UpdateReportChapterSortByReportId(reportId, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevReportChapter != nil {
+						_ = ob.UpdateReportChapterSortByReportId(reportId, prevReportChapter.ReportChapterId, prevSort, updateSortStr)
+					} else {
+						_ = ob.UpdateReportChapterSortByReportId(reportId, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		reportChapter.Sort = prevSort + 1
+		reportChapter.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else if prevReportChapter == nil && nextReportChapter == nil {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByReportId(reportId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		reportChapter.Sort = maxSort + 1 //那就是排在组内最后一位
+		reportChapter.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstReportChapter, tmpErr := ob.GetFirstReportChapterByReportId(reportId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取当前报告下,且排序数相同 的排序第一条的数据失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstReportChapter != nil && firstReportChapter.ReportChapterId != 0 && firstReportChapter.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = ob.UpdateReportChapterSortByReportId(reportId, firstReportChapter.ReportChapterId-1, 0, updateSortStr)
+		}
+
+		reportChapter.Sort = 0 //那就是排在第一位
+		reportChapter.ModifyTime = time.Now()
+		updateCol = append(updateCol, "Sort", "ModifyTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = reportChapter.UpdateChapter(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}
+
+// CheckChapterAuthByAdminIdList
+// @Description: 根据管理员id列表,判断当前用户是否有章节权限
+// @author: Roc
+// @datetime 2024-06-13 11:03:10
+// @param adminId int
+// @param createAdminId int
+// @param grantAdminIdList []int
+// @return isAuth bool
+func CheckChapterAuthByAdminIdList(adminId, createAdminId int, grantAdminIdList []int) (isAuth bool) {
+	// 如果是自己创建的报告,那么就有权限
+	if adminId == createAdminId {
+		isAuth = true
+		return
+	}
+	// 如果是授权用户,那么就有权限
+	if utils.IsCheckInList(grantAdminIdList, adminId) {
+		isAuth = true
+		return
+	}
+
+	return
+}
+
+// CheckChapterAuthByReportChapterInfo
+// @Description: 根据报告章节信息,判断当前用户是否有章节权限
+// @author: Roc
+// @datetime 2024-06-13 11:10:46
+// @param adminId int
+// @param createAdminId int
+// @param reportChapterInfo *models.ReportChapter
+// @return isAuth bool
+// @return err error
+func CheckChapterAuthByReportChapterInfo(adminId, createAdminId int, reportChapterInfo *models.ReportChapter) (isAuth bool, err error) {
+	// 如果是自己创建的报告,那么就有权限
+	if adminId == createAdminId {
+		isAuth = true
+		return
+	}
+
+	chapterGrantObj := report.ReportChapterGrant{}
+	chapterGrantList, err := chapterGrantObj.GetGrantListById(reportChapterInfo.ReportChapterId)
+	if err != nil {
+		return
+	}
+
+	for _, v := range chapterGrantList {
+		if v.AdminId == adminId {
+			isAuth = true
+			return
+		}
+	}
+
+	return
+}

+ 40 - 30
services/report_classify.go

@@ -1,6 +1,7 @@
 package services
 
 import (
+	"errors"
 	"eta/eta_api/models"
 	"eta/eta_api/services/alarm_msg"
 	"fmt"
@@ -65,7 +66,7 @@ func UpdateParentClassifyHasTel(classifyId, parentId, hasTeleconference int) (er
 // AfterUpdateClassifyNameOrParent 更新报告分类名称/父级分类后的操作
 // 当二级分类名称做了修改, 更新chart_permission_search_key_word_mapping对应的key_word
 // 以及report表中的classify_name_second, 不然报告的权限会有BUG
-func AfterUpdateClassifyNameOrParent(classifyId, parentId, originParentId int, originName, classifyName string) (err error) {
+func AfterUpdateClassifyNameOrParent(classifyId, parentId, originParentId int, originName, classifyName string, classifyLevel int) (err error) {
 	if classifyId == 0 {
 		return
 	}
@@ -75,46 +76,55 @@ func AfterUpdateClassifyNameOrParent(classifyId, parentId, originParentId int, o
 		}
 	}()
 
-	// 一级分类-修改名称
-	if originName != classifyName && parentId == 0 {
-		// 更新报告表分类字段
-		if e := models.UpdateReportFirstClassifyNameByClassifyId(classifyId, classifyName); e != nil {
-			err = fmt.Errorf("更新报告表一级分类名称失败, Err: %s", e.Error())
+	// 修改名称
+	// ETA1.8.3 现在任何一级都能挂报告,所以只需要名称变更就要去更新数据,不需要判断是否属于子分类(2024-6-18 10:37:17)
+	if originName != classifyName {
+		switch classifyLevel {
+		case 1: // 一级分类
+			if e := models.UpdateReportFirstClassifyNameByClassifyId(classifyId, classifyName); e != nil {
+				err = fmt.Errorf("更新报告表一级分类名称失败, Err: %s", e.Error())
+				return
+			}
+		case 2: // 二级分类
+			// 更新报告表分类字段
+			if e := models.UpdateReportSecondClassifyNameByClassifyId(classifyId, classifyName); e != nil {
+				err = fmt.Errorf("更新报告表二级分类名称失败, Err: %s", e.Error())
+				return
+			}
+		case 3: // 三级分类
+			// 更新报告表分类字段
+			if e := models.UpdateReportThirdClassifyNameByClassifyId(classifyId, classifyName); e != nil {
+				err = fmt.Errorf("更新报告表三级分类名称失败, Err: %s", e.Error())
+				return
+			}
+		default:
+			err = errors.New(fmt.Sprint("错误的分类级别,ClassifyId:", classifyId, ";层级:", classifyLevel))
 			return
 		}
-		return
-	}
 
-	// 二级分类-修改名称
-	if originName != classifyName && parentId > 0 {
 		// 更新关键词
 		if e := models.UpdateChartPermissionNameFromMappingByKeyword(classifyName, classifyId, "rddp"); e != nil {
 			err = fmt.Errorf("更新二级分类关键词失败, Err: %s", e.Error())
 			return
 		}
-		// 更新报告表分类字段
-		if e := models.UpdateReportSecondClassifyNameByClassifyId(classifyId, classifyName); e != nil {
-			err = fmt.Errorf("更新报告表二级分类名称失败, Err: %s", e.Error())
-			return
-		}
-
 		// 同步crm_master
 		_ = EditKeywordPermissionSync(classifyName, classifyId)
-		return
 	}
 
-	// 二级分类-修改了父级分类
-	if originParentId > 0 && parentId > 0 && originParentId != parentId {
-		parentClassify, e := models.GetClassifyById(parentId)
-		if e != nil {
-			err = fmt.Errorf("获取父级分类信息失败, Err: %s", e.Error())
-			return
-		}
-		// 更新报告表一级分类名称和ID
-		if e = models.UpdateReportSecondClassifyFirstNameByClassifyId(classifyId, parentClassify.Id, parentClassify.ClassifyName); e != nil {
-			err = fmt.Errorf("更新报告表一级分类名称和ID, Err: %s", e.Error())
-			return
-		}
-	}
+	//// 二级分类-修改了父级分类
+	//// ETA1.8.3已经不允许修改父级分类了(2024-6-18 10:37:17)
+	//if originParentId > 0 && parentId > 0 && originParentId != parentId {
+	//	parentClassify, e := models.GetClassifyById(parentId)
+	//	if e != nil {
+	//		err = fmt.Errorf("获取父级分类信息失败, Err: %s", e.Error())
+	//		return
+	//	}
+	//	// 更新报告表一级分类名称和ID
+	//	if e = models.UpdateReportSecondClassifyFirstNameByClassifyId(classifyId, parentClassify.Id, parentClassify.ClassifyName); e != nil {
+	//		err = fmt.Errorf("更新报告表一级分类名称和ID, Err: %s", e.Error())
+	//		return
+	//	}
+	//}
+
 	return
 }

+ 0 - 84
services/report_push.go

@@ -1,84 +0,0 @@
-package services
-
-import (
-	"fmt"
-	"eta/eta_api/models"
-	"eta/eta_api/services/alarm_msg"
-	"eta/eta_api/utils"
-	"strconv"
-	"time"
-)
-
-//func init() {
-//	report, _ := models.GetReportById(572)
-//	SendReportToThs(report)
-//}
-
-// SendReportToEmail 发送报告邮件
-func SendReportToEmail(report *models.ReportDetail) (err error) {
-	if utils.BusinessCode != utils.BusinessCodeRelease && utils.BusinessCode != utils.BusinessCodeSandbox && utils.BusinessCode != utils.BusinessCodeDebug {
-		// 客户不做报告邮件推送
-		return
-	}
-	fmt.Println("SendReportToEmail")
-	defer func() {
-		if err != nil {
-			//fmt.Println(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "发送消息至同花顺失败 ErrMsg:"+err.Error(), utils.EmailSendToUsers)
-			go alarm_msg.SendAlarmMsg("发送报告至邮件失败,SendReportToEmail ErrMsg:"+err.Error(), 3)
-			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "SendReportToEmail ErrMsg:"+err.Error(), utils.EmailSendToUsers)
-		}
-	}()
-	toUser := ``
-	jumpUrl := ``
-	if utils.RunMode == "debug" {
-		//toUser = `glji@hzinsights.com`
-		toUser = `317699326@qq.com`
-		jumpUrl = "http://rddpweb.brilliantstart.cn/reportdtl?id=1578" + strconv.Itoa(report.Id)
-	} else {
-		toUser = "lijun011112@gtjas.com"
-		jumpUrl = "https://ficc.hzinsights.com/reportdtl?id=" + strconv.Itoa(report.Id)
-	}
-
-	createDate, err := time.Parse(utils.FormatDateTime, report.CreateTime)
-	createDateFrom := createDate.Format("060102")
-	emailBody :=
-		`<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Email</title>
-</head>
-<body>
-    <div>
-        <h2 style="font-size: 16px;">弘则研报:<span>` + report.Title + "_" + createDateFrom + `</span><span></span></h2>
-        <div style="font-size: 14px;margin-left: 40px;">
-            <p>你好,</p>
-            <p>今日报告已推送。</p>
-            <p><span>报告类型:</span><span>` + report.ClassifyNameFirst + `</span></p>
-            <p><span>报告标题:</span><span>` + report.Title + `</span></p>
-            <p><span>报告链接:</span><a href="` + jumpUrl + `">弘则研究</a></p>
-        </div>
-    </div>
-</body>
-</html>`
-	fmt.Println("start SendReportToEmail")
-	result, err := utils.SendEmailByHz("弘则研报报告", emailBody, toUser)
-	fmt.Println("send result:", result, err)
-	return
-}
-
-// SendReportMiniToThs 发送报告-研报小程序到同花顺
-func SendReportMiniToThs(report *models.ReportDetail) (err error) {
-	defer func() {
-		if err != nil {
-			go alarm_msg.SendAlarmMsg("SendReportMiniToThs 发送报告-研报小程序到同花顺失败, ReportId:"+strconv.Itoa(report.Id)+", ErrMsg:"+err.Error(), 3)
-			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "SendReportMiniToThs发送报告至同花顺失败, ReportId:" + strconv.Itoa(report.Id) + ", ErrMsg:" + err.Error(), utils.EmailSendToUsers)
-		}
-	}()
-	if report.HasChapter == 0 {
-		go SendReportToEmail(report)
-	}
-	return
-}

+ 1447 - 0
services/report_v2.go

@@ -0,0 +1,1447 @@
+package services
+
+import (
+	"archive/zip"
+	"errors"
+	"eta/eta_api/models"
+	"eta/eta_api/models/report"
+	"eta/eta_api/models/report_approve"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/file"
+	"github.com/rdlucklib/rdluck_tools/http"
+	"html"
+	"os"
+	"path"
+	"strconv"
+	"time"
+)
+
+// AddReportAndChapter
+// @Description: 新增报告(包含章节)
+// @author: Roc
+// @datetime 2024-06-04 11:23:20
+// @param reportInfo *models.Report
+// @param inheritReportId int
+// @return err error
+// @return errMsg string
+func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAdminIdList []int) (err error, errMsg string) {
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperateAdd)
+	if e != nil {
+		//errMsg = "操作失败"
+		err = errors.New("校验报告当前状态失败, Err: " + e.Error())
+		return
+	}
+	// 报告状态
+	reportInfo.State = state
+
+	// 获取最小分类id
+	minClassifyId := reportInfo.ClassifyIdThird
+	if minClassifyId <= 0 {
+		minClassifyId = reportInfo.ClassifyIdSecond
+	}
+	if minClassifyId <= 0 {
+		minClassifyId = reportInfo.ClassifyIdFirst
+	}
+	if minClassifyId <= 0 {
+		errMsg = "分类异常"
+		err = errors.New(errMsg)
+		return
+	}
+
+	errMsg = "生成报告失败"
+
+	// 报告继承
+	if inheritReportId > 0 {
+		inheritReport, tmpErr := models.GetReportByReportId(inheritReportId)
+		if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+			errMsg = "获取待继承的报告失败"
+			err = tmpErr
+			return
+		}
+		if inheritReport != nil {
+			// 判断当前的报告分类与继承的报告分类是否一致
+			if inheritReport.ClassifyIdFirst != reportInfo.ClassifyIdFirst || inheritReport.ClassifyIdSecond != reportInfo.ClassifyIdSecond || inheritReport.ClassifyIdThird != reportInfo.ClassifyIdThird {
+				errMsg = "分类异常,与继承的报告分类不一致"
+				err = errors.New(errMsg)
+				return
+			}
+
+			reportInfo.ChapterType = inheritReport.ChapterType
+			reportInfo.Content = inheritReport.Content
+			reportInfo.ContentSub = inheritReport.ContentSub
+			reportInfo.ContentStruct = inheritReport.ContentStruct
+			reportInfo.HeadImg = inheritReport.HeadImg
+			reportInfo.EndImg = inheritReport.EndImg
+			reportInfo.CanvasColor = inheritReport.CanvasColor
+			reportInfo.NeedSplice = inheritReport.NeedSplice
+			reportInfo.HeadResourceId = inheritReport.HeadResourceId
+			reportInfo.EndResourceId = inheritReport.EndResourceId
+			reportInfo.InheritReportId = inheritReport.Id
+		}
+	}
+
+	// 获取待生成的报告章节
+	addChapterList, allGrantUserList, err, errMsg := getAddChapter(reportInfo, minClassifyId, inheritReportId, grantAdminIdList)
+
+	// 新增报告及章节
+	var reportId int64
+	reportId, err = models.AddReportAndChapter(reportInfo, allGrantUserList, addChapterList)
+	if err != nil {
+		err = errors.New("新增报告及章节失败, Err: " + err.Error())
+		return
+	}
+	reportCode := utils.MD5(strconv.Itoa(reportInfo.Id))
+	reportInfo.ReportCode = reportCode
+
+	// 修改唯一编码
+	{
+		go models.ModifyReportCode(reportId, reportCode)
+	}
+
+	// 报告权限处理
+	go handleReportPermission(reportId, minClassifyId)
+
+	return
+}
+
+// EditReport
+// @Description: 修改报告的基础信息、授权用户权限
+// @author: Roc
+// @datetime 2024-06-06 17:16:58
+// @param reportInfo *models.Report
+// @param req models.EditReq
+// @param sysUser *system.Admin
+// @return err error
+// @return errMsg string
+func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.Admin) (err error, errMsg string) {
+	errMsg = `保存失败`
+	//var stage int
+	//if reportInfo.ClassifyNameFirst != req.ClassifyNameFirst || reportInfo.ClassifyNameSecond != req.ClassifyNameSecond {
+	//	// 报告期数
+	//	maxStage, _ := models.GetReportStageEdit(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird, int(req.ReportId))
+	//	maxStage = maxStage + 1
+	//	stage = maxStage
+	//} else {
+	//	stage = reportInfo.Stage
+	//}
+
+	//if req.State != reportInfo.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)
+	//reportInfo.ClassifyIdFirst = req.ClassifyIdFirst
+	//reportInfo.ClassifyNameFirst = req.ClassifyNameFirst
+	//reportInfo.ClassifyIdSecond = req.ClassifyIdSecond
+	//reportInfo.ClassifyNameSecond = req.ClassifyNameSecond
+	//reportInfo.ClassifyIdThird = req.ClassifyIdThird
+	//reportInfo.ClassifyNameThird = req.ClassifyNameThird
+	reportInfo.Title = req.Title
+	reportInfo.Abstract = req.Abstract
+	reportInfo.Author = req.Author
+	reportInfo.Frequency = req.Frequency
+	//reportInfo.State = reportInfo.State // 编辑不变更状态
+	//reportInfo.Stage = stage // 编辑不变更期数
+	//reportInfo.Content = html.EscapeString(req.Content)	// 编辑不变更具体内容
+	//reportInfo.ContentSub = html.EscapeString(contentSub)	// 编辑不变更具体内容
+	reportInfo.CreateTime = req.CreateTime
+	//reportInfo.CollaborateType = req.CollaborateType
+	//reportInfo.ReportLayout = req.ReportLayout
+	if req.IsPublicPublish <= 0 {
+		req.IsPublicPublish = 1
+	}
+	reportInfo.IsPublicPublish = req.IsPublicPublish
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	state, e := CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperateEdit)
+	if e != nil {
+		//errMsg = "操作失败"
+		err = errors.New("校验报告当前状态失败, Err: " + e.Error())
+		return
+	}
+	// 报告状态
+	reportInfo.State = state
+
+	//updateCols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "ClassifyIdThird", "ClassifyNameThird", "Title", "Abstract", "Author", "Frequency", "Stage", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
+	updateCols := []string{"Title", "Abstract", "Author", "Frequency", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "State"}
+
+	if req.HeadResourceId > 0 {
+		reportInfo.HeadResourceId = req.HeadResourceId
+		updateCols = append(updateCols, "HeadResourceId")
+	}
+	if req.EndResourceId > 0 {
+		reportInfo.EndResourceId = req.EndResourceId
+		updateCols = append(updateCols, "EndResourceId")
+	}
+
+	// 需要添加的报告授权数据
+	addReportAdminList := make([]*report.ReportGrant, 0)
+	// 待移除的报告授权数据id
+	delReportGrantIdList := make([]int, 0)
+
+	// 处理当前报告需要新增/移除的授权信息
+	{
+		reportGrantObj := report.ReportGrant{}
+		// 获取当前报告已经授权的用户信息
+		reportGrantList, tmpErr := reportGrantObj.GetGrantListById(reportInfo.Id)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 当前报告已经授权的用户信息
+		currReportAdminMap := make(map[int]*report.ReportGrant)
+		// 需要删除的报告授权数据
+		delReportAdminMap := make(map[int]*report.ReportGrant)
+		for _, v := range reportGrantList {
+			currReportAdminMap[v.AdminId] = v
+			delReportAdminMap[v.AdminId] = v
+		}
+
+		// 先看需要新增哪些用户
+		for _, tmpAdminId := range req.GrantAdminIdList {
+			_, ok := currReportAdminMap[tmpAdminId]
+			// 如果章节中需要新增的用户 已经在 报告授权用户里面,那么就忽略,可以不用新增了
+			if ok {
+				delete(delReportAdminMap, tmpAdminId)
+				continue
+			}
+
+			// 如果不存在,那么就新增授权
+			addReportAdminList = append(addReportAdminList, &report.ReportGrant{
+				//GrantId:         0,
+				ReportId:   reportInfo.Id,
+				AdminId:    tmpAdminId,
+				CreateTime: time.Now(),
+			})
+		}
+
+		// 查出需要移除的授权id
+		for _, v := range delReportAdminMap {
+			delReportGrantIdList = append(delReportGrantIdList, v.GrantId)
+		}
+	}
+
+	// 修改报告的基本信息,以及报告的授权用户
+	err = models.EditReportAndPermission(reportInfo, updateCols, addReportAdminList, delReportGrantIdList)
+	if err != nil {
+		return
+	}
+
+	// 报告权限处理
+	{
+		minClassifyId, _, tmpErr := getMinClassify(reportInfo)
+		if tmpErr != nil {
+			return
+		}
+		go handleReportPermission(int64(reportInfo.Id), minClassifyId)
+	}
+
+	return
+}
+
+// getAddChapter
+// @Description: 获取待新增的报告章节列表
+// @author: Roc
+// @datetime 2024-06-04 13:10:58
+// @param reportInfo *models.Report
+// @param minClassifyId int
+// @param inheritReportId int
+// @param grantAdminIdList []int
+// @return chapterList []*models.ReportChapter
+// @return err error
+// @return errMsg string
+func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int, grantAdminIdList []int) (chapterList []models.AddReportChapter, allGrantUserList []*report.ReportGrant, err error, errMsg string) {
+	// 待生成的报告章节内容
+	chapterList = make([]models.AddReportChapter, 0)
+
+	// 所有的授权用户
+	allGrantUserList = make([]*report.ReportGrant, 0)
+	// 报告授权的用户ID
+	needAdminIdMap := make(map[int]bool)
+	for _, adminId := range grantAdminIdList {
+		needAdminIdMap[adminId] = true
+		allGrantUserList = append(allGrantUserList, &report.ReportGrant{
+			//GrantId:      0,
+			//ReportId:     0,
+			AdminId:    adminId,
+			CreateTime: time.Now(),
+		})
+	}
+	if reportInfo.HasChapter != 1 {
+		return
+	}
+
+	// 最小单元的分类
+	var minClassifyName string
+	if reportInfo.ClassifyIdThird == minClassifyId {
+		minClassifyName = reportInfo.ClassifyNameThird
+	} else if reportInfo.ClassifyIdSecond == minClassifyId {
+		minClassifyName = reportInfo.ClassifyNameSecond
+	} else {
+		minClassifyName = reportInfo.ClassifyNameFirst
+	}
+
+	errMsg = "生成报告章节失败"
+	// 章节类型列表
+	allTypeList, err := models.GetReportChapterTypeListByClassifyId(minClassifyId)
+	if err != nil {
+		err = errors.New(errMsg)
+		return
+	}
+
+	// 待添加的章节
+	chapterTypeList := make([]*models.ReportChapterType, 0)
+	// 待添加的章节类型id列表
+	chapterTypeIdList := make([]int, 0)
+
+	nowTime := time.Now().Local()
+	for _, chapterType := range allTypeList {
+		// 如果被永久暂停更新了
+		if chapterType.Enabled == 0 { //该章节已被永久禁用,那么就不允许继承或者新增该章节
+			continue
+		}
+
+		if chapterType.PauseStartTime != "" && chapterType.PauseEndTime != "" && chapterType.PauseStartTime != utils.EmptyDateStr && chapterType.PauseEndTime != utils.EmptyDateStr {
+			startTime, timeErr := time.ParseInLocation(utils.FormatDate, chapterType.PauseStartTime, time.Local)
+			if timeErr != nil {
+				utils.FileLog.Error("更新规则时间转换失败4001, Err: " + timeErr.Error())
+				continue
+			}
+			endTime, timeErr := time.ParseInLocation(utils.FormatDate, chapterType.PauseEndTime, time.Local)
+			if timeErr != nil {
+				utils.FileLog.Error("更新规则时间转换失败4002, Err: " + timeErr.Error())
+				continue
+			}
+			// 暂停更新
+			if nowTime.After(startTime) && nowTime.Before(endTime.AddDate(0, 0, 1)) {
+				continue
+			}
+		}
+
+		// 正常的章节类型状态,那么应该自动创建该章节
+		chapterTypeList = append(chapterTypeList, chapterType)
+		chapterTypeIdList = append(chapterTypeIdList, chapterType.ReportChapterTypeId)
+	}
+
+	// 待继承的章节类id的授权用户
+	oldChapterIdGrantListMap := make(map[int][]*report.ReportChapterGrant)
+	// 待继承的章节类id关联的品种列表
+	oldChapterPermissionListMap := make(map[int][]*report.ReportChapterPermissionMapping)
+	// 自定义章节列表
+	customAddChapterList := make([]models.AddReportChapter, 0)
+	// 报告继承
+	inheritChapterMap := make(map[int]*models.ReportChapter)
+
+	// 当前分类下配置的章节类型id所关联的品种列表(默认配置,不是从继承报告里面获取的,如果有继承章节,那么需要从继承报告里面获取)
+	currChapterTypePermissionListMap := make(map[int][]*report.ReportChapterPermissionMapping)
+
+	if len(chapterTypeIdList) > 0 {
+		mappingList, e := models.GetChapterTypePermissionByChapterTypeIdList(chapterTypeIdList)
+		if e != nil {
+			err = fmt.Errorf("获取章节类型权限列表失败, Err: " + e.Error())
+			return
+		}
+		hasPermissionMap := make(map[string]bool)
+		for _, v := range mappingList {
+			tmpChapterTypePermissionList, ok := currChapterTypePermissionListMap[v.ReportChapterTypeId]
+			if !ok {
+				tmpChapterTypePermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+			}
+			key := fmt.Sprint(v.ReportChapterTypeId, "-", v.ChartPermissionId)
+			if _, has := hasPermissionMap[key]; !has {
+				hasPermissionMap[key] = true
+				currChapterTypePermissionListMap[v.ReportChapterTypeId] = append(tmpChapterTypePermissionList, &report.ReportChapterPermissionMapping{
+					ReportChapterPermissionMappingId: 0,
+					ReportChapterId:                  0,
+					ChartPermissionId:                v.ChartPermissionId,
+					CreateTime:                       time.Now(),
+				})
+			}
+		}
+	}
+
+	if inheritReportId > 0 {
+		// 继承待继承的报告章节内容
+		inheritReportChapters, tmpErr := models.GetChapterListByReportId(inheritReportId)
+		if tmpErr != nil {
+			errMsg = "获取待继承的报告章节失败"
+			err = tmpErr
+			return
+		}
+		reportChaptersIdList := make([]int, 0)
+		for _, v := range inheritReportChapters {
+			reportChaptersIdList = append(reportChaptersIdList, v.ReportChapterId)
+		}
+
+		// 继承的报告章节用户map
+		grantListMap := make(map[int][]*report.ReportChapterGrant)
+
+		// 继承的报告章节的关联品种map
+		chapterPermissionListMap := make(map[int][]*report.ReportChapterPermissionMapping)
+
+		// 授权数据列表
+		if len(reportChaptersIdList) > 0 {
+			// 授权用户数据
+			obj := report.ReportChapterGrant{}
+			grantList, tmpErr := obj.GetGrantListByIdList(reportChaptersIdList)
+			if tmpErr != nil {
+				errMsg = "获取待继承的报告章节的授权用户列表失败"
+				err = tmpErr
+				return
+			}
+
+			for _, v := range grantList {
+				// 如果不在报告授权的用户ID里面,那么该章节就不继承该授权用户
+				if _, ok := needAdminIdMap[v.AdminId]; !ok {
+					continue
+				}
+
+				currReportChapterId := v.ReportChapterId
+				tmpGrantList, ok := grantListMap[currReportChapterId]
+				if !ok {
+					tmpGrantList = make([]*report.ReportChapterGrant, 0)
+				}
+				v.ReportChapterId = 0
+				v.GrantId = 0
+				tmpGrantList = append(tmpGrantList, v)
+				grantListMap[currReportChapterId] = tmpGrantList
+			}
+
+			// 授权关联品种数据
+			permissionObj := report.ReportChapterPermissionMapping{}
+			permissionList, tmpErr := permissionObj.GetPermissionListByIdList(reportChaptersIdList)
+			if tmpErr != nil {
+				errMsg = "获取待继承的报告章节的授权用户列表失败"
+				err = tmpErr
+				return
+			}
+
+			for _, v := range permissionList {
+				currReportChapterId := v.ReportChapterId
+				tmpPermissionList, ok := chapterPermissionListMap[currReportChapterId]
+				if !ok {
+					tmpPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+				}
+				v.ReportChapterId = 0
+				v.ReportChapterPermissionMappingId = 0
+				tmpPermissionList = append(tmpPermissionList, v)
+				chapterPermissionListMap[currReportChapterId] = tmpPermissionList
+			}
+		}
+
+		// 继承的报告章节内容
+		for i := 0; i < len(inheritReportChapters); i++ {
+			customChapter := inheritReportChapters[i]
+
+			// 授权用户列表
+			tmpGrantList, ok := grantListMap[customChapter.ReportChapterId]
+			if !ok {
+				tmpGrantList = make([]*report.ReportChapterGrant, 0)
+			}
+			oldChapterIdGrantListMap[customChapter.ReportChapterId] = tmpGrantList
+
+			// 关联品种列表
+			chapterPermissionList, ok := chapterPermissionListMap[customChapter.ReportChapterId]
+			if !ok {
+				chapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+			}
+			oldChapterPermissionListMap[customChapter.ReportChapterId] = chapterPermissionList
+
+			// 判断该章节是否是系统章节,如果是的话,那就是需要额外创建的
+			if customChapter.TypeId > 0 {
+				inheritChapterMap[customChapter.TypeId] = customChapter
+				continue
+			}
+
+			// 自定义的报告内容
+			customChapter.ReportId = reportInfo.Id
+			customChapter.ReportChapterId = 0
+			customChapter.ClassifyIdFirst = minClassifyId
+			customChapter.ClassifyNameFirst = minClassifyName
+			customChapter.Stage = reportInfo.Stage
+			customChapter.PublishState = 1
+			customChapter.CreateTime = reportInfo.CreateTime
+			customChapter.ModifyTime = time.Now()
+			customChapter.LastModifyAdminId = reportInfo.LastModifyAdminId
+			customChapter.LastModifyAdminName = reportInfo.LastModifyAdminName
+			customChapter.ContentModifyTime = time.Now()
+
+			customAddChapter := models.AddReportChapter{
+				ReportChapter:       customChapter,
+				GrantList:           tmpGrantList,
+				GrantPermissionList: chapterPermissionList,
+			}
+			customAddChapterList = append(customAddChapterList, customAddChapter)
+		}
+	}
+
+	// 最大排序
+	var maxSort int
+	for _, typeItem := range chapterTypeList {
+		v, ok := inheritChapterMap[typeItem.ReportChapterTypeId]
+
+		// 章节授权用户
+		var tmpGrantList []*report.ReportChapterGrant
+		// 章节关联品种
+		var tmpChapterPermissionList []*report.ReportChapterPermissionMapping
+
+		chapterItem := new(models.ReportChapter)
+
+		if ok && v != nil {
+			// 如果存在继承的章节,那么就从继承的章节内容中获取
+			chapterItem.AddType = 2
+			chapterItem.Title = v.Title
+			chapterItem.ReportType = v.ReportType
+			chapterItem.ClassifyIdFirst = minClassifyId
+			chapterItem.ClassifyNameFirst = minClassifyName
+			chapterItem.TypeId = typeItem.ReportChapterTypeId
+			chapterItem.TypeName = typeItem.ReportChapterTypeName
+			chapterItem.Content = v.Content
+			chapterItem.ContentSub = v.ContentSub
+			chapterItem.Stage = reportInfo.Stage
+			chapterItem.PublishState = 1
+			chapterItem.Sort = typeItem.Sort
+			chapterItem.CreateTime = reportInfo.CreateTime
+			chapterItem.ModifyTime = time.Now()
+
+			chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
+			chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
+			chapterItem.ContentModifyTime = time.Now()
+			chapterItem.ContentStruct = v.ContentStruct
+			chapterItem.ReportLayout = v.ReportLayout
+			chapterItem.ReportCreateTime = time.Now()
+
+			// 继承历史章节中的授权用户列表
+			tmpGrantList, ok = oldChapterIdGrantListMap[v.ReportChapterId]
+			if !ok {
+				tmpGrantList = make([]*report.ReportChapterGrant, 0)
+			}
+			// 继承历史章节中的关联品种列表
+			tmpChapterPermissionList, ok = oldChapterPermissionListMap[v.ReportChapterId]
+			if !ok {
+				tmpChapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+			}
+		} else {
+			chapterItem.AddType = 1
+			chapterItem.Title = typeItem.ReportChapterTypeName
+			chapterItem.ReportType = typeItem.ResearchType
+			chapterItem.ClassifyIdFirst = minClassifyId
+			chapterItem.ClassifyNameFirst = minClassifyName
+			chapterItem.TypeId = typeItem.ReportChapterTypeId
+			chapterItem.TypeName = typeItem.ReportChapterTypeName
+			chapterItem.Stage = reportInfo.Stage
+			chapterItem.PublishState = 1
+			chapterItem.Sort = typeItem.Sort
+			chapterItem.CreateTime = reportInfo.CreateTime
+			chapterItem.ModifyTime = time.Now()
+
+			chapterItem.LastModifyAdminId = reportInfo.LastModifyAdminId
+			chapterItem.LastModifyAdminName = reportInfo.LastModifyAdminName
+			chapterItem.ContentModifyTime = time.Now()
+			//chapterItem.ContentStruct = v.ContentStruct
+			chapterItem.ReportLayout = reportInfo.ReportLayout
+			chapterItem.ReportCreateTime = time.Now()
+
+			// 默认配置:从当前分类下配置的章节类型id所关联的品种列表
+			tmpChapterPermissionList, ok = currChapterTypePermissionListMap[typeItem.ReportChapterTypeId]
+			if !ok {
+				tmpChapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
+			}
+
+		}
+
+		if typeItem.Sort > maxSort {
+			maxSort = typeItem.Sort
+		}
+
+		addChapter := models.AddReportChapter{
+			ReportChapter:       chapterItem,
+			GrantList:           tmpGrantList,
+			GrantPermissionList: tmpChapterPermissionList,
+		}
+
+		chapterList = append(chapterList, addChapter)
+	}
+
+	// 将自定义的章节内容添加到待生成的章节内容中
+	for _, addChapterItem := range customAddChapterList {
+		maxSort++
+		addChapterItem.ReportChapter.Sort = maxSort
+		chapterList = append(chapterList, addChapterItem)
+	}
+
+	//hasGrantUserMap := make(map[int]bool)
+	//for _, grantList := range typeGrantListMap {
+	//	for _, grant := range grantList {
+	//		if _, ok := hasGrantUserMap[grant.AdminId]; !ok {
+	//			allGrantUserList = append(allGrantUserList, &report.ReportGrant{
+	//				//GrantId:      0,
+	//				//ReportId:     0,
+	//				AdminId:    grant.AdminId,
+	//				CreateTime: time.Now(),
+	//			})
+	//		}
+	//	}
+	//}
+
+	return
+}
+
+// AddChapterBaseInfoAndPermission
+// @Description:  添加章节基本信息及权限
+// @author: Roc
+// @datetime 2024-06-11 15:35:23
+// @param reportInfo *models.Report
+// @param reportChapterInfo *models.ReportChapter
+// @param permissionIdList []int
+// @param adminIdList []int
+// @return err error
+// @return errMsg string
+func AddChapterBaseInfoAndPermission(reportInfo *models.Report, reportChapterInfo *models.ReportChapter, permissionIdList []int, adminIdList []int) (err error, errMsg string) {
+	errMsg = "新增失败"
+
+	if reportInfo.State == 2 {
+		errMsg = "该报告已发布,不允许编辑"
+		err = errors.New(errMsg)
+		return
+	}
+
+	// 需要添加的报告章节授权数据
+	addChapterAdminList := make([]*report.ReportChapterGrant, 0)
+	for _, adminId := range adminIdList {
+		//新增授权
+		addChapterAdminList = append(addChapterAdminList, &report.ReportChapterGrant{
+			//GrantId:         0,
+			//ReportChapterId: reportChapterInfo.ReportChapterId,
+			AdminId:    adminId,
+			CreateTime: time.Now(),
+		})
+	}
+
+	// 需要添加的报告章节品种权限数据
+	addChapterPermissionList := make([]*report.ReportChapterPermissionMapping, 0)
+	// 品种权限
+	for _, permissionId := range permissionIdList {
+		// 如果不存在,那么就新增品种权限配置
+		addChapterPermissionList = append(addChapterPermissionList, &report.ReportChapterPermissionMapping{
+			//ReportChapterPermissionMappingId:         0,
+			//ReportChapterId:   reportChapterInfo.ReportChapterId,
+			ChartPermissionId: permissionId,
+			CreateTime:        time.Now(),
+		})
+	}
+
+	err = models.AddChapterBaseInfoAndPermission(reportChapterInfo, addChapterAdminList, addChapterPermissionList)
+
+	return
+}
+
+// EditChapterBaseInfoAndPermission
+// @Description: 修改报告章节的基础信息、授权用户权限、品种权限
+// @author: Roc
+// @datetime 2024-06-05 11:49:11
+// @param reportInfo *models.Report
+// @param reportChapterInfo *models.ReportChapter
+// @param title string
+// @param permissionIdList []int
+// @param adminIdList []int
+// @return err error
+// @return errMsg string
+func EditChapterBaseInfoAndPermission(reportInfo *models.Report, reportChapterInfo *models.ReportChapter, title string, permissionIdList []int, adminIdList []int) (err error, errMsg string) {
+	errMsg = "修改失败"
+
+	if reportInfo.State == 2 {
+		errMsg = "该报告已发布,不允许编辑"
+		err = errors.New(errMsg)
+		return
+	}
+
+	updateCols := make([]string, 0)
+	// 如果标题内容,那么就修改
+	if title != `` {
+		reportChapterInfo.Title = title
+		reportChapterInfo.ModifyTime = time.Now()
+		updateCols = append(updateCols, "Title", "ModifyTime")
+		reportChapterInfo.UpdateChapter(updateCols)
+	}
+
+	reportGrantObj := report.ReportGrant{}
+	chapterGrantObj := report.ReportChapterGrant{}
+	chapterPermissionObj := report.ReportChapterPermissionMapping{}
+
+	// 报告授权的用户map
+	reportGrantAdminIdMap := make(map[int]bool)
+	// 获取报告授权的用户信息
+	{
+		// 获取当前报告已经授权的用户信息
+		reportGrantList, tmpErr := reportGrantObj.GetGrantListById(reportInfo.Id)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		for _, v := range reportGrantList {
+			reportGrantAdminIdMap[v.AdminId] = true
+		}
+	}
+
+	// 需要添加的报告章节授权数据
+	addChapterAdminList := make([]*report.ReportChapterGrant, 0)
+	// 待移除的报告章节授权数据id
+	delReportChapterGrantIdList := make([]int, 0)
+
+	// 处理当前报告章节需要新增/移除的授权信息
+	{
+		// 获取当前章节已经授权的用户信息
+		chapterGrantList, tmpErr := chapterGrantObj.GetGrantListById(reportChapterInfo.ReportChapterId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 当前章节已经授权的用户信息
+		currChapterAdminMap := make(map[int]*report.ReportChapterGrant)
+		// 需要删除的报告章节授权数据
+		delChapterAdminMap := make(map[int]*report.ReportChapterGrant)
+		for _, v := range chapterGrantList {
+			currChapterAdminMap[v.AdminId] = v
+			delChapterAdminMap[v.AdminId] = v
+		}
+
+		for _, adminId := range adminIdList {
+			// 如果该用户 不在 报告授权的用户map 里面,说明这个用户是要移除的
+			if _, ok := reportGrantAdminIdMap[adminId]; !ok {
+				continue
+			}
+			_, ok := currChapterAdminMap[adminId]
+			// 如果存在,那么从 “需要删除的报告章节授权数据” 的map中移除
+			if ok {
+				delete(delChapterAdminMap, adminId)
+				continue
+			}
+			// 如果不存在,那么就新增授权
+			addChapterAdminList = append(addChapterAdminList, &report.ReportChapterGrant{
+				//GrantId:         0,
+				ReportChapterId: reportChapterInfo.ReportChapterId,
+				AdminId:         adminId,
+				CreateTime:      time.Now(),
+			})
+		}
+
+		// 查出需要移除的授权id
+		for _, v := range delChapterAdminMap {
+			delReportChapterGrantIdList = append(delReportChapterGrantIdList, v.GrantId)
+		}
+	}
+
+	// 其他章节授权的用户
+	otherChapterAdminMap := make(map[int]bool)
+	{
+		// 获取报告所有的章节id
+		reportChapterIdList, tmpErr := models.GetReportChapterIdList(reportInfo.Id)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		if len(reportChapterIdList) > 0 {
+			list, tmpErr := chapterGrantObj.GetGrantListByIdList(reportChapterIdList)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			for _, v := range list {
+				// 如果是当前章节,因为涉及到重新授权,所以得过滤
+				if v.ReportChapterId == reportChapterInfo.ReportChapterId {
+					continue
+				}
+				otherChapterAdminMap[v.AdminId] = true
+			}
+		}
+	}
+
+	// 需要添加的报告章节品种权限数据
+	addChapterPermissionList := make([]*report.ReportChapterPermissionMapping, 0)
+	// 待移除的报告章节品种权限数据id
+	delChapterPermissionMappingIdList := make([]int, 0)
+
+	// 处理当前报告章节需要新增/移除的品种权限信息
+	{
+		// 获取当前章节已经配置的品种权限信息
+		chapterPermissionList, tmpErr := chapterPermissionObj.GetPermissionListById(reportChapterInfo.ReportChapterId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 当前章节已经配置的品种权限信息
+		currChapterPermissionMap := make(map[int]*report.ReportChapterPermissionMapping)
+		// 需要删除的报告章节品种权限配置
+		delChapterPermissionMap := make(map[int]*report.ReportChapterPermissionMapping)
+		for _, v := range chapterPermissionList {
+			currChapterPermissionMap[v.ChartPermissionId] = v
+			delChapterPermissionMap[v.ChartPermissionId] = v
+		}
+
+		for _, permissionId := range permissionIdList {
+			_, ok := currChapterPermissionMap[permissionId]
+			// 如果存在,那么从 “需要删除的报告章节品种权限配置” 的map中移除
+			if ok {
+				delete(delChapterPermissionMap, permissionId)
+				continue
+			}
+			// 如果不存在,那么就新增品种权限配置
+			addChapterPermissionList = append(addChapterPermissionList, &report.ReportChapterPermissionMapping{
+				//ReportChapterPermissionMappingId:         0,
+				ReportChapterId:   reportChapterInfo.ReportChapterId,
+				ChartPermissionId: permissionId,
+				CreateTime:        time.Now(),
+			})
+		}
+
+		// 查出需要移除的品种权限配置
+		for _, v := range delChapterPermissionMap {
+			delChapterPermissionMappingIdList = append(delChapterPermissionMappingIdList, v.ReportChapterPermissionMappingId)
+		}
+	}
+
+	err = models.EditChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, updateCols, addChapterAdminList, addChapterPermissionList, delReportChapterGrantIdList, delChapterPermissionMappingIdList)
+
+	return
+}
+
+// DelChapter
+// @Description: 删除报告章节、授权用户权限、品种权限
+// @author: Roc
+// @datetime 2024-06-06 17:28:37
+// @param reportInfo *models.Report
+// @param reportChapterInfo *models.ReportChapter
+// @param sysUser *system.Admin
+// @return err error
+// @return errMsg string
+func DelChapter(reportInfo *models.Report, reportChapterInfo *models.ReportChapter, sysUser *system.Admin) (err error, errMsg string) {
+	errMsg = "删除失败"
+	if reportInfo.State == 2 {
+		errMsg = "该报告已发布,不允许删除"
+		err = errors.New(errMsg)
+		return
+	}
+
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+	updateReportCols := []string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
+	err = models.DelChapterAndPermission(reportInfo, updateReportCols, reportChapterInfo)
+
+	return
+}
+
+// DownloadVoice
+// @Description:  下载报告音频文件
+// @author: Roc
+// @datetime 2024-06-13 15:36:46
+// @param reportInfo *models.ReportDetail
+// @return savePath string
+// @return fileName string
+// @return err error
+// @return errMsg string
+func DownloadVoice(reportInfo *models.ReportDetail) (savePath, fileName string, err error, errMsg string) {
+	errMsg = `下载失败`
+	// 如果报告有音频,那么下载音频
+	if reportInfo.VideoUrl != `` {
+		savePath = time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".mp3"
+		fileName = reportInfo.VideoName + ".mp3"
+		fileBody, tmpErr := http.Get(reportInfo.VideoUrl)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "音频下载失败"
+			return
+		}
+		err = file.SaveFile(fileBody, savePath)
+		if err != nil {
+			errMsg = "音频保存失败"
+			return
+		}
+	}
+
+	// 如果是章节报告,那么就下载压缩包
+	if reportInfo.HasChapter == 1 {
+		videoList, tmpErr := models.GetReportChapterVideoList(reportInfo.Id)
+		if tmpErr != nil {
+			err = tmpErr
+			errMsg = "获取音频列表失败"
+			return
+		}
+
+		if len(videoList) > 0 {
+			// 创建zip
+			savePath = time.Now().Format(utils.FormatDateTimeUnSpace) + utils.GetRandString(5) + ".zip"
+			fileName = reportInfo.VideoName + ".zip"
+
+			zipFile, tmpErr := os.Create(savePath)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			zipWriter := zip.NewWriter(zipFile)
+			// 生成zip过程中报错关闭
+			defer func() {
+				if err != nil {
+					zipWriter.Close()
+					zipFile.Close()
+				}
+			}()
+
+			// 获取音频,写入zip
+			for i := 0; i < len(videoList); i++ {
+				item := videoList[i]
+				if item.VideoName == "" || item.VideoUrl == "" {
+					continue
+				}
+				// 音频文件后缀
+				ext := path.Ext(item.VideoUrl)
+				ioWriter, tmpErr := zipWriter.Create(fmt.Sprintf("%s%s", item.VideoName, ext))
+				if tmpErr != nil {
+					err = tmpErr
+					if os.IsPermission(err) {
+						fmt.Println("权限不足: ", err)
+						return
+					}
+					return
+				}
+
+				var content []byte
+				content, err = http.Get(item.VideoUrl)
+				if err != nil {
+					content = []byte("")
+				}
+
+				ioWriter.Write(content)
+			}
+			// 生成zip后关闭,否则下载文件会损坏
+			zipWriter.Close()
+			zipFile.Close()
+		}
+	}
+
+	return
+}
+
+// CheckReportAuthByAdminIdList
+// @Description: 根据管理员id列表,判断当前用户是否有报告权限
+// @author: Roc
+// @datetime 2024-06-13 11:03:10
+// @param adminId int
+// @param createAdminId int
+// @param grantAdminIdList []int
+// @return isAuth bool
+func CheckReportAuthByAdminIdList(adminId, createAdminId int, grantAdminIdList []int) (isAuth bool) {
+	// 如果是自己创建的报告,那么就有权限
+	if adminId == createAdminId {
+		isAuth = true
+		return
+	}
+	// 如果是授权用户,那么就有权限
+	if utils.IsCheckInList(grantAdminIdList, adminId) {
+		isAuth = true
+		return
+	}
+
+	return
+}
+
+// CheckReportAuthByReportChapterInfo
+// @Description: 根据报告ID,判断当前用户是否有报告权限
+// @author: Roc
+// @datetime 2024-06-13 16:21:28
+// @param adminId int
+// @param reportInfoId int
+// @return isAuth bool
+// @return err error
+func CheckReportAuthByReportChapterInfo(adminId, createAdminId int, reportInfoId int) (isAuth bool, err error) {
+	// 如果是自己创建的报告,那么就有权限
+	if adminId == createAdminId {
+		isAuth = true
+		return
+	}
+
+	obj := report.ReportGrant{}
+	chapterGrantList, err := obj.GetGrantListById(reportInfoId)
+	if err != nil {
+		return
+	}
+
+	for _, v := range chapterGrantList {
+		if v.AdminId == adminId {
+			isAuth = true
+			return
+		}
+	}
+
+	return
+}
+
+// EditReportLayoutImg
+// @Description: 修改报告的版图信息
+// @author: Roc
+// @datetime 2024-06-18 11:35:00
+// @param reportInfo *models.Report
+// @param req models.EditLayoutImgReq
+// @param sysUser *system.Admin
+// @return err error
+// @return errMsg string
+func EditReportLayoutImg(reportInfo *models.Report, req models.EditLayoutImgReq, sysUser *system.Admin) (err error, errMsg string) {
+	errMsg = `保存失败`
+
+	reportInfo.HeadResourceId = req.HeadResourceId
+	reportInfo.HeadImg = req.HeadImg
+	reportInfo.EndResourceId = req.EndResourceId
+	reportInfo.EndImg = req.EndImg
+	reportInfo.CanvasColor = req.CanvasColor
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	reportInfo.ModifyTime = time.Now()
+
+	updateCols := []string{"HeadResourceId", "HeadImg", "EndResourceId", "EndImg", "CanvasColor", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
+
+	err = reportInfo.UpdateReport(updateCols)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// PublishReport
+// @Description: 报告发布
+// @author: Roc
+// @datetime 2024-06-20 09:44:13
+// @param reportId int
+// @param reportUrl string
+// @param sysUser *system.Admin
+// @return tips string
+// @return err error
+// @return errMsg string
+func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips string, err error, errMsg string) {
+	errMsg = `报告发布失败`
+	reportInfo, err := models.GetReportByReportId(reportId)
+	if err != nil {
+		errMsg = "获取报告信息失败"
+		return
+	}
+	if reportInfo == nil {
+		errMsg = "获取报告信息失败"
+		err = errors.New(errMsg)
+		return
+	}
+
+	var publishTime time.Time
+	if reportInfo.MsgIsSend == 1 && reportInfo.PublishTime.IsZero() { //如果报告曾经发布过,并且已经发送过模版消息,则章节的发布时间为报告的发布时间
+		publishTime = reportInfo.PublishTime
+	} else {
+		publishTime = time.Now()
+	}
+	var tmpErr error
+
+	// 章节类型的报告(原来的晨周报)
+	if reportInfo.HasChapter == 1 {
+		tips, tmpErr, errMsg = PublishChapterReport(reportInfo, reportUrl, sysUser)
+		if tmpErr != nil {
+			err = errors.New("晨周报发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(reportId))
+		}
+		return
+	}
+
+	// 普通报告
+	if reportInfo.Content == "" {
+		errMsg = `报告内容为空,不可发布`
+		err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
+		return
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperatePublish)
+	if e != nil {
+		errMsg = "操作失败"
+		err = errors.New("校验报告当前状态失败, Err: " + e.Error())
+		return
+	}
+
+	if state == models.ReportStatePublished {
+		// 发布报告
+		if tmpErr = models.PublishReportById(reportId, publishTime, sysUser.AdminId, sysUser.RealName); tmpErr != nil {
+			err = errors.New("报告发布失败, Err:" + tmpErr.Error() + ", report_id:" + strconv.Itoa(reportId))
+			return
+		}
+		go func() {
+			// 生成音频
+			if reportInfo.VideoUrl == "" {
+				go UpdateReportVideo(reportInfo)
+			}
+			// 更新报告Es
+			_ = UpdateReportEs(reportId, 2)
+		}()
+	} else {
+		// 从无审批切换为有审批, 状态重置
+		if e = models.ResetReportById(reportId, state, sysUser.AdminId, sysUser.RealName); e != nil {
+			errMsg = "操作失败"
+			err = fmt.Errorf("重置报告状态失败, Err: %s, ReportId: %d", e.Error(), reportId)
+			return
+		}
+	}
+
+	recordItem := &models.ReportStateRecord{
+		ReportId:   reportId,
+		ReportType: 1,
+		State:      state,
+		AdminId:    sysUser.AdminId,
+		AdminName:  sysUser.AdminName,
+		CreateTime: time.Now(),
+	}
+	go func() {
+		_, _ = models.AddReportStateRecord(recordItem)
+	}()
+
+	// 生成报告pdf和长图
+	{
+		reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ReportLayout)
+		go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
+	}
+
+	// 报告权限处理
+	{
+		minClassifyId, _, tmpErr := getMinClassify(reportInfo)
+		if tmpErr != nil {
+			return
+		}
+		go handleReportPermission(int64(reportInfo.Id), minClassifyId)
+	}
+
+	return
+}
+
+// PublishChapterReport
+// @Description: 发布章节类型的报告
+// @author: Roc
+// @datetime 2024-06-28 10:38:50
+// @param reportInfo *models.Report
+// @param reportUrl string
+// @return tips string
+// @return err error
+// @return errMsg string
+func PublishChapterReport(reportInfo *models.Report, reportUrl string, sysUser *system.Admin) (tips string, err error, errMsg string) {
+	reportId := reportInfo.Id
+	if reportInfo.State == 2 {
+		return
+	}
+
+	// 获取所有章节列表
+	chapters, err := models.GetChapterListByReportId(reportId)
+	if err != nil {
+		return
+	}
+	chapterLen := len(chapters)
+	if chapterLen <= 0 {
+		tips = "报告章节为空,不可发布"
+		errMsg = tips
+		err = errors.New(tips)
+		return
+	}
+
+	// 获取报告中的所有章节列表
+	chapterList, err := models.GetChapterListByReportId(reportId)
+	if err != nil {
+		return
+	}
+	for _, chapter := range chapterList {
+		if chapter.PublishState == 1 {
+			tips = "还存在未发布的章节"
+			errMsg = tips
+			err = errors.New(tips)
+			return
+		}
+	}
+
+	// 根据审批开关及审批流判断当前报告状态
+	state, e := CheckReportCurrState(report_approve.FlowReportTypeChinese, reportInfo.ClassifyIdFirst, reportInfo.ClassifyIdSecond, reportInfo.ClassifyIdThird, models.ReportOperatePublish)
+	if e != nil {
+		//errMsg = "操作失败"
+		err = errors.New("校验报告当前状态失败, Err: " + e.Error())
+		return
+	}
+
+	// 如果状态不是已发布,那么就重置状态
+	if state != models.ReportStatePublished {
+		// 从无审批切换为有审批, 状态重置
+		if e = models.ResetReportById(reportId, state, sysUser.AdminId, sysUser.RealName); e != nil {
+			//errMsg = "操作失败"
+			err = fmt.Errorf("重置报告状态失败, Err: %s, ReportId: %d", e.Error(), reportId)
+			return
+		}
+		return
+	}
+
+	// 需发布整期
+	updateCols := make([]string, 0)
+	reportInfo.LastModifyAdminId = sysUser.AdminId
+	reportInfo.LastModifyAdminName = sysUser.RealName
+	updateCols = append(updateCols, "LastModifyAdminId", "LastModifyAdminName", "Title", "State", "ModifyTime")
+
+	// 发布后标题调整
+	//title := reportInfo.Title
+	//title = strings.ReplaceAll(title, "【弘则FICC晨报】", "")
+	//title = strings.ReplaceAll(title, "【弘则FICC周报】", "")
+	//if title == "" {
+	//	// 取第一个需发布章节的标题
+	//	firstId := publishIdArr[0]
+	//	firstTitle := ""
+	//	for i := 0; i < chapterLen; i++ {
+	//		if chapters[i].ReportChapterId == firstId {
+	//			firstTitle = chapters[i].Title
+	//			break
+	//		}
+	//	}
+	//	title = firstTitle
+	//}
+	//report.Title = title
+	reportInfo.State = state
+
+	// 研报后台4.4 只在没有发布过时更新发布时间,其余均按模版消息发送时间当作发布时间
+	if reportInfo.MsgIsSend == 0 || reportInfo.PublishTime.IsZero() {
+		reportInfo.PublishTime = time.Now().Local()
+		updateCols = append(updateCols, "PublishTime")
+
+	}
+	reportInfo.ModifyTime = time.Now().Local()
+
+	if e := models.PublishReportAndChapter(reportInfo, true, updateCols); e != nil {
+		err = errors.New("发布报告及章节失败")
+		return
+	}
+	// 生成章节音频
+	go UpdateReportVideo(reportInfo)
+
+	// 更新报告ES
+	go func() {
+		_ = UpdateReportEs(reportInfo.Id, 2)
+	}()
+
+	// 发布时备份内容
+	go SaveReportLogs(reportInfo, chapters, reportInfo.AdminId, reportInfo.AdminRealName)
+
+	// 生成报告pdf和长图
+	{
+		reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ReportLayout)
+		go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
+	}
+
+	return
+}
+
+// DeleteReportAndChapter 删除报告及章节
+func DeleteReportAndChapter(reportId int) (err error) {
+	reportInfo, err := models.GetReportByReportId(reportId)
+	if err != nil {
+		err = errors.New("报告信息有误, Err: " + err.Error())
+		return
+	}
+	if reportInfo.State == 2 {
+		err = errors.New("报告已发布,不可删除")
+		return
+	}
+	// 更新ES
+	_ = UpdateReportEs(reportId, 1)
+	// 删除
+	if reportInfo.HasChapter == 1 {
+		err = models.DeleteDayWeekReportAndChapter(reportId)
+	} else {
+		err = models.DeleteReport(reportId)
+	}
+	if err != nil {
+		err = errors.New("删除失败, Err: " + err.Error())
+		return
+	}
+	// 重置PPT关联报告
+	go func() {
+		_ = ResetPPTReport(reportId, false)
+	}()
+
+	return
+}
+
+// getMinClassify
+// @Description: 获取最小分类ID
+// @author: Roc
+// @datetime 2024-06-20 09:23:19
+// @param reportInfo *models.Report
+// @return minClassifyId int
+// @return minClassifyName string
+// @return err error
+func getMinClassify(reportInfo *models.Report) (minClassifyId int, minClassifyName string, err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("获取最小分类ID失败,报告ID:%d,Err:%s", reportInfo.Id, err.Error())
+		}
+	}()
+	minClassifyId = reportInfo.ClassifyIdThird
+	minClassifyName = reportInfo.ClassifyNameThird
+	if minClassifyId <= 0 {
+		minClassifyId = reportInfo.ClassifyIdSecond
+		minClassifyName = reportInfo.ClassifyNameSecond
+	}
+	if minClassifyId <= 0 {
+		minClassifyId = reportInfo.ClassifyIdFirst
+		minClassifyName = reportInfo.ClassifyNameFirst
+	}
+	if minClassifyId <= 0 {
+		err = errors.New("分类异常")
+	}
+
+	return
+}
+
+// handleReportPermission
+// @Description: 报告权限处理
+// @author: Roc
+// @datetime 2024-06-19 18:00:51
+// @param reportId int64
+// @param minClassifyId int
+func handleReportPermission(reportId int64, minClassifyId int) {
+	// TODO 报告权限处理
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("报告权限处理失败,报告ID:%d,分类ID:%d,Err:%s", reportId, minClassifyId, err.Error())
+			//alarm_msg.SendAlarmMsg("修改删除报告权限失败,Err:"+err.Error(), 3)
+		}
+	}()
+
+	err = models.RemoveChartPermissionChapterMapping(reportId)
+	if err != nil {
+		err = errors.New("修改删除报告权限失败,Err:" + err.Error())
+		return
+	}
+	permissionItems, err := models.GetPermission(minClassifyId)
+	if err != nil {
+		err = errors.New("获取权限失败,Err:" + err.Error())
+		return
+	}
+	for _, v := range permissionItems {
+		err = models.AddChartPermissionChapterMapping(v.ChartPermissionId, reportId)
+		if err != nil {
+			err = errors.New("新增权限失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	// 同步crm权限
+	_ = EditReportPermissionSync(reportId, minClassifyId)
+
+	return
+}
+
+// HandleReportPermission
+// @Description: 报告权限处理
+// @author: Roc
+// @datetime 2024-07-12 11:17:49
+// @param reportInfo *models.Report
+func HandleReportPermission(reportInfo *models.Report) {
+	minClassifyId, _, tmpErr := getMinClassify(reportInfo)
+	if tmpErr != nil {
+		return
+	}
+	handleReportPermission(int64(reportInfo.Id), minClassifyId)
+}
+
+// UpdateReportVideo
+// @Description: 更新报告音频
+// @author: Roc
+// @datetime 2024-07-17 18:11:10
+// @param reportInfo *models.Report
+func UpdateReportVideo(reportInfo *models.Report) {
+	if reportInfo.HasChapter == 1 {
+		reportChapterIdList := make([]int, 0)
+		// 获取报告中的所有章节列表
+		chapterList, err := models.GetChapterListByReportId(reportInfo.Id)
+		if err != nil {
+			return
+		}
+
+		reportContent := ""
+		for _, chapter := range chapterList {
+			if chapter.PublishState == 2 {
+				reportChapterIdList = append(reportChapterIdList, chapter.ReportChapterId)
+				reportContent += chapter.Title + `。`
+				reportContent += chapter.Content + `。`
+			}
+		}
+		if len(reportChapterIdList) > 0 {
+			err = UpdateChaptersVideo(reportChapterIdList)
+			if err != nil {
+				utils.FileLog.Info(fmt.Sprintf("%d报告音频生产失败:%s", reportInfo.Id, err.Error()))
+			}
+		}
+
+		// 生成汇总音频
+
+		{
+			if reportInfo.VideoUrl != "" && reportInfo.VideoName != "" && reportInfo.VideoSize != "" && reportInfo.VideoPlaySeconds != "" {
+				return
+			}
+			videoUrl, videoName, videoSize, videoPlaySeconds, e := CreateReportVideo(reportInfo.Title, html.UnescapeString(reportContent), time.Now().Format(utils.FormatDateTime))
+			if e != nil {
+				err = e
+				return
+			}
+
+			// 修改报告的音频信息
+			err = models.ModifyReportVideoByNoVideo(reportInfo.Id, videoUrl, videoName, videoSize, videoPlaySeconds)
+
+		}
+	} else {
+		err := CreateVideo(reportInfo)
+		if err != nil {
+			utils.FileLog.Info(fmt.Sprintf("%d报告音频生产失败:%s", reportInfo.Id, err.Error()))
+		}
+	}
+}
+
+// GetGeneralPdfUrl
+// @Description: 获取生成pdf的地址
+// @author: Roc
+// @datetime 2024-07-19 14:09:28
+// @param reportCode string
+// @param reportLayout int8
+// @return pdfUrl string
+func GetGeneralPdfUrl(reportCode string, reportLayout int8) (pdfUrl string) {
+	conf, e := models.GetBusinessConfByKey("ReportViewUrl")
+	if e != nil {
+		return
+	}
+
+	switch reportLayout {
+	case 1:
+		// 普通布局
+		pdfUrl = fmt.Sprintf("%s/reportshare_pdf?code=%s", conf.ConfVal, reportCode)
+	case 2:
+		// 智能布局
+		pdfUrl = fmt.Sprintf("%s/reportshare_smart_pdf?code=%s", conf.ConfVal, reportCode)
+	}
+
+	return
+}

+ 11 - 0
services/smart_report.go

@@ -282,6 +282,13 @@ finally:
 	return
 }
 
+// Report2pdfAndJpeg
+// @Description: 报告转pdf和图片
+// @author: Roc
+// @datetime 2024-07-19 14:11:38
+// @param reportUrl string
+// @param reportId int
+// @param reportType int
 func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 	var err error
 
@@ -292,6 +299,10 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 		}
 	}()
 
+	if reportUrl == `` {
+		return
+	}
+
 	// 先清空字段
 	if reportType == 1 {
 		err = models.UpdatePdfUrlReportById(reportId)

+ 21 - 22
services/task.go

@@ -2,7 +2,6 @@ package services
 
 import (
 	"eta/eta_api/models"
-	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
@@ -54,27 +53,27 @@ func Task() {
 	fmt.Println("task end")
 }
 
-// 每日发布晨报
-func AutoPublishDayReport() {
-	defer func() {
-		if err := recover(); err != nil {
-			fmt.Println("[AutoPublishDayReport]", err)
-		}
-	}()
-
-	// 每日8:42发布晨报
-	ticker := time.Tick(50 * time.Second)
-	for range ticker {
-		nowTime := time.Now()
-		clock := nowTime.Format("1504")
-		if clock == "0842" {
-			if err := PublishTodayDayReport(); err != nil {
-				go alarm_msg.SendAlarmMsg(fmt.Sprint("每日晨报自动发送 AutoPublishDayReport ERR:", err), 3)
-				//utils.SendEmail(utils.APPNAME+" "+utils.RunMode+" 失败提醒", fmt.Sprint("AutoPublishDayReport ERR:", err), utils.EmailSendToUsers)
-			}
-		}
-	}
-}
+//// 每日发布晨报
+//func AutoPublishDayReport() {
+//	defer func() {
+//		if err := recover(); err != nil {
+//			fmt.Println("[AutoPublishDayReport]", err)
+//		}
+//	}()
+//
+//	// 每日8:42发布晨报
+//	ticker := time.Tick(50 * time.Second)
+//	for range ticker {
+//		nowTime := time.Now()
+//		clock := nowTime.Format("1504")
+//		if clock == "0842" {
+//			if err := PublishTodayDayReport(); err != nil {
+//				go alarm_msg.SendAlarmMsg(fmt.Sprint("每日晨报自动发送 AutoPublishDayReport ERR:", err), 3)
+//				//utils.SendEmail(utils.APPNAME+" "+utils.RunMode+" 失败提醒", fmt.Sprint("AutoPublishDayReport ERR:", err), utils.EmailSendToUsers)
+//			}
+//		}
+//	}
+//}
 
 // ImportManualDataRefresh 导入手工数据后的刷新
 func ImportManualDataRefresh() {

+ 31 - 34
services/video.go

@@ -21,7 +21,7 @@ import (
 	"unicode"
 )
 
-func CreateVideo(report *models.ReportDetail) (err error) {
+func CreateVideo(report *models.Report) (err error) {
 	defer func() {
 		if err != nil {
 			utils.FileLog.Error("CreateVideo Err:%s", err.Error())
@@ -78,9 +78,18 @@ func CreateVideo(report *models.ReportDetail) (err error) {
 
 	saveName := utils.GetRandStringNoSpecialChar(16) + ".mp3"
 	savePath := "./" + saveName
-	//if utils.FileIsExist(savePath) {
-	//	os.Remove(savePath)
-	//}
+	defer func() {
+		if utils.FileIsExist(savePath) {
+			os.Remove(savePath)
+		}
+	}()
+
+	// 如果没有文本内容,那么就不生成了
+	videoContent = strings.TrimSpace(videoContent)
+	if videoContent == `` {
+		return
+	}
+
 	contentArr := GetChineseCount(videoContent)
 	for _, v := range contentArr {
 		newText := v
@@ -99,20 +108,6 @@ func CreateVideo(report *models.ReportDetail) (err error) {
 	}
 
 	uploadUrl := ``
-	//上传到阿里云 和 minio
-	//if utils.ObjectStorageClient == "minio" {
-	//	uploadUrl, err = UploadAudioToMinIo(saveName, savePath)
-	//	if err != nil {
-	//		err = errors.New("UploadAudioAliyun Err:" + err.Error())
-	//		return
-	//	}
-	//} else {
-	//	uploadUrl, err = UploadAudioAliyun(saveName, savePath)
-	//	if err != nil {
-	//		err = errors.New("UploadAudioAliyun Err:" + err.Error())
-	//		return
-	//	}
-	//}
 	ossClient := NewOssClient()
 	if ossClient == nil {
 		err = fmt.Errorf("初始化OSS服务失败")
@@ -124,7 +119,7 @@ func CreateVideo(report *models.ReportDetail) (err error) {
 		return
 	}
 
-	fileBody, err := ioutil.ReadFile(savePath)
+	fileBody, err := os.ReadFile(savePath)
 	videoSize := len(fileBody)
 	sizeFloat := (float64(videoSize) / float64(1024)) / float64(1024)
 	sizeStr := utils.SubFloatToFloatStr(sizeFloat, 2)
@@ -138,12 +133,9 @@ func CreateVideo(report *models.ReportDetail) (err error) {
 		}
 	}
 
-	if playSeconds > 0 {
-		if utils.FileIsExist(savePath) {
-			os.Remove(savePath)
-		}
-	}
+	// 修改报告的音频信息
 	err = models.ModifyReportVideo(report.Id, uploadUrl, videoName, sizeStr, playSeconds)
+
 	return
 }
 
@@ -235,7 +227,10 @@ func CreateReportVideo(reportTitle, reportContent, reportTime string) (uploadUrl
 			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"失败提醒", "CreateReportVideo, reportTitle:" + reportTitle +", Err:"+err.Error(), utils.EmailSendToUsers)
 		}
 	}()
-	if reportContent == "" {
+
+	// 如果没有文本内容,那么就不生成了
+	reportContent = strings.TrimSpace(reportContent)
+	if reportContent == `` {
 		return
 	}
 
@@ -288,11 +283,19 @@ func CreateReportVideo(reportTitle, reportContent, reportTime string) (uploadUrl
 	param.Data.Status = 2
 	videoContent := doc.Text()
 
+	// 如果没有文本内容,那么就不生成了
+	videoContent = strings.TrimSpace(videoContent)
+	if videoContent == `` {
+		return
+	}
+
 	saveName := utils.GetRandStringNoSpecialChar(16) + ".mp3"
 	savePath := "./" + saveName
-	//if utils.FileIsExist(savePath) {
-	//	os.Remove(savePath)
-	//}
+	defer func() {
+		if utils.FileIsExist(savePath) {
+			os.Remove(savePath)
+		}
+	}()
 	contentArr := GetChineseCount(videoContent)
 	for _, v := range contentArr {
 		newText := v
@@ -349,11 +352,5 @@ func CreateReportVideo(reportTitle, reportContent, reportTime string) (uploadUrl
 		}
 	}
 
-	if playSeconds > 0 {
-		if utils.FileIsExist(savePath) {
-			os.Remove(savePath)
-		}
-	}
-
 	return
 }

+ 34 - 21
services/wechat_send_msg.go

@@ -4,11 +4,12 @@ import (
 	"encoding/json"
 	"errors"
 	"eta/eta_api/models"
+	"eta/eta_api/models/company"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/third"
 	"eta/eta_api/utils"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strconv"
 	"strings"
@@ -27,6 +28,7 @@ type ClearQuotaResponse struct {
 
 // SendMiniProgramReportWxMsg 推送报告微信模板消息-小程序链接
 func SendMiniProgramReportWxMsg(reportId int) (err error) {
+	fmt.Println("推送模板消息1 services SendMsg:", reportId)
 	var msg string
 	reportIdStr := strconv.Itoa(reportId)
 	if utils.ETAMiniBridgeUrl != "" {
@@ -41,9 +43,9 @@ func SendMiniProgramReportWxMsg(reportId int) (err error) {
 			//go utils.SendEmail("SendMiniProgramReportWxMsg发送报告模版消息失败"+"【"+utils.APPNAME+"】"+"【"+utils.RunMode+"】"+time.Now().Format("2006-01-02 15:04:05"), "ReportId:"+reportIdStr+";"+msg+";Err:"+err.Error(), toUser)
 		}
 	}()
-	utils.FileLog.Info("%s", "services SendMsg")
+	fmt.Println("推送模板消息 services SendMsg:", reportId)
 
-	report, err := models.GetReportById(reportId)
+	report, err := models.GetReportByReportId(reportId)
 	if err != nil {
 		msg = "GetReportInfo Err:" + err.Error()
 		return
@@ -66,30 +68,34 @@ func SendMiniProgramReportWxMsg(reportId int) (err error) {
 	//}
 
 	var openIdArr []string
-	if report.ClassifyIdSecond <= 0 {
+
+	// 如果是弘则,且报告分类是晨报,那么就所有人推送
+	if (utils.BusinessCode == utils.BusinessCodeRelease || utils.BusinessCode == utils.BusinessCodeSandbox || utils.BusinessCode == utils.BusinessCodeDebug) && report.ClassifyNameFirst == "晨报" {
+		// 如果是章节,那就推送所有用户
 		openIdArr, err = models.GetOpenIdArr()
 		if err != nil {
 			msg = "get GetOpenIdArr err:" + err.Error()
 			return
 		}
 	} else {
-		classify, err := models.GetClassifyById(report.ClassifyIdSecond)
+		minClassifyId, _, err := getMinClassify(report)
+		if err != nil {
+			msg = "获取报告的最小分类失败 err:" + err.Error()
+			return err
+		}
+
+		// 判断分类是否存在
+		_, err = models.GetClassifyById(minClassifyId)
 		if err != nil {
 			msg = "获取报告分类失败 err:" + err.Error()
 			return err
 		}
-		if classify.IsMassSend == 1 {
-			openIdArr, err = models.GetOpenIdArr()
-			if err != nil {
-				msg = "get GetOpenIdArr err:" + err.Error()
-				return err
-			}
-		} else {
-			openIdArr, err = models.GetOpenIdArrByClassifyNameSecond(report.ClassifyNameSecond)
-			if err != nil {
-				msg = "GetOpenIdArrByClassifyNameSecond err:" + err.Error()
-				return err
-			}
+
+		// 获取该分类关联的openid列表
+		openIdArr, err = models.GetOpenIdArrByClassifyId(minClassifyId)
+		if err != nil {
+			msg = "GetOpenIdArrByClassifyNameSecond err:" + err.Error()
+			return err
 		}
 	}
 
@@ -107,7 +113,7 @@ func SendMiniProgramReportWxMsg(reportId int) (err error) {
 	first := fmt.Sprintf("Hi,最新一期%s已上线,欢迎查看", report.ClassifyNameFirst)
 	keyword1 := title
 	keyword2 := report.Title
-	keyword3 := report.PublishTime
+	keyword3 := report.PublishTime.Format(utils.FormatDateTime)
 	keyword4 := report.Abstract
 
 	//sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
@@ -121,7 +127,14 @@ func SendMiniProgramReportWxMsg(reportId int) (err error) {
 	//sendMap["data"] = sendData
 
 	var wxAppPath string
-	if report.ChapterType == utils.REPORT_TYPE_WEEK {
+	weekClassifyId, err := company.GetReportClassifyIdByConfigKey("report_week_classify_id")
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		msg = "获取周报ID配置失败:" + err.Error()
+		return err
+	}
+
+	// 周报走这个逻辑,我也不清楚为什么,原先就是这样(2024-6-20 16:46:07)
+	if report.ClassifyIdFirst == weekClassifyId {
 		wxAppPath = fmt.Sprintf("pages-report/chapterList?reportId=%s", reportIdStr)
 	} else {
 		wxAppPath = fmt.Sprintf("pages-report/reportDetail?reportId=%s", reportIdStr)
@@ -198,7 +211,7 @@ func SendTemplateMsg(sendInfo *SendWxTemplate) (err error) {
 		alarm_msg.SendAlarmMsg("SendTemplateMsg json.Marshal Err:"+err.Error(), 1)
 		return err
 	}
-	body := ioutil.NopCloser(strings.NewReader(string(postData)))
+	body := io.NopCloser(strings.NewReader(string(postData)))
 	client := &http.Client{}
 	req, err := http.NewRequest("POST", utils.SendWxTemplateMsgUrl, body)
 	if err != nil {
@@ -214,7 +227,7 @@ func SendTemplateMsg(sendInfo *SendWxTemplate) (err error) {
 		return err
 	}
 	defer resp.Body.Close()
-	b, err := ioutil.ReadAll(resp.Body)
+	b, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return err
 	}

+ 1 - 1
utils/common.go

@@ -2343,7 +2343,7 @@ func ContentXssCheck(content string) (err error) {
 					lowerKey := strings.ToLower(attr.Key)
 					lowerVal := strings.ToLower(attr.Val)
 					if lowerKey == "src" || lowerKey == "dynsrc" || lowerKey == "background" || lowerKey == "lowsrc" {
-						if !isValidSrc(lowerVal) {
+						if lowerVal != `` && !isValidSrc(lowerVal) {
 							err = fmt.Errorf("invalid src attribute value: %s", attr.Val)
 							return err
 						}

+ 1 - 0
utils/constants.go

@@ -310,6 +310,7 @@ const (
 // 图表样式类型
 const (
 	CHART_TYPE_CURVE           = 1  //曲线图
+	CHART_TYPE_SEASON          = 2  //季节性图
 	CHART_TYPE_BAR             = 7  //柱形图
 	CHART_TYPE_SECTION_SCATTER = 10 //截面散点图样式
 	CHART_TYPE_RADAR           = 11 //雷达图

部分文件因文件數量過多而無法顯示