feat: multi-tier agent hierarchy, wallet ledger, and player UX polish

Add configurable agent max level and default sub-agent credit ratio, per-agent block direct player login on suspend, admin/agent wallet transaction views, and match detail my-bets section with refreshed player card styling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-10 16:15:34 +08:00
parent 641c92a5f5
commit ef6b15f119
39 changed files with 2398 additions and 410 deletions

View File

@@ -50,6 +50,7 @@ import {
MinLength,
IsIn,
Min,
Max,
Equals,
ValidateIf,
} from 'class-validator';
@@ -257,6 +258,19 @@ class AgentSuspendSettingsDto {
suspendBlockPlayerLogin?: boolean;
}
class AgentHierarchySettingsDto {
@IsOptional()
@IsNumber()
@Min(0)
maxAgentLevel?: number;
@IsOptional()
@IsNumber()
@Min(1)
@Max(100)
defaultSubAgentCreditRatio?: number;
}
class ResetDatabaseDto {
@IsString()
@Equals('RESET')
@@ -340,6 +354,16 @@ class UpdateAgentAdminDto {
@IsOptional()
@IsBoolean()
freezeDirectPlayers?: boolean;
/** 冻结时是否禁止直属玩家登录 */
@IsOptional()
@IsBoolean()
blockDirectPlayerLogin?: boolean;
/** 解冻时是否级联解冻直属玩家 */
@IsOptional()
@IsBoolean()
unfreezeDirectPlayers?: boolean;
}
class DepositDto {
@@ -945,6 +969,30 @@ export class AdminController {
return jsonResponse(settings);
}
@Get('agents/settings/hierarchy')
@RequirePermissions(P.settings)
async getAgentHierarchySettings() {
const settings = await this.systemConfig.getAgentHierarchySettings();
return jsonResponse(settings);
}
@Put('agents/settings/hierarchy')
@RequirePermissions(P.settings)
async updateAgentHierarchySettings(
@CurrentUser('id') operatorId: bigint,
@Body() dto: AgentHierarchySettingsDto,
) {
const settings = await this.systemConfig.updateAgentHierarchySettings(dto);
await this.audit.log({
operatorId,
operatorType: 'ADMIN',
action: 'UPDATE_AGENT_HIERARCHY_SETTINGS',
module: 'AGENTS',
afterData: JSON.stringify(settings),
});
return jsonResponse(settings);
}
@Get('settings/betting-limits')
@RequirePermissions(P.settings)
async getBettingLimits() {
@@ -1122,6 +1170,13 @@ export class AdminController {
);
}
@Get('agents/level-counts')
@RequirePermissions(P.agentsView)
async getAgentLevelCounts() {
const counts = await this.agents.countAgentsByLevel();
return jsonResponse(counts);
}
@Get('agents')
@RequirePermissions(P.agentsView)
async listAgents(
@@ -1130,15 +1185,21 @@ export class AdminController {
@Query('keyword') keyword?: string,
@Query('status') status?: string,
@Query('level') level?: string,
@Query('minLevel') minLevel?: string,
@Query('maxLevel') maxLevel?: string,
@Query('parentAgentId') parentAgentId?: string,
) {
const parsedLevel = level === '2' ? 2 : level === '1' ? 1 : undefined;
const parsedLevel = level != null && level !== '' ? parseInt(level, 10) : undefined;
const parsedMinLevel = minLevel != null && minLevel !== '' ? parseInt(minLevel, 10) : undefined;
const parsedMaxLevel = maxLevel != null && maxLevel !== '' ? parseInt(maxLevel, 10) : undefined;
const result = await this.agents.listAgentsAdmin({
page: page ? parseInt(page, 10) : 1,
pageSize: pageSize ? parseInt(pageSize, 10) : 10,
keyword,
status,
level: parsedLevel,
level: Number.isFinite(parsedLevel) ? parsedLevel : undefined,
minLevel: Number.isFinite(parsedMinLevel) ? parsedMinLevel : undefined,
maxLevel: Number.isFinite(parsedMaxLevel) ? parsedMaxLevel : undefined,
parentAgentId: parentAgentId ? BigInt(parentAgentId) : undefined,
});
return jsonResponse(result);
@@ -1270,9 +1331,33 @@ export class AdminController {
}
@Get('wallet/transactions')
@RequirePermissions(P.walletDeposit, P.walletWithdraw)
async walletTransactions(@Query('userId') userId: string, @Query('page') page?: string) {
const result = await this.wallet.getTransactions(BigInt(userId), page ? parseInt(page) : 1);
@RequirePermissions(P.walletDeposit, P.walletWithdraw, P.reports)
async walletTransactions(
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query('playerId') playerId?: string,
@Query('parentAgentId') parentAgentId?: string,
@Query('parentAgentKeyword') parentAgentKeyword?: string,
@Query('keyword') keyword?: string,
@Query('operatorKeyword') operatorKeyword?: string,
@Query('transactionType') transactionType?: string,
@Query('typeCategory') typeCategory?: string,
@Query('dateFrom') dateFrom?: string,
@Query('dateTo') dateTo?: string,
) {
const result = await this.wallet.listWalletTransactionsAdmin({
page: page ? parseInt(page, 10) : 1,
pageSize: pageSize ? parseInt(pageSize, 10) : 20,
playerId: playerId ? BigInt(playerId) : undefined,
parentAgentId: parentAgentId ? BigInt(parentAgentId) : undefined,
parentAgentKeyword,
keyword,
operatorKeyword,
transactionType,
typeCategory,
dateFrom: dateFrom ? new Date(dateFrom) : undefined,
dateTo: dateTo ? new Date(dateTo) : undefined,
});
return jsonResponse(result);
}