feat: 手动充值、邀请码注册与后台管理增强

新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分),
支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏,
并补充前台注册、充值页及多语言错误码。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 12:20:11 +08:00
parent 618fb49511
commit 10485ecfaf
98 changed files with 7908 additions and 856 deletions

View File

@@ -4,7 +4,9 @@ import { SUPPORTED_LOCALES, isValidAvatarKey, assertPlayerUsername } from '@theb
import { PrismaService } from '../../shared/prisma/prisma.service';
import { SystemConfigService } from '../../shared/config/system-config.service';
import { AgentsService } from '../agent/agents.service';
import { CashbackService } from '../operations/cashback/cashback.service';
import { appBadRequest, appForbidden, appNotFound } from '../../shared/common/app-error';
import { Decimal } from '@prisma/client/runtime/library';
export type PlayerListFilters = {
keyword?: string;
@@ -19,6 +21,7 @@ export class UsersService {
private prisma: PrismaService,
private agents: AgentsService,
private systemConfig: SystemConfigService,
private cashback: CashbackService,
) {}
private buildAffiliationAgents(
@@ -103,6 +106,7 @@ export class UsersService {
parent?: { username: string; agentLevel: number | null } | null;
} | null;
auth?: { lastLoginAt: Date | null } | null;
usedInvite?: { code: string } | null;
},
bet?: { count: number; totalStake: string; totalReturn: string },
affiliationChain?: string[],
@@ -116,6 +120,7 @@ export class UsersService {
parentId: u.parentId?.toString() ?? null,
parentUsername: u.parent?.username ?? null,
affiliationAgents,
inviteCode: u.usedInvite?.code ?? null,
phone: u.preferences?.phone ?? null,
email: u.preferences?.email ?? null,
managedPassword: u.preferences?.managedPassword ?? null,
@@ -281,6 +286,7 @@ export class UsersService {
},
},
auth: { select: { lastLoginAt: true } },
usedInvite: { select: { code: true } },
},
skip,
take: pageSize,
@@ -320,17 +326,23 @@ export class UsersService {
},
},
auth: { select: { lastLoginAt: true, loginFailCount: true, lockedUntil: true } },
usedInvite: { select: { code: true } },
},
});
if (!user) throw appNotFound('PLAYER_NOT_FOUND');
const affiliationMap = await this.buildAffiliationChainMap([user.parentId]);
const [betCount, betStake] = await Promise.all([
const [betCount, betStake, customCashbackRate, defaultCashbackRate] = await Promise.all([
this.prisma.bet.count({ where: { userId: playerId } }),
this.prisma.bet.aggregate({
where: { userId: playerId },
_sum: { stake: true, actualReturn: true },
}),
this.cashback.getPlayerCustomCashbackRate(playerId),
this.cashback.resolvePlayerDefaultCashbackRate({
parentId: user.parentId,
inviteSponsorId: user.inviteSponsorId,
}),
]);
return {
@@ -345,6 +357,8 @@ export class UsersService {
betCount,
totalStake: betStake._sum.stake?.toString() ?? '0',
totalReturn: betStake._sum.actualReturn?.toString() ?? '0',
customCashbackRate: customCashbackRate?.toString() ?? null,
defaultCashbackRate: defaultCashbackRate.toString(),
};
}
@@ -357,6 +371,7 @@ export class UsersService {
email?: string;
username?: string;
password?: string;
cashbackRate?: number | null;
},
) {
const user = await this.prisma.user.findFirst({
@@ -440,6 +455,17 @@ export class UsersService {
});
}
if (data.cashbackRate !== undefined) {
if (data.cashbackRate != null && (!Number.isFinite(data.cashbackRate) || data.cashbackRate < 0)) {
throw appBadRequest('CASHBACK_RATE_NEGATIVE');
}
const rate =
data.cashbackRate != null && data.cashbackRate > 0
? new Decimal(data.cashbackRate)
: null;
await this.cashback.setPlayerCustomCashbackRate(playerId, rate);
}
return this.getPlayerAdminDetail(playerId);
}