feat: 邀请关系列表增加邀请人累计统计

This commit is contained in:
yml2213
2026-10-19 17:52:54 +08:00
parent 27a6442c18
commit 51855a0e17
4 changed files with 214 additions and 13 deletions
@@ -74,6 +74,110 @@ func TestListAdminInvitationsFiltersPhoneAndInviteCode(t *testing.T) {
}
}
func TestListAdminInvitationsIncludesInviterAggregateStats(t *testing.T) {
db := setupTestDB(t)
repo := NewRepository(db)
ctx := context.Background()
now := time.Now()
inviter := model.User{
Phone: "13800000001",
Nickname: "邀请人A",
Status: "active",
RealnameStatus: "verified",
}
if err := db.Create(&inviter).Error; err != nil {
t.Fatalf("创建邀请人失败: %v", err)
}
specs := []struct {
phone string
status string
siteID uint64
verified bool
}{
{phone: "13900000001", status: StatusActive, siteID: 1, verified: true},
{phone: "13900000002", status: StatusActive, siteID: 1, verified: true},
{phone: "13900000003", status: StatusPending, siteID: 2, verified: false},
{phone: "13900000004", status: StatusPending, siteID: 0, verified: false},
{phone: "13900000005", status: StatusInvalid, siteID: 1, verified: false},
}
for _, spec := range specs {
invitee := model.User{
Phone: spec.phone,
Nickname: "被邀请人",
Status: "active",
RealnameStatus: "verified",
}
if err := db.Create(&invitee).Error; err != nil {
t.Fatalf("创建被邀请人失败: %v", err)
}
record := model.UserInvitation{
InviterID: inviter.ID,
InviteeID: invitee.ID,
DistributionSiteID: spec.siteID,
InviteCode: "aggcode",
Status: spec.status,
CreatedAt: now,
}
if spec.verified {
record.VerifiedAt = &now
}
if err := db.Create(&record).Error; err != nil {
t.Fatalf("创建邀请关系失败: %v", err)
}
}
assertStats := func(t *testing.T, items []map[string]any, wantTotal, wantActive, wantPending, wantInvalid int64) {
t.Helper()
if len(items) == 0 {
t.Fatal("查询结果为空")
}
for _, item := range items {
if got := item["inviter_total_invited"]; got != wantTotal {
t.Fatalf("累计邀请人数 = %v, want %d", got, wantTotal)
}
if got := item["inviter_active_invited"]; got != wantActive {
t.Fatalf("已实名人数 = %v, want %d", got, wantActive)
}
if got := item["inviter_pending_invited"]; got != wantPending {
t.Fatalf("待实名人数 = %v, want %d", got, wantPending)
}
if got := item["inviter_invalid_invited"]; got != wantInvalid {
t.Fatalf("无效邀请人数 = %v, want %d", got, wantInvalid)
}
}
}
items, total, err := repo.ListAdminInvitations(ctx, AdminInvitationQuery{
Page: 1,
PageSize: 20,
InviterID: inviter.ID,
})
if err != nil {
t.Fatalf("查询邀请关系失败: %v", err)
}
if total != 5 {
t.Fatalf("查询结果数量 = %d, want 5", total)
}
assertStats(t, items, 5, 2, 2, 1)
siteID := uint64(1)
items, total, err = repo.ListAdminInvitations(ctx, AdminInvitationQuery{
Page: 1,
PageSize: 20,
InviterID: inviter.ID,
DistributionSiteID: &siteID,
})
if err != nil {
t.Fatalf("按站点查询邀请关系失败: %v", err)
}
if total != 3 {
t.Fatalf("站点查询结果数量 = %d, want 3", total)
}
assertStats(t, items, 3, 2, 0, 1)
}
func TestListAdminRebateLedgersFiltersPartialPhoneAndOrderNo(t *testing.T) {
db := setupTestDB(t)
repo := NewRepository(db)
@@ -922,21 +922,69 @@ func (r *Repository) ListAdminInvitations(ctx context.Context, query AdminInvita
return nil, 0, err
}
// 汇总本页邀请人名下的累计邀请人数(一次 IN 查询,避免逐行统计)。
inviterIDs := make([]uint64, 0, len(rows))
seenInviter := make(map[uint64]struct{}, len(rows))
for _, row := range rows {
if row.InviterID == 0 {
continue
}
if _, ok := seenInviter[row.InviterID]; ok {
continue
}
seenInviter[row.InviterID] = struct{}{}
inviterIDs = append(inviterIDs, row.InviterID)
}
type inviterStatRow struct {
InviterID uint64 `gorm:"column:inviter_id"`
TotalInvited int64 `gorm:"column:total_invited"`
ActiveInvited int64 `gorm:"column:active_invited"`
PendingInvited int64 `gorm:"column:pending_invited"`
InvalidInvited int64 `gorm:"column:invalid_invited"`
}
inviterStats := make(map[uint64]inviterStatRow, len(inviterIDs))
if len(inviterIDs) > 0 {
var statRows []inviterStatRow
statDB := r.db.WithContext(ctx).Model(&model.UserInvitation{}).
Select("inviter_id, COUNT(*) AS total_invited, "+
"SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active_invited, "+
"SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_invited, "+
"SUM(CASE WHEN status = 'invalid' THEN 1 ELSE 0 END) AS invalid_invited").
Where("inviter_id IN ?", inviterIDs).
Group("inviter_id")
// 分站后台只能看到本站范围内的累计数据,避免跨站泄露。
if query.DistributionSiteID != nil {
statDB = statDB.Where("distribution_site_id = ?", *query.DistributionSiteID)
}
if err := statDB.Scan(&statRows).Error; err != nil {
return nil, 0, err
}
for _, stat := range statRows {
inviterStats[stat.InviterID] = stat
}
}
list := make([]map[string]any, 0, len(rows))
for _, row := range rows {
stat := inviterStats[row.InviterID]
list = append(list, map[string]any{
"id": row.ID,
"inviter_id": row.InviterID,
"inviter_phone": row.InviterPhone,
"inviter_nickname": row.InviterNickname,
"invitee_id": row.InviteeID,
"invitee_phone": row.InviteePhone,
"invitee_nickname": row.InviteeNickname,
"distribution_site_id": row.DistributionSiteID,
"invite_code": row.InviteCode,
"status": row.Status,
"verified_at": row.VerifiedAt,
"created_at": row.CreatedAt,
"id": row.ID,
"inviter_id": row.InviterID,
"inviter_phone": row.InviterPhone,
"inviter_nickname": row.InviterNickname,
"inviter_total_invited": stat.TotalInvited,
"inviter_active_invited": stat.ActiveInvited,
"inviter_pending_invited": stat.PendingInvited,
"inviter_invalid_invited": stat.InvalidInvited,
"invitee_id": row.InviteeID,
"invitee_phone": row.InviteePhone,
"invitee_nickname": row.InviteeNickname,
"distribution_site_id": row.DistributionSiteID,
"invite_code": row.InviteCode,
"status": row.Status,
"verified_at": row.VerifiedAt,
"created_at": row.CreatedAt,
})
}
return list, total, nil