feat(admin-web): 添加面对面结算明细列表功能
- wallet-service: 新增 getOfflineSettlementEntries 方法和 API - reporting-service: 新增客户端方法和 API 转发 - admin-web: 添加明细列表组件和样式,支持展开/收起 🤖 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
9953f0eee5
commit
4e5d9685a1
|
|
@ -79,6 +79,30 @@ export class SystemAccountReportController {
|
||||||
return this.systemAccountReportService.getExpiredRewardsSummary({ startDate, endDate });
|
return this.systemAccountReportService.getExpiredRewardsSummary({ startDate, endDate });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:面对面结算明细列表
|
||||||
|
// 回滚方式:删除此方法
|
||||||
|
@Get('offline-settlement-entries')
|
||||||
|
@ApiOperation({ summary: '获取面对面结算明细列表' })
|
||||||
|
@ApiQuery({ name: 'page', required: false, description: '页码,默认1' })
|
||||||
|
@ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' })
|
||||||
|
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||||
|
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||||
|
@ApiResponse({ status: 200, description: '面对面结算明细列表' })
|
||||||
|
async getOfflineSettlementEntries(
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('pageSize') pageSize?: string,
|
||||||
|
@Query('startDate') startDate?: string,
|
||||||
|
@Query('endDate') endDate?: string,
|
||||||
|
) {
|
||||||
|
this.logger.log(`[getOfflineSettlementEntries] 请求面对面结算明细, page=${page}, pageSize=${pageSize}`);
|
||||||
|
return this.systemAccountReportService.getOfflineSettlementEntries({
|
||||||
|
page: page ? parseInt(page, 10) : undefined,
|
||||||
|
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// [2026-01-05] 新增:所有系统账户分类账明细
|
// [2026-01-05] 新增:所有系统账户分类账明细
|
||||||
@Get('all-ledger')
|
@Get('all-ledger')
|
||||||
@ApiOperation({ summary: '获取所有系统账户的分类账明细' })
|
@ApiOperation({ summary: '获取所有系统账户的分类账明细' })
|
||||||
|
|
@ -133,7 +157,7 @@ export class SystemAccountReportController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('fee-entries-detailed')
|
@Get('fee-entries-detailed')
|
||||||
@ApiOperation({ summary: '获取手续费类型的详细记录列表' })
|
@ApiOperation({ summary: '获取手续费类型的详细记录列表(系统费用,非提现手续费)' })
|
||||||
@ApiQuery({ name: 'page', required: false, description: '页码,默认1' })
|
@ApiQuery({ name: 'page', required: false, description: '页码,默认1' })
|
||||||
@ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' })
|
@ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' })
|
||||||
@ApiResponse({ status: 200, description: '手续费类型详细记录列表' })
|
@ApiResponse({ status: 200, description: '手续费类型详细记录列表' })
|
||||||
|
|
@ -147,4 +171,41 @@ export class SystemAccountReportController {
|
||||||
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-06] 新增:手续费归集账户统计接口(真正的提现手续费)
|
||||||
|
// 来源:wallet-service 中 S0000000006 账户的 FEE_COLLECTION 类型流水
|
||||||
|
// 回滚方式:删除以下 API 方法即可
|
||||||
|
|
||||||
|
@Get('fee-collection-summary')
|
||||||
|
@ApiOperation({ summary: '获取手续费归集账户汇总统计(提现手续费)' })
|
||||||
|
@ApiResponse({ status: 200, description: '手续费归集账户汇总统计' })
|
||||||
|
async getFeeCollectionSummary() {
|
||||||
|
this.logger.log(`[getFeeCollectionSummary] 请求手续费归集账户汇总统计`);
|
||||||
|
return this.systemAccountReportService.getFeeCollectionSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('fee-collection-entries')
|
||||||
|
@ApiOperation({ summary: '获取手续费归集账户流水明细(提现手续费)' })
|
||||||
|
@ApiQuery({ name: 'page', required: false, description: '页码,默认1' })
|
||||||
|
@ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' })
|
||||||
|
@ApiQuery({ name: 'feeType', required: false, description: '手续费类型筛选 (WITHDRAWAL_FEE/FIAT_WITHDRAWAL_FEE)' })
|
||||||
|
@ApiQuery({ name: 'startDate', required: false, description: '开始日期' })
|
||||||
|
@ApiQuery({ name: 'endDate', required: false, description: '结束日期' })
|
||||||
|
@ApiResponse({ status: 200, description: '手续费归集流水明细列表' })
|
||||||
|
async getFeeCollectionEntries(
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('pageSize') pageSize?: string,
|
||||||
|
@Query('feeType') feeType?: string,
|
||||||
|
@Query('startDate') startDate?: string,
|
||||||
|
@Query('endDate') endDate?: string,
|
||||||
|
) {
|
||||||
|
this.logger.log(`[getFeeCollectionEntries] 请求手续费归集流水明细, page=${page}, pageSize=${pageSize}, feeType=${feeType}`);
|
||||||
|
return this.systemAccountReportService.getFeeCollectionEntries({
|
||||||
|
page: page ? parseInt(page, 10) : undefined,
|
||||||
|
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
||||||
|
feeType,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
* 回滚方式:删除此文件,并从 application.module.ts 中移除注册
|
* 回滚方式:删除此文件,并从 application.module.ts 中移除注册
|
||||||
*/
|
*/
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { WalletServiceClient, OfflineSettlementSummary, AllSystemAccountsResponse, AllSystemAccountsLedgerResponse } from '../../infrastructure/external/wallet-service/wallet-service.client';
|
import { WalletServiceClient, OfflineSettlementSummary, AllSystemAccountsResponse, AllSystemAccountsLedgerResponse, FeeCollectionSummaryResponse, FeeCollectionEntriesResponse, OfflineSettlementEntriesResponse } from '../../infrastructure/external/wallet-service/wallet-service.client';
|
||||||
import { RewardServiceClient, ExpiredRewardsSummary, AllRewardTypeSummaries, RewardEntriesResponse } from '../../infrastructure/external/reward-service/reward-service.client';
|
import { RewardServiceClient, ExpiredRewardsSummary, AllRewardTypeSummaries, RewardEntriesResponse } from '../../infrastructure/external/reward-service/reward-service.client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -185,6 +185,21 @@ export class SystemAccountReportApplicationService {
|
||||||
return this.rewardServiceClient.getExpiredRewardsSummary(params);
|
return this.rewardServiceClient.getExpiredRewardsSummary(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:获取面对面结算明细列表
|
||||||
|
// 回滚方式:删除此方法
|
||||||
|
/**
|
||||||
|
* 获取面对面结算明细列表
|
||||||
|
*/
|
||||||
|
async getOfflineSettlementEntries(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}): Promise<OfflineSettlementEntriesResponse> {
|
||||||
|
this.logger.log('[getOfflineSettlementEntries] 获取面对面结算明细列表');
|
||||||
|
return this.walletServiceClient.getOfflineSettlementEntries(params);
|
||||||
|
}
|
||||||
|
|
||||||
// [2026-01-05] 新增:获取所有系统账户的分类账明细
|
// [2026-01-05] 新增:获取所有系统账户的分类账明细
|
||||||
/**
|
/**
|
||||||
* 获取所有系统账户的分类账明细
|
* 获取所有系统账户的分类账明细
|
||||||
|
|
@ -228,6 +243,7 @@ export class SystemAccountReportApplicationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取手续费类型的详细记录列表
|
* 获取手续费类型的详细记录列表
|
||||||
|
* @deprecated 使用 getFeeCollectionEntries 获取真正的提现手续费记录
|
||||||
*/
|
*/
|
||||||
async getFeeEntriesDetailed(params?: {
|
async getFeeEntriesDetailed(params?: {
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
@ -237,6 +253,33 @@ export class SystemAccountReportApplicationService {
|
||||||
return this.rewardServiceClient.getFeeEntriesDetailed(params);
|
return this.rewardServiceClient.getFeeEntriesDetailed(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-06] 新增:获取手续费归集账户统计(真正的提现手续费)
|
||||||
|
// 来源:wallet-service 中 S0000000006 账户的 FEE_COLLECTION 类型流水
|
||||||
|
// 回滚方式:删除以下方法
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取手续费归集账户汇总统计
|
||||||
|
* 统计 S0000000006 账户收到的所有提现手续费
|
||||||
|
*/
|
||||||
|
async getFeeCollectionSummary(): Promise<FeeCollectionSummaryResponse> {
|
||||||
|
this.logger.log('[getFeeCollectionSummary] 获取手续费归集账户汇总统计');
|
||||||
|
return this.walletServiceClient.getFeeCollectionSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取手续费归集账户流水明细
|
||||||
|
*/
|
||||||
|
async getFeeCollectionEntries(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
feeType?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}): Promise<FeeCollectionEntriesResponse> {
|
||||||
|
this.logger.log('[getFeeCollectionEntries] 获取手续费归集流水明细');
|
||||||
|
return this.walletServiceClient.getFeeCollectionEntries(params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组装固定账户数据
|
* 组装固定账户数据
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,25 @@ export interface OfflineSettlementSummary {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:面对面结算明细条目
|
||||||
|
export interface OfflineSettlementEntryDTO {
|
||||||
|
id: string;
|
||||||
|
accountSequence: string;
|
||||||
|
amount: number;
|
||||||
|
memo: string | null;
|
||||||
|
refOrderId: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:面对面结算明细列表响应
|
||||||
|
export interface OfflineSettlementEntriesResponse {
|
||||||
|
entries: OfflineSettlementEntryDTO[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SystemAccountBalance {
|
export interface SystemAccountBalance {
|
||||||
accountSequence: string;
|
accountSequence: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -179,6 +198,46 @@ export class WalletServiceClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:获取面对面结算明细列表
|
||||||
|
// 回滚方式:删除此方法
|
||||||
|
/**
|
||||||
|
* 获取面对面结算明细列表
|
||||||
|
*/
|
||||||
|
async getOfflineSettlementEntries(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}): Promise<OfflineSettlementEntriesResponse> {
|
||||||
|
try {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (params?.page) queryParams.append('page', params.page.toString());
|
||||||
|
if (params?.pageSize) queryParams.append('pageSize', params.pageSize.toString());
|
||||||
|
if (params?.startDate) queryParams.append('startDate', params.startDate);
|
||||||
|
if (params?.endDate) queryParams.append('endDate', params.endDate);
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}/api/v1/wallets/statistics/offline-settlement-entries?${queryParams.toString()}`;
|
||||||
|
this.logger.debug(`[getOfflineSettlementEntries] 请求: ${url}`);
|
||||||
|
|
||||||
|
const response = await firstValueFrom(
|
||||||
|
this.httpService.get<{ success: boolean; data: OfflineSettlementEntriesResponse; timestamp: string }>(url),
|
||||||
|
);
|
||||||
|
|
||||||
|
// wallet-service 返回 { success, data, timestamp } 包装格式
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`[getOfflineSettlementEntries] 失败: ${error.message}`);
|
||||||
|
// 返回默认值,不阻塞报表
|
||||||
|
return {
|
||||||
|
entries: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
pageSize: params?.pageSize ?? 50,
|
||||||
|
totalPages: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统账户余额
|
* 获取系统账户余额
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,36 @@ export class InternalWalletController {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:面对面结算明细列表 API
|
||||||
|
// 回滚方式:删除此方法
|
||||||
|
@Get('statistics/offline-settlement-entries')
|
||||||
|
@Public()
|
||||||
|
@ApiOperation({ summary: '面对面(线下)结算明细列表(内部API) - 用于系统账户报表' })
|
||||||
|
@ApiQuery({ name: 'page', required: false, description: '页码,默认1' })
|
||||||
|
@ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' })
|
||||||
|
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||||
|
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||||
|
@ApiResponse({ status: 200, description: '面对面结算明细列表' })
|
||||||
|
async getOfflineSettlementEntries(
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('pageSize') pageSize?: string,
|
||||||
|
@Query('startDate') startDate?: string,
|
||||||
|
@Query('endDate') endDate?: string,
|
||||||
|
) {
|
||||||
|
this.logger.log(`========== statistics/offline-settlement-entries 请求 ==========`);
|
||||||
|
this.logger.log(`page: ${page}, pageSize: ${pageSize}, startDate: ${startDate}, endDate: ${endDate}`);
|
||||||
|
|
||||||
|
const result = await this.walletService.getOfflineSettlementEntries({
|
||||||
|
page: page ? parseInt(page, 10) : undefined,
|
||||||
|
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
|
||||||
|
startDate: startDate ? new Date(startDate) : undefined,
|
||||||
|
endDate: endDate ? new Date(endDate) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`面对面结算明细结果: total=${result.total}, page=${result.page}, totalPages=${result.totalPages}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Get('statistics/system-accounts-balances')
|
@Get('statistics/system-accounts-balances')
|
||||||
@Public()
|
@Public()
|
||||||
@ApiOperation({ summary: '批量查询系统账户余额(内部API) - 用于系统账户报表' })
|
@ApiOperation({ summary: '批量查询系统账户余额(内部API) - 用于系统账户报表' })
|
||||||
|
|
@ -463,4 +493,52 @@ export class InternalWalletController {
|
||||||
this.logger.log(`所有系统账户分类账查询完成: 固定=${result.fixedAccountsLedger.length}, 省=${result.provinceAccountsLedger.length}, 市=${result.cityAccountsLedger.length}, 总流水=${totalLedgers}`);
|
this.logger.log(`所有系统账户分类账查询完成: 固定=${result.fixedAccountsLedger.length}, 省=${result.provinceAccountsLedger.length}, 市=${result.cityAccountsLedger.length}, 总流水=${totalLedgers}`);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-06] 新增:手续费归集账户统计 API
|
||||||
|
// 用于系统账户报表中的"手续费账户汇总" Tab
|
||||||
|
// 回滚方式:删除以下 API 方法即可
|
||||||
|
|
||||||
|
@Get('statistics/fee-collection-summary')
|
||||||
|
@Public()
|
||||||
|
@ApiOperation({ summary: '获取手续费归集账户汇总统计(内部API) - 用于系统账户报表' })
|
||||||
|
@ApiResponse({ status: 200, description: '手续费归集账户汇总统计' })
|
||||||
|
async getFeeCollectionSummary() {
|
||||||
|
this.logger.log(`========== statistics/fee-collection-summary 请求 ==========`);
|
||||||
|
|
||||||
|
const result = await this.walletService.getFeeCollectionSummary();
|
||||||
|
|
||||||
|
this.logger.log(`手续费归集统计结果: totalAmount=${result.totalAmount}, totalCount=${result.totalCount}, balance=${result.accountBalance}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('statistics/fee-collection-entries')
|
||||||
|
@Public()
|
||||||
|
@ApiOperation({ summary: '获取手续费归集账户流水明细(内部API) - 用于系统账户报表' })
|
||||||
|
@ApiQuery({ name: 'page', required: false, description: '页码,默认1' })
|
||||||
|
@ApiQuery({ name: 'pageSize', required: false, description: '每页条数,默认50' })
|
||||||
|
@ApiQuery({ name: 'feeType', required: false, description: '手续费类型筛选 (WITHDRAWAL_FEE/FIAT_WITHDRAWAL_FEE)' })
|
||||||
|
@ApiQuery({ name: 'startDate', required: false, description: '开始日期' })
|
||||||
|
@ApiQuery({ name: 'endDate', required: false, description: '结束日期' })
|
||||||
|
@ApiResponse({ status: 200, description: '手续费归集流水明细列表' })
|
||||||
|
async getFeeCollectionEntries(
|
||||||
|
@Query('page') page?: string,
|
||||||
|
@Query('pageSize') pageSize?: string,
|
||||||
|
@Query('feeType') feeType?: string,
|
||||||
|
@Query('startDate') startDate?: string,
|
||||||
|
@Query('endDate') endDate?: string,
|
||||||
|
) {
|
||||||
|
this.logger.log(`========== statistics/fee-collection-entries 请求 ==========`);
|
||||||
|
this.logger.log(`page=${page}, pageSize=${pageSize}, feeType=${feeType}`);
|
||||||
|
|
||||||
|
const result = await this.walletService.getFeeCollectionEntries({
|
||||||
|
page: page ? parseInt(page, 10) : 1,
|
||||||
|
pageSize: pageSize ? parseInt(pageSize, 10) : 50,
|
||||||
|
feeType,
|
||||||
|
startDate: startDate ? new Date(startDate) : undefined,
|
||||||
|
endDate: endDate ? new Date(endDate) : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log(`手续费归集流水查询结果: ${result.total} 条记录`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3138,6 +3138,91 @@ export class WalletApplicationService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:获取面对面结算明细列表
|
||||||
|
// 回滚方式:删除此方法
|
||||||
|
/**
|
||||||
|
* 获取面对面结算明细列表
|
||||||
|
* 查询 SPECIAL_DEDUCTION 类型的流水记录
|
||||||
|
*/
|
||||||
|
async getOfflineSettlementEntries(params: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
}): Promise<{
|
||||||
|
entries: Array<{
|
||||||
|
id: string;
|
||||||
|
accountSequence: string;
|
||||||
|
amount: number;
|
||||||
|
memo: string | null;
|
||||||
|
refOrderId: 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;
|
||||||
|
|
||||||
|
this.logger.log(`[getOfflineSettlementEntries] 查询面对面结算明细: page=${page}, pageSize=${pageSize}`);
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
const where: any = {
|
||||||
|
entryType: 'SPECIAL_DEDUCTION',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.startDate || params.endDate) {
|
||||||
|
where.createdAt = {};
|
||||||
|
if (params.startDate) {
|
||||||
|
where.createdAt.gte = params.startDate;
|
||||||
|
}
|
||||||
|
if (params.endDate) {
|
||||||
|
where.createdAt.lte = params.endDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询总数
|
||||||
|
const total = await this.prisma.ledgerEntry.count({ where });
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const entries = await this.prisma.ledgerEntry.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
skip,
|
||||||
|
take: pageSize,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
accountSequence: true,
|
||||||
|
amount: true,
|
||||||
|
memo: true,
|
||||||
|
refOrderId: true,
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(total / pageSize);
|
||||||
|
|
||||||
|
this.logger.log(`[getOfflineSettlementEntries] 查询结果: total=${total}, page=${page}, totalPages=${totalPages}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
entries: entries.map(entry => ({
|
||||||
|
id: entry.id.toString(),
|
||||||
|
accountSequence: entry.accountSequence,
|
||||||
|
amount: Math.abs(Number(entry.amount)), // SPECIAL_DEDUCTION 金额是负数,取绝对值
|
||||||
|
memo: entry.memo,
|
||||||
|
refOrderId: entry.refOrderId,
|
||||||
|
createdAt: entry.createdAt.toISOString(),
|
||||||
|
})),
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
totalPages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量查询系统账户余额
|
* 批量查询系统账户余额
|
||||||
* 用于系统账户报表展示固定账户(总部、成本、运营)的余额
|
* 用于系统账户报表展示固定账户(总部、成本、运营)的余额
|
||||||
|
|
@ -3248,4 +3333,206 @@ export class WalletApplicationService {
|
||||||
this.logger.log('[getAllSystemAccounts] 固定: ' + fixedAccounts.length + ', 省: ' + provinceAccounts.length + ', 市: ' + cityAccounts.length);
|
this.logger.log('[getAllSystemAccounts] 固定: ' + fixedAccounts.length + ', 省: ' + provinceAccounts.length + ', 市: ' + cityAccounts.length);
|
||||||
return { fixedAccounts, provinceAccounts, cityAccounts };
|
return { fixedAccounts, provinceAccounts, cityAccounts };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============== 手续费归集账户统计 API ===============
|
||||||
|
// [2026-01-06] 新增:获取手续费归集账户 (S0000000006) 的统计数据
|
||||||
|
// 用于系统账户报表中的"手续费账户汇总" Tab
|
||||||
|
// 回滚方式:删除以下方法
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取手续费归集账户汇总统计
|
||||||
|
* 统计 S0000000006 账户收到的所有 FEE_COLLECTION 类型流水
|
||||||
|
*/
|
||||||
|
async getFeeCollectionSummary(): 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;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
this.logger.log('[getFeeCollectionSummary] 查询手续费归集账户统计');
|
||||||
|
|
||||||
|
const feeAccountSequence = WalletApplicationService.FEE_COLLECTION_ACCOUNT;
|
||||||
|
|
||||||
|
// 1. 获取账户余额
|
||||||
|
const wallet = await this.prisma.walletAccount.findUnique({
|
||||||
|
where: { accountSequence: feeAccountSequence },
|
||||||
|
select: { usdtAvailable: true },
|
||||||
|
});
|
||||||
|
const accountBalance = Number(wallet?.usdtAvailable) || 0;
|
||||||
|
|
||||||
|
// 2. 统计所有 FEE_COLLECTION 类型的流水总额和总笔数
|
||||||
|
const aggregateResult = await this.prisma.ledgerEntry.aggregate({
|
||||||
|
where: {
|
||||||
|
accountSequence: feeAccountSequence,
|
||||||
|
entryType: 'FEE_COLLECTION',
|
||||||
|
},
|
||||||
|
_sum: {
|
||||||
|
amount: true,
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalAmount = Number(aggregateResult._sum.amount) || 0;
|
||||||
|
const totalCount = aggregateResult._count.id || 0;
|
||||||
|
|
||||||
|
// 3. 按手续费类型分组统计(从 payloadJson 中提取 feeType)
|
||||||
|
// 由于 Prisma 不支持直接 groupBy jsonb 字段,使用原生 SQL
|
||||||
|
const breakdownRaw = await this.prisma.$queryRaw<Array<{
|
||||||
|
feeType: string;
|
||||||
|
amount: any;
|
||||||
|
count: any;
|
||||||
|
}>>`
|
||||||
|
SELECT
|
||||||
|
COALESCE(payload_json->>'feeType', 'UNKNOWN') as "feeType",
|
||||||
|
SUM(amount) as amount,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM ledger_entries
|
||||||
|
WHERE account_sequence = ${feeAccountSequence}
|
||||||
|
AND entry_type = 'FEE_COLLECTION'
|
||||||
|
GROUP BY COALESCE(payload_json->>'feeType', 'UNKNOWN')
|
||||||
|
ORDER BY amount DESC
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 4. 按月统计
|
||||||
|
const byMonthRaw = await this.prisma.$queryRaw<Array<{
|
||||||
|
month: string;
|
||||||
|
amount: any;
|
||||||
|
count: any;
|
||||||
|
}>>`
|
||||||
|
SELECT
|
||||||
|
TO_CHAR(created_at, 'YYYY-MM') as month,
|
||||||
|
SUM(amount) as amount,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM ledger_entries
|
||||||
|
WHERE account_sequence = ${feeAccountSequence}
|
||||||
|
AND entry_type = 'FEE_COLLECTION'
|
||||||
|
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
|
||||||
|
ORDER BY month DESC
|
||||||
|
LIMIT 12
|
||||||
|
`;
|
||||||
|
|
||||||
|
this.logger.log(`[getFeeCollectionSummary] 统计完成: totalAmount=${totalAmount}, totalCount=${totalCount}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
accountSequence: feeAccountSequence,
|
||||||
|
accountBalance,
|
||||||
|
totalAmount,
|
||||||
|
totalCount,
|
||||||
|
breakdown: breakdownRaw.map(row => ({
|
||||||
|
feeType: row.feeType,
|
||||||
|
amount: Number(row.amount) || 0,
|
||||||
|
count: Number(row.count) || 0,
|
||||||
|
})),
|
||||||
|
byMonth: byMonthRaw.map(row => ({
|
||||||
|
month: row.month,
|
||||||
|
amount: Number(row.amount) || 0,
|
||||||
|
count: Number(row.count) || 0,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取手续费归集账户的详细流水列表
|
||||||
|
*/
|
||||||
|
async getFeeCollectionEntries(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 feeAccountSequence = WalletApplicationService.FEE_COLLECTION_ACCOUNT;
|
||||||
|
|
||||||
|
this.logger.log(`[getFeeCollectionEntries] 查询手续费归集流水: page=${page}, pageSize=${pageSize}, feeType=${params.feeType}`);
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
const where: any = {
|
||||||
|
accountSequence: feeAccountSequence,
|
||||||
|
entryType: 'FEE_COLLECTION',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.startDate || params.endDate) {
|
||||||
|
where.createdAt = {};
|
||||||
|
if (params.startDate) {
|
||||||
|
where.createdAt.gte = params.startDate;
|
||||||
|
}
|
||||||
|
if (params.endDate) {
|
||||||
|
where.createdAt.lte = params.endDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询总数
|
||||||
|
const total = await this.prisma.ledgerEntry.count({ where });
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const entries = await this.prisma.ledgerEntry.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
skip,
|
||||||
|
take: pageSize,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
amount: true,
|
||||||
|
refOrderId: true,
|
||||||
|
memo: true,
|
||||||
|
payloadJson: true,
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果指定了 feeType,在应用层过滤(因为 Prisma 不支持 jsonb 字段的 where 条件)
|
||||||
|
let filteredEntries = entries;
|
||||||
|
if (params.feeType) {
|
||||||
|
filteredEntries = entries.filter(entry => {
|
||||||
|
const payload = entry.payloadJson as any;
|
||||||
|
return payload?.feeType === params.feeType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
entries: filteredEntries.map(entry => {
|
||||||
|
const payload = entry.payloadJson as any;
|
||||||
|
return {
|
||||||
|
id: entry.id.toString(),
|
||||||
|
feeType: payload?.feeType || 'UNKNOWN',
|
||||||
|
amount: Number(entry.amount),
|
||||||
|
refOrderId: entry.refOrderId,
|
||||||
|
memo: entry.memo,
|
||||||
|
createdAt: entry.createdAt.toISOString(),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
totalPages: Math.ceil(total / pageSize),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,12 +156,31 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accountInfo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.accountLabel {
|
.accountLabel {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accountValue {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
font-family: monospace;
|
||||||
|
background: #fff;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.accountSequence {
|
.accountSequence {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
|
|
@ -293,6 +312,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* [2026-01-07] 新增:明细列表切换区域样式 */
|
||||||
|
.toggleSection {
|
||||||
|
margin-top: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleButton {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
border-color: #9ca3af;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.entriesSection {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paginationInfo {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
/* 页脚 */
|
/* 页脚 */
|
||||||
.footer {
|
.footer {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,12 @@ import type {
|
||||||
FeeAccountSummary,
|
FeeAccountSummary,
|
||||||
RewardEntriesResponse,
|
RewardEntriesResponse,
|
||||||
RewardEntryDTO,
|
RewardEntryDTO,
|
||||||
|
FeeCollectionSummaryResponse,
|
||||||
|
FeeCollectionEntriesResponse,
|
||||||
|
OfflineSettlementEntriesResponse,
|
||||||
|
OfflineSettlementEntryDTO,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
import { ENTRY_TYPE_LABELS, ACCOUNT_TYPE_LABELS, FEE_TYPE_LABELS, REWARD_RIGHT_TYPE_LABELS, REWARD_STATUS_LABELS } from '@/types';
|
import { ENTRY_TYPE_LABELS, ACCOUNT_TYPE_LABELS, FEE_TYPE_LABELS, REWARD_RIGHT_TYPE_LABELS, REWARD_STATUS_LABELS, FEE_COLLECTION_TYPE_LABELS } from '@/types';
|
||||||
import styles from './SystemAccountsTab.module.scss';
|
import styles from './SystemAccountsTab.module.scss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -239,11 +243,9 @@ export default function SystemAccountsTab() {
|
||||||
{activeTab === 'settlement' && <OfflineSettlementSection data={reportData.offlineSettlement} />}
|
{activeTab === 'settlement' && <OfflineSettlementSection data={reportData.offlineSettlement} />}
|
||||||
{activeTab === 'expired' && <ExpiredRewardsSection data={reportData.expiredRewards} />}
|
{activeTab === 'expired' && <ExpiredRewardsSection data={reportData.expiredRewards} />}
|
||||||
{activeTab === 'ledger' && <LedgerSection data={ledgerData} loading={ledgerLoading} onRefresh={loadLedgerData} />}
|
{activeTab === 'ledger' && <LedgerSection data={ledgerData} loading={ledgerLoading} onRefresh={loadLedgerData} />}
|
||||||
{/* [2026-01-06] 新增:收益类型汇总Tab内容 */}
|
{/* [2026-01-06] 更新:手续费账户汇总改用 wallet-service 的真正提现手续费数据 */}
|
||||||
{activeTab === 'feeAccount' && (
|
{activeTab === 'feeAccount' && (
|
||||||
<FeeAccountSection
|
<FeeAccountSection
|
||||||
data={rewardTypeSummaries?.feeAccountSummary}
|
|
||||||
loading={rewardTypeSummariesLoading}
|
|
||||||
onRefresh={loadRewardTypeSummaries}
|
onRefresh={loadRewardTypeSummaries}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -406,8 +408,35 @@ function RegionAccountsSection({ data, type }: { data: RegionAccountsSummary; ty
|
||||||
/**
|
/**
|
||||||
* 面对面结算统计区域
|
* 面对面结算统计区域
|
||||||
* [2026-01-05] 更新:USDT改为绿积分,添加空值检查
|
* [2026-01-05] 更新:USDT改为绿积分,添加空值检查
|
||||||
|
* [2026-01-07] 更新:添加明细列表显示
|
||||||
*/
|
*/
|
||||||
function OfflineSettlementSection({ data }: { data: SystemAccountReportResponse['offlineSettlement'] | null | undefined }) {
|
function OfflineSettlementSection({ data }: { data: SystemAccountReportResponse['offlineSettlement'] | null | undefined }) {
|
||||||
|
const [entriesData, setEntriesData] = useState<OfflineSettlementEntriesResponse | null>(null);
|
||||||
|
const [entriesLoading, setEntriesLoading] = useState(false);
|
||||||
|
const [showEntries, setShowEntries] = useState(false);
|
||||||
|
|
||||||
|
// 加载明细列表
|
||||||
|
const loadEntries = useCallback(async () => {
|
||||||
|
setEntriesLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await systemAccountReportService.getOfflineSettlementEntries({ pageSize: 100 });
|
||||||
|
if (response.data) {
|
||||||
|
setEntriesData(response.data);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load offline settlement entries:', err);
|
||||||
|
} finally {
|
||||||
|
setEntriesLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 切换显示明细时加载数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (showEntries && !entriesData && !entriesLoading) {
|
||||||
|
loadEntries();
|
||||||
|
}
|
||||||
|
}, [showEntries, entriesData, entriesLoading, loadEntries]);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
|
|
@ -460,7 +489,57 @@ function OfflineSettlementSection({ data }: { data: SystemAccountReportResponse[
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(!data.byMonth || data.byMonth.length === 0) && (
|
{/* 明细列表切换按钮 */}
|
||||||
|
<div className={styles.toggleSection}>
|
||||||
|
<button
|
||||||
|
className={styles.toggleButton}
|
||||||
|
onClick={() => setShowEntries(!showEntries)}
|
||||||
|
>
|
||||||
|
{showEntries ? '收起明细' : '查看明细列表'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 明细列表 */}
|
||||||
|
{showEntries && (
|
||||||
|
<div className={styles.entriesSection}>
|
||||||
|
<h4 className={styles.subTitle}>明细列表</h4>
|
||||||
|
{entriesLoading ? (
|
||||||
|
<div className={styles.loading}>加载中...</div>
|
||||||
|
) : entriesData && entriesData.entries.length > 0 ? (
|
||||||
|
<div className={styles.tableWrapper}>
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>账户</th>
|
||||||
|
<th>金额 (绿积分)</th>
|
||||||
|
<th>备注</th>
|
||||||
|
<th>时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{entriesData.entries.map((entry) => (
|
||||||
|
<tr key={entry.id}>
|
||||||
|
<td>{entry.accountSequence}</td>
|
||||||
|
<td>{formatAmount(entry.amount)}</td>
|
||||||
|
<td>{entry.memo || '-'}</td>
|
||||||
|
<td>{new Date(entry.createdAt).toLocaleString('zh-CN')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{entriesData.total > entriesData.entries.length && (
|
||||||
|
<div className={styles.paginationInfo}>
|
||||||
|
显示 {entriesData.entries.length} / {entriesData.total} 条
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.emptyTable}>暂无明细数据</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(!data.byMonth || data.byMonth.length === 0) && !showEntries && (
|
||||||
<div className={styles.emptyTable}>暂无面对面结算数据</div>
|
<div className={styles.emptyTable}>暂无面对面结算数据</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -777,39 +856,68 @@ function LedgerAccountCard({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [2026-01-06] 新增:手续费账户汇总组件
|
// [2026-01-06] 更新:手续费账户汇总组件 - 使用真正的提现手续费归集数据
|
||||||
|
// 数据来源: wallet-service 的 S0000000006 账户 FEE_COLLECTION 类型流水
|
||||||
|
// 而非 reward-service 的系统费用(成本费、运营费等)
|
||||||
/**
|
/**
|
||||||
* 手续费账户汇总区域
|
* 手续费账户汇总区域
|
||||||
*/
|
*/
|
||||||
function FeeAccountSection({
|
function FeeAccountSection({
|
||||||
data,
|
|
||||||
loading,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: {
|
}: {
|
||||||
data: FeeAccountSummary | undefined;
|
|
||||||
loading: boolean;
|
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [data, setData] = useState<FeeCollectionSummaryResponse | null>(null);
|
||||||
const [showDetails, setShowDetails] = useState(false);
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
const [detailsLoading, setDetailsLoading] = useState(false);
|
const [detailsLoading, setDetailsLoading] = useState(false);
|
||||||
const [detailsData, setDetailsData] = useState<RewardEntriesResponse | null>(null);
|
const [detailsData, setDetailsData] = useState<FeeCollectionEntriesResponse | null>(null);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
|
// 加载手续费归集汇总数据
|
||||||
|
const loadSummary = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await systemAccountReportService.getFeeCollectionSummary();
|
||||||
|
if (response.data) {
|
||||||
|
setData(response.data);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load fee collection summary:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 加载详细流水
|
||||||
const loadDetails = useCallback(async (page: number = 1) => {
|
const loadDetails = useCallback(async (page: number = 1) => {
|
||||||
setDetailsLoading(true);
|
setDetailsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await systemAccountReportService.getFeeEntriesDetailed({ page, pageSize: 20 });
|
const response = await systemAccountReportService.getFeeCollectionEntries({ page, pageSize: 20 });
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
setDetailsData(response.data);
|
setDetailsData(response.data);
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load fee entries details:', err);
|
console.error('Failed to load fee collection entries:', err);
|
||||||
} finally {
|
} finally {
|
||||||
setDetailsLoading(false);
|
setDetailsLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 初始加载
|
||||||
|
useEffect(() => {
|
||||||
|
loadSummary();
|
||||||
|
}, [loadSummary]);
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
loadSummary();
|
||||||
|
if (showDetails) {
|
||||||
|
loadDetails(currentPage);
|
||||||
|
}
|
||||||
|
onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleDetails = () => {
|
const handleToggleDetails = () => {
|
||||||
if (!showDetails && !detailsData) {
|
if (!showDetails && !detailsData) {
|
||||||
loadDetails(1);
|
loadDetails(1);
|
||||||
|
|
@ -831,7 +939,7 @@ function FeeAccountSection({
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.emptyTable}>
|
<div className={styles.emptyTable}>
|
||||||
<span>暂无手续费账户数据</span>
|
<span>暂无手续费账户数据</span>
|
||||||
<button onClick={onRefresh} className={styles.retryButton}>
|
<button onClick={handleRefresh} className={styles.retryButton}>
|
||||||
加载数据
|
加载数据
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -843,15 +951,25 @@ function FeeAccountSection({
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.sectionHeader}>
|
<div className={styles.sectionHeader}>
|
||||||
<h3 className={styles.sectionTitle}>手续费账户汇总</h3>
|
<h3 className={styles.sectionTitle}>手续费账户汇总</h3>
|
||||||
<button onClick={onRefresh} className={styles.refreshButton}>
|
<button onClick={handleRefresh} className={styles.refreshButton}>
|
||||||
刷新
|
刷新
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 账户信息 */}
|
||||||
|
<div className={styles.accountInfo}>
|
||||||
|
<span className={styles.accountLabel}>手续费归集账户: </span>
|
||||||
|
<span className={styles.accountValue}>{data.accountSequence}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 汇总卡片 */}
|
{/* 汇总卡片 */}
|
||||||
<div className={styles.summaryCards}>
|
<div className={styles.summaryCards}>
|
||||||
<div className={styles.summaryCard}>
|
<div className={styles.summaryCard}>
|
||||||
<span className={styles.summaryLabel}>总金额</span>
|
<span className={styles.summaryLabel}>当前余额</span>
|
||||||
|
<span className={styles.summaryValue}>{formatAmount(data.accountBalance)} 绿积分</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.summaryCard}>
|
||||||
|
<span className={styles.summaryLabel}>累计收取</span>
|
||||||
<span className={styles.summaryValue}>{formatAmount(data.totalAmount)} 绿积分</span>
|
<span className={styles.summaryValue}>{formatAmount(data.totalAmount)} 绿积分</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.summaryCard}>
|
<div className={styles.summaryCard}>
|
||||||
|
|
@ -860,23 +978,23 @@ function FeeAccountSection({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 按类型明细 */}
|
{/* 按手续费类型明细 */}
|
||||||
{data.breakdown && data.breakdown.length > 0 ? (
|
{data.breakdown && data.breakdown.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<h4 className={styles.subTitle}>按类型明细</h4>
|
<h4 className={styles.subTitle}>按手续费类型明细</h4>
|
||||||
<div className={styles.tableWrapper}>
|
<div className={styles.tableWrapper}>
|
||||||
<table className={styles.table}>
|
<table className={styles.table}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>费用类型</th>
|
<th>手续费类型</th>
|
||||||
<th>金额 (绿积分)</th>
|
<th>金额 (绿积分)</th>
|
||||||
<th>笔数</th>
|
<th>笔数</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.breakdown.map((item) => (
|
{data.breakdown.map((item) => (
|
||||||
<tr key={item.rightType}>
|
<tr key={item.feeType}>
|
||||||
<td>{FEE_TYPE_LABELS[item.rightType] || item.rightType}</td>
|
<td>{FEE_COLLECTION_TYPE_LABELS[item.feeType] || item.feeType}</td>
|
||||||
<td>{formatAmount(item.amount)}</td>
|
<td>{formatAmount(item.amount)}</td>
|
||||||
<td>{item.count}</td>
|
<td>{item.count}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -889,6 +1007,33 @@ function FeeAccountSection({
|
||||||
<div className={styles.emptyTable}>暂无手续费数据</div>
|
<div className={styles.emptyTable}>暂无手续费数据</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 按月统计 */}
|
||||||
|
{data.byMonth && data.byMonth.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h4 className={styles.subTitle}>月度统计</h4>
|
||||||
|
<div className={styles.tableWrapper}>
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>月份</th>
|
||||||
|
<th>金额 (绿积分)</th>
|
||||||
|
<th>笔数</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.byMonth.map((item) => (
|
||||||
|
<tr key={item.month}>
|
||||||
|
<td>{item.month}</td>
|
||||||
|
<td>{formatAmount(item.amount)}</td>
|
||||||
|
<td>{item.count}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 详细明细列表 */}
|
{/* 详细明细列表 */}
|
||||||
<div className={styles.detailsSection}>
|
<div className={styles.detailsSection}>
|
||||||
<button onClick={handleToggleDetails} className={styles.toggleDetailsButton}>
|
<button onClick={handleToggleDetails} className={styles.toggleDetailsButton}>
|
||||||
|
|
@ -909,26 +1054,20 @@ function FeeAccountSection({
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>时间</th>
|
<th>时间</th>
|
||||||
<th>账户</th>
|
<th>手续费类型</th>
|
||||||
<th>订单号</th>
|
<th>关联订单号</th>
|
||||||
<th>类型</th>
|
|
||||||
<th>金额 (绿积分)</th>
|
<th>金额 (绿积分)</th>
|
||||||
<th>状态</th>
|
<th>备注</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{detailsData.entries.map((entry) => (
|
{detailsData.entries.map((entry) => (
|
||||||
<tr key={entry.id}>
|
<tr key={entry.id}>
|
||||||
<td>{new Date(entry.createdAt).toLocaleString('zh-CN')}</td>
|
<td>{new Date(entry.createdAt).toLocaleString('zh-CN')}</td>
|
||||||
<td>{entry.accountSequence}</td>
|
<td>{FEE_COLLECTION_TYPE_LABELS[entry.feeType] || entry.feeType}</td>
|
||||||
<td className={styles.orderId}>{entry.sourceOrderId}</td>
|
<td className={styles.orderId}>{entry.refOrderId || '-'}</td>
|
||||||
<td>{REWARD_RIGHT_TYPE_LABELS[entry.rightType] || entry.rightType}</td>
|
<td>{formatAmount(entry.amount)}</td>
|
||||||
<td>{formatAmount(entry.usdtAmount)}</td>
|
<td>{entry.memo || '-'}</td>
|
||||||
<td>
|
|
||||||
<span className={`${styles.statusBadge} ${entry.rewardStatus === 'SETTLED' ? styles.settled : ''}`}>
|
|
||||||
{REWARD_STATUS_LABELS[entry.rewardStatus] || entry.rewardStatus}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
||||||
|
|
@ -230,10 +230,15 @@ export const API_ENDPOINTS = {
|
||||||
EXPIRED_REWARDS: '/v1/system-account-reports/expired-rewards',
|
EXPIRED_REWARDS: '/v1/system-account-reports/expired-rewards',
|
||||||
// [2026-01-05] 新增:所有系统账户分类账明细
|
// [2026-01-05] 新增:所有系统账户分类账明细
|
||||||
ALL_LEDGER: '/v1/system-account-reports/all-ledger',
|
ALL_LEDGER: '/v1/system-account-reports/all-ledger',
|
||||||
// [2026-01-06] 新增:收益类型汇总统计
|
// [2026-01-06] 新增:收益类型汇总统计(系统费用:成本费、运营费等)
|
||||||
REWARD_TYPE_SUMMARIES: '/v1/system-account-reports/reward-type-summaries',
|
REWARD_TYPE_SUMMARIES: '/v1/system-account-reports/reward-type-summaries',
|
||||||
// [2026-01-06] 新增:收益类型详细记录列表
|
// [2026-01-06] 新增:收益类型详细记录列表
|
||||||
REWARD_ENTRIES_BY_TYPE: '/v1/system-account-reports/reward-entries-by-type',
|
REWARD_ENTRIES_BY_TYPE: '/v1/system-account-reports/reward-entries-by-type',
|
||||||
FEE_ENTRIES_DETAILED: '/v1/system-account-reports/fee-entries-detailed',
|
FEE_ENTRIES_DETAILED: '/v1/system-account-reports/fee-entries-detailed',
|
||||||
|
// [2026-01-06] 新增:手续费归集账户统计(真正的提现手续费)
|
||||||
|
FEE_COLLECTION_SUMMARY: '/v1/system-account-reports/fee-collection-summary',
|
||||||
|
FEE_COLLECTION_ENTRIES: '/v1/system-account-reports/fee-collection-entries',
|
||||||
|
// [2026-01-07] 新增:面对面结算明细列表
|
||||||
|
OFFLINE_SETTLEMENT_ENTRIES: '/v1/system-account-reports/offline-settlement-entries',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ import type {
|
||||||
AllSystemAccountsLedgerResponse,
|
AllSystemAccountsLedgerResponse,
|
||||||
AllRewardTypeSummariesResponse,
|
AllRewardTypeSummariesResponse,
|
||||||
RewardEntriesResponse,
|
RewardEntriesResponse,
|
||||||
|
FeeCollectionSummaryResponse,
|
||||||
|
FeeCollectionEntriesResponse,
|
||||||
|
OfflineSettlementEntriesResponse,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -66,6 +69,19 @@ export const systemAccountReportService = {
|
||||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.OFFLINE_SETTLEMENT, { params });
|
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.OFFLINE_SETTLEMENT, { params });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:获取面对面结算明细列表
|
||||||
|
/**
|
||||||
|
* 获取面对面结算明细列表
|
||||||
|
*/
|
||||||
|
async getOfflineSettlementEntries(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}): Promise<ApiResponse<OfflineSettlementEntriesResponse>> {
|
||||||
|
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.OFFLINE_SETTLEMENT_ENTRIES, { params });
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取过期收益统计
|
* 获取过期收益统计
|
||||||
*/
|
*/
|
||||||
|
|
@ -103,7 +119,8 @@ export const systemAccountReportService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取手续费类型的详细记录列表
|
* 获取手续费类型的详细记录列表(系统费用:成本费、运营费等)
|
||||||
|
* @deprecated 使用 getFeeCollectionSummary 获取真正的提现手续费
|
||||||
*/
|
*/
|
||||||
async getFeeEntriesDetailed(params?: {
|
async getFeeEntriesDetailed(params?: {
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
@ -111,6 +128,31 @@ export const systemAccountReportService = {
|
||||||
}): Promise<ApiResponse<RewardEntriesResponse>> {
|
}): Promise<ApiResponse<RewardEntriesResponse>> {
|
||||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.FEE_ENTRIES_DETAILED, { params });
|
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.FEE_ENTRIES_DETAILED, { params });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// [2026-01-06] 新增:手续费归集账户统计(真正的提现手续费)
|
||||||
|
// 来源:wallet-service 中 S0000000006 账户的 FEE_COLLECTION 类型流水
|
||||||
|
// 回滚方式:删除以下方法
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取手续费归集账户汇总统计(提现手续费)
|
||||||
|
* 统计 S0000000006 账户收到的所有提现手续费
|
||||||
|
*/
|
||||||
|
async getFeeCollectionSummary(): Promise<ApiResponse<FeeCollectionSummaryResponse>> {
|
||||||
|
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.FEE_COLLECTION_SUMMARY);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取手续费归集账户流水明细(提现手续费)
|
||||||
|
*/
|
||||||
|
async getFeeCollectionEntries(params?: {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
feeType?: string;
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
}): Promise<ApiResponse<FeeCollectionEntriesResponse>> {
|
||||||
|
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.FEE_COLLECTION_ENTRIES, { params });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default systemAccountReportService;
|
export default systemAccountReportService;
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,31 @@ export interface OfflineSettlementSummary {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:面对面结算明细条目
|
||||||
|
/**
|
||||||
|
* 面对面结算明细条目
|
||||||
|
*/
|
||||||
|
export interface OfflineSettlementEntryDTO {
|
||||||
|
id: string;
|
||||||
|
accountSequence: string;
|
||||||
|
amount: number;
|
||||||
|
memo: string | null;
|
||||||
|
refOrderId: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [2026-01-07] 新增:面对面结算明细列表响应
|
||||||
|
/**
|
||||||
|
* 面对面结算明细列表响应
|
||||||
|
*/
|
||||||
|
export interface OfflineSettlementEntriesResponse {
|
||||||
|
entries: OfflineSettlementEntryDTO[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过期收益统计
|
* 过期收益统计
|
||||||
*/
|
*/
|
||||||
|
|
@ -302,3 +327,56 @@ export const REWARD_STATUS_LABELS: Record<string, string> = {
|
||||||
SETTLED: '已结算',
|
SETTLED: '已结算',
|
||||||
EXPIRED: '已过期',
|
EXPIRED: '已过期',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// [2026-01-06] 新增:手续费归集账户汇总类型(真正的提现手续费)
|
||||||
|
/**
|
||||||
|
* 手续费归集账户汇总响应
|
||||||
|
*/
|
||||||
|
export interface FeeCollectionSummaryResponse {
|
||||||
|
accountSequence: string;
|
||||||
|
accountBalance: number;
|
||||||
|
totalAmount: number;
|
||||||
|
totalCount: number;
|
||||||
|
breakdown: Array<{
|
||||||
|
feeType: string;
|
||||||
|
amount: number;
|
||||||
|
count: number;
|
||||||
|
}>;
|
||||||
|
byMonth: Array<{
|
||||||
|
month: string;
|
||||||
|
amount: number;
|
||||||
|
count: number;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手续费归集流水条目
|
||||||
|
*/
|
||||||
|
export interface FeeCollectionEntryDTO {
|
||||||
|
id: string;
|
||||||
|
feeType: string;
|
||||||
|
amount: number;
|
||||||
|
refOrderId: string | null;
|
||||||
|
memo: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手续费归集流水列表响应
|
||||||
|
*/
|
||||||
|
export interface FeeCollectionEntriesResponse {
|
||||||
|
entries: FeeCollectionEntryDTO[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手续费类型显示名称映射(提现手续费)
|
||||||
|
*/
|
||||||
|
export const FEE_COLLECTION_TYPE_LABELS: Record<string, string> = {
|
||||||
|
WITHDRAWAL_FEE: '区块链提现手续费',
|
||||||
|
FIAT_WITHDRAWAL_FEE: '法币提现手续费',
|
||||||
|
UNKNOWN: '未知类型',
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue