feat(admin,api,player): 结算预览分页、统计图表与返水限额
完善结算计算与预览 API(含后端分页),加强管理端结算/返水/权限,并优化玩家端投注单与队徽展示。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,9 +12,9 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { JwtAuthGuard, AdminGuard } from '../../domains/identity/guards';
|
||||
import { JwtAuthGuard, AdminGuard, PermissionsGuard } from '../../domains/identity/guards';
|
||||
import { ContentService } from '../../domains/operations/content/content.service';
|
||||
import { CurrentUser } from '../../shared/common/decorators';
|
||||
import { CurrentUser, RequirePermissions } from '../../shared/common/decorators';
|
||||
import { jsonResponse } from '../../shared/common/filters';
|
||||
import { UsersService } from '../../domains/identity/users.service';
|
||||
import { AgentsService } from '../../domains/agent/agents.service';
|
||||
@@ -27,9 +27,11 @@ import { CashbackService } from '../../domains/operations/cashback/cashback.serv
|
||||
import { I18nService } from '../../domains/operations/i18n/i18n.service';
|
||||
import { AuditService } from '../../domains/operations/audit/audit.service';
|
||||
import { BetsService } from '../../domains/betting/bets.service';
|
||||
import { BettingLimitsService } from '../../domains/betting/betting-limits.service';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { AdminDashboardService } from './admin-dashboard.service';
|
||||
import { SystemConfigService } from '../../shared/config/system-config.service';
|
||||
import { P } from './admin-permissions';
|
||||
import {
|
||||
IsString,
|
||||
IsNumber,
|
||||
@@ -341,17 +343,26 @@ function isZhiboBundlePayload(body: unknown): body is ZhiboMatchesBundleExport {
|
||||
}
|
||||
|
||||
class ScoreDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
htHome!: number;
|
||||
htHome?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
htAway!: number;
|
||||
htAway?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
ftHome!: number;
|
||||
ftHome?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
ftAway!: number;
|
||||
ftAway?: number;
|
||||
|
||||
/** 冠军盘结算:获胜球队 ID */
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
winnerTeamId?: number;
|
||||
}
|
||||
|
||||
/* 智能比分推荐已关闭
|
||||
@@ -571,9 +582,67 @@ class CashbackPreviewDto {
|
||||
periodEnd!: string;
|
||||
}
|
||||
|
||||
class ResettlePreviewDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
htHome?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
htAway?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
ftHome?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
ftAway?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
reason?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
winnerTeamId?: number;
|
||||
}
|
||||
|
||||
class BettingLimitsDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
minStake?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
maxStakeSingle?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
maxStakeParlay?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
maxPayoutSingle?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
maxPayoutParlay?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
dailyStakeLimit?: number;
|
||||
}
|
||||
|
||||
@ApiTags('Admin')
|
||||
@Controller('admin')
|
||||
@UseGuards(JwtAuthGuard, AdminGuard)
|
||||
@UseGuards(JwtAuthGuard, AdminGuard, PermissionsGuard)
|
||||
@ApiBearerAuth()
|
||||
export class AdminController {
|
||||
constructor(
|
||||
@@ -592,21 +661,25 @@ export class AdminController {
|
||||
private prisma: PrismaService,
|
||||
private readonly dashboardService: AdminDashboardService,
|
||||
private systemConfig: SystemConfigService,
|
||||
private bettingLimits: BettingLimitsService,
|
||||
) {}
|
||||
|
||||
@Get('dashboard')
|
||||
@RequirePermissions(P.reports)
|
||||
async getDashboard() {
|
||||
const overview = await this.dashboardService.getOverview();
|
||||
return jsonResponse(overview);
|
||||
}
|
||||
|
||||
@Get('users/settings/account')
|
||||
@RequirePermissions(P.settings)
|
||||
async getPlayerAccountSettings() {
|
||||
const settings = await this.systemConfig.getPlayerAccountSettings();
|
||||
return jsonResponse(settings);
|
||||
}
|
||||
|
||||
@Put('users/settings/account')
|
||||
@RequirePermissions(P.settings)
|
||||
async updatePlayerAccountSettings(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: PlayerAccountSettingsDto,
|
||||
@@ -622,7 +695,32 @@ export class AdminController {
|
||||
return jsonResponse(settings);
|
||||
}
|
||||
|
||||
@Get('settings/betting-limits')
|
||||
@RequirePermissions(P.settings)
|
||||
async getBettingLimits() {
|
||||
const limits = await this.bettingLimits.getLimits();
|
||||
return jsonResponse(limits);
|
||||
}
|
||||
|
||||
@Put('settings/betting-limits')
|
||||
@RequirePermissions(P.settings)
|
||||
async updateBettingLimits(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: BettingLimitsDto,
|
||||
) {
|
||||
const limits = await this.bettingLimits.updateLimits(dto);
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'UPDATE_BETTING_LIMITS',
|
||||
module: 'SETTINGS',
|
||||
afterData: limits,
|
||||
});
|
||||
return jsonResponse(limits);
|
||||
}
|
||||
|
||||
@Get('users')
|
||||
@RequirePermissions(P.usersView)
|
||||
async listUsers(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@@ -643,12 +741,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('users/:id')
|
||||
@RequirePermissions(P.usersView)
|
||||
async getUserDetail(@Param('id') id: string) {
|
||||
const detail = await this.users.getPlayerAdminDetail(BigInt(id));
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Put('users/:id')
|
||||
@RequirePermissions(P.usersCreate)
|
||||
async updateUser(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -666,6 +766,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('users')
|
||||
@RequirePermissions(P.usersCreate)
|
||||
async createPlayer(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: CreatePlayerAdminDto,
|
||||
@@ -700,6 +801,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('users/promotable-for-agent')
|
||||
@RequirePermissions(P.usersView)
|
||||
async listPromotableForAgent(@Query('keyword') keyword?: string) {
|
||||
const rows = await this.agents.listPromotablePlayers(keyword);
|
||||
return jsonResponse(
|
||||
@@ -716,6 +818,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('agents/options')
|
||||
@RequirePermissions(P.agentsView)
|
||||
async listAgentOptions() {
|
||||
const agents = await this.prisma.user.findMany({
|
||||
where: { userType: 'AGENT', deletedAt: null, agentLevel: 1 },
|
||||
@@ -728,6 +831,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('agents')
|
||||
@RequirePermissions(P.agentsView)
|
||||
async listAgents(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@@ -742,12 +846,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('agents/:id')
|
||||
@RequirePermissions(P.agentsView)
|
||||
async getAgentDetail(@Param('id') id: string) {
|
||||
const detail = await this.agents.getAgentAdminDetail(BigInt(id));
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Put('agents/:id')
|
||||
@RequirePermissions(P.agentsCreate)
|
||||
async updateAgent(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -765,6 +871,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('agents')
|
||||
@RequirePermissions(P.agentsCreate)
|
||||
async createAgent(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: CreateAgentAdminDto,
|
||||
@@ -787,6 +894,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('agents/:id/credit')
|
||||
@RequirePermissions(P.agentsCredit)
|
||||
async adjustCredit(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -803,6 +911,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('wallet/deposit')
|
||||
@RequirePermissions(P.walletDeposit)
|
||||
async deposit(@CurrentUser('id') operatorId: bigint, @Body() dto: DepositDto & { userId: string }) {
|
||||
const result = await this.wallet.deposit(
|
||||
BigInt(dto.userId),
|
||||
@@ -815,6 +924,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('wallet/withdraw')
|
||||
@RequirePermissions(P.walletWithdraw)
|
||||
async withdraw(@CurrentUser('id') operatorId: bigint, @Body() dto: DepositDto & { userId: string }) {
|
||||
const result = await this.wallet.withdraw(
|
||||
BigInt(dto.userId),
|
||||
@@ -827,12 +937,14 @@ 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);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('leagues')
|
||||
@RequirePermissions(P.matches)
|
||||
async createLeague(
|
||||
@Body() dto: CreatePlatformLeagueDto | { code: string; translations: Record<string, string> },
|
||||
) {
|
||||
@@ -853,6 +965,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('leagues')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async listLeagues(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@@ -871,6 +984,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('leagues/:leagueId/matches')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async listLeagueMatches(
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Query('status') status?: string,
|
||||
@@ -886,12 +1000,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('teams')
|
||||
@RequirePermissions(P.matches)
|
||||
async createTeam(@Body() dto: { code: string; translations: Record<string, string> }) {
|
||||
const team = await this.matches.createTeam(dto.code, dto.translations);
|
||||
return jsonResponse(team);
|
||||
}
|
||||
|
||||
@Get('matches')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async listMatches(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@@ -928,12 +1044,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('matches/:id')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async getMatch(@Param('id') id: string) {
|
||||
const match = await this.matches.getAdminMatchDetail(BigInt(id));
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Put('matches/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateMatch(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -964,12 +1082,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Delete('matches/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async deleteMatch(@Param('id') id: string) {
|
||||
await this.matches.deleteMatch(BigInt(id));
|
||||
return jsonResponse({ deleted: true });
|
||||
}
|
||||
|
||||
@Post('matches')
|
||||
@RequirePermissions(P.matches)
|
||||
async createMatch(@CurrentUser('id') operatorId: bigint, @Body() dto: CreatePlatformMatchDto) {
|
||||
const match = await this.matches.createPlatformMatch({
|
||||
leagueId: dto.leagueId ? BigInt(dto.leagueId) : undefined,
|
||||
@@ -997,6 +1117,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('matches/import')
|
||||
@RequirePermissions(P.matches)
|
||||
async importMatches(@CurrentUser('id') operatorId: bigint, @Body() dto: ZhiboMatchesBundleExport) {
|
||||
if (!isZhiboBundlePayload(dto)) {
|
||||
throw new BadRequestException('Invalid import payload: matches[] required');
|
||||
@@ -1006,18 +1127,21 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('matches/:id/publish')
|
||||
@RequirePermissions(P.matches)
|
||||
async publishMatch(@Param('id') id: string) {
|
||||
const match = await this.matches.publishMatch(BigInt(id));
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/close')
|
||||
@RequirePermissions(P.matches)
|
||||
async closeMatch(@Param('id') id: string) {
|
||||
const match = await this.matches.closeMatch(BigInt(id));
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/cancel')
|
||||
@RequirePermissions(P.matches)
|
||||
async cancelMatch(@Param('id') id: string) {
|
||||
await this.matches.cancelMatch(BigInt(id));
|
||||
const voided = await this.settlement.voidMatchBets(BigInt(id));
|
||||
@@ -1025,12 +1149,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('matches/:id/markets/templates')
|
||||
@RequirePermissions(P.matches)
|
||||
async generateTemplates(@Param('id') id: string, @Body() dto: MarketTemplatesDto) {
|
||||
const markets = await this.markets.generateTemplates(BigInt(id), dto.marketTypes);
|
||||
return jsonResponse(markets);
|
||||
}
|
||||
|
||||
@Put('matches/:id/odds')
|
||||
@RequirePermissions(P.matches)
|
||||
async batchUpdateMatchOdds(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -1045,6 +1171,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Patch('markets/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateMarket(@Param('id') id: string, @Body() dto: UpdateMarketDto) {
|
||||
const market = await this.markets.updateMarket(BigInt(id), {
|
||||
promoLabel: dto.promoLabel,
|
||||
@@ -1055,6 +1182,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Patch('selections/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateSelection(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -1073,6 +1201,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Put('selections/:id/odds')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateOdds(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -1083,18 +1212,21 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('outrights')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async listOutrights() {
|
||||
const data = await this.outright.listForAdmin();
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Get('outrights/leagues')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async listOutrightLeagues() {
|
||||
const data = await this.outright.listLeagueOptions();
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Post('outrights')
|
||||
@RequirePermissions(P.matches)
|
||||
async createOutright(@Body() dto: CreateOutrightDto) {
|
||||
const data = await this.outright.createForAdmin({
|
||||
leagueId: BigInt(dto.leagueId),
|
||||
@@ -1107,6 +1239,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('outrights/import/wc2026')
|
||||
@RequirePermissions(P.matches)
|
||||
async importWc2026Outright() {
|
||||
const data = await this.outright.importWc2026Canonical();
|
||||
return jsonResponse(data);
|
||||
@@ -1114,6 +1247,7 @@ export class AdminController {
|
||||
|
||||
/** @deprecated */
|
||||
@Get('outrights/wc2026')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async getWc2026OutrightLegacy() {
|
||||
const list = await this.outright.listForAdmin();
|
||||
const wc = list.find((e) => e.leagueCode === 'WC2026');
|
||||
@@ -1123,6 +1257,7 @@ export class AdminController {
|
||||
|
||||
/** @deprecated */
|
||||
@Put('outrights/wc2026/odds')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateWc2026OutrightOddsLegacy(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: BatchOutrightOddsDto,
|
||||
@@ -1137,17 +1272,20 @@ export class AdminController {
|
||||
|
||||
/** @deprecated */
|
||||
@Post('outrights/wc2026/apply-canonical')
|
||||
@RequirePermissions(P.matches)
|
||||
async applyWc2026CanonicalLegacy() {
|
||||
return jsonResponse(await this.outright.importWc2026Canonical());
|
||||
}
|
||||
|
||||
@Get('outrights/:matchId')
|
||||
@RequirePermissions(P.matches, P.reports)
|
||||
async getOutright(@Param('matchId') matchId: string) {
|
||||
const data = await this.outright.getForAdmin(BigInt(matchId));
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Put('outrights/:matchId')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateOutright(
|
||||
@Param('matchId') matchId: string,
|
||||
@Body() dto: UpdateOutrightDto,
|
||||
@@ -1157,6 +1295,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Put('outrights/:matchId/odds')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateOutrightOdds(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('matchId') matchId: string,
|
||||
@@ -1171,6 +1310,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('outrights/:matchId/selections')
|
||||
@RequirePermissions(P.matches)
|
||||
async addOutrightSelection(
|
||||
@Param('matchId') matchId: string,
|
||||
@Body() dto: AddOutrightSelectionDto,
|
||||
@@ -1180,6 +1320,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Patch('outrights/:matchId/selections/:selectionId')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateOutrightSelectionTeam(
|
||||
@Param('matchId') matchId: string,
|
||||
@Param('selectionId') selectionId: string,
|
||||
@@ -1194,6 +1335,7 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Delete('outrights/:matchId/selections/:selectionId')
|
||||
@RequirePermissions(P.matches)
|
||||
async removeOutrightSelection(
|
||||
@Param('matchId') matchId: string,
|
||||
@Param('selectionId') selectionId: string,
|
||||
@@ -1206,8 +1348,16 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('matches/:id/settlement/stats')
|
||||
async getMatchSettlementStats(@Param('id') id: string) {
|
||||
const data = await this.settlement.getMatchBetStats(BigInt(id));
|
||||
@RequirePermissions(P.settlement, P.reports)
|
||||
async getMatchSettlementStats(
|
||||
@Param('id') id: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
const data = await this.settlement.getMatchBetStats(BigInt(id), {
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 10)) : 10,
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@@ -1216,6 +1366,7 @@ export class AdminController {
|
||||
// async suggestSmartScore(...) { ... }
|
||||
|
||||
@Post('matches/:id/settlement/score')
|
||||
@RequirePermissions(P.settlement)
|
||||
async recordScore(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@@ -1223,28 +1374,99 @@ export class AdminController {
|
||||
) {
|
||||
const result = await this.settlement.recordScore(
|
||||
BigInt(id),
|
||||
dto.htHome,
|
||||
dto.htAway,
|
||||
dto.ftHome,
|
||||
dto.ftAway,
|
||||
dto.htHome ?? 0,
|
||||
dto.htAway ?? 0,
|
||||
dto.ftHome ?? 0,
|
||||
dto.ftAway ?? 0,
|
||||
operatorId,
|
||||
dto.winnerTeamId != null ? BigInt(dto.winnerTeamId) : undefined,
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('matches/:id/settlement/preview')
|
||||
async settlementPreview(@CurrentUser('id') operatorId: bigint, @Param('id') id: string) {
|
||||
const preview = await this.settlement.previewSettlement(BigInt(id), operatorId);
|
||||
@RequirePermissions(P.settlement)
|
||||
async settlementPreview(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto?: { page?: number; pageSize?: number },
|
||||
) {
|
||||
const preview = await this.settlement.previewSettlement(BigInt(id), operatorId, {
|
||||
page: dto?.page ? Math.max(1, dto.page) : 1,
|
||||
pageSize: dto?.pageSize ? Math.min(100, Math.max(1, dto.pageSize)) : 10,
|
||||
});
|
||||
return jsonResponse(preview);
|
||||
}
|
||||
|
||||
@Get('settlement/:batchId/preview-items')
|
||||
@RequirePermissions(P.settlement)
|
||||
async getSettlementPreviewItems(
|
||||
@Param('batchId') batchId: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
const data = await this.settlement.getPreviewSettlementItems(BigInt(batchId), {
|
||||
page: page ? Math.max(1, parseInt(page, 10) || 1) : 1,
|
||||
pageSize: pageSize ? Math.min(100, Math.max(1, parseInt(pageSize, 10) || 10)) : 10,
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Post('settlement/:batchId/confirm')
|
||||
@RequirePermissions(P.settlement)
|
||||
async confirmSettlement(@CurrentUser('id') operatorId: bigint, @Param('batchId') batchId: string) {
|
||||
const result = await this.settlement.confirmSettlement(BigInt(batchId), operatorId);
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'CONFIRM_SETTLEMENT',
|
||||
module: 'SETTLEMENT',
|
||||
targetId: batchId,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('matches/:id/resettle/preview')
|
||||
@RequirePermissions(P.resettle)
|
||||
async resettlePreview(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ResettlePreviewDto,
|
||||
) {
|
||||
const preview = await this.settlement.previewResettlement(
|
||||
BigInt(id),
|
||||
{
|
||||
htHome: dto.htHome ?? 0,
|
||||
htAway: dto.htAway ?? 0,
|
||||
ftHome: dto.ftHome ?? 0,
|
||||
ftAway: dto.ftAway ?? 0,
|
||||
},
|
||||
operatorId,
|
||||
dto.reason,
|
||||
dto.winnerTeamId != null ? BigInt(dto.winnerTeamId) : undefined,
|
||||
);
|
||||
return jsonResponse(preview);
|
||||
}
|
||||
|
||||
@Post('resettle/:batchId/confirm')
|
||||
@RequirePermissions(P.resettle)
|
||||
async confirmResettlement(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('batchId') batchId: string,
|
||||
) {
|
||||
const result = await this.settlement.confirmResettlement(BigInt(batchId), operatorId);
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'CONFIRM_RESETTLE',
|
||||
module: 'SETTLEMENT',
|
||||
targetId: batchId,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Get('bets')
|
||||
@RequirePermissions(P.bets)
|
||||
async listBets(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@@ -1267,12 +1489,14 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('bets/:id')
|
||||
@RequirePermissions(P.bets)
|
||||
async getBet(@Param('id') id: string) {
|
||||
const detail = await this.bets.getBetAdminDetail(BigInt(id));
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Post('cashbacks/preview')
|
||||
@RequirePermissions(P.cashback, P.reports)
|
||||
async cashbackPreview(@Body() dto: CashbackPreviewDto) {
|
||||
const preview = await this.cashback.previewBatch(
|
||||
new Date(dto.periodStart),
|
||||
@@ -1282,12 +1506,21 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Post('cashbacks/:batchId/confirm')
|
||||
@RequirePermissions(P.cashback)
|
||||
async cashbackConfirm(@CurrentUser('id') operatorId: bigint, @Param('batchId') batchId: string) {
|
||||
const result = await this.cashback.confirmBatch(BigInt(batchId), operatorId);
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'CONFIRM_CASHBACK',
|
||||
module: 'CASHBACK',
|
||||
targetId: batchId,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Get('contents')
|
||||
@RequirePermissions(P.content, P.reports)
|
||||
async listContents(
|
||||
@Query('type') type?: string,
|
||||
@Query('status') status?: string,
|
||||
@@ -1297,24 +1530,28 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Get('contents/:id')
|
||||
@RequirePermissions(P.content, P.reports)
|
||||
async getContent(@Param('id') id: string) {
|
||||
const item = await this.content.getForAdmin(BigInt(id));
|
||||
return jsonResponse(item);
|
||||
}
|
||||
|
||||
@Post('contents')
|
||||
@RequirePermissions(P.content)
|
||||
async createContent(@Body() dto: CreateContentDto) {
|
||||
const item = await this.content.create(dto);
|
||||
return jsonResponse(item);
|
||||
}
|
||||
|
||||
@Put('contents/:id')
|
||||
@RequirePermissions(P.content)
|
||||
async updateContent(@Param('id') id: string, @Body() dto: UpdateContentDto) {
|
||||
const item = await this.content.update(BigInt(id), dto);
|
||||
return jsonResponse(item);
|
||||
}
|
||||
|
||||
@Patch('contents/:id/status')
|
||||
@RequirePermissions(P.content)
|
||||
async updateContentStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ContentStatusDto,
|
||||
@@ -1324,18 +1561,21 @@ export class AdminController {
|
||||
}
|
||||
|
||||
@Delete('contents/:id')
|
||||
@RequirePermissions(P.content)
|
||||
async deleteContent(@Param('id') id: string) {
|
||||
const result = await this.content.remove(BigInt(id));
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Get('i18n/messages')
|
||||
@RequirePermissions(P.settings, P.reports)
|
||||
async getMessages(@Query('locale') locale = 'en-US') {
|
||||
const messages = await this.i18n.getMessages(locale);
|
||||
return jsonResponse(messages);
|
||||
}
|
||||
|
||||
@Get('audit-logs')
|
||||
@RequirePermissions(P.audit)
|
||||
async auditLogs(
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
|
||||
Reference in New Issue
Block a user