Browse Source

报告章节管理

xyxie 1 year ago
parent
commit
12b8780149

+ 70 - 0
controller/crm/chart_permission.go

@@ -142,3 +142,73 @@ func (cp *ChartPermissionController) MoveChartPermission(c *gin.Context) {
 	}
 	resp.Ok("操作成功", c)
 }
+
+// GetClassifyChartPermission
+// @Description  获取报告分类绑定的品种
+// @Success 200 {string} string "查询成功"
+// @Router /crm/chart_permission/classify [post]
+func (cp *ChartPermissionController) GetClassifyChartPermission(c *gin.Context) {
+	var req crm.ClassifyPermissionReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	ob := new(crm.ChartPermissionSearchKeyWordMapping)
+	list := make([]*crm.ChartPermissionSearchKeyWordMapping, 0)
+
+	if req.Keyword == "" {
+		list, err = ob.GetPermission()
+	} else {
+		list, err = ob.GetPermissionByKeyword(req.Keyword)
+	}
+	if err != nil {
+		resp.FailData("查询分类权限失败", err.Error(), c)
+		return
+	}
+	data := crm.ClassifyPermissionResp{List: list}
+	resp.OkData("查询成功", data, c)
+}
+
+// EditClassifyChartPermission
+// @Description  编辑分类权限
+// @Success 200 {string} string "操作成功"
+// @Router /crm/chart_permission/classify/edit [post]
+func (cp *ChartPermissionController) EditClassifyChartPermission(c *gin.Context) {
+	var req crm.ClassifyPermissionEditReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	if req.Keyword == "" {
+		resp.Fail("分类名称不能为空", c)
+		return
+	}
+	if req.NewKeyword == "" {
+		resp.Fail("分类名称不能为空", c)
+		return
+	}
+	if len(req.ChartPermissionIdList) == 0 {
+		resp.Fail("请选择权限", c)
+		return
+	}
+	ob := new(crm.ChartPermissionSearchKeyWordMapping)
+	e := ob.EditChartPermissionSearchKeyWordMappingMulti(req.Keyword, req.NewKeyword, req.ChartPermissionIdList)
+	if e != nil {
+		resp.FailData("设置分类权限失败", e.Error(), c)
+		return
+	}
+	resp.Ok("操作成功", c)
+}

+ 399 - 0
controller/crm/report_chapter_type.go

@@ -0,0 +1,399 @@
+package crm
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hz_crm_eta/controller/resp"
+	"hongze/hz_crm_eta/global"
+	"hongze/hz_crm_eta/models/crm"
+	crmService "hongze/hz_crm_eta/services/crm"
+	"hongze/hz_crm_eta/utils"
+	"time"
+)
+
+type ReportChapterTypeController struct{}
+
+// List
+// @Title 报告章节列表
+// @Description 报告章节列表
+// @Param   ReportType  query  string  true  "报告类型: day-晨报; week-周报"
+// @Success 200 {object} models.ReportChapterTypePageListResp
+// @router /chapter_type/list [get]
+func (this *ReportChapterTypeController) List(c *gin.Context) {
+	var req crm.ChapterTypeReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	reportType := req.ReportType
+	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
+	if !utils.InArrayByStr(typeArr, reportType) {
+		resp.Fail("请选择报告类型", c)
+		return
+	}
+
+	cond := ` research_type = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, reportType)
+	list, e := crm.GetReportChapterTypePageList(cond, pars)
+	if e != nil {
+		resp.FailData("获取失败", "获取报告章节列表失败, Err:"+e.Error(), c)
+		return
+	}
+
+	mappingList, e := crm.GetChapterTypePermissionByTypeIdAndResearchType(reportType)
+	if e != nil {
+		resp.FailData("获取章节类型权限列表失败", "获取章节类型权限列表失败, Err:"+e.Error(), c)
+		return
+	}
+	mappingMap := make(map[int][]int)
+	for _, v := range mappingList {
+		mappingMap[v.ReportChapterTypeId] = append(mappingMap[v.ReportChapterTypeId], v.ChartPermissionId)
+	}
+
+	respList := make([]*crm.ReportChapterTypeListItem, 0)
+	for i := range list {
+		permissionIds, _ := mappingMap[list[i].ReportChapterTypeId]
+		respList = append(respList, &crm.ReportChapterTypeListItem{
+			ReportChapterTypeId:   list[i].ReportChapterTypeId,
+			ReportChapterTypeName: list[i].ReportChapterTypeName,
+			Sort:                  list[i].Sort,
+			CreatedTime:           list[i].CreatedTime.Format(utils.FormatDateTime),
+			ResearchType:          list[i].ResearchType,
+			SelectedImage:         list[i].SelectedImage,
+			UnselectedImage:       list[i].UnselectedImage,
+			WordsImage:            list[i].YbBottomIcon, // 此处的不一样
+			EditImgUrl:            list[i].EditImgUrl,
+			IsShow:                list[i].IsShow,
+			Enabled:               list[i].Enabled,
+			ChartPermissionIdList: permissionIds,
+		})
+	}
+
+	data := new(crm.ReportChapterTypeListResp)
+	data.List = respList
+	resp.OkData("查询成功", data, c)
+}
+
+// Add
+// @Title 新增报告章节
+// @Description 新增报告章节
+// @Param	request  body  crm.ReportChapterTypeAddReq  true  "type json string"
+// @Success 200 string "操作成功"
+// @router /chapter_type/add [post]
+func (this *ReportChapterTypeController) Add(c *gin.Context) {
+	var req crm.ReportChapterTypeAddReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	if req.ReportChapterTypeName == "" {
+		resp.Fail("请输入章节名称", c)
+		return
+	}
+	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
+	if !utils.InArrayByStr(typeArr, req.ResearchType) {
+		resp.Fail("请选择报告类型", c)
+		return
+	}
+	// 重名校验
+	cond := ` report_chapter_type_name = ? AND research_type = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.ReportChapterTypeName, req.ResearchType)
+	exists, e := crm.GetReportChapterTypeByCondition(cond, pars)
+	if e != nil && e != utils.ErrNoRow {
+		resp.FailData("操作失败", "获取报告章节失败, Err:"+e.Error(), c)
+		return
+	}
+	if exists.ReportChapterTypeId > 0 {
+		resp.Fail("章节名称已存在", c)
+		return
+	}
+
+	nowTime := time.Now().Local()
+	item := new(crm.ReportChapterType)
+	maxSort, e := item.GetMaxSort()
+	if e != nil {
+		resp.FailData("操作失败", "获取章节最大排序失败, Err:"+e.Error(), c)
+		return
+	}
+	item.ReportChapterTypeName = req.ReportChapterTypeName
+	item.Sort = maxSort + 1
+	item.Enabled = 1
+	item.CreatedTime = nowTime
+	item.LastUpdatedTime = nowTime
+	item.ResearchType = req.ResearchType
+	item.ReportChapterTypeName = req.ReportChapterTypeName
+	item.IsSet = 0
+	item.ReportChapterTypeKey = req.ReportChapterTypeName
+	item.TickerTitle = req.ReportChapterTypeName
+	if e = item.Create(); e != nil {
+		resp.FailData("操作失败", "新增报告章节失败, Err:"+e.Error(), c)
+		return
+	}
+
+	// 设置权限
+	cond = ""
+	pars = make([]interface{}, 0)
+	permissionList, e := crmService.GetChartPermissionList(cond, pars)
+	if e != nil {
+		resp.FailData("操作失败", "获取权限列表失败, Err:"+e.Error(), c)
+		return
+	}
+	permissionIdName := make(map[int]string)
+	for i := range permissionList {
+		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
+	}
+
+	researchType := item.ResearchType
+	newPermissions := make([]*crm.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
+	newWeekPermissions := make([]*crm.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	for i := range req.ChartPermissionIdList {
+		// todo 检查权限是否被禁用,过滤一级品种,只能绑定二级品种
+		newPermissions = append(newPermissions, &crm.ReportChapterTypePermission{
+			ReportChapterTypeId:   item.ReportChapterTypeId,
+			ReportChapterTypeName: item.ReportChapterTypeName,
+			ChartPermissionId:     req.ChartPermissionIdList[i],
+			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
+			ResearchType:          researchType,
+			CreatedTime:           nowTime,
+		})
+		if researchType == utils.REPORT_TYPE_WEEK {
+			newWeekPermissions = append(newWeekPermissions, &crm.ChartPermissionChapterMapping{
+				ChartPermissionId:   req.ChartPermissionIdList[i],
+				ReportChapterTypeId: item.ReportChapterTypeId,
+				ResearchType:        researchType,
+			})
+		}
+	}
+
+	// 设置权限
+	e = crm.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	if e != nil {
+		resp.FailData("操作失败", "设置章节类型权限失败, Err:"+e.Error(), c)
+		return
+	}
+	// todo 清除小程序端的章节缓存
+	/*{
+		key := "hongze_yb:report_chapter_type:GetEffectTypeID"
+		_ = utils.Rc.Delete(key)
+	}*/
+
+	resp.Ok("操作成功", c)
+}
+
+// Edit
+// @Title 编辑报告章节
+// @Description 编辑报告章节
+// @Param	request  body  crm.ReportChapterTypeEditReq  true  "type json string"
+// @Success 200 string "操作成功"
+// @router /chapter_type/edit [post]
+func (this *ReportChapterTypeController) Edit(c *gin.Context) {
+	var req crm.ReportChapterTypeEditReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	if req.ReportChapterTypeId <= 0 {
+		resp.Fail("章节ID有误", c)
+		return
+	}
+	if req.ReportChapterTypeName == "" {
+		resp.Fail("请输入章节名称", c)
+		return
+	}
+	if len(req.ChartPermissionIdList) == 0 {
+		resp.Fail("请选择权限", c)
+		return
+	}
+	typeArr := []string{utils.REPORT_TYPE_DAY, utils.REPORT_TYPE_WEEK}
+	if !utils.InArrayByStr(typeArr, req.ResearchType) {
+		resp.Fail("请选择报告类型", c)
+		return
+	}
+	// 重名校验
+	cond := ` report_chapter_type_name = ? AND research_type = ?`
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.ReportChapterTypeName, req.ResearchType)
+	exists, e := crm.GetReportChapterTypeByCondition(cond, pars)
+	if e != nil && e != utils.ErrNoRow {
+		resp.FailData("操作失败", "获取重名报告章节失败, Err:"+e.Error(), c)
+		return
+	}
+	if exists != nil && exists.ReportChapterTypeId > 0 && exists.ReportChapterTypeId != req.ReportChapterTypeId {
+		resp.Fail("章节名称已存在", c)
+		return
+	}
+	ob := new(crm.ReportChapterType)
+	item, e := ob.GetReportChapterTypeById(req.ReportChapterTypeId)
+	if e != nil {
+		if e == utils.ErrNoRow {
+			resp.Fail("报告章节不存在", c)
+			return
+		}
+		resp.FailData("操作失败", "获取报告章节失败, Err:"+e.Error(), c)
+		return
+	}
+	originReportChapterTypeName := item.ReportChapterTypeName
+	item.ReportChapterTypeName = req.ReportChapterTypeName
+	item.ResearchType = req.ResearchType
+	item.ReportChapterTypeKey = req.ReportChapterTypeName
+	item.TickerTitle = req.ReportChapterTypeName
+	updateCols := []string{"ReportChapterTypeName", "ResearchType", "ReportChapterTypeKey", "TickerTitle"}
+	if e = item.Update(updateCols); e != nil {
+		resp.FailData("操作失败", "更新报告章节失败, Err:"+e.Error(), c)
+		return
+	}
+
+	// 设置权限
+	cond = ""
+	pars = make([]interface{}, 0)
+	permissionList, e := crmService.GetChartPermissionList(cond, pars)
+	if e != nil {
+		resp.FailData("操作失败", "获取权限列表失败, Err:"+e.Error(), c)
+		return
+	}
+	permissionIdName := make(map[int]string)
+	for i := range permissionList {
+		permissionIdName[permissionList[i].ChartPermissionId] = permissionList[i].PermissionName
+	}
+
+	researchType := item.ResearchType
+	nowTime := time.Now().Local()
+	newPermissions := make([]*crm.ReportChapterTypePermission, 0)       // 报告章节权限表(新)
+	newWeekPermissions := make([]*crm.ChartPermissionChapterMapping, 0) // 报告章节权限表(老)
+	for i := range req.ChartPermissionIdList {
+		newPermissions = append(newPermissions, &crm.ReportChapterTypePermission{
+			ReportChapterTypeId:   item.ReportChapterTypeId,
+			ReportChapterTypeName: item.ReportChapterTypeName,
+			ChartPermissionId:     req.ChartPermissionIdList[i],
+			PermissionName:        permissionIdName[req.ChartPermissionIdList[i]],
+			ResearchType:          researchType,
+			CreatedTime:           nowTime,
+		})
+		if researchType == utils.REPORT_TYPE_WEEK {
+			newWeekPermissions = append(newWeekPermissions, &crm.ChartPermissionChapterMapping{
+				ChartPermissionId:   req.ChartPermissionIdList[i],
+				ReportChapterTypeId: item.ReportChapterTypeId,
+				ResearchType:        researchType,
+			})
+		}
+	}
+
+	// 设置权限
+	e = crm.SetReportChapterTypePermission(item.ReportChapterTypeId, researchType, newPermissions, newWeekPermissions)
+	if e != nil {
+		resp.FailData("操作失败", "设置章节类型权限失败, Err:"+e.Error(), c)
+		return
+	}
+	var ret crm.ReportChapterTypeEditResp
+	ret.OriginReportChapterTypeName = originReportChapterTypeName
+	// todo 清除小程序端的章节缓存
+	/*	{
+		key := "hongze_yb:report_chapter_type:GetEffectTypeID"
+		_ = utils.Rc.Delete(key)
+	}*/
+
+	resp.Ok("操作成功", c)
+}
+
+// SetEnabled
+// @Title 启用/禁用分类接口
+// @Description 启用/禁用分类
+// @Param	request	body models.ClassifyMoveReq true "type json string"
+// @Success 200 新增成功
+// @router /chapter_type/enabled/set [post]
+func (this *ReportChapterTypeController) SetEnabled(c *gin.Context) {
+	var req crm.ReportChapterTypeEnabledReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	if req.ReportChapterTypeId <= 0 {
+		resp.Fail("请选择章节", c)
+		return
+	}
+	if req.Enabled != 0 && req.Enabled != 1 {
+		resp.Fail("请选择正确的启用禁用状态", c)
+		return
+	}
+	ob := new(crm.ReportChapterType)
+	item, err := ob.GetReportChapterTypeById(req.ReportChapterTypeId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.FailData("章节不存在", "Err:"+err.Error(), c)
+			return
+		}
+
+		resp.FailData("获取章节信息失败", "Err:"+err.Error(), c)
+		return
+	}
+	if item == nil {
+		resp.Fail("章节不存在", c)
+		return
+	}
+	//设置分类启用、禁用状态
+	err = ob.SetEnabled(req.ReportChapterTypeId, req.Enabled)
+	if err != nil {
+		resp.FailData("操作失败", "Err:"+err.Error(), c)
+		return
+	}
+	resp.Ok("操作成功", c)
+}
+
+// Move
+// @Description  移动报告章节类型
+// @Success 200 {string} string "操作成功"
+// @Router /report_chapter_type/move [post]
+func (this *ReportChapterTypeController) Move(c *gin.Context) {
+	var req crm.ReportChapterTypeMoveReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	if req.ReportChapterTypeId <= 0 {
+		resp.Fail("请选择报告章节类型", c)
+		return
+	}
+
+	e, msg := crmService.MoveReportChapterType(req)
+	if e != nil {
+		resp.FailData(msg, e.Error(), c)
+		return
+	}
+	resp.Ok("操作成功", c)
+}

+ 377 - 0
controller/eta/classify.go

@@ -0,0 +1,377 @@
+package eta
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/go-playground/validator/v10"
+	"hongze/hz_crm_eta/controller/resp"
+	"hongze/hz_crm_eta/global"
+	"hongze/hz_crm_eta/models/crm"
+	"hongze/hz_crm_eta/models/eta"
+	eta2 "hongze/hz_crm_eta/services/eta"
+	"hongze/hz_crm_eta/utils"
+	"time"
+)
+
+type ClassifyController struct{}
+
+// ListClassify
+// @Title 获取分类列表
+// @Description 获取分类列表
+// @Param   KeyWord   query   string  true       "检索关键词"
+// @Param   CompanyType   query   string  false       "产品类型,枚举值:'ficc','权益';不传默认返回全部"
+// @Param   HideDayWeek   query   int  false       "是否隐藏晨周报"
+// @Success 200 {object} models.Classify
+// @router /list [get]
+func (this *ClassifyController) ListClassify(c *gin.Context) {
+	var req eta.ClassifyListReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	keyWord := req.Keyword
+	companyType := req.CompanyType
+	hideDayWeek := req.HideDayWeek
+
+	list, err := eta.GetClassifyList(keyWord, companyType, hideDayWeek)
+	if err != nil {
+		resp.FailData("获取失败", err.Error(), c)
+		return
+	}
+
+	parentIds := make([]int, 0)
+	for i := range list {
+		parentIds = append(parentIds, list[i].Id)
+	}
+	parentIdLen := len(parentIds)
+	if parentIdLen == 0 {
+		data := &eta.ClassifyListResp{
+			List: list,
+		}
+		resp.OkData("操作成功", data, c)
+		return
+	}
+
+	// 获取一级分类-子目录列表
+	menuListMap := make(map[int][]*eta.ClassifyMenu, 0)
+	var menuCond string
+	var menuPars []interface{}
+	menuCond += ` classify_id IN (` + utils.GetOrmInReplace(parentIdLen) + `)`
+	menuPars = append(menuPars, parentIds)
+	menuOb := new(eta.ClassifyMenu)
+	parentMenus, e := menuOb.GetClassifyMenuList(menuCond, menuPars)
+	if e != nil {
+		resp.FailData("获取失败", "获取一级分类子目录列表失败", c)
+		return
+	}
+	for i := range parentMenus {
+		if menuListMap[parentMenus[i].ClassifyId] == nil {
+			menuListMap[parentMenus[i].ClassifyId] = make([]*eta.ClassifyMenu, 0)
+		}
+		menuListMap[parentMenus[i].ClassifyId] = append(menuListMap[parentMenus[i].ClassifyId], parentMenus[i])
+	}
+
+	// 获取子分类
+	children, e := eta.GetClassifyChildByParentIds(parentIds, keyWord)
+	if e != nil {
+		resp.FailData("获取失败", "获取子分类失败", c)
+		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 += ` classify_id IN (` + utils.GetOrmInReplace(childrenIdsLen) + `)`
+		relatePars = append(relatePars, childrenIds)
+		menuRelationOb := new(eta.ClassifyMenuRelation)
+		relates, e := menuRelationOb.GetClassifyMenuRelationList(relateCond, relatePars)
+		if e != nil {
+			resp.FailData("获取失败", "获取二级分类子目录关联失败, Err: "+e.Error(), c)
+			return
+		}
+		for i := range relates {
+			relateMap[relates[i].ClassifyId] = relates[i].MenuId
+		}
+	}
+
+	permissionMappingOb := new(crm.ChartPermissionSearchKeyWordMapping)
+	permissionList, e := permissionMappingOb.GetPermission()
+	if e != nil {
+		resp.FailData("查询权限失败", e.Error(), c)
+		return
+	}
+	classifyPermissionMap := make(map[string][]int, 0)
+	if len(permissionList) > 0 {
+		for _, v := range permissionList {
+			classifyPermissionMap[v.KeyWord] = append(classifyPermissionMap[v.KeyWord], v.ChartPermissionId)
+		}
+	}
+	// 二级分类
+	childrenMap := make(map[int][]*eta.ClassifyItem, 0)
+	for i := range children {
+
+		if childrenMap[children[i].ParentId] == nil {
+			childrenMap[children[i].ParentId] = make([]*eta.ClassifyItem, 0)
+		}
+		tmp := &eta.ClassifyItem{
+			Classify:       *children[i],
+			ClassifyMenuId: relateMap[children[i].Id],
+		}
+		if permissionIds, ok := classifyPermissionMap[children[i].ClassifyName]; 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]
+	}
+
+	data := new(eta.ClassifyListResp)
+	data.List = list
+	resp.OkData("操作成功", data, c)
+}
+
+// SetEnabled
+// @Title 启用/禁用分类接口
+// @Description 启用/禁用分类
+// @Param	request	body models.ClassifyMoveReq true "type json string"
+// @Success 200 新增成功
+// @router /enabled/set [post]
+func (this *ClassifyController) SetEnabled(c *gin.Context) {
+	var req eta.ClassifySetEnabledReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		resp.Fail("请选择分类", c)
+		return
+	}
+	if req.Enabled != 0 && req.Enabled != 1 {
+		resp.Fail("请选择正确的启用禁用状态", c)
+		return
+	}
+	ob := new(eta.Classify)
+	item, err := ob.GetClassifyById(req.ClassifyId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.FailData("分类不存在", "Err:"+err.Error(), c)
+			return
+		}
+
+		resp.FailData("获取信息失败", "Err:"+err.Error(), c)
+		return
+	}
+	if item == nil {
+		resp.Fail("分类不存在", c)
+		return
+	}
+	//设置分类启用、禁用状态
+	err = ob.SetEnabled(req.ClassifyId, req.Enabled)
+	if err != nil {
+		resp.FailData("操作失败", "Err:"+err.Error(), c)
+		return
+	}
+
+	resp.Ok("操作成功", c)
+}
+
+// Edit
+// @Title 修改分类接口
+// @Description 修改分类
+// @Param	request	body models.EditClassifyReq true "type json string"
+// @Success 200 Ret=200,修改成功
+// @router /edit [post]
+func (this *ClassifyController) Edit(c *gin.Context) {
+	var req eta.EditClassifyReq
+	err := c.Bind(&req)
+	if err != nil {
+		errs, ok := err.(validator.ValidationErrors)
+		if !ok {
+			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+			return
+		}
+		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+		return
+	}
+	// 获取系统菜单, 如果没有对应的字段的特殊处理项, 则忽略必填
+	sysMenuObject := new(eta.SysMenu)
+	menus, e := sysMenuObject.GetSysMenuItemsByCondition(` AND hidden = 0`, make([]interface{}, 0), []string{}, ``)
+	if e != nil {
+		resp.FailData("保存失败", "获取菜单列表失败, Err:"+err.Error(), c)
+		return
+	}
+	menuMap := make(map[string]bool)
+	for _, m := range menus {
+		if m.ButtonCode != "" {
+			menuMap[m.ButtonCode] = true
+		}
+	}
+
+	if req.ClassifyId <= 0 {
+		resp.Fail("参数错误", c)
+		return
+	}
+
+	ob := new(eta.Classify)
+	item, err := ob.GetClassifyById(req.ClassifyId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			resp.Fail("分类不存在", c)
+			return
+		}
+
+		resp.FailData("获取信息失败", "Err:"+err.Error(), c)
+		return
+	}
+	if item == nil {
+		resp.Fail("分类不存在", c)
+		return
+	}
+
+	if menuMap[eta.MenuSpecialHandleClassifyShowType] && item.ParentId != 0 && req.ShowType == 0 {
+		resp.Fail("展示类型不可为空", c)
+		return
+	}
+	if menuMap[eta.MenuSpecialHandleClassifyReportImgs] && (req.ShowType == 1 || req.ShowType == 3) && req.YbRightBanner == "" && item.ParentId == 0 { //当一级报告分类为列表、品种时,增加“报告合集配图”的配置项
+		resp.Fail("报告合集配图不可为空", c)
+		return
+	}
+	//originRelateTel := item.RelateTel
+	item.ModifyTime = time.Now().Local()
+	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, "Abstract", "Descript", "ReportAuthor", "AuthorDescript", "ColumnImgUrl",
+		"HeadImgUrl", "AvatarImgUrl", "ReportImgUrl", "HomeImgUrl", "ClassifyLabel", "ShowType", "HasTeleconference", "VipTitle",
+		"IsShow", "YbFiccSort", "YbFiccIcon", "YbFiccPcIcon", "YbIconUrl", "YbBgUrl", "YbListImg", "YbShareBgImg", "YbRightBanner",
+		"RelateTel", "RelateVideo", "ModifyTime")
+	if e = item.UpdateClassify(cols); e != nil {
+		resp.FailData("修改失败", "Err:"+e.Error(), c)
+		return
+	}
+	// 一级分类关联设置会强制修改二级分类的所有关联设置
+	if item.ParentId == 0 {
+		if e = item.UpdateChildClassifyRelateSetting(item.Id, req.RelateTel, req.RelateVideo); e != nil {
+			resp.FailData("更新二级分类关联设置失败", "更新二级分类关联设置失败 Err:"+err.Error(), c)
+			return
+		}
+	}
+
+	// 为二级分类时, 更新父级分类是否含电话会字段
+	if item.ParentId > 0 {
+		go func() {
+			_ = eta2.UpdateParentClassifyHasTel(req.ClassifyId, item.ParentId, req.HasTeleconference)
+		}()
+	}
+
+	// 获取编辑前子目录列表
+	classifyId := item.Id
+	var menuCond string
+	var menuPars []interface{}
+	menuCond += ` classify_id = ?`
+	menuPars = append(menuPars, classifyId)
+	menuOb := new(eta.ClassifyMenu)
+	menuList, e := menuOb.GetClassifyMenuList(menuCond, menuPars)
+	if e != nil {
+		resp.FailData("保存失败", "获取分类子目录列表失败, Err:"+err.Error(), c)
+		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([]*eta.ClassifyMenu, 0)
+		editMenus := make([]*eta.ClassifyMenu, 0)
+		deleteMenuIds := make([]int, 0)
+		menuIds := make([]int, 0)
+		for i := range req.MenuList {
+			m := req.MenuList[i]
+
+			v := new(eta.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 = eta.InsertAndUpdateClassifyMenu(insertMenus, editMenus, deleteMenuIds); e != nil {
+			resp.FailData("保存失败", "新增/编辑/删除分类子目录失败, Err:"+e.Error(), c)
+			return
+		}
+	}
+
+	// 二级分类-新增子目录关联
+	if item.ParentId > 0 {
+		if e := eta.DeleteAndInsertClassifyMenuRelation(classifyId, req.ClassifyMenuId); e != nil {
+			resp.FailData("新增子目录关联失败", "新增子目录关联失败 Err:"+e.Error(), c)
+			return
+		}
+	}
+
+	resp.Ok("操作成功", c)
+}

+ 1 - 0
init_serve/router.go

@@ -20,5 +20,6 @@ func InitRouter() (r *gin.Engine) {
 	routers.InitAuth(rBase)
 	routers.InitEtaTrial(rBase)
 	routers.InitChartPermission(rBase)
+	routers.InitReportChapterType(rBase)
 	return
 }

+ 12 - 0
models/crm/chart_permission_chapter_mapping.go

@@ -0,0 +1,12 @@
+package crm
+
+type ChartPermissionChapterMapping struct {
+	Id                  int    `gorm:"column:id;primary_key;AUTO_INCREMENT;NOT NULL"`
+	ChartPermissionId   int    `gorm:"column:chart_permission_id;default:0"`
+	ReportChapterTypeId int    `gorm:"column:report_chapter_type_id;default:0;comment:'report_chapter_type表主键id或research_report表主键id或tactic表主键id'"`
+	ResearchType        string `gorm:"column:research_type;default:"`
+}
+
+func (c *ChartPermissionChapterMapping) TableName() string {
+	return "chart_permission_chapter_mapping"
+}

+ 74 - 0
models/crm/chart_permission_search_key_word_mapping.go

@@ -0,0 +1,74 @@
+package crm
+
+import "hongze/hz_crm_eta/global"
+
+type ChartPermissionSearchKeyWordMapping struct {
+	Id                 int    `gorm:"column:id;primary_key;AUTO_INCREMENT;NOT NULL"`
+	ChartPermissionId  int    `gorm:"column:chart_permission_id;default:0"`
+	KeyWord            string `gorm:"column:key_word;default:"`
+	From               string `gorm:"column:from;default:"`
+	TacticType         string `gorm:"column:tactic_type;default:;comment:'策略表type字段值'"`
+	TeleconferenceSort int    `gorm:"column:teleconference_sort;default:0;comment:'电话会类型排序'"`
+}
+
+func (c *ChartPermissionSearchKeyWordMapping) TableName() string {
+	return "chart_permission_search_key_word_mapping"
+}
+
+type ClassifyPermissionReq struct {
+	Keyword string
+}
+
+type ClassifyPermissionResp struct {
+	List []*ChartPermissionSearchKeyWordMapping
+}
+
+func (c *ChartPermissionSearchKeyWordMapping) GetPermissionByKeyword(classifyNameSecond string) (items []*ChartPermissionSearchKeyWordMapping, err error) {
+	err = global.MYSQL["hz_crm"].Where("`from`='rddp' AND key_word = ?", classifyNameSecond).Find(&items).Error
+	return
+}
+
+func (c *ChartPermissionSearchKeyWordMapping) GetPermission() (items []*ChartPermissionSearchKeyWordMapping, err error) {
+	err = global.MYSQL["hz_crm"].Where("`from`='rddp'").Find(&items).Error
+	return
+}
+
+// ClassifyPermissionEditReq 编辑分类权限请求
+type ClassifyPermissionEditReq struct {
+	Keyword               string
+	ChartPermissionIdList []int `description:"权限id数组"`
+	NewKeyword            string
+}
+
+// EditChartPermissionSearchKeyWordMappingMulti 修改报告报告权限(先删除原有的权限,再添加新的权限)
+func (c *ChartPermissionSearchKeyWordMapping) EditChartPermissionSearchKeyWordMappingMulti(keyword, newKeyword string, permissionIdList []int) (err error) {
+	tx := global.MYSQL["hz_crm"].Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	sql1 := "delete from chart_permission_search_key_word_mapping WHERE `from`='rddp' AND key_word=? "
+	err = tx.Exec(sql1, keyword).Error
+	if err != nil {
+		return
+	}
+	// 删除相关联的回复
+	if len(permissionIdList) > 0 {
+		chartPermissionSearchKeyWordMappingList := make([]*ChartPermissionSearchKeyWordMapping, 0)
+		for _, permissionId := range permissionIdList {
+			tmpChartPermissionSearchKeyWordMapping := &ChartPermissionSearchKeyWordMapping{
+				ChartPermissionId:  permissionId,
+				KeyWord:            newKeyword,
+				From:               "rddp",
+				TacticType:         "",
+				TeleconferenceSort: 0,
+			}
+			chartPermissionSearchKeyWordMappingList = append(chartPermissionSearchKeyWordMappingList, tmpChartPermissionSearchKeyWordMapping)
+		}
+		err = tx.CreateInBatches(chartPermissionSearchKeyWordMappingList, len(chartPermissionSearchKeyWordMappingList)).Error
+	}
+	return
+}

+ 157 - 0
models/crm/report_chapter_type.go

@@ -0,0 +1,157 @@
+package crm
+
+import (
+	"fmt"
+	"hongze/hz_crm_eta/global"
+	"time"
+)
+
+type ReportChapterType struct {
+	ReportChapterTypeId    int       `gorm:"column:report_chapter_type_id;primary_key;AUTO_INCREMENT;NOT NULL;comment:'报告章节类型id'"`
+	ReportChapterTypeKey   string    `gorm:"column:report_chapter_type_key;default:NULL;comment:'章节key'"`
+	ReportChapterTypeThumb string    `gorm:"column:report_chapter_type_thumb;default:NULL;comment:'H5展示的图片'"`
+	BannerUrl              string    `gorm:"column:banner_url;default:NULL;comment:'banner显示图片'"`
+	ReportChapterTypeName  string    `gorm:"column:report_chapter_type_name;default:NULL;comment:'报告章节类型名称'"`
+	Sort                   int       `gorm:"column:sort;default:0;NOT NULL;comment:'排序字段'"`
+	Enabled                int       `gorm:"column:enabled;default:NULL;comment:'报告类型状态'"`
+	CreatedTime            time.Time `gorm:"column:created_time;default:CURRENT_TIMESTAMP;comment:'创建时间'"`
+	LastUpdatedTime        time.Time `gorm:"column:last_updated_time;default:CURRENT_TIMESTAMP;NOT NULL"`
+	ResearchType           string    `gorm:"column:research_type;default:week;comment:'研报类型'"`
+	SelectedImage          string    `gorm:"column:selected_image;default:NULL;comment:'选中时的图片'"`
+	UnselectedImage        string    `gorm:"column:unselected_image;default:NULL;comment:'没选中时的图片'"`
+	PcSelectedImage        string    `gorm:"column:pc_selected_image;default:NULL;comment:'选中时的图片'"`
+	PcUnselectedImage      string    `gorm:"column:pc_unselected_image;default:NULL;comment:'没选中时的图片'"`
+	EditImgUrl             string    `gorm:"column:edit_img_url;default:NULL;comment:'管理后台编辑时选用的图'"`
+	TickerTitle            string    `gorm:"column:ticker_title;default:NULL;comment:'指标列的标题'"`
+	IsShow                 int       `gorm:"column:is_show;default:1;comment:'是否显示'"`
+	PauseStartTime         string    `gorm:"column:pause_start_time;default:NULL;comment:'暂停开始日期'"`
+	PauseEndTime           string    `gorm:"column:pause_end_time;default:NULL;comment:'暂停结束日期'"`
+	IsSet                  int       `gorm:"column:is_set;default:0;comment:'是否设置:0为设置,1已设置'"`
+	YbIconUrl              string    `gorm:"column:yb_icon_url;default:NULL;comment:'研报小程序3.0图标地址'"`
+	YbBottomIcon           string    `gorm:"column:yb_bottom_icon;default:NULL;comment:'研报小程序3.0底部菜单图标地址'"`
+	YbHidden               uint      `gorm:"column:yb_hidden;default:0;NOT NULL;comment:'研报小程序隐藏章节: 0-显示; 1-隐藏'"`
+}
+
+func (r *ReportChapterType) TableName() string {
+	return "report_chapter_type"
+}
+
+type ChapterTypeReq struct {
+	ReportType string
+}
+
+// GetReportChapterTypePageList 获取章节类型列表
+func GetReportChapterTypePageList(condition string, pars []interface{}) (items []*ReportChapterType, err error) {
+	err = global.MYSQL["hz_crm"].Model(ReportChapterType{}).Where(condition, pars...).Order("sort ASC, created_time DESC").Scan(&items).Error
+	return
+}
+
+// ReportChapterTypeListItem 章节类型列表信息
+type ReportChapterTypeListItem struct {
+	ReportChapterTypeId   int    `description:"报告章节类型id"`
+	ReportChapterTypeName string `description:"报告章节类型名称"`
+	Sort                  int    `description:"排序字段"`
+	CreatedTime           string `description:"创建时间"`
+	ResearchType          string `description:"研报类型"`
+	SelectedImage         string `description:"选中时的图片"`
+	UnselectedImage       string `description:"没选中时的图片"`
+	WordsImage            string `description:"带字的icon"`
+	EditImgUrl            string `description:"管理后台编辑时选用的图"`
+	IsShow                int    `description:"显示隐藏: 1-显示; 0-隐藏"`
+	Enabled               int
+	ChartPermissionIdList []int `description:"权限id数组"`
+}
+
+// ReportChapterTypeAddReq 新增章节类型请求体
+type ReportChapterTypeAddReq struct {
+	ReportChapterTypeName string `description:"报告章节类型名称"`
+	ResearchType          string `description:"研报类型"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+}
+
+// ReportChapterTypeEditReq 编辑章节类型请求体
+type ReportChapterTypeEditReq struct {
+	ReportChapterTypeId   int    `description:"报告章节类型id"`
+	ReportChapterTypeName string `description:"报告章节类型名称"`
+	ResearchType          string `description:"研报类型"`
+	ChartPermissionIdList []int  `description:"权限id数组"`
+}
+
+// ReportChapterTypeEditResp 编辑章节返回
+type ReportChapterTypeEditResp struct {
+	OriginReportChapterTypeName string `description:"报告章节类型原来的名称"`
+}
+
+// GetReportChapterTypeByCondition 获取章节类型
+func GetReportChapterTypeByCondition(condition string, pars []interface{}) (item *ReportChapterType, err error) {
+	err = global.MYSQL["hz_crm"].Model(ReportChapterType{}).Where(condition, pars...).First(&item).Error
+	return
+}
+
+type ReportChapterTypeListResp struct {
+	List []*ReportChapterTypeListItem
+}
+
+// Create 新增权限
+func (r *ReportChapterType) Create() (err error) {
+	err = global.MYSQL["hz_crm"].Create(r).Error
+	return
+}
+
+// Update 更新权限
+func (r *ReportChapterType) Update(cols []string) (err error) {
+	err = global.MYSQL["hz_crm"].Model(r).Select(cols).Updates(r).Error
+	return
+}
+
+// SetEnabled 更新启动禁用
+func (r *ReportChapterType) SetEnabled(id, enabled int) (err error) {
+	err = global.MYSQL["hz_crm"].Model(r).Where("report_chapter_type_id = ?", id).Update("enabled", enabled).Error
+	return
+}
+
+// GetReportChapterTypeById 获取章节类型
+func (r *ReportChapterType) GetReportChapterTypeById(reportChapterTypeId int) (item *ReportChapterType, err error) {
+	err = global.MYSQL["hz_crm"].Model(r).Where("report_chapter_type_id = ?", reportChapterTypeId).First(&item).Error
+	return
+}
+
+// GetMaxSort 获取最大的排序值
+func (r *ReportChapterType) GetMaxSort() (maxSort int, err error) {
+	err = global.MYSQL["hz_crm"].Model(r).Select("max(sort)").Scan(&maxSort).Error
+	return
+}
+
+// GetMaxSortByResearchType 获取最大的排序值
+func (r *ReportChapterType) GetMaxSortByResearchType(researchType string) (maxSort int, err error) {
+	err = global.MYSQL["hz_crm"].Model(r).Select("max(sort)").Where("research_type = ?", researchType).Scan(&maxSort).Error
+	return
+}
+
+type ReportChapterTypeEnabledReq struct {
+	ReportChapterTypeId int `description:"报告章节类型id"`
+	Enabled             int `description:"是否可用,1可用,0禁用"`
+}
+
+type ReportChapterTypeMoveReq struct {
+	ReportChapterTypeId int `description:"报告章节类型id"`
+	//	ParentChartPermissionId int `description:"父级品种id"`
+	PrevReportChapterTypeId int `description:"上一个兄弟节点报告章节类型id"`
+	NextReportChapterTypeId int `description:"下一个兄弟节点报告章节类型id"`
+}
+
+// UpdateReportChapterTypeSortByResearchType 根据父类id更新排序
+func UpdateReportChapterTypeSortByResearchType(researchType string, reportChapterTypeId, nowSort int, updateSort string) (err error) {
+	sql := ` update report_chapter_type set sort = ` + updateSort + ` WHERE research_type=? AND sort > ?`
+	if reportChapterTypeId > 0 {
+		sql += ` or ( report_chapter_type_id > ` + fmt.Sprint(reportChapterTypeId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	err = global.MYSQL["hz_crm"].Exec(sql, researchType, nowSort).Error
+	return
+}
+
+// GetFirstReportChapterTypeByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func (r *ReportChapterType) GetFirstReportChapterTypeByResearchType(researchType string) (item *ReportChapterType, err error) {
+	err = global.MYSQL["hz_crm"].Where("research_type = ?", researchType).Order("sort asc, report_chapter_type_id asc").First(&item).Error
+	return
+}

+ 74 - 0
models/crm/report_chapter_type_permission.go

@@ -0,0 +1,74 @@
+package crm
+
+import (
+	"hongze/hz_crm_eta/global"
+	"hongze/hz_crm_eta/utils"
+	"time"
+)
+
+type ReportChapterTypePermission struct {
+	Id                    int       `gorm:"column:id;primary_key;AUTO_INCREMENT;NOT NULL;comment:'自增序号'"`
+	ReportChapterTypeId   int       `gorm:"column:report_chapter_type_id;NOT NULL;comment:'报告章节类型id'"`
+	CreatedTime           time.Time `gorm:"column:created_time;default:CURRENT_TIMESTAMP;NOT NULL;comment:'创建时间'"`
+	ChartPermissionId     int       `gorm:"column:chart_permission_id;NOT NULL;comment:'权限ID,对应chart_permission权限表'"`
+	ResearchType          string    `gorm:"column:research_type;default:week;NOT NULL;comment:'研报类型:day:晨报,week:周报'"`
+	PermissionName        string    `gorm:"column:permission_name;default:NULL;comment:'权限名称'"`
+	ReportChapterTypeName string    `gorm:"column:report_chapter_type_name;default:NULL;comment:'章节名称'"`
+}
+
+func (r *ReportChapterTypePermission) TableName() string {
+	return "report_chapter_type_permission"
+}
+
+// SetReportChapterTypePermission 设置报告章节类型权限
+func SetReportChapterTypePermission(chapterTypeId int, researchType string, newPermissions []*ReportChapterTypePermission, newWeekPermissions []*ChartPermissionChapterMapping) (err error) {
+	tx := global.MYSQL["hz_crm"].Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 删除原章节类型权限
+	sql := `DELETE FROM report_chapter_type_permission WHERE report_chapter_type_id = ? AND research_type = ?`
+	err = tx.Exec(sql, chapterTypeId, researchType).Error
+	if err != nil {
+		return
+	}
+
+	// 新增章节类型权限
+	if len(newPermissions) > 0 {
+		err = tx.CreateInBatches(newPermissions, len(newPermissions)).Error
+		if err != nil {
+			return
+		}
+	}
+
+	// 周报章节调整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 = tx.Exec(sql, chapterTypeId, researchType).Error
+		if err != nil {
+			return
+		}
+
+		// 新增权限
+		if len(newWeekPermissions) > 0 {
+			err = tx.CreateInBatches(newWeekPermissions, len(newWeekPermissions)).Error
+			if err != nil {
+				return
+			}
+		}
+	}
+	return
+}
+
+// GetChapterTypePermissionByTypeIdAndResearchType 根据章节类型ID及研报类型获取章节类型权限列表
+func GetChapterTypePermissionByTypeIdAndResearchType(researchType string) (items []*ReportChapterTypePermission, err error) {
+	err = global.MYSQL["hz_crm"].Model(ReportChapterTypePermission{}).Where("research_type = ?", researchType).Order("chart_permission_id ASC").Scan(&items).Error
+	return
+	return
+}

+ 240 - 0
models/eta/classify.go

@@ -0,0 +1,240 @@
+package eta
+
+import (
+	"hongze/hz_crm_eta/global"
+	"hongze/hz_crm_eta/utils"
+	"time"
+)
+
+type Classify struct {
+	Id                int       `gorm:"column:id;primary_key;AUTO_INCREMENT;NOT NULL"`
+	ClassifyName      string    `gorm:"column:classify_name;default:;comment:'分类名称'"`
+	Sort              int       `gorm:"column:sort;default:0;comment:'排序'"`
+	ParentId          int       `gorm:"column:parent_id;default:0;comment:'父级分类id'"`
+	CreateTime        time.Time `gorm:"column:create_time;default:CURRENT_TIMESTAMP"`
+	ModifyTime        time.Time `gorm:"column:modify_time;default:CURRENT_TIMESTAMP"`
+	Abstract          string    `gorm:"column:abstract;default:;comment:'栏目简介'"`
+	Descript          string    `gorm:"column:descript;default:;comment:'分享描述'"`
+	ReportAuthor      string    `gorm:"column:report_author;default:;comment:'栏目作者'"`
+	AuthorDescript    string    `gorm:"column:author_descript;default:;comment:'作者简介'"`
+	ReportImgUrl      string    `gorm:"column:report_img_url;default:;comment:'报告配图'"`
+	HeadImgUrl        string    `gorm:"column:head_img_url;default:;comment:'头部banner'"`
+	AvatarImgUrl      string    `gorm:"column:avatar_img_url;default:;comment:'头像'"`
+	ColumnImgUrl      string    `gorm:"column:column_img_url;default:;comment:'栏目配图'"`
+	IsHomeColumn      int       `gorm:"column:is_home_column;default:0;comment:'1:首页专栏'"`
+	HomeImgUrl        string    `gorm:"column:home_img_url;default:;comment:'首页配图'"`
+	ClassifyLabel     string    `gorm:"column:classify_label;default:"`
+	ShowType          int       `gorm:"column:show_type;default:0;NOT NULL;comment:'展示类型:1-列表 2-专栏 3-品种'"`
+	HasTeleconference int       `gorm:"column:has_teleconference;default:0;NOT NULL;comment:'是否有电话会 0-否 1-是'"`
+	VipTitle          string    `gorm:"column:vip_title;default:NULL;comment:'研究员头衔'"`
+	YbIconUrl         string    `gorm:"column:yb_icon_url;default:;comment:'研报3.0已购页面icon图片地址'"`
+	YbBgUrl           string    `gorm:"column:yb_bg_url;default:;comment:'研报3.0已购详情背景图地址'"`
+	IsShow            int       `gorm:"column:is_show;default:1;NOT NULL;comment:'是否展示报告:1,展示该分类下的报告,0隐藏分类下的报告'"`
+	YbFiccSort        int       `gorm:"column:yb_ficc_sort;default:0;comment:'研报小程序端ficc页面排序'"`
+	YbFiccIcon        string    `gorm:"column:yb_ficc_icon;default:NULL;comment:'研报小程序端ficc页码图标'"`
+	YbFiccPcIcon      string    `gorm:"column:yb_ficc_pc_icon;default:NULL;comment:'研报 pc端ficc页码图标'"`
+	YbListImg         string    `gorm:"column:yb_list_img;default:;NOT NULL;comment:'研报小程序-列表封面图'"`
+	YbShareBgImg      string    `gorm:"column:yb_share_bg_img;default:;NOT NULL;comment:'研报小程序-报告分享背景图'"`
+	YbRightBanner     string    `gorm:"column:yb_right_banner;default:NULL;comment:'Pc端详情页,右侧,报告合集背景图'"`
+	RelateTel         int       `gorm:"column:relate_tel;default:0;NOT NULL;comment:'是否在电话会中可选: 0-否; 1-是'"`
+	RelateVideo       int       `gorm:"column:relate_video;default:0;NOT NULL;comment:'是否在路演视频中可选: 0-否; 1-是'"`
+	IsMassSend        int       `gorm:"column:is_mass_send;default:0;comment:'1:群发,0:非群发'"`
+	Enabled           int       `gorm:"column:enabled;default:1;comment:'是否可用'"`
+}
+
+func (c *Classify) TableName() string {
+	return "classify"
+}
+
+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
+}
+
+type ClassifyItem struct {
+	Classify
+	ClassifyMenuId        int `description:"二级分类-子目录ID"`
+	ClassifyMenuList      []*ClassifyMenu
+	ChartPermissionIdList []int `description:"绑定的权限ID"`
+}
+
+// 获取分类列表
+func GetClassifyList(keyWord, companyType string, hideDayWeek 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)  "
+	}
+	pars := make([]interface{}, 0)
+	if keyWord != "" {
+		sql = `SELECT * FROM (
+                   SELECT * FROM classify
+                   WHERE parent_id=0 ` + companyTypeSqlStr + `  AND classify_name LIKE ?
+                   UNION
+                   SELECT * FROM classify
+                   WHERE id IN( SELECT parent_id FROM classify
+                   WHERE parent_id>0 ` + companyTypeSqlStr + `  AND classify_name LIKE ? )
+                   )AS t
+                   ORDER BY sort ASC,create_time ASC`
+		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`
+	}
+	pars = append(pars)
+	err = global.MYSQL["rddp"].Raw(sql, pars...).Scan(&items).Error
+	return
+}
+
+func GetClassifyChildByParentIds(parentId []int, keyWord string) (items []*Classify, err error) {
+	parentIdLen := len(parentId)
+	if parentIdLen == 0 {
+		return
+	}
+	sql := ``
+	pars := make([]interface{}, 0)
+	pars = append(pars, parentId)
+	if keyWord != "" {
+		sql = `SELECT * FROM classify WHERE parent_id IN (` + utils.GetOrmInReplace(parentIdLen) + `) AND classify_name LIKE ? ORDER BY create_time ASC `
+		pars = append(pars, utils.GetLikeKeyword(keyWord))
+	} else {
+		sql = `SELECT * FROM classify WHERE parent_id IN (` + utils.GetOrmInReplace(parentIdLen) + `) ORDER BY create_time ASC `
+	}
+	err = global.MYSQL["rddp"].Raw(sql, pars...).Scan(&items).Error
+	return
+}
+
+func GetClassifyChild(parentId int, keyWord string) (items []*Classify, err error) {
+	sql := ``
+	pars := make([]interface{}, 0)
+	if keyWord != "" {
+		sql = `SELECT * FROM classify WHERE classify_name LIKE ? AND parent_id=? ORDER BY create_time ASC `
+		pars = append(pars, utils.GetLikeKeyword(keyWord))
+	} else {
+		sql = `SELECT * FROM classify WHERE parent_id=? ORDER BY create_time ASC `
+	}
+	pars = append(pars, parentId)
+	err = global.MYSQL["rddp"].Raw(sql, pars...).Scan(&items).Error
+	return
+}
+
+type ClassifyListReq struct {
+	Keyword     string
+	CompanyType string
+	HideDayWeek int
+}
+
+type ClassifyListResp struct {
+	List []*ClassifyList
+}
+
+type ClassifySetEnabledReq struct {
+	ClassifyId int `description:"分类ID"`
+	Enabled    int `description:"是否可用,1可用,0禁用"`
+}
+
+func (c *Classify) SetEnabled(id, enabled int) (err error) {
+	tx := global.MYSQL["rddp"].Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	sql := ` UPDATE classify SET enabled =?  WHERE id = ?`
+	err = tx.Exec(sql, enabled, id).Error
+	if err != nil {
+		return
+	}
+	sql = ` UPDATE classify SET enabled =?  WHERE parent_id = ?`
+	err = tx.Exec(sql, enabled, id).Error
+	if err != nil {
+		return
+	}
+	return
+}
+
+// GetClassifyById 查询分类
+func (c *Classify) GetClassifyById(classifyId int) (item *Classify, err error) {
+	err = global.MYSQL["rddp"].Where("id = ?", classifyId).First(&item).Error
+	return
+}
+
+type EditClassifyReq struct {
+	ClassifyId        int                    `description:"分类ID"`
+	Abstract          string                 `description:"栏目简介"`
+	Descript          string                 `description:"分享描述"`
+	ReportAuthor      string                 `description:"栏目作者"`
+	AuthorDescript    string                 `description:"作者简介"`
+	ColumnImgUrl      string                 `description:"栏目配图"`
+	ReportImgUrl      string                 `description:"报告配图"`
+	HeadImgUrl        string                 `description:"头部banner"`
+	AvatarImgUrl      string                 `description:"头像"`
+	HomeImgUrl        string                 `description:"首页配图"`
+	ClassifyLabel     string                 `description:"分类标签"`
+	ShowType          int                    `description:"展示类型:1-列表 2-专栏"`
+	HasTeleconference int                    `description:"是否有电话会:0-否 1-是"`
+	VipTitle          string                 `description:"研究员头衔"`
+	Sort              int                    `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端详情页,右侧,报告合集背景图"`
+	MenuList          []*ClassifyMenuSaveReq `description:"子目录列表"`
+	ClassifyMenuId    int                    `description:"二级分类-子目录ID"`
+	RelateTel         int                    `description:"是否在电话会中可选: 0-否; 1-是"`
+	RelateVideo       int                    `description:"是否在路演视频中可选: 0-否; 1-是"`
+}
+
+// ClassifyMenuSaveReq 保存分类子目录请求体
+type ClassifyMenuSaveReq struct {
+	MenuId   int    `description:"子目录ID, 0为新增, 大于0为编辑"`
+	MenuName string `description:"子目录名称"`
+}
+
+// UpdateClassify 更新权限
+func (c *Classify) UpdateClassify(cols []string) (err error) {
+	err = global.MYSQL["rddp"].Model(c).Select(cols).Updates(c).Error
+	return
+}
+
+// UpdateChildClassifyRelateSetting 更新子分类关联设置
+func (c *Classify) UpdateChildClassifyRelateSetting(parentId, relateTel, relateVideo int) (err error) {
+	sql := `UPDATE classify SET relate_tel = ?, relate_video = ? WHERE parent_id = ?`
+	err = global.MYSQL["rddp"].Exec(sql, relateTel, relateVideo, parentId).Error
+	return
+}

+ 79 - 0
models/eta/classify_menu.go

@@ -0,0 +1,79 @@
+package eta
+
+import (
+	"fmt"
+	"hongze/hz_crm_eta/global"
+	"time"
+)
+
+type ClassifyMenu struct {
+	MenuId     int       `gorm:"column:menu_id;primary_key;AUTO_INCREMENT;NOT NULL;comment:'子目录ID'"`
+	MenuName   string    `gorm:"column:menu_name;default:;NOT NULL;comment:'子目录名称'"`
+	ClassifyId int       `gorm:"column:classify_id;default:0;NOT NULL;comment:'分类ID'"`
+	Sort       int       `gorm:"column:sort;default:0;NOT NULL;comment:'排序'"`
+	CreateTime time.Time `gorm:"column:create_time;default:NULL;comment:'创建时间'"`
+	ModifyTime time.Time `gorm:"column:modify_time;default:NULL;comment:'更新时间'"`
+}
+
+func (c *ClassifyMenu) TableName() string {
+	return "classify_menu"
+}
+
+// GetClassifyMenuList 获取子目录列表
+func (c *ClassifyMenu) GetClassifyMenuList(condition string, pars []interface{}) (items []*ClassifyMenu, err error) {
+	// todo 当condition为空时
+	err = global.MYSQL["rddp"].Model(c).Where(condition, pars...).Order("sort ASC, create_time ASC").Scan(&items).Error
+	return
+}
+
+// InsertAndUpdateClassifyMenu 新增/编辑/删除分类子目录
+func InsertAndUpdateClassifyMenu(insertMenus []*ClassifyMenu, editMenus []*ClassifyMenu, deleteMenuIds []int) (err error) {
+	tx := global.MYSQL["rddp"].Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 编辑
+	sql := ``
+	if len(editMenus) > 0 {
+		for i := range editMenus {
+			sql = fmt.Sprintf(`UPDATE classify_menu SET menu_name = '%s', sort = %d, modify_time = NOW() WHERE menu_id = %d`,
+				editMenus[i].MenuName, editMenus[i].Sort, editMenus[i].MenuId)
+			if e := tx.Exec(sql).Error; e != nil {
+				err = e
+				return
+			}
+		}
+	}
+
+	// 删除
+	if len(deleteMenuIds) > 0 {
+		for i := range deleteMenuIds {
+			sql = fmt.Sprintf(`DELETE FROM classify_menu WHERE menu_id = %d LIMIT 1`, deleteMenuIds[i])
+			if e := tx.Exec(sql).Error; e != nil {
+				err = e
+				return
+			}
+			// 删除关联关系
+			sql = fmt.Sprintf(`DELETE FROM classify_menu_relation WHERE menu_id = %d`, deleteMenuIds[i])
+			if e := tx.Exec(sql).Error; e != nil {
+				err = e
+				return
+			}
+		}
+	}
+
+	// 新增
+	if len(insertMenus) > 0 {
+		e := tx.CreateInBatches(insertMenus, len(insertMenus)).Error
+		if e != nil {
+			err = e
+			return
+		}
+	}
+	return
+}

+ 56 - 0
models/eta/classify_menu_relation.go

@@ -0,0 +1,56 @@
+package eta
+
+import (
+	"hongze/hz_crm_eta/global"
+	"time"
+)
+
+type ClassifyMenuRelation struct {
+	Id         int       `gorm:"column:id;primary_key;AUTO_INCREMENT;NOT NULL;comment:'主键ID'"`
+	ClassifyId int       `gorm:"column:classify_id;default:0;NOT NULL;comment:'二级分类ID'"`
+	MenuId     int       `gorm:"column:menu_id;default:0;NOT NULL;comment:'子目录ID'"`
+	CreateTime time.Time `gorm:"column:create_time;default:NULL;comment:'创建时间'"`
+}
+
+func (c *ClassifyMenuRelation) TableName() string {
+	return "classify_menu_relation"
+}
+
+// GetClassifyMenuRelationList 获取子目录关联列表
+func (c *ClassifyMenuRelation) GetClassifyMenuRelationList(condition string, pars []interface{}) (items []*ClassifyMenuRelation, err error) {
+	err = global.MYSQL["rddp"].Model(c).Where(condition, pars...).Order("create_time ASC").Scan(&items).Error
+	return
+}
+
+// DeleteAndInsertClassifyMenuRelation 新增子目录关联
+func DeleteAndInsertClassifyMenuRelation(classifyId, menuId int) (err error) {
+	tx := global.MYSQL["rddp"].Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	// 删除
+	sql := `DELETE FROM classify_menu_relation WHERE classify_id = ?`
+	if e := tx.Exec(sql, classifyId).Error; e != nil {
+		err = e
+		return
+	}
+
+	// 新增
+	if menuId > 0 {
+		relate := &ClassifyMenuRelation{
+			ClassifyId: classifyId,
+			MenuId:     menuId,
+			CreateTime: time.Now().Local(),
+		}
+		e := tx.Create(relate).Error
+		if e != nil {
+			err = e
+		}
+	}
+	return
+}

+ 42 - 0
models/eta/report_chapter.go

@@ -0,0 +1,42 @@
+package eta
+
+import (
+	"database/sql"
+	"time"
+)
+
+type ReportChapter struct {
+	ReportChapterId   int32          `gorm:"column:report_chapter_id;primary_key;AUTO_INCREMENT;NOT NULL"`
+	ReportId          int32          `gorm:"column:report_id;default:0;NOT NULL;comment:'报告ID'"`
+	ReportCode        string         `gorm:"column:report_code;default:;NOT NULL;comment:'报告唯一编码'"`
+	ReportType        string         `gorm:"column:report_type;default:;NOT NULL;comment:'晨报-day; 周报-week;'"`
+	ClassifyIdFirst   int32          `gorm:"column:classify_id_first;default:0;comment:'一级分类id'"`
+	ClassifyNameFirst string         `gorm:"column:classify_name_first;default:;comment:'一级分类名称'"`
+	TypeId            int32          `gorm:"column:type_id;default:0;NOT NULL;comment:'品种ID'"`
+	TypeName          string         `gorm:"column:type_name;default:;NOT NULL;comment:'品种名称'"`
+	Title             string         `gorm:"column:title;default:;NOT NULL;comment:'章节标题'"`
+	Abstract          string         `gorm:"column:abstract;default:;NOT NULL;comment:'摘要'"`
+	AddType           int8           `gorm:"column:add_type;default:0;NOT NULL;comment:'是否为继承报告 1-空白报告 2-继承报告'"`
+	Author            string         `gorm:"column:author;default:;NOT NULL;comment:'作者'"`
+	Content           sql.NullString `gorm:"column:content;comment:'内容'"`
+	ContentSub        sql.NullString `gorm:"column:content_sub;comment:'内容前两个章节'"`
+	Stage             int32          `gorm:"column:stage;default:0;NOT NULL;comment:'期数'"`
+	Trend             string         `gorm:"column:trend;default:;NOT NULL;comment:'趋势观点'"`
+	Sort              int32          `gorm:"column:sort;default:0;NOT NULL;comment:'排序: 数值越小越靠前'"`
+	IsEdit            int8           `gorm:"column:is_edit;default:0;NOT NULL;comment:'是否编辑过 0-未编辑 1-已编辑'"`
+	PublishState      int8           `gorm:"column:publish_state;default:0;NOT NULL;comment:'发布状态 1-待发布 2-已发布'"`
+	PublishTime       time.Time      `gorm:"column:publish_time;default:NULL;comment:'发布时间'"`
+	VideoUrl          string         `gorm:"column:video_url;default:;NOT NULL;comment:'音频文件URL'"`
+	VideoName         string         `gorm:"column:video_name;default:;NOT NULL;comment:'音频文件名称'"`
+	VideoPlaySeconds  string         `gorm:"column:video_play_seconds;default:;NOT NULL;comment:'音频播放时长'"`
+	VideoSize         string         `gorm:"column:video_size;default:;NOT NULL;comment:'音频文件大小,单位M'"`
+	VideoKind         int8           `gorm:"column:video_kind;default:0;comment:'音频生成方式:1,手动上传,2:自动生成'"`
+	CreateTime        time.Time      `gorm:"column:create_time;default:CURRENT_TIMESTAMP;comment:'创建时间'"`
+	ModifyTime        time.Time      `gorm:"column:modify_time;default:CURRENT_TIMESTAMP;comment:'修改时间'"`
+	OriginalVideoUrl  string         `gorm:"column:original_video_url;default:;comment:'原始音频文件URL'"`
+	ContentCode       string         `gorm:"column:content_code;default:NULL;comment:'文本内容唯一标识'"`
+}
+
+func (r *ReportChapter) TableName() string {
+	return "report_chapter"
+}

+ 51 - 0
models/eta/sys_menu.go

@@ -0,0 +1,51 @@
+package eta
+
+import (
+	"hongze/hz_crm_eta/global"
+	"strings"
+	"time"
+)
+
+type SysMenu struct {
+	MenuId     int32     `gorm:"column:menu_id;primary_key;AUTO_INCREMENT;NOT NULL"`
+	ParentId   int32     `gorm:"column:parent_id;default:0;comment:'父级菜单ID\n'"`
+	Name       string    `gorm:"column:name;default:;comment:'菜单名称或者按钮名称'"`
+	Sort       int8      `gorm:"column:sort;default:0;comment:'排序\n'"`
+	Path       string    `gorm:"column:path;default:;comment:'路径'"`
+	IconPath   string    `gorm:"column:icon_path;default:;comment:'菜单图标地址\n'"`
+	Component  string    `gorm:"column:component;default:;comment:'组件路径\n'"`
+	Hidden     int8      `gorm:"column:hidden;default:0;comment:'是否隐藏:1-隐藏 0-显示\n'"`
+	IsLevel    int8      `gorm:"column:is_level;default:0;NOT NULL;comment:'1,只有一级;2,有多级'"`
+	LevelPath  string    `gorm:"column:level_path;default:"`
+	MenuType   int8      `gorm:"column:menu_type;default:0;NOT NULL;comment:'菜单类型: 0-菜单; 1-按钮; 2-字段(需要特殊处理)'"`
+	ButtonCode string    `gorm:"column:button_code;default:;NOT NULL;comment:'按钮唯一编码'"`
+	CreateTime time.Time `gorm:"column:create_time;default:NULL;comment:'创建时间'"`
+	ModifyTime time.Time `gorm:"column:modify_time;default:NULL;comment:'更新时间'"`
+	Api        string    `gorm:"column:api;NOT NULL;comment:'按钮相关api'"`
+	NameEn     string    `gorm:"column:name_en;default:;comment:'菜单名称或者按钮名称(英文)'"`
+}
+
+func (s *SysMenu) TableName() string {
+	return "sys_menu"
+}
+
+const (
+	MenuSpecialHandleClassifyChildMenu  = "classifyList:cnClassify:childMenu"
+	MenuSpecialHandleClassifyShowType   = "classifyList:cnClassify:showType"
+	MenuSpecialHandleClassifyReportImgs = "classifyList:cnClassify:reportImgs"
+	MenuSpecialHandleSandboxVariety     = "sandbox:variety"
+)
+
+// GetSysMenuItemsByCondition 获取菜单列表
+func (s *SysMenu) GetSysMenuItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*SysMenu, err error) {
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := ` create_time DESC`
+	if orderRule != "" {
+		order = orderRule
+	}
+	err = global.MYSQL["hz_eta"].Select(s).Select(fields).Where(condition, pars...).Order(order).Scan(&items).Error
+	return
+}

+ 2 - 0
routers/chart_permission.go

@@ -12,4 +12,6 @@ func InitChartPermission(r *gin.RouterGroup) {
 	group.POST("add", control.AddChartPermission)
 	group.POST("edit", control.EditChartPermission)
 	group.POST("move", control.MoveChartPermission)
+	group.POST("classify", control.GetClassifyChartPermission)
+	group.POST("classify/edit", control.EditClassifyChartPermission)
 }

+ 16 - 0
routers/report_chapter_type.go

@@ -0,0 +1,16 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hz_crm_eta/controller/crm"
+)
+
+func InitReportChapterType(r *gin.RouterGroup) {
+	control := new(crm.ReportChapterTypeController)
+	group := r.Group("crm/chapter_type")
+	group.POST("list", control.List)
+	group.POST("add", control.Add)
+	group.POST("edit", control.Edit)
+	group.POST("move", control.Move)
+	group.POST("enabled/set", control.SetEnabled)
+}

+ 153 - 0
services/crm/report_chapter_type.go

@@ -0,0 +1,153 @@
+package crm
+
+import (
+	"fmt"
+	"hongze/hz_crm_eta/models/crm"
+	"hongze/hz_crm_eta/utils"
+	"time"
+)
+
+// MoveReportChapterType 移动报告章节
+func MoveReportChapterType(req crm.ReportChapterTypeMoveReq) (err error, errMsg string) {
+	ob := new(crm.ReportChapterType)
+	reportChapterTypeId := req.ReportChapterTypeId
+	prevReportChapterTypeId := req.PrevReportChapterTypeId
+	nextReportChapterTypeId := req.NextReportChapterTypeId
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		reportChapterType     *crm.ReportChapterType
+		prevReportChapterType *crm.ReportChapterType
+		nextReportChapterType *crm.ReportChapterType
+
+		prevSort int
+		nextSort int
+	)
+
+	// 移动对象为分类, 判断权限
+	reportChapterType, err = ob.GetReportChapterTypeById(reportChapterTypeId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			errMsg = "当前报告章节不存在"
+			err = fmt.Errorf("获取报告章节信息失败,Err:" + err.Error())
+			return
+		}
+		errMsg = "移动失败"
+		err = fmt.Errorf("获取章节信息失败,Err:" + err.Error())
+		return
+	} else if reportChapterType.ReportChapterTypeId == 0 {
+		errMsg = "当前报告章节不存在"
+		err = fmt.Errorf("获取报告章节信息失败,Err:" + err.Error())
+		return
+	}
+
+	researchType := reportChapterType.ResearchType
+	if prevReportChapterTypeId > 0 {
+		prevReportChapterType, err = ob.GetReportChapterTypeById(prevReportChapterTypeId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevReportChapterType.Sort
+	}
+
+	if nextReportChapterTypeId > 0 {
+		//下一个兄弟节点
+		nextReportChapterType, err = ob.GetReportChapterTypeById(nextReportChapterTypeId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextReportChapterType.Sort
+	}
+
+	err, errMsg = moveReportChapterType(reportChapterType, prevReportChapterType, nextReportChapterType, researchType, prevSort, nextSort)
+	return
+}
+
+// moveReportChapterType 移动指标分类
+func moveReportChapterType(reportChapterType, prevReportChapterType, nextReportChapterType *crm.ReportChapterType, researchType string, prevSort, nextSort int) (err error, errMsg string) {
+	ob := new(crm.ReportChapterType)
+	updateCol := make([]string, 0)
+
+	if prevSort > 0 {
+		//如果是移动在两个兄弟节点之间
+		if nextSort > 0 {
+			//下一个兄弟节点
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevSort == nextSort || prevSort == reportChapterType.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+
+				//变更分类
+				if prevReportChapterType != nil {
+					_ = crm.UpdateReportChapterTypeSortByResearchType(researchType, prevReportChapterType.ReportChapterTypeId, prevReportChapterType.Sort, updateSortStr)
+				} else {
+					_ = crm.UpdateReportChapterTypeSortByResearchType(researchType, 0, prevSort, updateSortStr)
+				}
+
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextSort-prevSort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+
+					//变更分类
+					if prevReportChapterType != nil {
+						_ = crm.UpdateReportChapterTypeSortByResearchType(researchType, prevReportChapterType.ReportChapterTypeId, prevSort, updateSortStr)
+					} else {
+						_ = crm.UpdateReportChapterTypeSortByResearchType(researchType, 0, prevSort, updateSortStr)
+					}
+
+				}
+			}
+		}
+
+		reportChapterType.Sort = prevSort + 1
+		reportChapterType.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	} else if prevReportChapterType == nil && nextReportChapterType == nil && researchType != "" {
+		//处理只拖动到目录里,默认放到目录底部的情况
+		var maxSort int
+		maxSort, err = ob.GetMaxSortByResearchType(researchType)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("查询组内排序信息失败,Err:" + err.Error())
+			return
+		}
+		reportChapterType.Sort = maxSort + 1 //那就是排在组内最后一位
+		reportChapterType.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	} else {
+		// 拖动到父级分类的第一位
+		firstReportChapterType, tmpErr := ob.GetFirstReportChapterTypeByResearchType(researchType)
+		if tmpErr != nil && tmpErr != utils.ErrNoRow {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstReportChapterType != nil && firstReportChapterType.ReportChapterTypeId != 0 && firstReportChapterType.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = crm.UpdateReportChapterTypeSortByResearchType(researchType, firstReportChapterType.ReportChapterTypeId-1, 0, updateSortStr)
+		}
+
+		reportChapterType.Sort = 0 //那就是排在第一位
+		reportChapterType.LastUpdatedTime = time.Now()
+		updateCol = append(updateCol, "Sort", "LastUpdatedTime")
+	}
+
+	//更新
+	if len(updateCol) > 0 {
+		err = reportChapterType.Update(updateCol)
+		if err != nil {
+			errMsg = "移动失败"
+			err = fmt.Errorf("修改失败,Err:" + err.Error())
+			return
+		}
+	}
+	return
+}

+ 72 - 0
services/eta/report_classify.go

@@ -0,0 +1,72 @@
+package eta
+
+import (
+	"fmt"
+	"hongze/hz_crm_eta/models/eta"
+	"hongze/hz_crm_eta/services/alarm_msg"
+	"hongze/hz_crm_eta/utils"
+)
+
+// UpdateParentClassifyHasTel 更新父级分类是否含有电话字段
+func UpdateParentClassifyHasTel(classifyId, parentId, hasTeleconference int) (err error) {
+	if classifyId <= 0 || parentId <= 0 {
+		return
+	}
+	defer func() {
+		if err != nil {
+			alarm_msg.SendAlarmMsg("编辑分类后-修改父级分类电话会信息失败, ErrMsg: "+err.Error(), 3)
+		}
+	}()
+	ob := new(eta.Classify)
+	parentClassify, e := ob.GetClassifyById(parentId)
+	if e != nil {
+		if e == utils.ErrNoRow {
+			err = fmt.Errorf("父级分类不存在")
+			return
+		}
+		err = fmt.Errorf("获取父级分类信息失败, Err: %s", e.Error())
+		return
+	}
+	if parentClassify == nil {
+		err = fmt.Errorf("父级分类不存在")
+		return
+	}
+	updateParent := false
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "HasTeleconference")
+	if hasTeleconference == 1 {
+		// 二级分类包含电话会,则一级分类也默认包含电话会
+		if parentClassify.HasTeleconference == 0 {
+			parentClassify.HasTeleconference = 1
+			updateParent = true
+		}
+	} else {
+		// 二级分类均无电话会,则一级分类也无电话会
+		if parentClassify.HasTeleconference == 1 {
+			child, e := eta.GetClassifyChild(parentClassify.Id, "")
+			if e != nil {
+				err = fmt.Errorf("获取子分类失败, Err: %s", e.Error())
+				return
+			}
+			// 存在同一级分类下的二级分类有电话会则不变动
+			hasTel := false
+			for i := 0; i < len(child); i++ {
+				if child[i].HasTeleconference == 1 && child[i].Id != classifyId {
+					hasTel = true
+					break
+				}
+			}
+			if !hasTel {
+				parentClassify.HasTeleconference = 0
+				updateParent = true
+			}
+		}
+	}
+	if updateParent {
+		if e = parentClassify.UpdateClassify(updateCols); e != nil {
+			err = fmt.Errorf("更新父级分类失败, Err: %s", e.Error())
+			return
+		}
+	}
+	return
+}

+ 73 - 0
utils/common.go

@@ -1071,3 +1071,76 @@ func GetWeekDay() (string, string) {
 	l := lastOfWeeK.Unix()
 	return time.Unix(f, 0).Format("2006-01-02") + " 00:00:00", time.Unix(l, 0).Format("2006-01-02") + " 23:59:59"
 }
+
+// @Description: 获取sql查询中的参数切片
+// @author: Roc
+// @datetime2023-10-23 14:50:18
+// @param pars []interface{}
+// @param keyword string
+// @param num int
+// @return newPars []interface{}
+func GetLikeKeywordPars(pars []interface{}, keyword string, num int) (newPars []interface{}) {
+	newPars = pars
+	if newPars == nil {
+		newPars = make([]interface{}, 0)
+	}
+	for i := 1; i <= num; i++ {
+		newPars = append(newPars, `%`+keyword+`%`)
+	}
+	return
+}
+
+// GetOrmInReplace 获取orm的in查询替换?的方法
+func GetOrmInReplace(num int) string {
+	template := make([]string, num)
+	for i := 0; i < num; i++ {
+		template[i] = "?"
+	}
+	return strings.Join(template, ",")
+}
+
+// GetLikeKeyword
+//
+//	@Description: 获取sql查询中的like查询字段
+//	@author: Roc
+//	@datetime2023-10-23 14:46:32
+//	@param keyword string
+//	@return string
+func GetLikeKeyword(keyword string) string {
+	return `%` + keyword + `%`
+}
+
+// MinusInt 获取两个[]int差集
+func MinusInt(a []int, b []int) []int {
+	var diff []int
+	mpA, mpB := make(map[int]bool), make(map[int]bool)
+
+	for _, v := range a {
+		mpA[v] = true
+	}
+	for _, v := range b {
+		mpB[v] = true
+	}
+	for _, s := range a {
+		if !mpB[s] {
+			diff = append(diff, s)
+		}
+	}
+	for _, s := range b {
+		if !mpA[s] {
+			diff = append(diff, s)
+		}
+	}
+	return diff
+}
+
+// InArrayByStr php中的in_array(判断String类型的切片中是否存在该string值)
+func InArrayByStr(idStrList []string, searchId string) (has bool) {
+	for _, id := range idStrList {
+		if id == searchId {
+			has = true
+			return
+		}
+	}
+	return
+}

+ 9 - 0
utils/constants.go

@@ -95,3 +95,12 @@ const (
 	BusinessCodeSandbox = "E2023080700" // ETA体验版
 	BusinessCodeRelease = "E2023080900" // 弘则ETA
 )
+
+// 研报类型标识
+var (
+	REPORT_TYPE_DAY      = "day"
+	REPORT_TYPE_WEEK     = "week"
+	REPORT_TYPE_TWO_WEEK = "two_week"
+	REPORT_TYPE_MONTH    = "month"
+	REPORT_TYPE_OTHER    = "other"
+)