feat: 手动充值、邀请码注册与后台管理增强

新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分),
支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏,
并补充前台注册、充值页及多语言错误码。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 12:20:11 +08:00
parent 618fb49511
commit 10485ecfaf
98 changed files with 7908 additions and 856 deletions

View File

@@ -287,7 +287,7 @@ export class WalletService {
let typeWhere: Record<string, unknown> = {};
if (typeFilter === 'deposit') {
typeWhere = { transactionType: { in: ['MANUAL_DEPOSIT', 'DEPOSIT', 'MANUAL_ADJUST'] } };
typeWhere = { transactionType: { in: ['MANUAL_DEPOSIT', 'DEPOSIT', 'MANUAL_ADJUST', 'PLAYER_DEPOSIT'] } };
} else if (typeFilter === 'withdraw') {
typeWhere = { transactionType: { in: ['MANUAL_WITHDRAW', 'WITHDRAW'] } };
} else if (typeFilter === 'bet') {
@@ -312,7 +312,7 @@ export class WalletService {
private walletTypeCategoryWhere(category?: string): Prisma.WalletTransactionWhereInput {
const cat = category?.trim();
if (cat === 'deposit') {
return { transactionType: { in: ['MANUAL_DEPOSIT', 'DEPOSIT', 'MANUAL_ADJUST'] } };
return { transactionType: { in: ['MANUAL_DEPOSIT', 'DEPOSIT', 'MANUAL_ADJUST', 'PLAYER_DEPOSIT'] } };
}
if (cat === 'withdraw') {
return { transactionType: { in: ['MANUAL_WITHDRAW', 'WITHDRAW'] } };
@@ -341,6 +341,99 @@ export class WalletService {
return {};
}
private static readonly DEPOSIT_RECHARGE_TYPES = new Set([
'MANUAL_DEPOSIT',
'DEPOSIT',
'PLAYER_DEPOSIT',
]);
private paymentMethodDisplayName(pm: {
displayName: string | null;
bankName: string | null;
usdtAddress: string | null;
methodType: string;
} | null | undefined): string | null {
if (!pm) return null;
return pm.displayName ?? pm.bankName ?? pm.usdtAddress ?? pm.methodType ?? null;
}
private async resolveDepositMethodsForRows(
rows: Array<{ id: bigint; transactionType: string; referenceId: string | null; operatorId: bigint | null }>,
) {
const rechargeRows = rows.filter((r) => WalletService.DEPOSIT_RECHARGE_TYPES.has(r.transactionType));
const result = new Map<
string,
{ depositMethodKey: string | null; depositMethodName: string | null }
>();
if (!rechargeRows.length) return result;
const orderNos = [
...new Set(
rechargeRows
.filter((r) => r.transactionType === 'PLAYER_DEPOSIT' && r.referenceId)
.map((r) => r.referenceId!),
),
];
const manualOperatorIds = [
...new Set(
rechargeRows
.filter((r) => r.transactionType !== 'PLAYER_DEPOSIT' && r.operatorId)
.map((r) => r.operatorId!),
),
];
const [orders, operators] = await Promise.all([
orderNos.length
? this.prisma.depositOrder.findMany({
where: { orderNo: { in: orderNos } },
include: {
paymentMethod: {
select: {
displayName: true,
bankName: true,
usdtAddress: true,
methodType: true,
},
},
},
})
: [],
manualOperatorIds.length
? this.prisma.user.findMany({
where: { id: { in: manualOperatorIds } },
select: { id: true, userType: true },
})
: [],
]);
const orderByNo = new Map(orders.map((o) => [o.orderNo, o]));
const operatorTypeById = new Map(operators.map((o) => [o.id.toString(), o.userType]));
for (const row of rechargeRows) {
const rowKey = row.id.toString();
if (row.transactionType === 'PLAYER_DEPOSIT' && row.referenceId) {
const order = orderByNo.get(row.referenceId);
result.set(rowKey, {
depositMethodKey: null,
depositMethodName: this.paymentMethodDisplayName(order?.paymentMethod),
});
continue;
}
const operatorType = row.operatorId
? operatorTypeById.get(row.operatorId.toString())
: null;
let depositMethodKey = 'MANUAL';
if (operatorType === 'ADMIN') depositMethodKey = 'MANUAL_ADMIN';
else if (operatorType === 'AGENT') depositMethodKey = 'MANUAL_AGENT';
result.set(rowKey, { depositMethodKey, depositMethodName: null });
}
return result;
}
async listWalletTransactionsAdmin(params: {
page?: number;
pageSize?: number;
@@ -495,11 +588,13 @@ export class WalletService {
const playerById = new Map(players.map((p) => [p.id.toString(), p]));
const operatorById = new Map(operators.map((u) => [u.id.toString(), u.username]));
const parentById = new Map(parentAgents.map((a) => [a.id.toString(), a.username]));
const depositMethodByRowId = await this.resolveDepositMethodsForRows(rows);
return {
items: rows.map((row) => {
const player = playerById.get(row.userId.toString());
const parentId = player?.parentId;
const depositMethod = depositMethodByRowId.get(row.id.toString());
return {
id: row.id.toString(),
transactionId: row.transactionId,
@@ -516,6 +611,8 @@ export class WalletService {
referenceType: row.referenceType,
referenceId: row.referenceId,
betNo: row.referenceType === 'BET' ? row.referenceId : null,
depositMethodKey: depositMethod?.depositMethodKey ?? null,
depositMethodName: depositMethod?.depositMethodName ?? null,
operatorId: row.operatorId?.toString() ?? null,
operatorUsername: row.operatorId
? (operatorById.get(row.operatorId.toString()) ?? null)