Quellcode durchsuchen

问答流程调整

hsun vor 2 Jahren
Ursprung
Commit
a7dcc01f49

+ 112 - 2
controller/community/question.go

@@ -5,6 +5,7 @@ import (
 	"github.com/gin-gonic/gin"
 	"hongze/hongze_yb/controller/response"
 	"hongze/hongze_yb/global"
+	userLogic "hongze/hongze_yb/logic/user"
 	"hongze/hongze_yb/logic/yb_community_question"
 	"hongze/hongze_yb/models/request"
 
@@ -118,6 +119,14 @@ func QuestionAsk(c *gin.Context) {
 		response.Fail("内容不可超过50个字符", c)
 		return
 	}
+	if req.VarietyClassifyId <= 0 {
+		response.Fail("请选择品种分类", c)
+		return
+	}
+	if req.VarietyTagId <= 0 {
+		response.Fail("请选择品种", c)
+		return
+	}
 	// 敏感词校验, 只有小程序的用户才能走敏感词过滤接口
 	userinfo := user.GetInfoByClaims(c)
 	if userinfo.RecordInfo.OpenID != "" && userinfo.RecordInfo.CreatePlatform == 6 {
@@ -130,7 +139,7 @@ func QuestionAsk(c *gin.Context) {
 			}
 		}
 	}
-	if err := community.CreateQuestion(int(userinfo.UserID), userinfo.Mobile, userinfo.RealName, req.QuestionContent); err != nil {
+	if err := community.CreateQuestion(int(userinfo.UserID), req.VarietyClassifyId, req.VarietyTagId, userinfo.Mobile, userinfo.RealName, req.QuestionContent); err != nil {
 		response.FailMsg("提交失败", "QuestionAsk ErrMsg:"+err.Error(), c)
 		return
 	}
@@ -307,7 +316,6 @@ func QuestionUnread(c *gin.Context) {
 func ResearchGroupList(c *gin.Context) {
 	list, err := community.GetResearchGroupTree()
 	if err != nil {
-		fmt.Println(err.Error())
 		response.FailMsg("获取失败", "ResearchGroupList ErrMsg:"+err.Error(), c)
 		return
 	}
@@ -373,3 +381,105 @@ func SetLikeOrTease(c *gin.Context) {
 
 	return
 }
+
+// QuestionTransfer 转移问答
+// @Tags 问答社区模块
+// @Description 转移问答
+// @Param	request  body  request.QuestionTransferReq  true  "type json string"
+// @Success 200 {string} string "操作成功"
+// @Failure 400 {string} string "操作失败"
+// @Router /question/transfer [post]
+func QuestionTransfer(c *gin.Context) {
+	var req request.QuestionTransferReq
+	if err := c.ShouldBind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.CommunityQuestionID <= 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.VarietyClassifyId <= 0 {
+		response.Fail("请选择品种分类", c)
+		return
+	}
+	if req.VarietyTagId <= 0 {
+		response.Fail("请选择品种", c)
+		return
+	}
+	if req.AdminId <= 0 {
+		response.Fail("请选择研究员", c)
+		return
+	}
+
+	userInfo := user.GetInfoByClaims(c)
+	isInner, isResearcher, _, adminInfo, e := userLogic.GetUserIdentity(int(userInfo.CompanyID), userInfo.Mobile, userInfo.Email)
+	if e != nil {
+		response.FailMsg("操作失败", "QuestionTransfer ErrMsg:"+e.Error(), c)
+		return
+	}
+	if isInner == 0 || isResearcher == 0 {
+		response.Fail("无权操作", c)
+		return
+	}
+
+	errMsg, e := community.TransferQuestion(req.CommunityQuestionID, int(userInfo.UserID), int(adminInfo.AdminID), req.VarietyClassifyId, req.VarietyTagId, req.AdminId, adminInfo.RealName)
+	if e != nil {
+		response.FailMsg(errMsg, "QuestionTransfer ErrMsg:"+e.Error(), c)
+		return
+	}
+	response.Ok("操作成功", c)
+}
+
+// QuestionStop 终止问答
+// @Tags 问答社区模块
+// @Description 终止问答
+// @Param	request  body  request.QuestionStopReq  true  "type json string"
+// @Success 200 {string} string "操作成功"
+// @failure 400 {string} string "操作失败"
+// @Router /question/stop [post]
+func QuestionStop(c *gin.Context) {
+	var req request.QuestionStopReq
+	if err := c.ShouldBind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.CommunityQuestionID <= 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.Reason == "" {
+		response.Fail("请输入终止原因", c)
+		return
+	}
+	// 敏感词校验, 只有小程序的用户才能走敏感词过滤接口
+	userinfo := user.GetInfoByClaims(c)
+	if userinfo.RecordInfo.OpenID != "" && userinfo.RecordInfo.CreatePlatform == 6 {
+		checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.Reason)
+		if e == nil {
+			if checkResult.Result != nil && checkResult.Result.Suggest != "pass" {
+				errMsg := "含有违禁词, 不允许发布: " + checkResult.Result.Suggest + ", 命中标签: " + strconv.Itoa(checkResult.Result.Label)
+				response.FailMsg("含有违禁词, 不允许发布", errMsg, c)
+				return
+			}
+		}
+	}
+
+	userInfo := user.GetInfoByClaims(c)
+	isInner, isResearcher, _, _, e := userLogic.GetUserIdentity(int(userInfo.CompanyID), userInfo.Mobile, userInfo.Email)
+	if e != nil {
+		response.FailMsg("操作失败", "QuestionTransfer ErrMsg:"+e.Error(), c)
+		return
+	}
+	if isInner == 0 || isResearcher == 0 {
+		response.Fail("无权操作", c)
+		return
+	}
+
+	errMsg, e := community.StopQuestion(req.CommunityQuestionID, int(userInfo.UserID), req.Reason)
+	if e != nil {
+		response.FailMsg(errMsg, "QuestionStop ErrMsg:"+e.Error(), c)
+		return
+	}
+	response.Ok("操作成功", c)
+}

+ 3 - 1
controller/public.go

@@ -16,6 +16,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
+	"strconv"
 	"time"
 )
 
@@ -157,7 +158,8 @@ func GetSuncodeScene(c *gin.Context) {
 // @failure 400 {string} string "获取失败"
 // @Router /public/get_variety_tag_tree [get]
 func GetVarietyTagTree(c *gin.Context) {
-	list, err := services.GetTagTree()
+	questionId, _ := strconv.Atoi(c.Query("community_question_id"))
+	list, err := services.GetTagTree(questionId)
 	if err != nil {
 		response.FailMsg("获取标签树失败", "获取标签树失败, Err: "+err.Error(), c)
 		return

+ 17 - 1
models/request/community.go

@@ -14,7 +14,9 @@ type QuestionDetailReq struct {
 }
 
 type QuestionAskReq struct {
-	QuestionContent string `json:"question_content"`
+	QuestionContent   string `json:"question_content"`
+	VarietyClassifyId int    `json:"variety_classify_id" description:"标签分类"`
+	VarietyTagId      int    `json:"variety_tag_id" description:"标签ID"`
 }
 
 type QuestionReplyReq struct {
@@ -90,3 +92,17 @@ type ReqComment struct {
 type ReqDel struct {
 	CommunityQuestionCommentID uint64 `description:"留言ID" json:"community_question_comment_id"`
 }
+
+// QuestionTransferReq 转移问答请求体
+type QuestionTransferReq struct {
+	CommunityQuestionID int `json:"community_question_id"`
+	VarietyClassifyId   int `json:"variety_classify_id" description:"标签分类"`
+	VarietyTagId        int `json:"variety_tag_id" description:"标签ID"`
+	AdminId             int `json:"admin_id"`
+}
+
+// QuestionStopReq 终止问答请求体
+type QuestionStopReq struct {
+	CommunityQuestionID int    `json:"community_question_id"`
+	Reason              string `json:"reason" description:"终止原因"`
+}

+ 7 - 7
models/response/community.go

@@ -57,13 +57,13 @@ type CommunityQuestionAudioUpload struct {
 
 // ResearchGroup 研究方向分组表
 type ResearchGroupItem struct {
-	ResearchGroupId   int    `json:"research_group_id" description:"研究方向分组ID"`
-	ResearchGroupName string `json:"research_group_name" description:"研究方向分组名称"`
-	ParentId          int    `json:"parent_id" description:"父类ID"`
-	ChartPermissionId int    `json:"chart_permission_id" description:"品种权限ID"`
-	Sort              int    `json:"sort" description:"排序"`
-	//Members           []*ResearchGroupMember `json:"members"`
-	Children []*ResearchGroupItem `json:"children"`
+	ResearchGroupId   int                    `json:"research_group_id" description:"研究方向分组ID"`
+	ResearchGroupName string                 `json:"research_group_name" description:"研究方向分组名称"`
+	ParentId          int                    `json:"parent_id" description:"父类ID"`
+	ChartPermissionId int                    `json:"chart_permission_id" description:"品种权限ID"`
+	Sort              int                    `json:"sort" description:"排序"`
+	Members           []*ResearchGroupMember `json:"members"`
+	Children          []*ResearchGroupItem   `json:"children"`
 }
 
 // ResearchGroupMember 研究方向组员信息

+ 20 - 0
models/tables/admin/query.go

@@ -26,3 +26,23 @@ func GetAdminByEmail(mobile string) (item *Admin, err error) {
 func GetVWangInfo() (item *Admin, err error) {
 	return GetByAdminId(66)
 }
+
+// ResearcherAdminAndUser 研究员admin信息及wx_user信息
+type ResearcherAdminAndUser struct {
+	UserId    int    `description:"用户ID"`
+	UserName  string `description:"用户名称"`
+	AdminId   int    `description:"管理员ID"`
+	AdminName string `description:"管理员姓名"`
+	OpenId    string `description:"openid"`
+}
+
+// GetResearcherAdminAndWxUserByAdminId 通过adminId获取研究员admin及user信息
+func GetResearcherAdminAndWxUserByAdminId(adminId int) (item *ResearcherAdminAndUser, err error) {
+	sql := `SELECT a.admin_id, a.real_name AS admin_name, b.user_id, b.real_name AS user_name, c.open_id FROM admin AS a
+		JOIN wx_user AS b ON a.mobile = b.mobile
+		JOIN user_record AS c ON b.user_id = c.user_id AND c.create_platform = 1
+		WHERE a.admin_id = ?`
+	//JOIN user_record AS c ON b.user_id = c.user_id AND c.subscribe = 1 AND c.create_platform = 1
+	err = global.DEFAULT_MYSQL.Raw(sql, adminId).First(&item).Error
+	return
+}

+ 2 - 1
models/tables/research_group/model.go

@@ -9,4 +9,5 @@ func GetResearchGroupList() (list []*ResearchGroup, err error) {
 		Order("sort ASC").
 		Scan(&list).Error
 	return
-}
+}
+

+ 20 - 1
models/tables/research_group_relation/model.go

@@ -6,4 +6,23 @@ import "hongze/hongze_yb/global"
 func GetResearchGroupRelationByAdminId(adminId int) (items []*ResearchGroupRelation, err error) {
 	err = global.DEFAULT_MYSQL.Model(ResearchGroupRelation{}).Where("admin_id = ?", adminId).Scan(&items).Error
 	return
-}
+}
+
+// ResearchGroupRelation 研究方向分组关系表
+type ResearchGroupRelationItem struct {
+	ResearchGroupId int    `json:"research_group_id" description:"研究方向分组ID"`
+	AdminId         int    `json:"admin_id" description:"研究员ID"`
+	AdminName       string `json:"admin_name" description:"研究员姓名"`
+}
+
+// GetResearchGroupRelationList 获取研究方向分组关系列表
+func GetResearchGroupRelationList() (list []*ResearchGroupRelationItem, err error) {
+	err = global.DEFAULT_MYSQL.
+		Table("research_group_relation AS a ").
+		Select("a.research_group_id, a.admin_id, b.real_name AS admin_name").
+		Joins("INNER JOIN admin AS b ON a.admin_id = b.admin_id").
+		Where("b.enabled = 1").
+		Order("a.research_group_id ASC, a.admin_id ASC").
+		Find(&list).Error
+	return
+}

+ 19 - 0
models/tables/research_variety_tag_relation/model.go

@@ -7,3 +7,22 @@ func GetResearchVarietyTagRelationByAdminId(adminId int) (items []*ResearchVarie
 	err = global.DEFAULT_MYSQL.Model(ResearchVarietyTagRelation{}).Where("admin_id = ?", adminId).Scan(&items).Error
 	return
 }
+
+// ResearchVarietyTagRelationItem 研究员标签关系
+type ResearchVarietyTagRelationItem struct {
+	VarietyTagId int    `json:"variety_tag_id" description:"标签ID"`
+	AdminId      int    `json:"admin_id" description:"研究员ID"`
+	AdminName    string `json:"admin_name" description:"研究员姓名"`
+}
+
+// GetResearchVarietyTagRelationList 获取研究方向分组关系列表
+func GetResearchVarietyTagRelationList() (list []*ResearchVarietyTagRelationItem, err error) {
+	err = global.DEFAULT_MYSQL.
+		Table("research_variety_tag_relation AS a ").
+		Select("a.variety_tag_id, a.admin_id, b.real_name AS admin_name").
+		Joins("INNER JOIN admin AS b ON a.admin_id = b.admin_id").
+		Where("b.enabled = 1").
+		Order("a.variety_tag_id ASC, a.admin_id ASC").
+		Find(&list).Error
+	return
+}

+ 7 - 0
models/tables/variety_classify/model.go

@@ -9,3 +9,10 @@ func GetVarietyClassifyList() (list []*VarietyClassify, err error) {
 		Scan(&list).Error
 	return
 }
+
+func GetVarietyClassifyById(varietyClassifyId int) (item *VarietyClassify, err error) {
+	err = global.DEFAULT_MYSQL.Model(VarietyClassify{}).
+		Where("variety_classify_id = ? AND state = 1", varietyClassifyId).
+		First(&item).Error
+	return
+}

+ 7 - 0
models/tables/variety_tag/model.go

@@ -9,3 +9,10 @@ func GetVarietyTagList() (list []*VarietyTag, err error) {
 		Scan(&list).Error
 	return
 }
+
+func GetVarietyTagById(varietyTagId int) (item *VarietyTag, err error) {
+	err = global.DEFAULT_MYSQL.Model(VarietyTag{}).
+		Where("variety_tag_id = ? AND state = 1", varietyTagId).
+		First(&item).Error
+	return
+}

+ 10 - 1
models/tables/yb_community_question/entity.go

@@ -4,6 +4,14 @@ import (
 	"time"
 )
 
+const (
+	// 回复状态
+	ReplyStatusDitribute = 1
+	ReplyStatusWait      = 2
+	ReplyStatusDone      = 3
+	ReplyStatusStop      = 4
+)
+
 // YbCommunityQuestion 研报-问答社区表
 type YbCommunityQuestion struct {
 	CommunityQuestionID     int       `gorm:"primaryKey;column:community_question_id;type:int(10) unsigned;not null" json:"-"`
@@ -27,13 +35,14 @@ type YbCommunityQuestion struct {
 	VarietyTagName          string    `gorm:"column:variety_tag_name;type:varchar(100);not null;default:''" json:"varietyTagName"`                            // 标签名称
 	IsRead                  int       `gorm:"column:is_read;type:tinyint(4) unsigned;not null;default:0" json:"isRead"`                                       // 用户是否已读 0-未读 1-已读
 	ReplierIsRead           int       `gorm:"column:replier_is_read;type:tinyint(4) unsigned;not null;default:0" json:"replierIsRead"`                        // 回复者是否已读  0-未读 1-已读
-	ReplyStatus             int       `gorm:"column:reply_status;type:tinyint(4) unsigned;not null;default:0" json:"replyStatus"`                             // 回复状态 1-待分配 2-待回答 3-已回答
+	ReplyStatus             int       `gorm:"column:reply_status;type:tinyint(4) unsigned;not null;default:0" json:"replyStatus"`                             // 回复状态 1-待分配 2-待回答 3-已回答 4-已终止
 	MsgSendStatus           int       `gorm:"column:msg_send_status;type:tinyint(4) unsigned;not null;default:0" json:"msgSendStatus"`                        // 消息推送进度 0-待推送 1-已推送回答人 2-已推送提问人
 	ReplyTime               time.Time `gorm:"column:reply_time;type:datetime" json:"replyTime"`                                                               // 回复时间
 	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP" json:"createTime"`                                   // 提问时间
 	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:CURRENT_TIMESTAMP" json:"modifyTime"`                                   // 修改时间
 	IsDeleted               int       `gorm:"column:is_deleted;type:tinyint(4) unsigned;not null;default:0" json:"isDeleted"`                                 // 是否已删除 0-否 1-是
 	DeleteTime              time.Time `gorm:"column:delete_time;type:datetime" json:"deleteTime"`                                                             // 删除时间
+	StopReason              string    `gorm:"column:stop_reason;type:varchar(255);not null;default:''" json:"stopReason"`                                     // 终止原因
 }
 
 // TableName get sql table name.获取数据库表名

+ 6 - 6
models/tables/yb_community_question/model.go

@@ -17,7 +17,7 @@ func (item *YbCommunityQuestion) Update(updateCols []string) (err error) {
 }
 
 // GetPageListByCondition 获取问答列表-分页
-func GetPageListByCondition(condition string, pars []interface{},pageIndex, pageSize int) (list []*YbCommunityQuestion, err error) {
+func GetPageListByCondition(condition string, pars []interface{}, pageIndex, pageSize int) (list []*YbCommunityQuestion, err error) {
 	offset := (pageIndex - 1) * pageSize
 	err = global.DEFAULT_MYSQL.Model(YbCommunityQuestion{}).Where(condition, pars...).Offset(offset).Limit(pageSize).Order("create_time DESC").Scan(&list).Error
 	return
@@ -57,8 +57,8 @@ func UpdateQuestionAndAudioList(item *YbCommunityQuestion, updateCols []string,
 type QuestionListCount struct {
 	ReplierUserId uint64 `json:"replier_user_id"`
 	UserId        uint64 `json:"user_id"`
-	ReplyStatus   int `json:"reply_status"`
-	Total         int `json:"total"`
+	ReplyStatus   int    `json:"reply_status"`
+	Total         int    `json:"total"`
 }
 
 // GetQuestionListCount 获取问答数量统计
@@ -83,7 +83,7 @@ func UpdateReplierRead(replierUserId int, questionIds []int) (err error) {
 		Where("replier_user_id = ? AND community_question_id IN (?) AND replier_is_read=0", replierUserId, questionIds).
 		Updates(YbCommunityQuestion{
 			ReplierIsRead: 1,
-			ModifyTime: time.Now().Local(),
+			ModifyTime:    time.Now().Local(),
 		}).Error
 	return
 }
@@ -93,8 +93,8 @@ func UpdateUserRead(userId int, questionIds []int) (err error) {
 	err = global.DEFAULT_MYSQL.Model(YbCommunityQuestion{}).
 		Where("user_id = ? AND community_question_id IN (?) AND is_read=0", userId, questionIds).
 		Updates(YbCommunityQuestion{
-			IsRead: 1,
+			IsRead:     1,
 			ModifyTime: time.Now().Local(),
 		}).Error
 	return
-}
+}

+ 2 - 0
routers/community.go

@@ -20,6 +20,8 @@ func InitCommunity(r *gin.Engine) {
 	rGroup.GET("/question/unread", community.QuestionUnread)
 	rGroup.GET("/question/research_group", community.ResearchGroupList)
 	rGroup.POST("/question/audio/log", community.AddAudioLog)
+	rGroup.POST("/question/transfer", community.QuestionTransfer)
+	rGroup.POST("/question/stop", community.QuestionStop)
 	// 视频社区
 	noAuthGroup := r.Group("api/community").Use(middleware.Token())
 	rGroup.GET("/video/list", community.VideoList)

+ 267 - 19
services/community/question.go

@@ -10,11 +10,15 @@ import (
 	"hongze/hongze_yb/models/tables/company"
 	"hongze/hongze_yb/models/tables/company_product"
 	"hongze/hongze_yb/models/tables/research_group"
-	"hongze/hongze_yb/models/tables/user_record"
+	"hongze/hongze_yb/models/tables/research_group_relation"
+	"hongze/hongze_yb/models/tables/research_variety_tag_relation"
+	"hongze/hongze_yb/models/tables/variety_classify"
+	"hongze/hongze_yb/models/tables/variety_tag"
 	"hongze/hongze_yb/models/tables/wx_user"
 	"hongze/hongze_yb/models/tables/yb_community_audio_listen_log"
 	"hongze/hongze_yb/models/tables/yb_community_question"
 	"hongze/hongze_yb/models/tables/yb_community_question_audio"
+	"hongze/hongze_yb/models/tables/yb_community_question_process"
 	"hongze/hongze_yb/services/alarm_msg"
 	"hongze/hongze_yb/services/company_approval_message"
 	"hongze/hongze_yb/services/user"
@@ -49,6 +53,10 @@ func GetQuestionList(pageIndex, pageSize, onlyMine, varietyTagId, replyStatus, g
 			} else if replyStatus == 0 { //分配给研究员或者研究员提问的所以问题
 				condition += " and (replier_user_id=? or user_id=?)"
 				pars = append(pars, userInfo.UserID, userInfo.UserID)
+			} else if replyStatus == 5 {
+				// 已终止状态
+				condition += " and replier_user_id=? and reply_status = 4"
+				pars = append(pars, userInfo.UserID)
 			}
 		} else {
 			condition += " and user_id=?"
@@ -213,32 +221,77 @@ func GetQuestionDetail(questionId int, userInfo user.UserInfo) (item *response.C
 }
 
 // CreateQuestion 新增问答
-func CreateQuestion(userId int, mobile, realName, content string) (err error) {
-	// 获取用户公众号openid, 获取不到则置空
-	userRecord, e := user_record.GetByUserId(userId, utils.USER_RECORD_PLATFORM_RDDP)
-	if e != nil && e != utils.ErrNoRow {
-		err = errors.New("获取用户公众号openid失败 Err:" + e.Error())
+func CreateQuestion(userId, varietyClassifyId, varietyTagId int, mobile, realName, content string) (err error) {
+	// 获取分类
+	classifyItem, e := variety_classify.GetVarietyClassifyById(varietyClassifyId)
+	if e != nil {
+		err = errors.New("获取问答标签分类失败 Err:" + e.Error())
 		return
 	}
-	openid := ""
-	if userRecord != nil {
-		openid = userRecord.OpenID
+
+	// 获取标签
+	tagItem, e := variety_tag.GetVarietyTagById(varietyTagId)
+	if e != nil {
+		err = errors.New("获取问答标签失败 Err:" + e.Error())
+		return
 	}
+
+	// 获取标签对应的研究方向分组-研究员信息
+	researcher, e := GetFirstResearcherByVarietyTagId(tagItem.VarietyTagID)
+	if e != nil {
+		err = errors.New("获取标签对应研究员信息失败 Err:" + e.Error())
+		return
+	}
+
+	nowTime := time.Now().Local()
 	item := &yb_community_question.YbCommunityQuestion{
-		UserID:          userId,
-		UserOpenid:      openid,
-		Mobile:          mobile,
-		RealName:        realName,
-		QuestionContent: content,
-		ReplyStatus:     1,
-		IsRead:          1,
-		ReplierIsRead:   1,
+		UserID:                  userId,
+		Mobile:                  mobile,
+		RealName:                realName,
+		QuestionContent:         content,
+		ReplyStatus:             2,
+		IsRead:                  1,
+		ReplierIsRead:           0,
+		MsgSendStatus:           1,
+		ReplierUserID:           researcher.UserId,
+		ReplierAdminID:          researcher.AdminId,
+		ReplierRealName:         researcher.AdminName,
+		ResearchGroupFirstID:    classifyItem.VarietyClassifyID,
+		ResearchGroupFirstName:  classifyItem.ClassifyName,
+		ResearchGroupSecondID:   tagItem.VarietyTagID,
+		ResearchGroupSecondName: tagItem.TagName,
+		VarietyTagID:            tagItem.VarietyTagID,
+		VarietyTagName:          tagItem.TagName,
+		CreateTime:              nowTime,
 	}
 	if e := item.Create(); e != nil {
 		err = errors.New("新增问题失败 Err:" + e.Error())
+		return
 	}
 
-	// 新增问答后,消息通知管理员
+	// 新增问答流程
+	go func() {
+		pro := new(yb_community_question_process.YbCommunityQuestionProcess)
+		pro.CommunityQuestionID = item.CommunityQuestionID
+		pro.ReplierUserID = researcher.UserId
+		pro.ReplierAdminID = researcher.AdminId
+		pro.ReplierAdminName = researcher.AdminName
+		pro.VarietyClassifyID = classifyItem.VarietyClassifyID
+		pro.VarietyTagID = tagItem.VarietyTagID
+		pro.Remark = fmt.Sprintf("自动分配问题给研究员%s", researcher.AdminName)
+		pro.ProcessType = yb_community_question_process.ProcessTypeDistribute
+		pro.CreateTime = nowTime
+		if e = pro.Create(); e != nil {
+			return
+		}
+	}()
+
+	// 推送待回复模板消息给研究员
+	go func() {
+		_ = wechat.SendQuestionToResearcher(item.CommunityQuestionID, researcher.OpenId, item.QuestionContent, "")
+	}()
+
+	// 新增问答后,消息通知随手办公管理员
 	go messageToAdmin(userId, item)
 	return
 }
@@ -270,7 +323,7 @@ func ReplyUserQuestion(replierId, questionId int, audios []*request.ReplyReqAudi
 	updateCols := make([]string, 0)
 	updateCols = append(updateCols, "reply_status", "reply_time", "modify_time", "msg_send_status", "is_read")
 	nowTime := time.Now().Local()
-	item.ReplyStatus = 3
+	item.ReplyStatus = yb_community_question.ReplyStatusDone
 	item.ReplyTime = nowTime
 	item.ModifyTime = nowTime
 	item.MsgSendStatus = 2
@@ -446,6 +499,24 @@ func GetResearchGroupTree() (respList []*response.ResearchGroupItem, err error)
 	if len(firstList) == 0 {
 		return
 	}
+	// 匹配成员
+	relationList, e := research_group_relation.GetResearchGroupRelationList()
+	if e != nil {
+		err = errors.New("获取研究方向关系失败, Err:" + e.Error())
+		return
+	}
+	for _, v := range secondList {
+		members := make([]*response.ResearchGroupMember, 0)
+		for _, r := range relationList {
+			if v.ResearchGroupId == r.ResearchGroupId {
+				members = append(members, &response.ResearchGroupMember{
+					AdminId:   r.AdminId,
+					AdminName: r.AdminName,
+				})
+			}
+		}
+		v.Members = members
+	}
 	// 匹配子分类
 	for _, p := range firstList {
 		children := make([]*response.ResearchGroupItem, 0)
@@ -598,3 +669,180 @@ func wxMessageToAdmin(adminInfo admin.Admin, ybCommunityQuestion *yb_community_q
 	err = wechat.SendQuestionToAdmin(ybCommunityQuestion.CommunityQuestionID, int(adminInfo.AdminID), adminInfo.OpenId, ybCommunityQuestion.QuestionContent)
 	return
 }
+
+// GetFirstResearcherByVarietyTagId 通过品种标签ID获取首位研究员admin信息及wx_user信息
+func GetFirstResearcherByVarietyTagId(tagId int) (researcher *admin.ResearcherAdminAndUser, err error) {
+	researcher = new(admin.ResearcherAdminAndUser)
+
+	// 获取标签研究员关系组
+	relationList, e := research_variety_tag_relation.GetResearchVarietyTagRelationList()
+	if e != nil {
+		err = errors.New("获取研究员分组失败, Err: " + e.Error())
+		return
+	}
+	if len(relationList) == 0 {
+		err = errors.New("获取研究员分组有误")
+		return
+	}
+
+	// 从当前组取出有效的研究员信息
+	for i := range relationList {
+		if relationList[i].VarietyTagId == tagId {
+			item, e := admin.GetResearcherAdminAndWxUserByAdminId(relationList[i].AdminId)
+			if e != nil && e != utils.ErrNoRow {
+				err = errors.New("获取研究员成员信息失败, Err: " + e.Error())
+				return
+			}
+			if item != nil && item.UserId > 0 && item.AdminId > 0 && item.OpenId != "" {
+				researcher = item
+				break
+			}
+		}
+	}
+	// 未在当前组找到有效的研究员信息, 去其他组找
+	if researcher.UserId == 0 || researcher.AdminId == 0 || researcher.OpenId == "" {
+		for i := range relationList {
+			if relationList[i].VarietyTagId != tagId {
+				item, e := admin.GetResearcherAdminAndWxUserByAdminId(relationList[i].AdminId)
+				if e != nil && e != utils.ErrNoRow {
+					err = errors.New("获取其他组研究员成员信息失败, Err: " + e.Error())
+					return
+				}
+				if item != nil && item.UserId > 0 && item.AdminId > 0 && item.OpenId != "" {
+					researcher = item
+					break
+				}
+			}
+		}
+	}
+	// 无有效研究员
+	if researcher.UserId == 0 || researcher.AdminId == 0 || researcher.OpenId == "" {
+		err = errors.New("无有效研究员可分配")
+	}
+	return
+}
+
+// TransferQuestion 转移问答
+func TransferQuestion(questionId, userId, userAdminId, varietyClassifyId, varietyTagId, adminId int, userAdminName string) (errMsg string, err error) {
+	errMsg = "操作失败"
+	item, e := yb_community_question.GetItemById(questionId)
+	if e != nil {
+		err = errors.New("获取提问信息失败 Err:" + e.Error())
+		return
+	}
+	if item.ReplyStatus != yb_community_question.ReplyStatusWait {
+		errMsg = "当前状态不可转移"
+		err = errors.New("回复状态有误")
+		return
+	}
+	if item.ReplierUserID != userId {
+		errMsg = "无权操作"
+		err = errors.New(fmt.Sprintf("回复人与分配人不匹配, 当前回复人ID: %d, 分配的回复人ID: %d", userId, item.ReplierUserID))
+		return
+	}
+
+	// 获取分类
+	classifyItem, e := variety_classify.GetVarietyClassifyById(varietyClassifyId)
+	if e != nil {
+		err = errors.New("获取问答标签分类失败 Err:" + e.Error())
+		return
+	}
+
+	// 获取标签
+	tagItem, e := variety_tag.GetVarietyTagById(varietyTagId)
+	if e != nil {
+		err = errors.New("获取问答标签失败 Err:" + e.Error())
+		return
+	}
+
+	// 获取研究员信息失败
+	researcher, e := admin.GetResearcherAdminAndWxUserByAdminId(adminId)
+	if e != nil {
+		errMsg = "该研究员未关注公众号或无法正确接收模板消息"
+		err = errors.New("获取研究员信息失败 Err:" + e.Error())
+		return
+	}
+
+	// 更新问答
+	nowTime := time.Now().Local()
+	updateCols := []string{
+		"ReplierIsRead", "ReplierUserID", "ReplierAdminID", "ReplierRealName", "ResearchGroupFirstID", "ResearchGroupFirstName",
+		"ResearchGroupSecondID", "ResearchGroupSecondName", "VarietyTagID", "VarietyTagName", "ModifyTime",
+	}
+	item.ReplierIsRead = 0
+	item.ReplierUserID = researcher.UserId
+	item.ReplierAdminID = researcher.AdminId
+	item.ReplierRealName = researcher.AdminName
+	item.ResearchGroupFirstID = classifyItem.VarietyClassifyID
+	item.ResearchGroupFirstName = classifyItem.ClassifyName
+	item.ResearchGroupSecondID = tagItem.VarietyTagID
+	item.ResearchGroupSecondName = tagItem.TagName
+	item.VarietyTagID = tagItem.VarietyTagID
+	item.VarietyTagName = tagItem.TagName
+	item.ModifyTime = nowTime
+	if e = item.Update(updateCols); e != nil {
+		err = errors.New("更新问答失败 Err:" + e.Error())
+		return
+	}
+
+	// 新增问答流程
+	go func() {
+		pro := new(yb_community_question_process.YbCommunityQuestionProcess)
+		pro.CommunityQuestionID = item.CommunityQuestionID
+		pro.TransferUserID = userId
+		pro.TransferAdminID = userAdminId
+		pro.TransferAdminName = userAdminName
+		pro.ReplierUserID = researcher.UserId
+		pro.ReplierAdminID = researcher.AdminId
+		pro.ReplierAdminName = researcher.AdminName
+		pro.VarietyClassifyID = classifyItem.VarietyClassifyID
+		pro.VarietyTagID = tagItem.VarietyTagID
+		pro.Remark = fmt.Sprintf("转至%s", researcher.AdminName)
+		pro.ProcessType = yb_community_question_process.ProcessTypeTransfer
+		pro.CreateTime = nowTime
+		if e = pro.Create(); e != nil {
+			return
+		}
+	}()
+
+	// 推送转移模板消息给研究员
+	go func() {
+		remark := fmt.Sprintf("%s移交了新的问答给您, 请点击详情尽快处理", userAdminName)
+		_ = wechat.SendQuestionToResearcher(item.CommunityQuestionID, researcher.OpenId, item.QuestionContent, remark)
+	}()
+	return
+}
+
+// StopQuestion 终止问答
+func StopQuestion(questionId, userId int, reason string) (errMsg string, err error) {
+	errMsg = "操作失败"
+	item, e := yb_community_question.GetItemById(questionId)
+	if e != nil {
+		err = errors.New("获取提问信息失败 Err:" + e.Error())
+		return
+	}
+	if item.ReplyStatus != yb_community_question.ReplyStatusWait {
+		errMsg = "当前状态不可终止"
+		err = errors.New("回复状态有误")
+		return
+	}
+	if item.ReplierUserID != userId {
+		errMsg = "无权操作"
+		err = errors.New(fmt.Sprintf("回复人与分配人不匹配, 当前回复人ID: %d, 分配的回复人ID: %d", userId, item.ReplierUserID))
+		return
+	}
+
+	// 更新问答
+	nowTime := time.Now().Local()
+	updateCols := []string{
+		"ReplyStatus", "StopReason", "ModifyTime",
+	}
+	item.ReplyStatus = yb_community_question.ReplyStatusStop
+	item.StopReason = reason
+	item.ModifyTime = nowTime
+	if e = item.Update(updateCols); e != nil {
+		err = errors.New("更新问答失败 Err:" + e.Error())
+		return
+	}
+	return
+}

+ 62 - 14
services/variety_tag.go

@@ -2,8 +2,11 @@ package services
 
 import (
 	"errors"
+	"fmt"
+	"hongze/hongze_yb/models/tables/research_variety_tag_relation"
 	"hongze/hongze_yb/models/tables/variety_classify"
 	"hongze/hongze_yb/models/tables/variety_tag"
+	"hongze/hongze_yb/models/tables/yb_community_question_process"
 	"hongze/hongze_yb/models/tables/yb_price_driven_tag"
 )
 
@@ -19,24 +22,26 @@ type TagTreeItem struct {
 
 // TagItem 标签
 type TagItem struct {
-	TagId             int    `json:"tag_id" description:"标签ID"`
-	TagName           string `json:"tag_name" description:"标签名称"`
-	Sort              int    `json:"sort" description:"排序"`
-	ClassifyId        int    `json:"classify_id" description:"分类ID"`
-	ChartPermissionId int    `json:"chart_permission_id" description:"品种权限ID"`
-	PriceDrivenState  int    `json:"price_driven_state" description:"价格驱动状态:0-关闭 1-开启"`
-	//ResearcherList    []*TagResearcher `json:"researcher_list" description:"研究员列表"`
+	TagId             int              `json:"tag_id" description:"标签ID"`
+	TagName           string           `json:"tag_name" description:"标签名称"`
+	Sort              int              `json:"sort" description:"排序"`
+	ClassifyId        int              `json:"classify_id" description:"分类ID"`
+	ChartPermissionId int              `json:"chart_permission_id" description:"品种权限ID"`
+	PriceDrivenState  int              `json:"price_driven_state" description:"价格驱动状态:0-关闭 1-开启"`
+	ResearcherList    []*TagResearcher `json:"researcher_list" description:"研究员列表"`
 }
 
 // TagResearcher 研究员信息
 type TagResearcher struct {
-	AdminId   int    `json:"admin_id"`
-	AdminName string `json:"admin_name"`
+	AdminId     int    `json:"admin_id"`
+	AdminName   string `json:"admin_name"`
+	AllowSelect bool   `json:"allow_select" description:"是否允许选中"`
 }
 
-// 获取标签树
-func GetTagTree() (respList []*TagTreeItem, err error) {
+// GetTagTree 获取标签树
+func GetTagTree(questionId int) (respList []*TagTreeItem, err error) {
 	respList = make([]*TagTreeItem, 0)
+
 	// 获取标签分类
 	classifyList, e := variety_classify.GetVarietyClassifyList()
 	if e != nil {
@@ -47,12 +52,22 @@ func GetTagTree() (respList []*TagTreeItem, err error) {
 	if classifyLen == 0 {
 		return
 	}
+
 	// 获取标签列表
 	tagList, e := variety_tag.GetVarietyTagList()
 	if e != nil {
 		err = errors.New("获取标签列表失败, Err: " + e.Error())
 		return
 	}
+
+	// 标签研究员组
+	relationList := make([]*research_variety_tag_relation.ResearchVarietyTagRelationItem, 0)
+	relationList, e = research_variety_tag_relation.GetResearchVarietyTagRelationList()
+	if e != nil {
+		err = errors.New("获取研究员标签关系失败, Err:" + e.Error())
+		return
+	}
+
 	// 价格驱动标签
 	priceTagList, e := yb_price_driven_tag.GetPriceDrivenTagList()
 	if e != nil {
@@ -64,21 +79,54 @@ func GetTagTree() (respList []*TagTreeItem, err error) {
 	for i := 0; i < priceTagLen; i++ {
 		priceTagMap[priceTagList[i].VarietyTagID] = priceTagList[i].State
 	}
-	// 价格驱动状态
+
+	// 问答社区-研究员是否允许被二次选中
+	processMap := make(map[string]bool)
+	if questionId > 0 {
+		processList, e := yb_community_question_process.GetListByQuestionId(questionId)
+		if e != nil {
+			err = errors.New("获取问答流程列表失败, Err:" + e.Error())
+			return
+		}
+		for i := range processList {
+			k := fmt.Sprintf("%d-%d", processList[i].VarietyTagID, processList[i].ReplierAdminID)
+			processMap[k] = true
+		}
+	}
+
+	// 研究员、价格驱动状态
 	tagsArr := make([]*TagItem, 0)
 	tagLen := len(tagList)
 	for i := 0; i < tagLen; i++ {
 		item := tagList[i]
 		priceState := priceTagMap[item.VarietyTagID]
-		tagsArr = append(tagsArr, &TagItem{
+		tmp := &TagItem{
 			TagId:             item.VarietyTagID,
 			TagName:           item.TagName,
 			Sort:              item.Sort,
 			ClassifyId:        item.VarietyClassifyID,
 			ChartPermissionId: item.ChartPermissionID,
 			PriceDrivenState:  priceState,
-		})
+		}
+		members := make([]*TagResearcher, 0)
+		for _, r := range relationList {
+			if item.VarietyTagID == r.VarietyTagId {
+				k := fmt.Sprintf("%d-%d", item.VarietyTagID, r.AdminId)
+				as := true
+				if processMap[k] {
+					as = false
+				}
+				members = append(members, &TagResearcher{
+					AdminId:     r.AdminId,
+					AdminName:   r.AdminName,
+					AllowSelect: as,
+				})
+			}
+		}
+		tmp.ResearcherList = members
+		tagsArr = append(tagsArr, tmp)
 	}
+
 	// 分类标签
 	for i := 0; i < classifyLen; i++ {
 		classifyItem := new(TagTreeItem)

+ 55 - 2
services/wechat/template_msg.go

@@ -492,7 +492,7 @@ func SendQuestionToAdmin(questionId, adminId int, adminOpenId string, questionTi
 }
 
 // SendQuestionCommentToAdmin 推送研报小程序模板消息-用户评论问答时,通知到管理员
-func SendQuestionCommentToAdmin(commentId, adminId int, adminOpenId string,  commentContent string) (err error) {
+func SendQuestionCommentToAdmin(commentId, adminId int, adminOpenId string, commentContent string) (err error) {
 	if adminId == 0 || adminOpenId == "" {
 		return
 	}
@@ -554,4 +554,57 @@ func SendQuestionCommentToAdmin(commentId, adminId int, adminOpenId string,  com
 
 	err = SendTemplateMsg(sendInfo)
 	return
-}
+}
+
+// SendQuestionToResearcher 推送研报小程序模板消息-用户提问时,通知到研究员
+func SendQuestionToResearcher(questionId int, adminOpenId, questionTitle, remark string) (err error) {
+	if adminOpenId == "" {
+		return
+	}
+	var errMsg string
+	defer func() {
+		if err != nil {
+			alarmMsg := fmt.Sprintf("SendQuestionToAdmin-推送研报小程序模板消息-用户提问时,通知到研究员; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg)
+			go alarm_msg.SendAlarmMsg(alarmMsg, 3)
+		}
+	}()
+
+	openIdList := make([]*OpenIdList, 0)
+	openIdList = append(openIdList, &OpenIdList{
+		OpenId: adminOpenId,
+		UserId: 0,
+	})
+
+	first := "您好,有新的提问待回复"
+	keyword1 := questionTitle
+	keyword2 := "待回复"
+	if remark == "" {
+		remark = "请点击详情尽快处理"
+	}
+
+	wxAppPath := fmt.Sprintf("pages-question/answerDetail?id=%d", questionId)
+	if global.CONFIG.Serve.RunMode == "debug" {
+		// 仅测试环境测试用
+		wxAppPath = "pages-approve/seal/list"
+	}
+
+	openIdArr := make([]string, len(openIdList))
+	for i, v := range openIdList {
+		openIdArr[i] = v.OpenId
+	}
+	sendInfo := new(SendWxTemplate)
+	sendInfo.WxAppId = AdminWxAppId
+	sendInfo.First = first
+	sendInfo.Keyword1 = keyword1
+	sendInfo.Keyword2 = keyword2
+	sendInfo.Remark = remark
+	sendInfo.RedirectUrl = wxAppPath
+	sendInfo.TemplateId = TemplateIdWithCommunityQuestionNotifyAdmin
+	sendInfo.RedirectTarget = 1
+	sendInfo.Resource = wxAppPath
+	sendInfo.SendType = utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION
+	sendInfo.OpenIdArr = openIdArr
+
+	err = SendTemplateMsg(sendInfo)
+	return
+}

+ 9 - 12
services/wechat/wechat.go

@@ -19,22 +19,22 @@ import (
 )
 
 var (
-	WxId                            string //微信原始ID
-	WxAppId                         string
-	WxAppSecret                     string
-	TemplateIdWithCommunityQuestion string // 问答社区回复模板消息ID
+	WxId                                       string //微信原始ID
+	WxAppId                                    string
+	WxAppSecret                                string
+	TemplateIdWithCommunityQuestion            string // 问答社区回复模板消息ID
 	TemplateIdWithCommunityQuestionNotifyAdmin string // 问答社区 有提问通知管理员
-	WxYbAppId                       string // 研报小程序AppID
-	PcWxAppId                       string //pc版AppId
-	PcWxAppSecret                   string //pc版AppSecret
+	WxYbAppId                                  string // 研报小程序AppID
+	PcWxAppId                                  string //pc版AppId
+	PcWxAppSecret                              string //pc版AppSecret
 
 	WxMobileCrmAppId     string //随手办公小程序appid
 	WxMobileCrmId        string //随手办公小程序id
 	WxAppMobileCrmSecret string //随手办公小程序秘钥
 
 	//内部员工公众号(弘则部门)
-	AdminWxAppId                    string
-	AdminWxAppSecret                string
+	AdminWxAppId     string
+	AdminWxAppSecret string
 )
 
 func init() {
@@ -106,7 +106,6 @@ func GetJsConfig(signUrl string) (jsConf *js.Config, err error) {
 type WechatAccessToken struct {
 }
 
-
 // GetAccessToken 获取accessToken
 func (wechat WechatAccessToken) GetAccessToken() (accessToken string, err error) {
 	wxToken, err := wx_token.GetById()
@@ -168,5 +167,3 @@ func PcWxGetUserInfo(openId, accessToken string) (item *pc.WxUserInfo, err error
 	err = json.Unmarshal(result, &item)
 	return
 }
-
-