fix(test):修复单元测试发现的问题

This commit is contained in:
qian committed 2026-05-30 00:04:21 +08:00
1 parent b9d363cd09
commit cdd314675b
29 files changed
+388 -167

No files matched your search

+10 -2
View File
@@ -1,6 +1,10 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 审计管理 ===
@@ -81,7 +85,11 @@ type RequestAuditLogDetailReq struct {
}
type RequestAuditLogDetailRes struct {
Data map[string]any `json:"data"`
Data map[string]any `json:"-"`
}
func (r *RequestAuditLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
}
// OperationLogExportReq 导出操作日志请求
+10 -2
View File
@@ -1,6 +1,10 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 数据治理设置 ===
@@ -9,7 +13,11 @@ type DataGovernanceSettingsGetReq struct {
}
type DataGovernanceSettingsGetRes struct {
Data map[string]any `json:"data"`
Data map[string]any `json:"-"`
}
func (r *DataGovernanceSettingsGetRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
}
type DataGovernanceSettingsUpdateReq struct {
+10 -2
View File
@@ -1,6 +1,10 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 系统错误日志 ===
@@ -29,7 +33,11 @@ type ErrorLogDetailReq struct {
}
type ErrorLogDetailRes struct {
Data map[string]any `json:"data"`
Data map[string]any `json:"-"`
}
func (r *ErrorLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
}
type ErrorLogResolveReq struct {
+2 -2
View File
@@ -94,7 +94,7 @@ type GroupModelItem struct {
type GroupModelsSetReq struct {
g.Meta `path:"/model-groups/{id}/models" method:"put" mime:"json" tags:"管理后台-模型分组" summary:"设置分组内模型"`
ID int64 `json:"id" in:"path" v:"required" dc:"分组ID"`
ModelIds []int64 `json:"model_ids" v:"required#请选择模型" dc:"模型ID列表"`
ModelIds []int64 `json:"model_ids" dc:"模型ID列表(空数组表示清空)"`
}
// GroupModelsSetRes 设置分组内模型响应
@@ -126,7 +126,7 @@ type TenantGroupItem struct {
type TenantGroupsSetReq struct {
g.Meta `path:"/tenants/{tenant_id}/groups" method:"put" mime:"json" tags:"管理后台-模型分组" summary:"设置租户关联分组"`
TenantID int64 `json:"tenant_id" in:"path" v:"required" dc:"租户ID"`
GroupIds []int64 `json:"group_ids" v:"required#请选择分组" dc:"分组ID列表"`
GroupIds []int64 `json:"group_ids" dc:"分组ID列表(空数组表示清空)"`
}
// TenantGroupsSetRes 设置租户关联分组响应
+14 -2
View File
@@ -45,7 +45,11 @@ type MonitorDBPoolReq struct {
}
type MonitorDBPoolRes struct {
Data map[string]any `json:"data"`
ActiveConnections int `json:"active_connections"`
IdleConnections int `json:"idle_connections"`
TotalConnections int `json:"total_connections"`
MaxConnections int `json:"max_connections"`
WaitingQueries int `json:"waiting_queries"`
}
type MonitorRedisPoolReq struct {
@@ -53,7 +57,15 @@ type MonitorRedisPoolReq struct {
}
type MonitorRedisPoolRes struct {
Data map[string]any `json:"data"`
ConnectedClients int `json:"connected_clients"`
UsedMemoryMB float64 `json:"used_memory_mb"`
MaxMemoryMB float64 `json:"max_memory_mb"`
UsedMemoryPercent float64 `json:"used_memory_percent"`
TotalCommands int64 `json:"total_commands"`
InstantaneousOps int64 `json:"instantaneous_ops"`
KeyspaceHits int64 `json:"keyspace_hits"`
KeyspaceMisses int64 `json:"keyspace_misses"`
HitRate float64 `json:"hit_rate"`
}
// === 实时监控 ===
+18 -2
View File
@@ -38,7 +38,15 @@ type TemplateGetReq struct {
}
type TemplateGetRes struct {
Data map[string]any `json:"data"`
Id int64 `json:"id"`
Code string `json:"code"`
Channel string `json:"channel"`
Subject string `json:"subject"`
BodyTemplate string `json:"body_template"`
Variables string `json:"variables"`
Status string `json:"status"`
CreatedAt *gtime.Time `json:"created_at"`
UpdatedAt *gtime.Time `json:"updated_at"`
}
type TemplateUpdateReq struct {
@@ -57,8 +65,16 @@ type TemplateTestReq struct {
Variables map[string]any `json:"variables"`
}
type TemplateRenderResult struct {
Original string `json:"original"`
Rendered string `json:"rendered,omitempty"`
Error string `json:"error,omitempty"`
}
type TemplateTestRes struct {
Data map[string]any `json:"data"`
Subject TemplateRenderResult `json:"subject"`
Body TemplateRenderResult `json:"body"`
Channel string `json:"channel"`
}
// === 站内消息管理 ===
+1 -1
View File
@@ -53,7 +53,7 @@ type OrderDetailReq struct {
}
type OrderDetailRes struct {
Data map[string]any `json:"data"`
*OrderItem
}
type OrderRefundReq struct {
+37 -1
View File
@@ -46,8 +46,44 @@ type TicketGetReq struct {
Id int64 `json:"id" in:"path" v:"required|min:1"`
}
type TicketReplyItem struct {
Id int64 `json:"id"`
TicketId int64 `json:"ticket_id"`
UserId int64 `json:"user_id"`
UserType string `json:"user_type"`
UserName string `json:"user_name"`
Content string `json:"content"`
CreatedAt *gtime.Time `json:"created_at"`
}
type TicketAttachmentItem struct {
Id int64 `json:"id"`
TicketId int64 `json:"ticket_id"`
ReplyId int64 `json:"reply_id"`
FileName string `json:"file_name"`
FileUrl string `json:"file_url"`
FileSize int `json:"file_size"`
ContentType string `json:"content_type"`
CreatedAt *gtime.Time `json:"created_at"`
}
type TicketGetRes struct {
Data map[string]any `json:"data"`
Id int64 `json:"id"`
TenantId int64 `json:"tenant_id"`
UserId int64 `json:"user_id"`
Category string `json:"category"`
Title string `json:"title"`
Description string `json:"description"`
Urgency string `json:"urgency"`
Status string `json:"status"`
AssignedAdminId int64 `json:"assigned_admin_id"`
TenantName string `json:"tenant_name"`
UserDisplayName string `json:"user_display_name"`
AssignedAdminName string `json:"assigned_admin_name"`
CreatedAt *gtime.Time `json:"created_at"`
UpdatedAt *gtime.Time `json:"updated_at"`
Replies []*TicketReplyItem `json:"replies"`
Attachments []*TicketAttachmentItem `json:"attachments"`
}
type TicketAssignReq struct {
+10 -2
View File
@@ -1,6 +1,10 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// === 租户审计 ===
@@ -60,5 +64,9 @@ type TenantRequestAuditLogDetailReq struct {
}
type TenantRequestAuditLogDetailRes struct {
Data map[string]any `json:"data"`
Data map[string]any `json:"-"`
}
func (r *TenantRequestAuditLogDetailRes) MarshalJSON() ([]byte, error) {
return json.Marshal(r.Data)
}
+10 -2
View File
@@ -23,7 +23,15 @@ type TenantImportRecordGetReq struct {
}
type TenantImportRecordGetRes struct {
Data map[string]any `json:"data"`
Id int64 `json:"id"`
Filename string `json:"filename"`
TotalCount int `json:"total_count"`
SuccessCount int `json:"success_count"`
FailCount int `json:"fail_count"`
SkipCount int `json:"skip_count"`
Status string `json:"status"`
ErrorMessage string `json:"error_message"`
Details any `json:"details,omitempty"`
}
type TenantMemberImportReq struct {
@@ -31,5 +39,5 @@ type TenantMemberImportReq struct {
}
type TenantMemberImportRes struct {
Data map[string]any `json:"data"`
ID int64 `json:"id"`
}
+3 -1
View File
@@ -67,7 +67,9 @@ type TenantNotificationPreferencesGetReq struct {
}
type TenantNotificationPreferencesGetRes struct {
Data map[string]any `json:"data"`
OrgPreferences map[string]any `json:"org_preferences"`
UserPreferences map[string]any `json:"user_preferences"`
Merged map[string]any `json:"merged"`
}
type TenantNotificationPreferencesUpdateReq struct {
+1 -1
View File
@@ -80,7 +80,7 @@ type TenantOrderDetailReq struct {
}
type TenantOrderDetailRes struct {
Data map[string]any `json:"data"`
*TenantOrderItem
}
type TenantOrderCancelReq struct {
+10 -1
View File
@@ -61,7 +61,16 @@ type TenantProjectGetReq struct {
}
type TenantProjectGetRes struct {
Data map[string]any `json:"data"`
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status string `json:"status"`
Budget *string `json:"budget"`
CreatedBy int64 `json:"created_by"`
ActiveKeys int `json:"active_keys"`
TotalKeys int `json:"total_keys"`
MonthCost float64 `json:"month_cost"`
MonthRequests int64 `json:"month_requests"`
}
// === 项目子资源(owner/admin 权限) ===
+10 -2
View File
@@ -13,7 +13,12 @@ type TenantRedeemCodeReq struct {
}
type TenantRedeemCodeRes struct {
Data map[string]any `json:"data"`
Code string `json:"code"`
Type string `json:"type"`
Credited float64 `json:"credited,omitempty"`
PlanId int64 `json:"plan_id,omitempty"`
Months int `json:"months,omitempty"`
ExtendedDays int `json:"extended_days,omitempty"`
}
type TenantValidatePromoCodeReq struct {
@@ -23,7 +28,10 @@ type TenantValidatePromoCodeReq struct {
}
type TenantValidatePromoCodeRes struct {
Data map[string]any `json:"data"`
PromoCodeId int64 `json:"promo_code_id"`
Type string `json:"type"`
Discount float64 `json:"discount"`
FinalAmount float64 `json:"final_amount"`
}
type TenantRedemptionUsagesReq struct {
+34 -1
View File
@@ -53,8 +53,41 @@ type TenantTicketGetReq struct {
Id int64 `json:"id" in:"path" v:"required|min:1"`
}
type TenantTicketReplyItem struct {
Id int64 `json:"id"`
TicketId int64 `json:"ticket_id"`
UserId int64 `json:"user_id"`
UserType string `json:"user_type"`
Content string `json:"content"`
CreatedAt *gtime.Time `json:"created_at"`
}
type TenantTicketAttachmentItem struct {
Id int64 `json:"id"`
TicketId int64 `json:"ticket_id"`
ReplyId int64 `json:"reply_id"`
FileName string `json:"file_name"`
FileUrl string `json:"file_url"`
FileSize int `json:"file_size"`
ContentType string `json:"content_type"`
CreatedAt *gtime.Time `json:"created_at"`
}
type TenantTicketGetRes struct {
Data map[string]any `json:"data"`
Id int64 `json:"id"`
TenantId int64 `json:"tenant_id"`
UserId int64 `json:"user_id"`
Category string `json:"category"`
Title string `json:"title"`
Description string `json:"description"`
Urgency string `json:"urgency"`
Status string `json:"status"`
AssignedAdminId int64 `json:"assigned_admin_id"`
AssignedAdminName string `json:"assigned_admin_name"`
CreatedAt *gtime.Time `json:"created_at"`
UpdatedAt *gtime.Time `json:"updated_at"`
Replies []*TenantTicketReplyItem `json:"replies"`
Attachments []*TenantTicketAttachmentItem `json:"attachments"`
}
type TenantTicketReplyReq struct {
+22 -30
View File
@@ -40,16 +40,17 @@ func (s *sAdmin) ListTemplates(ctx context.Context, req *v1.TemplateListReq) (*v
// GetTemplate 获取单个通知模板
func (s *sAdmin) GetTemplate(ctx context.Context, req *v1.TemplateGetReq) (*v1.TemplateGetRes, error) {
record, err := dao.NtfTemplates.Ctx(ctx).
var tpl *v1.TemplateGetRes
err := dao.NtfTemplates.Ctx(ctx).
Where("code", req.Code).
One()
Scan(&tpl)
if err != nil {
return nil, err
}
if record == nil {
if tpl == nil {
return nil, common.NewNotFoundError("template")
}
return &v1.TemplateGetRes{Data: record.Map()}, nil
return tpl, nil
}
// UpdateTemplate 更新通知模板
@@ -92,41 +93,32 @@ func (s *sAdmin) TestTemplate(ctx context.Context, req *v1.TemplateTestReq) (*v1
return nil, common.NewNotFoundError("template")
}
type renderResult struct {
Original string `json:"original"`
Rendered string `json:"rendered"`
Error string `json:"error,omitempty"`
}
subjectRendered, subjectErr := renderGoTemplate(tpl.Subject, req.Variables)
bodyRendered, bodyErr := renderGoTemplate(tpl.BodyTemplate, req.Variables)
result := map[string]any{
"subject": renderResult{
Original: tpl.Subject,
Rendered: subjectRendered,
},
"body": renderResult{
Original: tpl.BodyTemplate,
Rendered: bodyRendered,
},
"channel": tpl.Channel,
subjectResult := v1.TemplateRenderResult{
Original: tpl.Subject,
}
if subjectErr != nil {
subjectResult.Error = subjectErr.Error()
} else {
subjectResult.Rendered = subjectRendered
}
if subjectErr != nil {
result["subject"] = renderResult{
Original: tpl.Subject,
Error: subjectErr.Error(),
}
bodyResult := v1.TemplateRenderResult{
Original: tpl.BodyTemplate,
}
if bodyErr != nil {
result["body"] = renderResult{
Original: tpl.BodyTemplate,
Error: bodyErr.Error(),
}
bodyResult.Error = bodyErr.Error()
} else {
bodyResult.Rendered = bodyRendered
}
return &v1.TemplateTestRes{Data: result}, nil
return &v1.TemplateTestRes{
Subject: subjectResult,
Body: bodyResult,
Channel: tpl.Channel,
}, nil
}
// SendMessage 创建手动站内消息并推送 WebSocket 通知
+5 -4
View File
@@ -45,16 +45,17 @@ func (s *sAdmin) ListOrders(ctx context.Context, req *v1.OrderListReq) (*v1.Orde
// GetOrder 获取订单详情
func (s *sAdmin) GetOrder(ctx context.Context, req *v1.OrderDetailReq) (*v1.OrderDetailRes, error) {
record, err := dao.OrdOrders.Ctx(ctx).
var order *v1.OrderItem
err := dao.OrdOrders.Ctx(ctx).
Where("id", req.Id).
One()
Scan(&order)
if err != nil {
return nil, err
}
if record == nil {
if order == nil {
return nil, common.NewNotFoundError("订单")
}
return &v1.OrderDetailRes{Data: record.Map()}, nil
return &v1.OrderDetailRes{OrderItem: order}, nil
}
// RefundOrder 发起退款
+50 -25
View File
@@ -4,7 +4,6 @@ import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
v1 "github.com/qianfree/team-api/api/admin/v1"
@@ -113,50 +112,76 @@ func (s *sAdmin) GetTicketAdmin(ctx context.Context, req *v1.TicketGetReq) (*v1.
return nil, common.NewNotFoundError("工单")
}
type replyItem struct {
type replyRow struct {
entity.SptReplies
UserName string `json:"user_name"`
UserName string `json:"user_name" orm:"user_name"`
}
var replies []*replyItem
var replyRows []*replyRow
err = dao.SptReplies.Ctx(ctx).
LeftJoin("sys_admin_users sa", "spt_replies.user_type = 'admin' AND spt_replies.user_id = sa.id").
LeftJoin("tnt_users tu", "spt_replies.user_type = 'tenant' AND spt_replies.user_id = tu.id").
Fields("spt_replies.*, CASE WHEN spt_replies.user_type = 'admin' THEN COALESCE(sa.username, '') ELSE COALESCE(tu.display_name, '') END as user_name").
Where("spt_replies.ticket_id", req.Id).
OrderAsc("spt_replies.created_at").
Scan(&replies)
Scan(&replyRows)
if err != nil {
return nil, err
}
var attachments []*entity.SptAttachments
replies := make([]*v1.TicketReplyItem, 0, len(replyRows))
for _, r := range replyRows {
replies = append(replies, &v1.TicketReplyItem{
Id: r.Id,
TicketId: r.TicketId,
UserId: r.UserId,
UserType: r.UserType,
UserName: r.UserName,
Content: r.Content,
CreatedAt: r.CreatedAt,
})
}
var attachRows []*entity.SptAttachments
err = dao.SptAttachments.Ctx(ctx).
Where("ticket_id", req.Id).
OrderAsc("created_at").
Scan(&attachments)
Scan(&attachRows)
if err != nil {
return nil, err
}
data := g.Map{
"id": ticket.Id,
"tenant_id": ticket.TenantId,
"user_id": ticket.UserId,
"category": ticket.Category,
"title": ticket.Title,
"description": ticket.Description,
"urgency": ticket.Urgency,
"status": ticket.Status,
"assigned_admin_id": ticket.AssignedAdminId,
"tenant_name": ticket.TenantName,
"user_display_name": ticket.UserDisplayName,
"assigned_admin_name": ticket.AssignedAdminName,
"created_at": ticket.CreatedAt,
"updated_at": ticket.UpdatedAt,
"replies": replies,
"attachments": attachments,
attachments := make([]*v1.TicketAttachmentItem, 0, len(attachRows))
for _, a := range attachRows {
attachments = append(attachments, &v1.TicketAttachmentItem{
Id: a.Id,
TicketId: a.TicketId,
ReplyId: a.ReplyId,
FileName: a.FileName,
FileUrl: a.FileUrl,
FileSize: a.FileSize,
ContentType: a.ContentType,
CreatedAt: a.CreatedAt,
})
}
return &v1.TicketGetRes{Data: data}, nil
return &v1.TicketGetRes{
Id: ticket.Id,
TenantId: ticket.TenantId,
UserId: ticket.UserId,
Category: ticket.Category,
Title: ticket.Title,
Description: ticket.Description,
Urgency: ticket.Urgency,
Status: ticket.Status,
AssignedAdminId: ticket.AssignedAdminId,
TenantName: ticket.TenantName,
UserDisplayName: ticket.UserDisplayName,
AssignedAdminName: ticket.AssignedAdminName,
CreatedAt: ticket.CreatedAt,
UpdatedAt: ticket.UpdatedAt,
Replies: replies,
Attachments: attachments,
}, nil
}
// AssignTicket 分配工单给管理员
+18 -18
View File
@@ -71,13 +71,13 @@ func (s *sMonitor) DBPool(ctx context.Context, _ *v1.MonitorDBPoolReq) (*v1.Moni
if err != nil {
return nil, err
}
return &v1.MonitorDBPoolRes{Data: g.Map{
"active_connections": data.ActiveConnections,
"idle_connections": data.IdleConnections,
"total_connections": data.TotalConnections,
"max_connections": data.MaxConnections,
"waiting_queries": data.WaitingQueries,
}}, nil
return &v1.MonitorDBPoolRes{
ActiveConnections: data.ActiveConnections,
IdleConnections: data.IdleConnections,
TotalConnections: data.TotalConnections,
MaxConnections: data.MaxConnections,
WaitingQueries: data.WaitingQueries,
}, nil
}
func (s *sMonitor) RedisPool(ctx context.Context, _ *v1.MonitorRedisPoolReq) (*v1.MonitorRedisPoolRes, error) {
@@ -85,17 +85,17 @@ func (s *sMonitor) RedisPool(ctx context.Context, _ *v1.MonitorRedisPoolReq) (*v
if err != nil {
return nil, err
}
return &v1.MonitorRedisPoolRes{Data: g.Map{
"connected_clients": data.ConnectedClients,
"used_memory_mb": data.UsedMemoryMB,
"max_memory_mb": data.MaxMemoryMB,
"used_memory_percent": data.UsedMemoryPercent,
"total_commands": data.TotalCommands,
"instantaneous_ops": data.InstantaneousOps,
"keyspace_hits": data.KeyspaceHits,
"keyspace_misses": data.KeyspaceMisses,
"hit_rate": data.HitRate,
}}, nil
return &v1.MonitorRedisPoolRes{
ConnectedClients: data.ConnectedClients,
UsedMemoryMB: data.UsedMemoryMB,
MaxMemoryMB: data.MaxMemoryMB,
UsedMemoryPercent: data.UsedMemoryPercent,
TotalCommands: data.TotalCommands,
InstantaneousOps: int64(data.InstantaneousOps),
KeyspaceHits: data.KeyspaceHits,
KeyspaceMisses: data.KeyspaceMisses,
HitRate: data.HitRate,
}, nil
}
func (s *sMonitor) Realtime(ctx context.Context, _ *v1.MonitorRealtimeReq) (*v1.MonitorRealtimeRes, error) {
+14 -2
View File
@@ -474,7 +474,9 @@ func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveRe
// Check if target is owner
var user struct {
Role string `json:"role"`
Role string `json:"role"`
DisplayName string `json:"display_name"`
Username string `json:"username"`
}
err := dao.TntUsers.Ctx(ctx).
Where("id", memberID).
@@ -487,6 +489,16 @@ func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveRe
return nil, common.NewBadRequestError("不能移除组织所有者")
}
removedDisplayName := user.DisplayName
if removedDisplayName == "" {
removedDisplayName = user.Username
}
if removedDisplayName != "" {
removedDisplayName = fmt.Sprintf("[已移除] %s", removedDisplayName)
} else {
removedDisplayName = "[已移除成员]"
}
// Revoke all sessions
common.RevokeAllSessions(ctx, "tenant", memberID)
@@ -511,7 +523,7 @@ func (s *sTenant) RemoveMember(ctx context.Context, req *v1.TenantMemberRemoveRe
Data(do.TntUsers{
Status: "removed",
Email: fmt.Sprintf("deleted_%d@removed.local", memberID),
DisplayName: "[已移除成员]",
DisplayName: removedDisplayName,
Username: fmt.Sprintf("deleted_%d", memberID),
}).Update()
if err != nil {
+12 -14
View File
@@ -55,9 +55,7 @@ func (s *sTenant) MemberImport(ctx context.Context, req *v1.TenantMemberImportRe
return nil, err
}
return &v1.TenantMemberImportRes{Data: map[string]any{
"id": importID,
}}, nil
return &v1.TenantMemberImportRes{ID: importID}, nil
}
// ImportRecords returns a paginated list of import records.
@@ -144,24 +142,24 @@ func (s *sTenant) ImportRecordGet(ctx context.Context, req *v1.TenantImportRecor
return nil, common.NewNotFoundError("导入记录")
}
result := map[string]any{
"id": record.ID,
"filename": record.Filename,
"total_count": record.TotalCount,
"success_count": record.SuccessCount,
"fail_count": record.FailCount,
"skip_count": record.SkipCount,
"status": record.Status,
"error_message": record.ErrorMessage,
res := &v1.TenantImportRecordGetRes{
Id: record.ID,
Filename: record.Filename,
TotalCount: record.TotalCount,
SuccessCount: record.SuccessCount,
FailCount: record.FailCount,
SkipCount: record.SkipCount,
Status: record.Status,
ErrorMessage: record.ErrorMessage,
}
if record.ResultJSON != "" {
var details []ImportResult
json.Unmarshal([]byte(record.ResultJSON), &details)
result["details"] = details
res.Details = details
}
return &v1.TenantImportRecordGetRes{Data: result}, nil
return res, nil
}
// -- internal helpers --
+5 -6
View File
@@ -282,12 +282,11 @@ func (s *sTenant) NotificationPreferencesGet(ctx context.Context, req *v1.Tenant
orgPrefs := loadPreferences(ctx, tenantID, 0, "org")
userPrefs := loadPreferences(ctx, tenantID, userID, "user")
result := map[string]any{
"org_preferences": orgPrefs,
"user_preferences": userPrefs,
"merged": mergePreferences(orgPrefs, userPrefs),
}
return &v1.TenantNotificationPreferencesGetRes{Data: result}, nil
return &v1.TenantNotificationPreferencesGetRes{
OrgPreferences: orgPrefs,
UserPreferences: userPrefs,
Merged: mergePreferences(orgPrefs, userPrefs),
}, nil
}
// NotificationPreferencesUpdate 更新通知偏好
+2 -2
View File
@@ -57,7 +57,7 @@ func (s *sTenant) OrderDetail(ctx context.Context, req *v1.TenantOrderDetailReq)
return nil, lcommon.NewForbiddenError("需要 owner 或 admin 权限")
}
tenantID := middleware.GetTenantID(ctx)
var order map[string]any
var order *v1.TenantOrderItem
err := dao.OrdOrders.Ctx(ctx).
Where("id", req.Id).
Where("tenant_id", tenantID).
@@ -68,7 +68,7 @@ func (s *sTenant) OrderDetail(ctx context.Context, req *v1.TenantOrderDetailReq)
if order == nil {
return nil, lcommon.NewNotFoundError("订单")
}
return &v1.TenantOrderDetailRes{Data: order}, nil
return &v1.TenantOrderDetailRes{TenantOrderItem: order}, nil
}
// OrderCreate 创建订单
+10 -12
View File
@@ -346,18 +346,16 @@ func (s *sTenant) ProjectGet(ctx context.Context, req *v1.TenantProjectGetReq) (
Scan(&monthUsage)
return &v1.TenantProjectGetRes{
Data: map[string]any{
"id": p.Id,
"name": p.Name,
"description": p.Description,
"status": p.Status,
"budget": p.Budget,
"created_by": p.CreatedBy,
"active_keys": keyStats.Active,
"total_keys": keyStats.Total,
"month_cost": monthUsage.TotalCost,
"month_requests": monthUsage.RequestCount,
},
Id: p.Id,
Name: p.Name,
Description: p.Description,
Status: p.Status,
Budget: p.Budget,
CreatedBy: p.CreatedBy,
ActiveKeys: keyStats.Active,
TotalKeys: keyStats.Total,
MonthCost: monthUsage.TotalCost,
MonthRequests: int64(monthUsage.RequestCount),
}, nil
}
+6 -6
View File
@@ -86,12 +86,12 @@ func (s *sTenant) ValidatePromoCode(ctx context.Context, req *v1.TenantValidateP
return nil, lcommon.NewBusinessError(500, fmt.Sprintf("未知的优惠码类型: %s", promo.Type))
}
return &v1.TenantValidatePromoCodeRes{Data: map[string]any{
"promo_code_id": promo.ID,
"type": promo.Type,
"discount": discount,
"final_amount": req.Amount - discount,
}}, nil
return &v1.TenantValidatePromoCodeRes{
PromoCodeId: promo.ID,
Type: promo.Type,
Discount: discount,
FinalAmount: req.Amount - discount,
}, nil
}
// applyPromoCode 应用优惠码到订单
+6 -6
View File
@@ -65,7 +65,7 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) (
return nil, lcommon.NewBusinessError(422, "兑换码已全部使用")
}
result := g.Map{"code": req.Code, "type": redemption.Type}
res := &v1.TenantRedeemCodeRes{Code: req.Code, Type: redemption.Type}
var txID int64
usageValue := float64(0)
@@ -73,7 +73,7 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) (
case "quota":
txID = creditWalletForRedemption(ctx, tenantID, redemption.Value, redemption.ID)
usageValue = redemption.Value
result["credited"] = redemption.Value
res.Credited = redemption.Value
case "plan":
if redemption.PlanID == 0 {
@@ -90,15 +90,15 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) (
if err != nil {
return nil, gerror.Wrapf(err, "激活套餐失败")
}
result["plan_id"] = redemption.PlanID
result["months"] = months
res.PlanId = redemption.PlanID
res.Months = months
case "duration":
if redemption.DurationDays <= 0 {
return nil, lcommon.NewBusinessError(422, "时长兑换码缺少duration_days")
}
extendPlanDuration(ctx, tenantID, redemption.DurationDays)
result["extended_days"] = redemption.DurationDays
res.ExtendedDays = redemption.DurationDays
default:
return nil, gerror.Newf("未知的兑换类型: %s", redemption.Type)
@@ -119,7 +119,7 @@ func (s *sTenant) RedeemCode(ctx context.Context, req *v1.TenantRedeemCodeReq) (
"UPDATE ord_redemptions SET used_count = used_count + 1, redeemed_by = ?, redeemed_at = ?, updated_at = ? WHERE id = ?",
tenantID, time.Now(), time.Now(), redemption.ID)
return &v1.TenantRedeemCodeRes{Data: result}, nil
return res, nil
}
func creditWalletForRedemption(ctx context.Context, tenantID int64, amount float64, redemptionID int64) int64 {
+50 -8
View File
@@ -4,6 +4,7 @@ import (
"context"
"github.com/qianfree/team-api/internal/dao"
do "github.com/qianfree/team-api/internal/model/do"
"github.com/qianfree/team-api/internal/model/entity"
v1 "github.com/qianfree/team-api/api/tenant/v1"
"github.com/qianfree/team-api/internal/logic/common"
@@ -74,7 +75,21 @@ func (s *sTenant) TicketGet(ctx context.Context, req *v1.TenantTicketGetReq) (*v
tenantID := middleware.GetTenantID(ctx)
userID := middleware.GetUserID(ctx)
var ticket map[string]any
type ticketRow 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"`
Urgency string `json:"urgency" orm:"urgency"`
Status string `json:"status" orm:"status"`
AssignedAdminId int64 `json:"assigned_admin_id" orm:"assigned_admin_id"`
AssignedAdminName string `json:"assigned_admin_name" orm:"assigned_admin_name"`
CreatedAt *gtime.Time `json:"created_at" orm:"created_at"`
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"`
}
var ticket *ticketRow
err := dao.SptTickets.Ctx(ctx).
LeftJoin("sys_admin_users sa", "spt_tickets.assigned_admin_id = sa.id").
Fields("spt_tickets.*, COALESCE(sa.username, '') as assigned_admin_name").
@@ -89,27 +104,54 @@ func (s *sTenant) TicketGet(ctx context.Context, req *v1.TenantTicketGetReq) (*v
return nil, common.NewNotFoundError("工单")
}
replies := make([]map[string]any, 0)
var replyRows []*v1.TenantTicketReplyItem
err = dao.SptReplies.Ctx(ctx).
Where("ticket_id", req.Id).
OrderAsc("created_at").
Scan(&replies)
Scan(&replyRows)
if err != nil {
return nil, err
}
attachments := make([]map[string]any, 0)
var attachRows []*entity.SptAttachments
err = dao.SptAttachments.Ctx(ctx).
Where("ticket_id", req.Id).
OrderAsc("created_at").
Scan(&attachments)
Scan(&attachRows)
if err != nil {
return nil, err
}
ticket["replies"] = replies
ticket["attachments"] = attachments
return &v1.TenantTicketGetRes{Data: ticket}, nil
attachments := make([]*v1.TenantTicketAttachmentItem, 0, len(attachRows))
for _, a := range attachRows {
attachments = append(attachments, &v1.TenantTicketAttachmentItem{
Id: a.Id,
TicketId: a.TicketId,
ReplyId: a.ReplyId,
FileName: a.FileName,
FileUrl: a.FileUrl,
FileSize: a.FileSize,
ContentType: a.ContentType,
CreatedAt: a.CreatedAt,
})
}
return &v1.TenantTicketGetRes{
Id: ticket.Id,
TenantId: ticket.TenantId,
UserId: ticket.UserId,
Category: ticket.Category,
Title: ticket.Title,
Description: ticket.Description,
Urgency: ticket.Urgency,
Status: ticket.Status,
AssignedAdminId: ticket.AssignedAdminId,
AssignedAdminName: ticket.AssignedAdminName,
CreatedAt: ticket.CreatedAt,
UpdatedAt: ticket.UpdatedAt,
Replies: replyRows,
Attachments: attachments,
}, nil
}
// TicketReply 租户用户回复工单
+1 -1
View File
@@ -231,7 +231,7 @@ func TestModelGroupDuplicateCode(t *testing.T) {
// 获取已创建分组的 code
listResp := client.Get("/api/admin/model-groups", map[string]string{
"page": "1", "page_size": "200",
"page": "1", "page_size": "100",
})
listResp.AssertSuccess(t)
+7 -9
View File
@@ -44,18 +44,16 @@ func TestProjectCRUD(t *testing.T) {
detailResp.AssertSuccess(t)
var detail struct {
Data struct {
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Budget string `json:"budget"`
Status string `json:"status"`
} `json:"data"`
ID int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Budget *string `json:"budget"`
Status string `json:"status"`
}
detailResp.DecodeData(t, &detail)
if detail.Data.ID != projectID {
t.Fatalf("expected id=%d, got %d", projectID, detail.Data.ID)
if detail.ID != projectID {
t.Fatalf("expected id=%d, got %d", projectID, detail.ID)
}
// --- Update ---