feat(wallet-service): 添加手续费归集统计的历史数据兼容

当 FEE_COLLECTION 流水为空时,自动从提现订单表查询历史手续费:
- getFeeCollectionSummary: 从 withdrawal_orders 和 fiat_withdrawal_orders 聚合统计
- getFeeCollectionEntries: 从两个订单表查询明细列表,支持分页和类型筛选
- 按月统计使用 UNION ALL 合并两种提现订单数据
- 明细记录添加备注说明区分来源(区块链/法币)

回滚方式:删除 fallback 代码块和两个私有方法

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-06 21:11:37 -08:00
parent 4dcdfb8a3c
commit 272b4ffdbf
1 changed files with 236 additions and 0 deletions

View File

@ -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<Array<{
month: string;
amount: any;
count: any;
}>>`
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),
};
}
}