feat(admin,api,player): 代理层级管理、额度上下分与玩家钱包详情

新增代理管理器与二级代理体系,完善信用额度/上下分上下文与冻结策略;代理端玩家与子代理管理增强;玩家端新增钱包详情页与交易筛选优化。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 15:34:12 +08:00
parent b2216abd0c
commit 414998ce36
54 changed files with 6641 additions and 481 deletions

View File

@@ -2,6 +2,7 @@ import {
Controller,
Get,
Post,
Put,
Body,
Param,
Query,
@@ -15,7 +16,7 @@ import { AgentsService } from '../../domains/agent/agents.service';
import { WalletService } from '../../domains/ledger/wallet.service';
import { BetsService } from '../../domains/betting/bets.service';
import { PrismaService } from '../../shared/prisma/prisma.service';
import { IsString, IsNumber, MinLength, IsOptional } from 'class-validator';
import { IsString, IsNumber, MinLength, IsOptional, Min, IsBoolean } from 'class-validator';
class CreatePlayerDto {
@IsString()
@@ -24,12 +25,71 @@ class CreatePlayerDto {
@IsString()
@MinLength(8)
password!: string;
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
email?: string;
@IsOptional()
@IsString()
locale?: string;
@IsOptional()
@IsNumber()
@Min(0)
initialDeposit?: number;
@IsOptional()
@IsString()
remark?: string;
}
class CreateSubAgentDto extends CreatePlayerDto {
@IsOptional()
@IsNumber()
creditLimit?: number;
@IsOptional()
@IsNumber()
@Min(0)
cashbackRate?: number;
@IsOptional()
@IsNumber()
@Min(0)
maxSingleDeposit?: number;
@IsOptional()
@IsNumber()
@Min(0)
maxDailyDeposit?: number;
}
class UpdatePlayerDto {
@IsOptional()
@IsString()
username?: string;
@IsOptional()
@IsString()
@MinLength(8)
password?: string;
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
email?: string;
@IsOptional()
@IsString()
status?: string;
}
class TransferDto {
@@ -46,6 +106,33 @@ class CreditDto extends TransferDto {
remark?: string;
}
class UpdateSubAgentDto {
@IsOptional()
@IsString()
username?: string;
@IsOptional()
@IsString()
@MinLength(8)
password?: string;
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
email?: string;
@IsOptional()
@IsString()
status?: string;
@IsOptional()
@IsBoolean()
freezeDirectPlayers?: boolean;
}
@ApiTags('Agent Portal')
@Controller('agent')
@UseGuards(JwtAuthGuard, AgentGuard)
@@ -76,8 +163,57 @@ export class AgentPortalController {
username: dto.username,
password: dto.password,
parentId: agentId,
locale: dto.locale,
phone: dto.phone,
email: dto.email,
});
return jsonResponse(user);
if (dto.initialDeposit != null && dto.initialDeposit > 0) {
await this.agents.depositToPlayer(
agentId,
user.id,
dto.initialDeposit,
`agent-create-${user.id}-${Date.now()}`,
dto.remark ?? '开户初始余额',
);
}
const wallet = await this.prisma.wallet.findUnique({ where: { userId: user.id } });
return jsonResponse({
id: user.id,
username: user.username,
status: user.status,
createdAt: user.createdAt,
availableBalance: wallet?.availableBalance ?? 0,
});
}
@Get('players/:id')
async getPlayer(@CurrentUser('id') agentId: bigint, @Param('id') playerId: string) {
const detail = await this.agents.getDirectPlayerDetail(agentId, BigInt(playerId));
return jsonResponse(detail);
}
@Get('players/:id/transfer-context')
async getPlayerTransferContext(
@CurrentUser('id') agentId: bigint,
@Param('id') playerId: string,
) {
const ctx = await this.agents.getPlayerTransferContext(BigInt(playerId), {
actingAgentId: agentId,
});
return jsonResponse(ctx);
}
@Put('players/:id')
async updatePlayer(
@CurrentUser('id') agentId: bigint,
@Param('id') playerId: string,
@Body() dto: UpdatePlayerDto,
) {
const detail = await this.agents.updateDirectPlayer(agentId, BigInt(playerId), dto);
return jsonResponse(detail);
}
@Get('agents')
@@ -85,7 +221,7 @@ export class AgentPortalController {
if (level !== 1) {
return jsonResponse([]);
}
const agents = await this.agents.getChildAgents(agentId);
const agents = await this.agents.listChildAgentsSummary(agentId);
return jsonResponse(agents);
}
@@ -100,6 +236,9 @@ export class AgentPortalController {
level: 2,
parentAgentId: agentId,
creditLimit: dto.creditLimit,
cashbackRate: dto.cashbackRate,
maxSingleDeposit: dto.maxSingleDeposit,
maxDailyDeposit: dto.maxDailyDeposit,
});
return jsonResponse(user);
}
@@ -124,6 +263,47 @@ export class AgentPortalController {
return jsonResponse(result);
}
@Get('agents/:id')
async getSubAgent(
@CurrentUser('id') agentId: bigint,
@CurrentUser('agentLevel') level: number,
@Param('id') subAgentId: string,
) {
if (level !== 1) {
return jsonResponse(null, 'Only level 1 agents can manage sub-agents');
}
const detail = await this.agents.getSubAgentForParent(agentId, BigInt(subAgentId));
return jsonResponse(detail);
}
@Put('agents/:id')
async updateSubAgent(
@CurrentUser('id') agentId: bigint,
@CurrentUser('agentLevel') level: number,
@Param('id') subAgentId: string,
@Body() dto: UpdateSubAgentDto,
) {
if (level !== 1) {
return jsonResponse(null, 'Only level 1 agents can manage sub-agents');
}
const detail = await this.agents.updateSubAgentForParent(agentId, BigInt(subAgentId), dto);
return jsonResponse(detail);
}
@Get('agents/:id/players')
async listSubAgentPlayers(
@CurrentUser('id') agentId: bigint,
@CurrentUser('agentLevel') level: number,
@Param('id') subAgentId: string,
) {
if (level !== 1) {
return jsonResponse([]);
}
await this.agents.assertDirectChildAgent(agentId, BigInt(subAgentId));
const players = await this.agents.getDirectPlayers(BigInt(subAgentId));
return jsonResponse(players);
}
@Post('agents/:id/credit')
async allocateCredit(
@CurrentUser('id') agentId: bigint,