feat(reporting): fix system account report to use wallet-service data
The system account balances were showing 0 because data was being fetched from authorization-service.system_accounts table instead of the actual wallet-service.wallet_accounts table where funds are stored. Changes: - wallet-service: Add getAllSystemAccounts() method to query all system accounts (fixed S*, province 9*, city 8*) with actual balances - wallet-service: Add /wallets/statistics/all-system-accounts API endpoint - reporting-service: Update SystemAccountReportApplicationService to fetch data from wallet-service instead of authorization-service - reporting-service: Fix default service URLs to use correct container names and ports (rwa-wallet-service:3001, rwa-reward-service:3005) - docker-compose: Add WALLET_SERVICE_URL and REWARD_SERVICE_URL env vars for reporting-service 🤖 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
83384ff198
commit
838d5c1d3b
|
|
@ -501,6 +501,9 @@ services:
|
|||
- KAFKA_BROKERS=kafka:29092
|
||||
- KAFKA_CLIENT_ID=reporting-service
|
||||
- KAFKA_GROUP_ID=reporting-service-group
|
||||
# [2026-01-05] 新增:系统账户报表统计所需的外部服务 URL
|
||||
- WALLET_SERVICE_URL=http://rwa-wallet-service:3001
|
||||
- REWARD_SERVICE_URL=http://rwa-reward-service:3005
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
|
|
@ -1,12 +1,37 @@
|
|||
/**
|
||||
* 系统账户报表应用服务
|
||||
* [2026-01-04] 新增:用于聚合各服务的系统账户报表数据
|
||||
* [2026-01-05] 修改:改为从 wallet-service 获取系统账户数据(实际余额所在位置)
|
||||
* 回滚方式:删除此文件,并从 application.module.ts 中移除注册
|
||||
*/
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { WalletServiceClient, OfflineSettlementSummary, SystemAccountBalance } from '../../infrastructure/external/wallet-service/wallet-service.client';
|
||||
import { WalletServiceClient, OfflineSettlementSummary, AllSystemAccountsResponse } from '../../infrastructure/external/wallet-service/wallet-service.client';
|
||||
import { RewardServiceClient, ExpiredRewardsSummary } from '../../infrastructure/external/reward-service/reward-service.client';
|
||||
import { AuthorizationServiceClient, SystemAccountDTO, RegionAccountsSummary, AllAccountsSummary } from '../../infrastructure/external/authorization-service/authorization-service.client';
|
||||
|
||||
/**
|
||||
* 固定系统账户信息
|
||||
*/
|
||||
export interface FixedAccountInfo {
|
||||
accountSequence: string;
|
||||
accountType: string;
|
||||
usdtBalance: string;
|
||||
totalReceived: string;
|
||||
totalTransferred: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域账户信息
|
||||
*/
|
||||
export interface RegionAccountInfo {
|
||||
accountSequence: string;
|
||||
regionCode: string;
|
||||
regionName: string;
|
||||
usdtBalance: string;
|
||||
totalReceived: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统账户报表完整响应
|
||||
|
|
@ -14,15 +39,15 @@ import { AuthorizationServiceClient, SystemAccountDTO, RegionAccountsSummary, Al
|
|||
export interface SystemAccountReportResponse {
|
||||
// 固定系统账户
|
||||
fixedAccounts: {
|
||||
costAccount: SystemAccountWithBalance | null; // 成本账户 S0000000001
|
||||
operationAccount: SystemAccountWithBalance | null; // 运营账户 S0000000002
|
||||
hqCommunity: SystemAccountWithBalance | null; // 总部社区 S0000000003
|
||||
rwadPoolPending: SystemAccountWithBalance | null; // RWAD待发放池 S0000000004
|
||||
platformFee: SystemAccountWithBalance | null; // 平台手续费 S0000000005
|
||||
costAccount: FixedAccountInfo | null; // 成本账户 S0000000001
|
||||
operationAccount: FixedAccountInfo | null; // 运营账户 S0000000002
|
||||
hqCommunity: FixedAccountInfo | null; // 总部社区 S0000000003
|
||||
rwadPoolPending: FixedAccountInfo | null; // RWAD待发放池 S0000000004
|
||||
platformFee: FixedAccountInfo | null; // 平台手续费 S0000000005
|
||||
};
|
||||
// 省区域账户汇总
|
||||
provinceSummary: {
|
||||
accounts: SystemAccountDTO[];
|
||||
accounts: RegionAccountInfo[];
|
||||
summary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
|
|
@ -31,7 +56,7 @@ export interface SystemAccountReportResponse {
|
|||
};
|
||||
// 市区域账户汇总
|
||||
citySummary: {
|
||||
accounts: SystemAccountDTO[];
|
||||
accounts: RegionAccountInfo[];
|
||||
summary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
|
|
@ -46,33 +71,15 @@ export interface SystemAccountReportResponse {
|
|||
generatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统账户带钱包余额信息
|
||||
*/
|
||||
export interface SystemAccountWithBalance extends SystemAccountDTO {
|
||||
walletBalance?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 固定账户类型映射
|
||||
*/
|
||||
const FIXED_ACCOUNT_TYPES = {
|
||||
const FIXED_ACCOUNT_TYPES: Record<string, string> = {
|
||||
COST_ACCOUNT: 'costAccount',
|
||||
OPERATION_ACCOUNT: 'operationAccount',
|
||||
HQ_COMMUNITY: 'hqCommunity',
|
||||
RWAD_POOL_PENDING: 'rwadPoolPending',
|
||||
PLATFORM_FEE: 'platformFee',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 固定账户序列号映射
|
||||
*/
|
||||
const FIXED_ACCOUNT_SEQUENCES = {
|
||||
costAccount: 'S0000000001',
|
||||
operationAccount: 'S0000000002',
|
||||
hqCommunity: 'S0000000003',
|
||||
rwadPoolPending: 'S0000000004',
|
||||
platformFee: 'S0000000005',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -82,11 +89,11 @@ export class SystemAccountReportApplicationService {
|
|||
constructor(
|
||||
private readonly walletServiceClient: WalletServiceClient,
|
||||
private readonly rewardServiceClient: RewardServiceClient,
|
||||
private readonly authorizationServiceClient: AuthorizationServiceClient,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取完整的系统账户报表
|
||||
* [2026-01-05] 修改:从 wallet-service 获取所有系统账户数据
|
||||
*/
|
||||
async getFullReport(params?: {
|
||||
startDate?: string;
|
||||
|
|
@ -96,26 +103,23 @@ export class SystemAccountReportApplicationService {
|
|||
|
||||
// 并行获取所有数据
|
||||
const [
|
||||
allAccountsSummary,
|
||||
provinceSummary,
|
||||
citySummary,
|
||||
allSystemAccounts,
|
||||
offlineSettlement,
|
||||
expiredRewards,
|
||||
fixedAccountsBalances,
|
||||
] = await Promise.all([
|
||||
this.authorizationServiceClient.getAllAccountsSummary(),
|
||||
this.authorizationServiceClient.getRegionAccountsList('province'),
|
||||
this.authorizationServiceClient.getRegionAccountsList('city'),
|
||||
this.walletServiceClient.getAllSystemAccounts(),
|
||||
this.walletServiceClient.getOfflineSettlementSummary(params),
|
||||
this.rewardServiceClient.getExpiredRewardsSummary(params),
|
||||
this.getFixedAccountsBalances(),
|
||||
]);
|
||||
|
||||
// 组装固定账户数据
|
||||
const fixedAccounts = this.assembleFixedAccounts(
|
||||
allAccountsSummary.fixedAccounts,
|
||||
fixedAccountsBalances,
|
||||
);
|
||||
const fixedAccounts = this.assembleFixedAccounts(allSystemAccounts.fixedAccounts);
|
||||
|
||||
// 组装省账户汇总
|
||||
const provinceSummary = this.assembleRegionSummary(allSystemAccounts.provinceAccounts);
|
||||
|
||||
// 组装市账户汇总
|
||||
const citySummary = this.assembleRegionSummary(allSystemAccounts.cityAccounts);
|
||||
|
||||
const report: SystemAccountReportResponse = {
|
||||
fixedAccounts,
|
||||
|
|
@ -133,33 +137,31 @@ export class SystemAccountReportApplicationService {
|
|||
/**
|
||||
* 获取固定账户列表(带余额)
|
||||
*/
|
||||
async getFixedAccountsWithBalances(): Promise<SystemAccountWithBalance[]> {
|
||||
const [fixedAccounts, balances] = await Promise.all([
|
||||
this.authorizationServiceClient.getFixedAccountsList(),
|
||||
this.getFixedAccountsBalances(),
|
||||
]);
|
||||
|
||||
return fixedAccounts.map(account => {
|
||||
const balance = balances.find(b => b.accountSequence === this.getAccountSequence(account));
|
||||
return {
|
||||
...account,
|
||||
walletBalance: balance?.balance,
|
||||
};
|
||||
});
|
||||
async getFixedAccountsWithBalances(): Promise<FixedAccountInfo[]> {
|
||||
const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts();
|
||||
return allSystemAccounts.fixedAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取省区域账户汇总
|
||||
*/
|
||||
async getProvinceAccountsSummary(): Promise<RegionAccountsSummary> {
|
||||
return this.authorizationServiceClient.getRegionAccountsList('province');
|
||||
async getProvinceAccountsSummary(): Promise<{
|
||||
accounts: RegionAccountInfo[];
|
||||
summary: { totalBalance: string; totalReceived: string; count: number };
|
||||
}> {
|
||||
const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts();
|
||||
return this.assembleRegionSummary(allSystemAccounts.provinceAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取市区域账户汇总
|
||||
*/
|
||||
async getCityAccountsSummary(): Promise<RegionAccountsSummary> {
|
||||
return this.authorizationServiceClient.getRegionAccountsList('city');
|
||||
async getCityAccountsSummary(): Promise<{
|
||||
accounts: RegionAccountInfo[];
|
||||
summary: { totalBalance: string; totalReceived: string; count: number };
|
||||
}> {
|
||||
const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts();
|
||||
return this.assembleRegionSummary(allSystemAccounts.cityAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -182,20 +184,11 @@ export class SystemAccountReportApplicationService {
|
|||
return this.rewardServiceClient.getExpiredRewardsSummary(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取固定账户余额
|
||||
*/
|
||||
private async getFixedAccountsBalances(): Promise<SystemAccountBalance[]> {
|
||||
const sequences = Object.values(FIXED_ACCOUNT_SEQUENCES);
|
||||
return this.walletServiceClient.getSystemAccountsBalances(sequences);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装固定账户数据
|
||||
*/
|
||||
private assembleFixedAccounts(
|
||||
fixedAccounts: SystemAccountDTO[],
|
||||
balances: SystemAccountBalance[],
|
||||
fixedAccounts: AllSystemAccountsResponse['fixedAccounts'],
|
||||
): SystemAccountReportResponse['fixedAccounts'] {
|
||||
const result: SystemAccountReportResponse['fixedAccounts'] = {
|
||||
costAccount: null,
|
||||
|
|
@ -206,14 +199,9 @@ export class SystemAccountReportApplicationService {
|
|||
};
|
||||
|
||||
for (const account of fixedAccounts) {
|
||||
const fieldName = FIXED_ACCOUNT_TYPES[account.accountType as keyof typeof FIXED_ACCOUNT_TYPES];
|
||||
if (fieldName) {
|
||||
const sequence = FIXED_ACCOUNT_SEQUENCES[fieldName];
|
||||
const balance = balances.find(b => b.accountSequence === sequence);
|
||||
result[fieldName] = {
|
||||
...account,
|
||||
walletBalance: balance?.balance,
|
||||
};
|
||||
const fieldName = FIXED_ACCOUNT_TYPES[account.accountType];
|
||||
if (fieldName && fieldName in result) {
|
||||
(result as any)[fieldName] = account;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,13 +209,29 @@ export class SystemAccountReportApplicationService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 根据账户类型获取账户序列号
|
||||
* 组装区域账户汇总
|
||||
*/
|
||||
private getAccountSequence(account: SystemAccountDTO): string | undefined {
|
||||
const fieldName = FIXED_ACCOUNT_TYPES[account.accountType as keyof typeof FIXED_ACCOUNT_TYPES];
|
||||
if (fieldName) {
|
||||
return FIXED_ACCOUNT_SEQUENCES[fieldName];
|
||||
private assembleRegionSummary(
|
||||
accounts: RegionAccountInfo[],
|
||||
): {
|
||||
accounts: RegionAccountInfo[];
|
||||
summary: { totalBalance: string; totalReceived: string; count: number };
|
||||
} {
|
||||
let totalBalance = 0;
|
||||
let totalReceived = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
totalBalance += parseFloat(account.usdtBalance) || 0;
|
||||
totalReceived += parseFloat(account.totalReceived) || 0;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
return {
|
||||
accounts,
|
||||
summary: {
|
||||
totalBalance: totalBalance.toFixed(8),
|
||||
totalReceived: totalReceived.toFixed(8),
|
||||
count: accounts.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export class RewardServiceClient {
|
|||
) {
|
||||
this.baseUrl = this.configService.get<string>(
|
||||
'REWARD_SERVICE_URL',
|
||||
'http://reward-service:3004',
|
||||
'http://rwa-reward-service:3005',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,35 @@ export interface SystemAccountBalance {
|
|||
createdAt: string;
|
||||
}
|
||||
|
||||
// [2026-01-05] 新增:所有系统账户响应类型
|
||||
export interface AllSystemAccountsResponse {
|
||||
fixedAccounts: Array<{
|
||||
accountSequence: string;
|
||||
accountType: string;
|
||||
usdtBalance: string;
|
||||
totalReceived: string;
|
||||
totalTransferred: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
provinceAccounts: Array<{
|
||||
accountSequence: string;
|
||||
regionCode: string;
|
||||
regionName: string;
|
||||
usdtBalance: string;
|
||||
totalReceived: string;
|
||||
status: string;
|
||||
}>;
|
||||
cityAccounts: Array<{
|
||||
accountSequence: string;
|
||||
regionCode: string;
|
||||
regionName: string;
|
||||
usdtBalance: string;
|
||||
totalReceived: string;
|
||||
status: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WalletServiceClient {
|
||||
private readonly logger = new Logger(WalletServiceClient.name);
|
||||
|
|
@ -38,7 +67,7 @@ export class WalletServiceClient {
|
|||
) {
|
||||
this.baseUrl = this.configService.get<string>(
|
||||
'WALLET_SERVICE_URL',
|
||||
'http://wallet-service:3002',
|
||||
'http://rwa-wallet-service:3001',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -93,4 +122,29 @@ export class WalletServiceClient {
|
|||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// [2026-01-05] 新增:获取所有系统账户(固定+区域)
|
||||
/**
|
||||
* 获取所有系统账户列表
|
||||
* 从 wallet-service 获取实际余额数据
|
||||
*/
|
||||
async getAllSystemAccounts(): Promise<AllSystemAccountsResponse> {
|
||||
try {
|
||||
const url = `${this.baseUrl}/wallets/statistics/all-system-accounts`;
|
||||
this.logger.debug(`[getAllSystemAccounts] 请求: ${url}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get<AllSystemAccountsResponse>(url),
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getAllSystemAccounts] 失败: ${error.message}`);
|
||||
return {
|
||||
fixedAccounts: [],
|
||||
provinceAccounts: [],
|
||||
cityAccounts: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,4 +388,15 @@ export class InternalWalletController {
|
|||
this.logger.log(`系统账户余额查询结果: ${result.length} 个账户`);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('statistics/all-system-accounts')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '获取所有系统账户列表(内部API) - 用于系统账户报表' })
|
||||
@ApiResponse({ status: 200, description: '所有系统账户列表(固定+区域)' })
|
||||
async getAllSystemAccounts() {
|
||||
this.logger.log(`========== statistics/all-system-accounts 请求 ==========`);
|
||||
const result = await this.walletService.getAllSystemAccounts();
|
||||
this.logger.log(`系统账户查询结果: 固定=${result.fixedAccounts.length}, 省=${result.provinceAccounts.length}, 市=${result.cityAccounts.length}`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2953,4 +2953,43 @@ export class WalletApplicationService {
|
|||
createdAt: wallet.createdAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计 API - 增强版 ===============
|
||||
// [2026-01-05] 新增:获取所有系统账户列表(固定+区域)
|
||||
// 回滚方式:删除以下方法
|
||||
|
||||
async getAllSystemAccounts(): Promise<{
|
||||
fixedAccounts: Array<{ accountSequence: string; accountType: string; usdtBalance: string; totalReceived: string; totalTransferred: string; status: string; createdAt: string }>;
|
||||
provinceAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }>;
|
||||
cityAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }>;
|
||||
}> {
|
||||
this.logger.log('[getAllSystemAccounts] 查询所有系统账户...');
|
||||
const wallets = await this.prisma.walletAccount.findMany({
|
||||
where: { OR: [{ accountSequence: { startsWith: 'S' } }, { accountSequence: { startsWith: '9' } }, { accountSequence: { startsWith: '8' } }] },
|
||||
select: { accountSequence: true, usdtAvailable: true, status: true, createdAt: true },
|
||||
});
|
||||
const accountSeqs = wallets.map(w => w.accountSequence);
|
||||
const receivedStats2 = await this.prisma.ledgerEntry.groupBy({ by: ['accountSequence'], where: { accountSequence: { in: accountSeqs }, amount: { gt: 0 } }, _sum: { amount: true } });
|
||||
const transferredStats = await this.prisma.ledgerEntry.groupBy({ by: ['accountSequence'], where: { accountSequence: { in: accountSeqs }, amount: { lt: 0 } }, _sum: { amount: true } });
|
||||
const receivedMap2 = new Map(receivedStats2.map(s => [s.accountSequence, s._sum.amount]));
|
||||
const transferredMap = new Map(transferredStats.map(s => [s.accountSequence, s._sum.amount]));
|
||||
const fixedAccountTypes: Record<string, string> = { 'S0000000001': 'COST_ACCOUNT', 'S0000000002': 'OPERATION_ACCOUNT', 'S0000000003': 'HQ_COMMUNITY', 'S0000000004': 'RWAD_POOL_PENDING', 'S0000000005': 'PLATFORM_FEE' };
|
||||
const fixedAccounts: Array<{ accountSequence: string; accountType: string; usdtBalance: string; totalReceived: string; totalTransferred: string; status: string; createdAt: string }> = [];
|
||||
const provinceAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }> = [];
|
||||
const cityAccounts: Array<{ accountSequence: string; regionCode: string; regionName: string; usdtBalance: string; totalReceived: string; status: string }> = [];
|
||||
for (const wallet of wallets) {
|
||||
const seq = wallet.accountSequence;
|
||||
const received = Number(receivedMap2.get(seq) || 0);
|
||||
const transferred = Math.abs(Number(transferredMap.get(seq) || 0));
|
||||
if (seq.startsWith('S')) {
|
||||
fixedAccounts.push({ accountSequence: seq, accountType: fixedAccountTypes[seq] || 'UNKNOWN', usdtBalance: String(wallet.usdtAvailable), totalReceived: String(received), totalTransferred: String(transferred), status: wallet.status, createdAt: wallet.createdAt.toISOString() });
|
||||
} else if (seq.startsWith('9')) {
|
||||
provinceAccounts.push({ accountSequence: seq, regionCode: seq.substring(1), regionName: '省区域 ' + seq.substring(1), usdtBalance: String(wallet.usdtAvailable), totalReceived: String(received), status: wallet.status });
|
||||
} else if (seq.startsWith('8')) {
|
||||
cityAccounts.push({ accountSequence: seq, regionCode: seq.substring(1), regionName: '市区域 ' + seq.substring(1), usdtBalance: String(wallet.usdtAvailable), totalReceived: String(received), status: wallet.status });
|
||||
}
|
||||
}
|
||||
this.logger.log('[getAllSystemAccounts] 固定: ' + fixedAccounts.length + ', 省: ' + provinceAccounts.length + ', 市: ' + cityAccounts.length);
|
||||
return { fixedAccounts, provinceAccounts, cityAccounts };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue