diff --git a/backend/services/wallet-service/src/application/services/wallet-application.service.ts b/backend/services/wallet-service/src/application/services/wallet-application.service.ts index c35cecf5..88528c8a 100644 --- a/backend/services/wallet-service/src/application/services/wallet-application.service.ts +++ b/backend/services/wallet-service/src/application/services/wallet-application.service.ts @@ -3425,6 +3425,13 @@ export class WalletApplicationService { this.logger.log(`[getFeeCollectionSummary] 统计完成: totalAmount=${totalAmount}, totalCount=${totalCount}`); + // [2026-01-07] 兼容:如果 FEE_COLLECTION 流水为空,从提现订单表查询历史手续费 + // 回滚方式:删除此 fallback 代码块 + if (totalCount === 0) { + this.logger.log('[getFeeCollectionSummary] FEE_COLLECTION 流水为空,使用提现订单表作为 fallback'); + return this.getFeeCollectionSummaryFromOrders(feeAccountSequence, accountBalance); + } + return { accountSequence: feeAccountSequence, accountBalance, @@ -3443,6 +3450,97 @@ export class WalletApplicationService { }; } + // [2026-01-07] 新增:从提现订单表查询历史手续费(兼容旧数据) + // 回滚方式:删除此方法 + private async getFeeCollectionSummaryFromOrders( + accountSequence: string, + accountBalance: number, + ): Promise<{ + accountSequence: string; + accountBalance: number; + totalAmount: number; + totalCount: number; + breakdown: Array<{ feeType: string; amount: number; count: number }>; + byMonth: Array<{ month: string; amount: number; count: number }>; + }> { + // 1. 统计区块链提现订单手续费 (CONFIRMED 状态) + const withdrawalStats = await this.prisma.withdrawalOrder.aggregate({ + where: { status: 'CONFIRMED' }, + _sum: { fee: true }, + _count: { id: true }, + }); + + // 2. 统计法币提现订单手续费 (COMPLETED 状态) + const fiatStats = await this.prisma.fiatWithdrawalOrder.aggregate({ + where: { status: 'COMPLETED' }, + _sum: { fee: true }, + _count: { id: true }, + }); + + const withdrawalFeeAmount = Number(withdrawalStats._sum.fee) || 0; + const withdrawalFeeCount = withdrawalStats._count.id || 0; + const fiatFeeAmount = Number(fiatStats._sum.fee) || 0; + const fiatFeeCount = fiatStats._count.id || 0; + + const totalAmount = withdrawalFeeAmount + fiatFeeAmount; + const totalCount = withdrawalFeeCount + fiatFeeCount; + + // 3. 构建 breakdown + const breakdown: Array<{ feeType: string; amount: number; count: number }> = []; + if (withdrawalFeeCount > 0) { + breakdown.push({ + feeType: 'WITHDRAWAL_FEE', + amount: withdrawalFeeAmount, + count: withdrawalFeeCount, + }); + } + if (fiatFeeCount > 0) { + breakdown.push({ + feeType: 'FIAT_WITHDRAWAL_FEE', + amount: fiatFeeAmount, + count: fiatFeeCount, + }); + } + + // 4. 按月统计(合并两种提现订单) + const byMonthRaw = await this.prisma.$queryRaw>` + SELECT month, SUM(amount) as amount, SUM(count) as count + FROM ( + SELECT TO_CHAR(confirmed_at, 'YYYY-MM') as month, SUM(fee) as amount, COUNT(*) as count + FROM withdrawal_orders + WHERE status = 'CONFIRMED' AND confirmed_at IS NOT NULL + GROUP BY TO_CHAR(confirmed_at, 'YYYY-MM') + UNION ALL + SELECT TO_CHAR(completed_at, 'YYYY-MM') as month, SUM(fee) as amount, COUNT(*) as count + FROM fiat_withdrawal_orders + WHERE status = 'COMPLETED' AND completed_at IS NOT NULL + GROUP BY TO_CHAR(completed_at, 'YYYY-MM') + ) combined + GROUP BY month + ORDER BY month DESC + LIMIT 12 + `; + + this.logger.log(`[getFeeCollectionSummaryFromOrders] Fallback 统计完成: totalAmount=${totalAmount}, totalCount=${totalCount}`); + + return { + accountSequence, + accountBalance, + totalAmount, + totalCount, + breakdown, + byMonth: byMonthRaw.map(row => ({ + month: row.month, + amount: Number(row.amount) || 0, + count: Number(row.count) || 0, + })), + }; + } + /** * 获取手续费归集账户的详细流水列表 */ @@ -3517,6 +3615,13 @@ export class WalletApplicationService { }); } + // [2026-01-07] 兼容:如果 FEE_COLLECTION 流水为空,从提现订单表查询历史手续费 + // 回滚方式:删除此 fallback 代码块 + if (total === 0) { + this.logger.log('[getFeeCollectionEntries] FEE_COLLECTION 流水为空,使用提现订单表作为 fallback'); + return this.getFeeCollectionEntriesFromOrders(params); + } + return { entries: filteredEntries.map(entry => { const payload = entry.payloadJson as any; @@ -3535,4 +3640,135 @@ export class WalletApplicationService { totalPages: Math.ceil(total / pageSize), }; } + + // [2026-01-07] 新增:从提现订单表查询历史手续费明细(兼容旧数据) + // 回滚方式:删除此方法 + private async getFeeCollectionEntriesFromOrders(params: { + page?: number; + pageSize?: number; + feeType?: string; + startDate?: Date; + endDate?: Date; + }): Promise<{ + entries: Array<{ + id: string; + feeType: string; + amount: number; + refOrderId: string | null; + memo: string | null; + createdAt: string; + }>; + total: number; + page: number; + pageSize: number; + totalPages: number; + }> { + const page = params.page ?? 1; + const pageSize = params.pageSize ?? 50; + const skip = (page - 1) * pageSize; + + // 构建日期过滤条件 + const dateFilter: any = {}; + if (params.startDate) { + dateFilter.gte = params.startDate; + } + if (params.endDate) { + dateFilter.lte = params.endDate; + } + + // 根据 feeType 决定查询哪些表 + const shouldQueryWithdrawal = !params.feeType || params.feeType === 'WITHDRAWAL_FEE'; + const shouldQueryFiat = !params.feeType || params.feeType === 'FIAT_WITHDRAWAL_FEE'; + + // 从两种订单表查询数据 + const withdrawalEntries: Array<{ + id: string; + feeType: string; + amount: number; + refOrderId: string | null; + memo: string | null; + createdAt: Date; + }> = []; + const fiatEntries: Array<{ + id: string; + feeType: string; + amount: number; + refOrderId: string | null; + memo: string | null; + createdAt: Date; + }> = []; + + if (shouldQueryWithdrawal) { + const withdrawalWhere: any = { status: 'CONFIRMED' }; + if (Object.keys(dateFilter).length > 0) { + withdrawalWhere.confirmedAt = dateFilter; + } + const withdrawals = await this.prisma.withdrawalOrder.findMany({ + where: withdrawalWhere, + orderBy: { confirmedAt: 'desc' }, + select: { + id: true, + orderNo: true, + fee: true, + toAddress: true, + chainType: true, + confirmedAt: true, + }, + }); + withdrawalEntries.push(...withdrawals.map(w => ({ + id: `W-${w.id.toString()}`, + feeType: 'WITHDRAWAL_FEE', + amount: Number(w.fee), + refOrderId: w.orderNo, + memo: `区块链提现手续费 (${w.chainType})`, + createdAt: w.confirmedAt!, + }))); + } + + if (shouldQueryFiat) { + const fiatWhere: any = { status: 'COMPLETED' }; + if (Object.keys(dateFilter).length > 0) { + fiatWhere.completedAt = dateFilter; + } + const fiatOrders = await this.prisma.fiatWithdrawalOrder.findMany({ + where: fiatWhere, + orderBy: { completedAt: 'desc' }, + select: { + id: true, + orderNo: true, + fee: true, + paymentMethod: true, + completedAt: true, + }, + }); + fiatEntries.push(...fiatOrders.map(f => ({ + id: `F-${f.id.toString()}`, + feeType: 'FIAT_WITHDRAWAL_FEE', + amount: Number(f.fee), + refOrderId: f.orderNo, + memo: `法币提现手续费 (${f.paymentMethod})`, + createdAt: f.completedAt!, + }))); + } + + // 合并并按时间排序 + const allEntries = [...withdrawalEntries, ...fiatEntries] + .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + + const total = allEntries.length; + const paginatedEntries = allEntries.slice(skip, skip + pageSize); + + this.logger.log(`[getFeeCollectionEntriesFromOrders] Fallback 查询完成: total=${total}, page=${page}`); + + return { + entries: paginatedEntries.map(entry => ({ + ...entry, + createdAt: entry.createdAt.toISOString(), + })), + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize), + }; + } }