Files

237 lines
6.7 KiB
Go

package admin
import (
"context"
v1 "github.com/qianfree/team-api/api/admin/v1"
"github.com/qianfree/team-api/internal/dao"
"github.com/qianfree/team-api/internal/logic/common"
do "github.com/qianfree/team-api/internal/model/do"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// ListAllFeedbacks 管理后台反馈列表
func (s *sAdmin) ListAllFeedbacks(ctx context.Context, req *v1.FeedbackListAllReq) (*v1.FeedbackListAllRes, error) {
page, pageSize := common.NormalizePagination(req.Page, req.PageSize)
query := dao.SptFeedbacks.Ctx(ctx).
LeftJoin("tnt_tenants tt", "spt_feedbacks.tenant_id = tt.id").
LeftJoin("tnt_users tu", "spt_feedbacks.user_id = tu.id")
if req.Status != "" {
query = query.Where("spt_feedbacks.status", req.Status)
}
if req.Category != "" {
query = query.Where("spt_feedbacks.category", req.Category)
}
if req.TenantID > 0 {
query = query.Where("spt_feedbacks.tenant_id", req.TenantID)
}
if req.Priority != "" {
query = query.Where("spt_feedbacks.priority", req.Priority)
}
type feedbackRow struct {
Id int64 `json:"id" orm:"id"`
TenantId int64 `json:"tenant_id" orm:"tenant_id"`
UserId int64 `json:"user_id" orm:"user_id"`
Category string `json:"category" orm:"category"`
Title string `json:"title" orm:"title"`
Description string `json:"description" orm:"description"`
Status string `json:"status" orm:"status"`
Priority string `json:"priority" orm:"priority"`
AdminReply string `json:"admin_reply" orm:"admin_reply"`
Resolution string `json:"resolution" orm:"resolution"`
CreatedAt *gtime.Time `json:"created_at" orm:"created_at"`
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"`
TenantName string `json:"tenant_name" orm:"tenant_name"`
UserDisplayName string `json:"user_display_name" orm:"user_display_name"`
}
var total int
rows := make([]feedbackRow, 0)
err := query.
Fields("spt_feedbacks.*, COALESCE(tt.name, '') as tenant_name, COALESCE(tu.display_name, '') as user_display_name").
OrderDesc("spt_feedbacks.created_at").
Page(page, pageSize).
ScanAndCount(&rows, &total, false)
if err != nil {
return nil, err
}
items := make([]*v1.FeedbackAdminItem, 0, len(rows))
for _, r := range rows {
items = append(items, &v1.FeedbackAdminItem{
Id: r.Id,
TenantId: r.TenantId,
TenantName: r.TenantName,
UserId: r.UserId,
UserDisplayName: r.UserDisplayName,
Category: r.Category,
Title: r.Title,
Description: r.Description,
Status: r.Status,
Priority: r.Priority,
AdminReply: r.AdminReply,
Resolution: r.Resolution,
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
})
}
return &v1.FeedbackListAllRes{
List: items,
Total: total,
Page: page,
PageSize: pageSize,
}, nil
}
// ReplyToFeedback 管理员回复反馈
func (s *sAdmin) ReplyToFeedback(ctx context.Context, req *v1.FeedbackReplyReq) (*v1.FeedbackReplyRes, error) {
var fb *struct {
Id int64 `json:"id"`
TenantId int64 `json:"tenant_id"`
UserId int64 `json:"user_id"`
Title string `json:"title"`
}
err := dao.SptFeedbacks.Ctx(ctx).Where("id", req.Id).Scan(&fb)
if err = common.IgnoreScanNoRows(err); err != nil {
return nil, err
}
if fb == nil {
return nil, common.NewBusinessError(10063, "反馈不存在")
}
updateData := do.SptFeedbacks{
AdminReply: req.Reply,
AdminReplyBy: common.GetCtxUserID(ctx),
AdminReplyAt: gtime.Now(),
}
if req.Status != "" {
updateData.Status = req.Status
}
if req.Resolution != "" {
updateData.Resolution = req.Resolution
}
_, err = dao.SptFeedbacks.Ctx(ctx).
Where("id", req.Id).
Data(updateData).
Update()
if err != nil {
return nil, err
}
// 通知用户
if req.Status == "acknowledged" || req.Status == "resolved" {
templateCode := "feedback_acknowledged"
if req.Status == "resolved" {
templateCode = "feedback_resolved"
}
engine := common.NewNotificationEngine()
_ = engine.SendNotification(ctx, fb.TenantId, fb.UserId, templateCode, map[string]any{
"title": fb.Title,
"resolution": req.Resolution,
})
}
return &v1.FeedbackReplyRes{}, nil
}
// UpdateFeedbackStatus 更新反馈状态
func (s *sAdmin) UpdateFeedbackStatus(ctx context.Context, req *v1.FeedbackUpdateStatusReq) (*v1.FeedbackUpdateStatusRes, error) {
var fb *struct {
Id int64 `json:"id"`
}
err := dao.SptFeedbacks.Ctx(ctx).Where("id", req.Id).Scan(&fb)
if err = common.IgnoreScanNoRows(err); err != nil {
return nil, err
}
if fb == nil {
return nil, common.NewBusinessError(10063, "反馈不存在")
}
updateData := do.SptFeedbacks{Status: req.Status}
if req.Priority != "" {
updateData.Priority = req.Priority
}
_, err = dao.SptFeedbacks.Ctx(ctx).
Where("id", req.Id).
Data(updateData).
Update()
if err != nil {
return nil, err
}
return &v1.FeedbackUpdateStatusRes{}, nil
}
// GetFeedbackStats 反馈统计
func (s *sAdmin) GetFeedbackStats(ctx context.Context, req *v1.FeedbackStatsReq) (*v1.FeedbackStatsRes, error) {
type countRow struct {
Status string `json:"status" orm:"status"`
Count int `json:"count" orm:"count"`
}
var statusCounts []countRow
err := dao.SptFeedbacks.Ctx(ctx).
Fields("status, COUNT(*) as count").
Group("status").
Scan(&statusCounts)
if err = common.IgnoreScanNoRows(err); err != nil {
return nil, err
}
stats := &v1.FeedbackStatsRes{ByCategory: make(map[string]int)}
for _, r := range statusCounts {
switch r.Status {
case "pending":
stats.Pending = r.Count
case "acknowledged":
stats.Acknowledged = r.Count
case "in_progress":
stats.InProgress = r.Count
case "resolved":
stats.Resolved = r.Count
case "closed":
stats.Closed = r.Count
}
stats.Total += r.Count
}
type catRow struct {
Category string `json:"category" orm:"category"`
Count int `json:"count" orm:"count"`
}
var catCounts []catRow
err = dao.SptFeedbacks.Ctx(ctx).
Fields("category, COUNT(*) as count").
Group("category").
Scan(&catCounts)
if err != nil {
g.Log().Warningf(ctx, "GetFeedbackStats category query failed: %v", err)
}
for _, r := range catCounts {
stats.ByCategory[r.Category] = r.Count
}
type trendRow struct {
Date string `json:"date" orm:"date"`
Count int `json:"count" orm:"count"`
}
err = dao.SptFeedbacks.Ctx(ctx).
Fields("DATE(created_at) as date, COUNT(*) as count").
Where("created_at >= NOW() - INTERVAL '30 days'").
Group("DATE(created_at)").
OrderAsc("date").
Scan(&stats.RecentTrend)
if err != nil {
g.Log().Warningf(ctx, "GetFeedbackStats trend query failed: %v", err)
}
return stats, nil
}