feat(admin): 从已有玩家升级代理、修复 i18n 与过期 .js 冲突
- 新建一级代理改为选择已有玩家;新建用户可选一级代理 - 操作日志/注单等扁平 key 翻译;清理 src 内误生成 .js,Vite 优先解析 .ts Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -88,6 +88,20 @@ class CreatePlayerAdminDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
remark?: string;
|
||||
|
||||
/** 创建为一级代理(非玩家) */
|
||||
@IsOptional()
|
||||
asTier1Agent?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
creditLimit?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
cashbackRate?: number;
|
||||
}
|
||||
|
||||
class UpdatePlayerAdminDto {
|
||||
@@ -114,21 +128,14 @@ class UpdatePlayerAdminDto {
|
||||
}
|
||||
|
||||
class CreateAgentAdminDto {
|
||||
/** 已有玩家用户 ID,升级为一级代理 */
|
||||
@IsString()
|
||||
username!: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(8)
|
||||
password!: string;
|
||||
userId!: string;
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
creditLimit!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
locale?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
phone?: string;
|
||||
@@ -331,18 +338,41 @@ export class AdminController {
|
||||
initialDeposit: dto.initialDeposit,
|
||||
depositRemark: dto.remark,
|
||||
depositRequestId: `create-player-${dto.username}-${Date.now()}`,
|
||||
asTier1Agent: dto.asTier1Agent,
|
||||
creditLimit: dto.creditLimit,
|
||||
cashbackRate: dto.cashbackRate,
|
||||
});
|
||||
await this.audit.log({
|
||||
operatorId,
|
||||
operatorType: 'ADMIN',
|
||||
action: 'CREATE_PLAYER',
|
||||
module: 'USERS',
|
||||
action: dto.asTier1Agent ? 'CREATE_AGENT' : 'CREATE_PLAYER',
|
||||
module: dto.asTier1Agent ? 'AGENTS' : 'USERS',
|
||||
targetId: user.id.toString(),
|
||||
});
|
||||
if (dto.asTier1Agent) {
|
||||
const detail = await this.agents.getAgentAdminDetail(user.id);
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
const detail = await this.users.getPlayerAdminDetail(user.id);
|
||||
return jsonResponse(detail);
|
||||
}
|
||||
|
||||
@Get('users/promotable-for-agent')
|
||||
async listPromotableForAgent(@Query('keyword') keyword?: string) {
|
||||
const rows = await this.agents.listPromotablePlayers(keyword);
|
||||
return jsonResponse(
|
||||
rows.map((u) => ({
|
||||
id: u.id.toString(),
|
||||
username: u.username,
|
||||
status: u.status,
|
||||
parentId: u.parentId?.toString() ?? null,
|
||||
parentUsername: u.parent?.username ?? null,
|
||||
phone: u.preferences?.phone ?? null,
|
||||
email: u.preferences?.email ?? null,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@Get('agents/options')
|
||||
async listAgentOptions() {
|
||||
const agents = await this.prisma.user.findMany({
|
||||
@@ -397,12 +427,8 @@ export class AdminController {
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: CreateAgentAdminDto,
|
||||
) {
|
||||
const user = await this.agents.createAgent(operatorId, {
|
||||
username: dto.username,
|
||||
password: dto.password,
|
||||
level: 1,
|
||||
const user = await this.agents.promotePlayerToTier1Agent(BigInt(dto.userId), {
|
||||
creditLimit: dto.creditLimit,
|
||||
locale: dto.locale,
|
||||
phone: dto.phone,
|
||||
email: dto.email,
|
||||
cashbackRate: dto.cashbackRate,
|
||||
|
||||
@@ -358,6 +358,113 @@ export class AgentsService {
|
||||
return this.getAgentAdminDetail(agentId);
|
||||
}
|
||||
|
||||
/** 可升级为一级代理的玩家(尚无代理档案) */
|
||||
async listPromotablePlayers(keyword?: string) {
|
||||
const q = keyword?.trim();
|
||||
return this.prisma.user.findMany({
|
||||
where: {
|
||||
userType: 'PLAYER',
|
||||
deletedAt: null,
|
||||
agentProfile: null,
|
||||
...(q
|
||||
? { username: { contains: q, mode: 'insensitive' } }
|
||||
: {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
status: true,
|
||||
parentId: true,
|
||||
preferences: { select: { phone: true, email: true } },
|
||||
parent: { select: { username: true } },
|
||||
},
|
||||
orderBy: { id: 'desc' },
|
||||
take: 50,
|
||||
});
|
||||
}
|
||||
|
||||
/** 将已有玩家账号升级为一级代理(不新建用户) */
|
||||
async promotePlayerToTier1Agent(
|
||||
userId: bigint,
|
||||
data: {
|
||||
creditLimit: number;
|
||||
cashbackRate?: number;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
},
|
||||
) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: { agentProfile: true, preferences: true },
|
||||
});
|
||||
if (!user || user.deletedAt) {
|
||||
throw new NotFoundException('用户不存在');
|
||||
}
|
||||
if (user.userType !== 'PLAYER') {
|
||||
throw new BadRequestException('仅玩家账号可设为代理');
|
||||
}
|
||||
if (user.agentProfile) {
|
||||
throw new BadRequestException('该用户已是代理');
|
||||
}
|
||||
|
||||
const oldParentId = user.parentId;
|
||||
const phone =
|
||||
data.phone !== undefined
|
||||
? data.phone.trim() || null
|
||||
: user.preferences?.phone ?? null;
|
||||
const email =
|
||||
data.email !== undefined
|
||||
? data.email.trim() || null
|
||||
: user.preferences?.email ?? null;
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await tx.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
userType: 'AGENT',
|
||||
agentLevel: 1,
|
||||
parentId: null,
|
||||
},
|
||||
});
|
||||
|
||||
if (user.preferences) {
|
||||
await tx.userPreference.update({
|
||||
where: { userId },
|
||||
data: { phone, email },
|
||||
});
|
||||
} else {
|
||||
await tx.userPreference.create({
|
||||
data: {
|
||||
userId,
|
||||
locale: user.locale,
|
||||
phone,
|
||||
email,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await tx.agentProfile.create({
|
||||
data: {
|
||||
userId,
|
||||
level: 1,
|
||||
parentAgentId: null,
|
||||
creditLimit: data.creditLimit,
|
||||
cashbackRate: data.cashbackRate ?? 0,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.agentClosure.create({
|
||||
data: { ancestorId: userId, descendantId: userId, depth: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
if (oldParentId) {
|
||||
await this.recalculateUsedCredit(oldParentId);
|
||||
}
|
||||
|
||||
return this.prisma.user.findUnique({ where: { id: userId } });
|
||||
}
|
||||
|
||||
async createAgent(
|
||||
operatorId: bigint,
|
||||
data: {
|
||||
@@ -455,8 +562,30 @@ export class AgentsService {
|
||||
initialDeposit?: number;
|
||||
depositRemark?: string;
|
||||
depositRequestId?: string;
|
||||
asTier1Agent?: boolean;
|
||||
creditLimit?: number;
|
||||
cashbackRate?: number;
|
||||
},
|
||||
) {
|
||||
if (data.asTier1Agent) {
|
||||
if (data.parentId != null) {
|
||||
throw new BadRequestException('一级代理不可设置上级玩家');
|
||||
}
|
||||
if (data.initialDeposit && data.initialDeposit > 0) {
|
||||
throw new BadRequestException('设为代理时请使用授信额度,勿填玩家初始余额');
|
||||
}
|
||||
return this.createAgent(operatorId, {
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
level: 1,
|
||||
creditLimit: data.creditLimit ?? 0,
|
||||
cashbackRate: data.cashbackRate ?? 0,
|
||||
locale: data.locale,
|
||||
phone: data.phone,
|
||||
email: data.email,
|
||||
});
|
||||
}
|
||||
|
||||
let parentId: bigint | null = null;
|
||||
if (data.parentId != null) {
|
||||
const parent = await this.prisma.user.findUnique({ where: { id: data.parentId } });
|
||||
|
||||
Reference in New Issue
Block a user