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:
parent
4dcdfb8a3c
commit
272b4ffdbf
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue