feat(admin+api): 代理停用默认、结算加固与冒烟配置探针

- 代理层级默认授信比例与停用冻结/禁登全局默认

- 结算预览去重、比分校验、串关当场判负与市场类型校验

- 站内信 Banner/公告自动通知开关;开发环境动态 API 端口

- 扩充 RBAC/结算/认证/返现单元测试与 agent skills
This commit is contained in:
2026-06-23 11:08:41 +08:00
parent fa06fee64c
commit ce84226219
47 changed files with 9284 additions and 118 deletions

View File

@@ -40,6 +40,15 @@ export class AgentsService {
return agentLevel < maxLevel;
}
/** 与 Admin 创建下级代理预填逻辑一致:可用授信 × 比例(%),非 100% 时向下取整到百位 */
computeDefaultSubAgentCredit(availableCredit: number, ratioPercent: number): number {
if (availableCredit <= 0) return 0;
const pct = Math.min(100, Math.max(1, ratioPercent)) / 100;
const raw = availableCredit * pct;
const rounded = pct >= 1 ? availableCredit : Math.floor(raw / 100) * 100;
return Math.min(availableCredit, Math.max(0, rounded));
}
private async buildAgentAncestorChainMap(parentAgentIds: (bigint | null | undefined)[]) {
return this.network.buildAgentAncestorChainMap(parentAgentIds);
}
@@ -923,11 +932,17 @@ export class AgentsService {
});
}
// Handle status change (per-action cascade freeze / login block)
// Handle status change (per-action cascade freeze / login block; 未传参时用全局默认)
if (data.status) {
const profilePatch: Prisma.AgentProfileUpdateInput = { status: data.status };
let freezeDirectPlayers = false;
if (data.status === 'SUSPENDED') {
profilePatch.blockDirectPlayerLogin = data.blockDirectPlayerLogin === true;
const suspendDefaults = await this.systemConfig.getAgentSuspendSettings();
const blockDirectPlayerLogin =
data.blockDirectPlayerLogin ?? suspendDefaults.suspendBlockPlayerLogin;
freezeDirectPlayers =
data.freezeDirectPlayers ?? suspendDefaults.suspendFreezeDirectPlayers;
profilePatch.blockDirectPlayerLogin = blockDirectPlayerLogin === true;
} else if (data.status === 'ACTIVE') {
profilePatch.blockDirectPlayerLogin = false;
}
@@ -943,7 +958,7 @@ export class AgentsService {
}),
]);
if (data.status === 'SUSPENDED' && data.freezeDirectPlayers) {
if (data.status === 'SUSPENDED' && freezeDirectPlayers) {
await this.prisma.user.updateMany({
where: { parentId: agentId, userType: 'PLAYER', deletedAt: null },
data: { status: 'SUSPENDED' },
@@ -1156,20 +1171,31 @@ export class AgentsService {
) {
await this.validateAgentLevel(data.level, data.parentAgentId);
let resolvedCreditLimit = data.creditLimit;
let resolvedCashbackRate = data.cashbackRate ?? 0;
if (data.parentAgentId) {
const parentProfile = await this.prisma.agentProfile.findUnique({
where: { userId: data.parentAgentId },
select: { cashbackRate: true },
select: { cashbackRate: true, creditLimit: true, usedCredit: true },
});
resolvedCashbackRate =
data.cashbackRate ?? (parentProfile ? Number(parentProfile.cashbackRate) : 0);
if (resolvedCreditLimit === undefined && parentProfile) {
const hierarchy = await this.systemConfig.getAgentHierarchySettings();
const available = new Decimal(parentProfile.creditLimit).sub(parentProfile.usedCredit);
resolvedCreditLimit = this.computeDefaultSubAgentCredit(
available.toNumber(),
hierarchy.defaultSubAgentCreditRatio,
);
}
await this.assertChildAgentWithinParent(data.parentAgentId, {
creditLimit: data.creditLimit ?? 0,
creditLimit: resolvedCreditLimit ?? 0,
cashbackRate: resolvedCashbackRate,
maxSingleDeposit: data.maxSingleDeposit,
maxDailyDeposit: data.maxDailyDeposit,
});
} else if (resolvedCreditLimit === undefined) {
resolvedCreditLimit = 0;
}
const maxSingleDeposit = this.normalizeOptionalLimit(data.maxSingleDeposit);
@@ -1208,7 +1234,7 @@ export class AgentsService {
userId: user.id,
level: data.level,
parentAgentId: data.parentAgentId,
creditLimit: data.creditLimit ?? 0,
creditLimit: resolvedCreditLimit ?? 0,
cashbackRate: resolvedCashbackRate,
maxSingleDeposit,
maxDailyDeposit,