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:
@@ -5,6 +5,15 @@ export const PLAYER_ALLOW_PASSWORD_CHANGE = 'player.allow_password_change';
|
||||
export const PLAYER_ALLOW_USERNAME_CHANGE = 'player.allow_username_change';
|
||||
export const AGENT_SUSPEND_FREEZE_DIRECT_PLAYERS = 'agent.suspend_freeze_direct_players';
|
||||
export const AGENT_SUSPEND_BLOCK_PLAYER_LOGIN = 'agent.suspend_block_player_login';
|
||||
export const AGENT_MAX_LEVEL = 'agent.max_level';
|
||||
export const AGENT_DEFAULT_SUB_CREDIT_RATIO = 'agent.default_sub_credit_ratio';
|
||||
|
||||
export type AgentHierarchySettings = {
|
||||
/** 最大代理层级;0 = 不限制 */
|
||||
maxAgentLevel: number;
|
||||
/** 创建下级代理时默认授信占上级可用授信的比例(1–100) */
|
||||
defaultSubAgentCreditRatio: number;
|
||||
};
|
||||
|
||||
export type PlayerAccountSettings = {
|
||||
allowPasswordChange: boolean;
|
||||
@@ -40,6 +49,25 @@ export class SystemConfigService {
|
||||
});
|
||||
}
|
||||
|
||||
async getInt(key: string, defaultValue: number): Promise<number> {
|
||||
const row = await this.prisma.systemConfig.findUnique({ where: { configKey: key } });
|
||||
if (!row) return defaultValue;
|
||||
const parsed = parseInt(row.configValue, 10);
|
||||
return Number.isFinite(parsed) ? parsed : defaultValue;
|
||||
}
|
||||
|
||||
async setInt(key: string, value: number, description?: string) {
|
||||
await this.prisma.systemConfig.upsert({
|
||||
where: { configKey: key },
|
||||
create: {
|
||||
configKey: key,
|
||||
configValue: String(value),
|
||||
description,
|
||||
},
|
||||
update: { configValue: String(value) },
|
||||
});
|
||||
}
|
||||
|
||||
async getPlayerAccountSettings(): Promise<PlayerAccountSettings> {
|
||||
const [allowPasswordChange, allowUsernameChange] = await Promise.all([
|
||||
this.getBoolean(PLAYER_ALLOW_PASSWORD_CHANGE, true),
|
||||
@@ -91,4 +119,40 @@ export class SystemConfigService {
|
||||
}
|
||||
return this.getAgentSuspendSettings();
|
||||
}
|
||||
|
||||
async getAgentHierarchySettings(): Promise<AgentHierarchySettings> {
|
||||
const [maxAgentLevel, defaultSubAgentCreditRatio] = await Promise.all([
|
||||
this.getInt(AGENT_MAX_LEVEL, 0),
|
||||
this.getInt(AGENT_DEFAULT_SUB_CREDIT_RATIO, 50),
|
||||
]);
|
||||
return { maxAgentLevel, defaultSubAgentCreditRatio };
|
||||
}
|
||||
|
||||
async updateAgentHierarchySettings(data: Partial<AgentHierarchySettings>) {
|
||||
if (data.maxAgentLevel !== undefined) {
|
||||
if (!Number.isInteger(data.maxAgentLevel) || data.maxAgentLevel < 0) {
|
||||
throw new Error('maxAgentLevel must be a non-negative integer');
|
||||
}
|
||||
await this.setInt(
|
||||
AGENT_MAX_LEVEL,
|
||||
data.maxAgentLevel,
|
||||
'最大代理层级;0 表示不限制',
|
||||
);
|
||||
}
|
||||
if (data.defaultSubAgentCreditRatio !== undefined) {
|
||||
if (
|
||||
!Number.isInteger(data.defaultSubAgentCreditRatio) ||
|
||||
data.defaultSubAgentCreditRatio < 1 ||
|
||||
data.defaultSubAgentCreditRatio > 100
|
||||
) {
|
||||
throw new Error('defaultSubAgentCreditRatio must be an integer between 1 and 100');
|
||||
}
|
||||
await this.setInt(
|
||||
AGENT_DEFAULT_SUB_CREDIT_RATIO,
|
||||
data.defaultSubAgentCreditRatio,
|
||||
'创建下级代理时默认授信占上级可用授信的比例(%)',
|
||||
);
|
||||
}
|
||||
return this.getAgentHierarchySettings();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user