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:
hailin 2026-01-04 23:10:20 -08:00
parent 83384ff198
commit 838d5c1d3b
6 changed files with 196 additions and 85 deletions

View File

@ -501,6 +501,9 @@ services:
- KAFKA_BROKERS=kafka:29092 - KAFKA_BROKERS=kafka:29092
- KAFKA_CLIENT_ID=reporting-service - KAFKA_CLIENT_ID=reporting-service
- KAFKA_GROUP_ID=reporting-service-group - 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: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy

View File

@ -1,12 +1,37 @@
/** /**
* *
* [2026-01-04] * [2026-01-04]
* [2026-01-05] wallet-service
* application.module.ts * application.module.ts
*/ */
import { Injectable, Logger } from '@nestjs/common'; 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 { 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 { export interface SystemAccountReportResponse {
// 固定系统账户 // 固定系统账户
fixedAccounts: { fixedAccounts: {
costAccount: SystemAccountWithBalance | null; // 成本账户 S0000000001 costAccount: FixedAccountInfo | null; // 成本账户 S0000000001
operationAccount: SystemAccountWithBalance | null; // 运营账户 S0000000002 operationAccount: FixedAccountInfo | null; // 运营账户 S0000000002
hqCommunity: SystemAccountWithBalance | null; // 总部社区 S0000000003 hqCommunity: FixedAccountInfo | null; // 总部社区 S0000000003
rwadPoolPending: SystemAccountWithBalance | null; // RWAD待发放池 S0000000004 rwadPoolPending: FixedAccountInfo | null; // RWAD待发放池 S0000000004
platformFee: SystemAccountWithBalance | null; // 平台手续费 S0000000005 platformFee: FixedAccountInfo | null; // 平台手续费 S0000000005
}; };
// 省区域账户汇总 // 省区域账户汇总
provinceSummary: { provinceSummary: {
accounts: SystemAccountDTO[]; accounts: RegionAccountInfo[];
summary: { summary: {
totalBalance: string; totalBalance: string;
totalReceived: string; totalReceived: string;
@ -31,7 +56,7 @@ export interface SystemAccountReportResponse {
}; };
// 市区域账户汇总 // 市区域账户汇总
citySummary: { citySummary: {
accounts: SystemAccountDTO[]; accounts: RegionAccountInfo[];
summary: { summary: {
totalBalance: string; totalBalance: string;
totalReceived: string; totalReceived: string;
@ -46,33 +71,15 @@ export interface SystemAccountReportResponse {
generatedAt: string; generatedAt: string;
} }
/**
*
*/
export interface SystemAccountWithBalance extends SystemAccountDTO {
walletBalance?: number;
}
/** /**
* *
*/ */
const FIXED_ACCOUNT_TYPES = { const FIXED_ACCOUNT_TYPES: Record<string, string> = {
COST_ACCOUNT: 'costAccount', COST_ACCOUNT: 'costAccount',
OPERATION_ACCOUNT: 'operationAccount', OPERATION_ACCOUNT: 'operationAccount',
HQ_COMMUNITY: 'hqCommunity', HQ_COMMUNITY: 'hqCommunity',
RWAD_POOL_PENDING: 'rwadPoolPending', RWAD_POOL_PENDING: 'rwadPoolPending',
PLATFORM_FEE: 'platformFee', PLATFORM_FEE: 'platformFee',
} as const;
/**
*
*/
const FIXED_ACCOUNT_SEQUENCES = {
costAccount: 'S0000000001',
operationAccount: 'S0000000002',
hqCommunity: 'S0000000003',
rwadPoolPending: 'S0000000004',
platformFee: 'S0000000005',
}; };
@Injectable() @Injectable()
@ -82,11 +89,11 @@ export class SystemAccountReportApplicationService {
constructor( constructor(
private readonly walletServiceClient: WalletServiceClient, private readonly walletServiceClient: WalletServiceClient,
private readonly rewardServiceClient: RewardServiceClient, private readonly rewardServiceClient: RewardServiceClient,
private readonly authorizationServiceClient: AuthorizationServiceClient,
) {} ) {}
/** /**
* *
* [2026-01-05] wallet-service
*/ */
async getFullReport(params?: { async getFullReport(params?: {
startDate?: string; startDate?: string;
@ -96,26 +103,23 @@ export class SystemAccountReportApplicationService {
// 并行获取所有数据 // 并行获取所有数据
const [ const [
allAccountsSummary, allSystemAccounts,
provinceSummary,
citySummary,
offlineSettlement, offlineSettlement,
expiredRewards, expiredRewards,
fixedAccountsBalances,
] = await Promise.all([ ] = await Promise.all([
this.authorizationServiceClient.getAllAccountsSummary(), this.walletServiceClient.getAllSystemAccounts(),
this.authorizationServiceClient.getRegionAccountsList('province'),
this.authorizationServiceClient.getRegionAccountsList('city'),
this.walletServiceClient.getOfflineSettlementSummary(params), this.walletServiceClient.getOfflineSettlementSummary(params),
this.rewardServiceClient.getExpiredRewardsSummary(params), this.rewardServiceClient.getExpiredRewardsSummary(params),
this.getFixedAccountsBalances(),
]); ]);
// 组装固定账户数据 // 组装固定账户数据
const fixedAccounts = this.assembleFixedAccounts( const fixedAccounts = this.assembleFixedAccounts(allSystemAccounts.fixedAccounts);
allAccountsSummary.fixedAccounts,
fixedAccountsBalances, // 组装省账户汇总
); const provinceSummary = this.assembleRegionSummary(allSystemAccounts.provinceAccounts);
// 组装市账户汇总
const citySummary = this.assembleRegionSummary(allSystemAccounts.cityAccounts);
const report: SystemAccountReportResponse = { const report: SystemAccountReportResponse = {
fixedAccounts, fixedAccounts,
@ -133,33 +137,31 @@ export class SystemAccountReportApplicationService {
/** /**
* *
*/ */
async getFixedAccountsWithBalances(): Promise<SystemAccountWithBalance[]> { async getFixedAccountsWithBalances(): Promise<FixedAccountInfo[]> {
const [fixedAccounts, balances] = await Promise.all([ const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts();
this.authorizationServiceClient.getFixedAccountsList(), return allSystemAccounts.fixedAccounts;
this.getFixedAccountsBalances(),
]);
return fixedAccounts.map(account => {
const balance = balances.find(b => b.accountSequence === this.getAccountSequence(account));
return {
...account,
walletBalance: balance?.balance,
};
});
} }
/** /**
* *
*/ */
async getProvinceAccountsSummary(): Promise<RegionAccountsSummary> { async getProvinceAccountsSummary(): Promise<{
return this.authorizationServiceClient.getRegionAccountsList('province'); accounts: RegionAccountInfo[];
summary: { totalBalance: string; totalReceived: string; count: number };
}> {
const allSystemAccounts = await this.walletServiceClient.getAllSystemAccounts();
return this.assembleRegionSummary(allSystemAccounts.provinceAccounts);
} }
/** /**
* *
*/ */
async getCityAccountsSummary(): Promise<RegionAccountsSummary> { async getCityAccountsSummary(): Promise<{
return this.authorizationServiceClient.getRegionAccountsList('city'); 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); return this.rewardServiceClient.getExpiredRewardsSummary(params);
} }
/**
*
*/
private async getFixedAccountsBalances(): Promise<SystemAccountBalance[]> {
const sequences = Object.values(FIXED_ACCOUNT_SEQUENCES);
return this.walletServiceClient.getSystemAccountsBalances(sequences);
}
/** /**
* *
*/ */
private assembleFixedAccounts( private assembleFixedAccounts(
fixedAccounts: SystemAccountDTO[], fixedAccounts: AllSystemAccountsResponse['fixedAccounts'],
balances: SystemAccountBalance[],
): SystemAccountReportResponse['fixedAccounts'] { ): SystemAccountReportResponse['fixedAccounts'] {
const result: SystemAccountReportResponse['fixedAccounts'] = { const result: SystemAccountReportResponse['fixedAccounts'] = {
costAccount: null, costAccount: null,
@ -206,14 +199,9 @@ export class SystemAccountReportApplicationService {
}; };
for (const account of fixedAccounts) { for (const account of fixedAccounts) {
const fieldName = FIXED_ACCOUNT_TYPES[account.accountType as keyof typeof FIXED_ACCOUNT_TYPES]; const fieldName = FIXED_ACCOUNT_TYPES[account.accountType];
if (fieldName) { if (fieldName && fieldName in result) {
const sequence = FIXED_ACCOUNT_SEQUENCES[fieldName]; (result as any)[fieldName] = account;
const balance = balances.find(b => b.accountSequence === sequence);
result[fieldName] = {
...account,
walletBalance: balance?.balance,
};
} }
} }
@ -221,13 +209,29 @@ export class SystemAccountReportApplicationService {
} }
/** /**
* *
*/ */
private getAccountSequence(account: SystemAccountDTO): string | undefined { private assembleRegionSummary(
const fieldName = FIXED_ACCOUNT_TYPES[account.accountType as keyof typeof FIXED_ACCOUNT_TYPES]; accounts: RegionAccountInfo[],
if (fieldName) { ): {
return FIXED_ACCOUNT_SEQUENCES[fieldName]; 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,
},
};
} }
} }

View File

@ -35,7 +35,7 @@ export class RewardServiceClient {
) { ) {
this.baseUrl = this.configService.get<string>( this.baseUrl = this.configService.get<string>(
'REWARD_SERVICE_URL', 'REWARD_SERVICE_URL',
'http://reward-service:3004', 'http://rwa-reward-service:3005',
); );
} }

View File

@ -27,6 +27,35 @@ export interface SystemAccountBalance {
createdAt: string; 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() @Injectable()
export class WalletServiceClient { export class WalletServiceClient {
private readonly logger = new Logger(WalletServiceClient.name); private readonly logger = new Logger(WalletServiceClient.name);
@ -38,7 +67,7 @@ export class WalletServiceClient {
) { ) {
this.baseUrl = this.configService.get<string>( this.baseUrl = this.configService.get<string>(
'WALLET_SERVICE_URL', 'WALLET_SERVICE_URL',
'http://wallet-service:3002', 'http://rwa-wallet-service:3001',
); );
} }
@ -93,4 +122,29 @@ export class WalletServiceClient {
return []; 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: [],
};
}
}
} }

View File

@ -388,4 +388,15 @@ export class InternalWalletController {
this.logger.log(`系统账户余额查询结果: ${result.length} 个账户`); this.logger.log(`系统账户余额查询结果: ${result.length} 个账户`);
return result; 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;
}
} }

View File

@ -2953,4 +2953,43 @@ export class WalletApplicationService {
createdAt: wallet.createdAt.toISOString(), 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 };
}
}