feat: 开户备注、账单展示优化与后台代理管理增强
- 新增初始上分备注(日常上分/开户赠金/自定义)及前后台校验与展示 - 优化钱包流水类型与备注显示,区分管理员/代理/玩家上下分 - 修复登录后语言被后端覆盖的问题,登录时同步当前语言到服务端 - 后台代理/玩家表格操作栏重构,充值订单增加备注列 - 前台个人中心、充值、账单与验证码组件体验优化 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -556,6 +556,12 @@ class UpdatePlatformMatchDto {
|
||||
awayTeamLogoUrl?: string;
|
||||
}
|
||||
|
||||
class ReopenMatchDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
startTime?: string;
|
||||
}
|
||||
|
||||
class BatchMatchOddsDto {
|
||||
@IsArray()
|
||||
updates!: OutrightOddsUpdateItemDto[];
|
||||
@@ -1643,6 +1649,14 @@ export class AdminController {
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/reopen')
|
||||
@RequirePermissions(P.matches)
|
||||
async reopenMatch(@Param('id') id: string, @Body() dto: ReopenMatchDto) {
|
||||
const startTime = dto.startTime ? new Date(dto.startTime) : undefined;
|
||||
const match = await this.matches.reopenMatch(BigInt(id), startTime);
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/cancel')
|
||||
@RequirePermissions(P.matches)
|
||||
async cancelMatch(@Param('id') id: string) {
|
||||
|
||||
@@ -12,7 +12,8 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard, AgentGuard } from '../../domains/identity/guards';
|
||||
import { CurrentUser } from '../../shared/common/decorators';
|
||||
import { jsonResponse } from '../../shared/common/filters';
|
||||
import { appForbidden } from '../../shared/common/app-error';
|
||||
import { appBadRequest, appForbidden } from '../../shared/common/app-error';
|
||||
import { validateInitialDepositRemark } from '@thebet365/shared';
|
||||
import { AgentsService } from '../../domains/agent/agents.service';
|
||||
import { WalletService } from '../../domains/ledger/wallet.service';
|
||||
import { BetsService } from '../../domains/betting/bets.service';
|
||||
@@ -164,7 +165,11 @@ export class AgentPortalController {
|
||||
const profile = await this.agents.getProfile(agentId);
|
||||
const maxLevel = await this.agents.getMaxAgentLevel();
|
||||
return jsonResponse({
|
||||
...profile,
|
||||
level: profile.level,
|
||||
creditLimit: profile.creditLimit.toString(),
|
||||
usedCredit: profile.usedCredit.toString(),
|
||||
availableCredit: profile.availableCredit.toString(),
|
||||
cashbackRate: profile.cashbackRate.toString(),
|
||||
maxAgentLevel: maxLevel,
|
||||
canManageSubAgents: this.agents.canCreateSubAgent(level, maxLevel),
|
||||
});
|
||||
@@ -176,6 +181,25 @@ export class AgentPortalController {
|
||||
return jsonResponse(players);
|
||||
}
|
||||
|
||||
@Get('players/scoped')
|
||||
async listScopedPlayers(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@Query('keyword') keyword?: string,
|
||||
@Query('status') status?: string,
|
||||
@Query('parentAgentId') parentAgentId?: string,
|
||||
) {
|
||||
const result = await this.agents.listSubtreePlayersForPortal(agentId, {
|
||||
page: page ? parseInt(page, 10) : undefined,
|
||||
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
||||
keyword,
|
||||
status,
|
||||
parentAgentId,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('players')
|
||||
async createPlayer(@CurrentUser('id') agentId: bigint, @Body() dto: CreatePlayerDto) {
|
||||
const user = await this.agents.createPlayer(agentId, {
|
||||
@@ -188,12 +212,14 @@ export class AgentPortalController {
|
||||
});
|
||||
|
||||
if (dto.initialDeposit != null && dto.initialDeposit > 0) {
|
||||
const remarkResult = validateInitialDepositRemark(dto.initialDeposit, dto.remark, 'agent');
|
||||
if (!remarkResult.ok) throw appBadRequest(remarkResult.code);
|
||||
await this.agents.depositToPlayer(
|
||||
agentId,
|
||||
user.id,
|
||||
dto.initialDeposit,
|
||||
`agent-create-${user.id}-${Date.now()}`,
|
||||
dto.remark ?? '开户初始余额',
|
||||
remarkResult.remark,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,6 +271,40 @@ export class AgentPortalController {
|
||||
return jsonResponse(agents);
|
||||
}
|
||||
|
||||
@Get('agents/level-counts')
|
||||
async subtreeAgentLevelCounts(@CurrentUser('id') agentId: bigint) {
|
||||
const counts = await this.agents.countSubtreeAgentsByLevel(agentId);
|
||||
return jsonResponse(counts);
|
||||
}
|
||||
|
||||
@Get('agents/options')
|
||||
async subtreeAgentOptions(@CurrentUser('id') agentId: bigint) {
|
||||
const options = await this.agents.listSubtreeAgentOptions(agentId);
|
||||
return jsonResponse(options);
|
||||
}
|
||||
|
||||
@Get('agents/by-level')
|
||||
async subtreeAgentsByLevel(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@Query('level') level: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@Query('keyword') keyword?: string,
|
||||
@Query('status') status?: string,
|
||||
) {
|
||||
const lvl = parseInt(level, 10);
|
||||
if (!Number.isFinite(lvl) || lvl < 1) {
|
||||
return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
}
|
||||
const result = await this.agents.listSubtreeAgentsAtLevel(agentId, lvl, {
|
||||
page: page ? parseInt(page, 10) : undefined,
|
||||
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
||||
keyword,
|
||||
status,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('agents')
|
||||
async createSubAgent(@CurrentUser('id') agentId: bigint, @CurrentUser('agentLevel') level: number, @Body() dto: CreateSubAgentDto) {
|
||||
const maxLevel = await this.agents.getMaxAgentLevel();
|
||||
@@ -323,6 +383,19 @@ export class AgentPortalController {
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Get('agents/:id/downline')
|
||||
async getSubAgentDownline(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@CurrentUser('agentLevel') level: number,
|
||||
@Param('id') subAgentId: string,
|
||||
) {
|
||||
if (!(await this.canManageSubAgents(level))) {
|
||||
return jsonResponse({ agents: [], players: [] });
|
||||
}
|
||||
const downline = await this.agents.getDirectChildDownlineView(agentId, BigInt(subAgentId));
|
||||
return jsonResponse(downline);
|
||||
}
|
||||
|
||||
@Get('agents/:id/players')
|
||||
async listSubAgentPlayers(
|
||||
@CurrentUser('id') agentId: bigint,
|
||||
@@ -332,8 +405,8 @@ export class AgentPortalController {
|
||||
if (!(await this.canManageSubAgents(level))) {
|
||||
return jsonResponse([]);
|
||||
}
|
||||
await this.agents.assertDirectChildAgent(agentId, BigInt(subAgentId));
|
||||
const players = await this.agents.getDirectPlayers(BigInt(subAgentId));
|
||||
await this.agents.assertDescendantAgent(agentId, BigInt(subAgentId));
|
||||
const players = await this.agents.getPortalAgentDirectPlayers(agentId, BigInt(subAgentId));
|
||||
return jsonResponse(players);
|
||||
}
|
||||
|
||||
@@ -494,16 +567,7 @@ export class AgentPortalController {
|
||||
return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
}
|
||||
if (playerId) {
|
||||
const player = await this.prisma.user.findFirst({
|
||||
where: { id: BigInt(playerId), userType: 'PLAYER', deletedAt: null },
|
||||
select: { id: true, parentId: true },
|
||||
});
|
||||
if (
|
||||
!player?.parentId ||
|
||||
!scopedParentAgentIds.some((id) => id === player.parentId)
|
||||
) {
|
||||
return jsonResponse({ items: [], total: 0, page: 1, pageSize: 20 });
|
||||
}
|
||||
await this.agents.requirePlayerInPortalSubtree(agentId, BigInt(playerId));
|
||||
}
|
||||
const result = await this.wallet.listWalletTransactionsAdmin({
|
||||
page: page ? parseInt(page, 10) : 1,
|
||||
|
||||
Reference in New Issue
Block a user