feat(reporting): add system account report aggregation feature
## Changes - Add system account report aggregation APIs in reporting-service - Add internal statistics APIs in wallet-service, reward-service, authorization-service - Add system accounts tab in admin-web statistics page - Enhanced metadata in reward entries for traceability ## Backend Changes - wallet-service: Add offline settlement summary and system accounts balances APIs - reward-service: Add expired rewards summary API - authorization-service: Add fixed accounts list, region accounts summary APIs - reporting-service: Add HTTP clients and aggregation service for system account reports ## Frontend Changes - admin-web: Add SystemAccountsTab component with fixed accounts, region summaries, offline settlement stats, and expired rewards display ## Rollback Instructions Each file includes rollback comments with [2026-01-04] tag marking new additions. To rollback: delete files marked as new, remove code sections marked with date comments. 🤖 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
99b2b10ba0
commit
6e395ce58c
|
|
@ -1,6 +1,6 @@
|
|||
import { Controller, Get, Query, Logger } from '@nestjs/common'
|
||||
import { ApiTags, ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger'
|
||||
import { AuthorizationApplicationService } from '@/application/services'
|
||||
import { AuthorizationApplicationService, SystemAccountApplicationService } from '@/application/services'
|
||||
|
||||
/**
|
||||
* 内部授权 API - 供其他微服务调用
|
||||
|
|
@ -11,7 +11,11 @@ import { AuthorizationApplicationService } from '@/application/services'
|
|||
export class InternalAuthorizationController {
|
||||
private readonly logger = new Logger(InternalAuthorizationController.name)
|
||||
|
||||
constructor(private readonly applicationService: AuthorizationApplicationService) {}
|
||||
constructor(
|
||||
private readonly applicationService: AuthorizationApplicationService,
|
||||
// [2026-01-04] 新增:用于系统账户报表统计
|
||||
private readonly systemAccountService: SystemAccountApplicationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 查找用户推荐链中最近的社区授权用户
|
||||
|
|
@ -280,4 +284,60 @@ export class InternalAuthorizationController {
|
|||
Number(treeCount),
|
||||
)
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计 API ===============
|
||||
// [2026-01-04] 新增:用于 reporting-service 聚合系统账户报表数据
|
||||
// 回滚方式:删除以下 API 方法即可
|
||||
|
||||
/**
|
||||
* 获取所有固定系统账户列表
|
||||
* 包括:总部社区、成本账户、运营账户、RWAD底池
|
||||
*/
|
||||
@Get('statistics/fixed-accounts')
|
||||
@ApiOperation({ summary: '获取固定系统账户列表(内部 API)- 用于系统账户报表' })
|
||||
@ApiResponse({ status: 200, description: '固定系统账户列表' })
|
||||
async getFixedAccountsList() {
|
||||
this.logger.log(`========== statistics/fixed-accounts 请求 ==========`)
|
||||
|
||||
const result = await this.systemAccountService.getAllFixedAccounts()
|
||||
|
||||
this.logger.log(`固定账户查询结果: ${result.length} 个账户`)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域系统账户列表
|
||||
* 支持按省份或城市类型筛选
|
||||
*/
|
||||
@Get('statistics/region-accounts')
|
||||
@ApiOperation({ summary: '获取区域系统账户列表(内部 API)- 用于系统账户报表' })
|
||||
@ApiQuery({ name: 'type', description: '账户类型: province 或 city' })
|
||||
@ApiResponse({ status: 200, description: '区域系统账户列表' })
|
||||
async getRegionAccountsList(
|
||||
@Query('type') type: 'province' | 'city',
|
||||
) {
|
||||
this.logger.log(`========== statistics/region-accounts 请求 ==========`)
|
||||
this.logger.log(`type: ${type}`)
|
||||
|
||||
const result = await this.systemAccountService.getRegionAccountsSummary(type)
|
||||
|
||||
this.logger.log(`区域账户查询结果: ${result.accounts.length} 个账户, 总余额: ${result.summary.totalBalance}`)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统账户汇总统计
|
||||
* 包括固定账户和区域账户的汇总
|
||||
*/
|
||||
@Get('statistics/all-accounts-summary')
|
||||
@ApiOperation({ summary: '获取所有系统账户汇总(内部 API)- 用于系统账户报表' })
|
||||
@ApiResponse({ status: 200, description: '所有系统账户汇总' })
|
||||
async getAllAccountsSummary() {
|
||||
this.logger.log(`========== statistics/all-accounts-summary 请求 ==========`)
|
||||
|
||||
const result = await this.systemAccountService.getAllAccountsSummary()
|
||||
|
||||
this.logger.log(`所有账户汇总: 固定${result.fixedAccounts.length}个, 省${result.provinceSummary.count}个, 市${result.citySummary.count}个`)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -434,4 +434,79 @@ export class SystemAccountApplicationService implements OnModuleInit {
|
|||
createdAt: entry.createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计方法 ===============
|
||||
// [2026-01-04] 新增:用于 reporting-service 聚合系统账户报表数据
|
||||
// 回滚方式:删除以下方法即可
|
||||
|
||||
/**
|
||||
* 获取区域系统账户汇总
|
||||
* 用于系统账户报表展示省/市区域账户列表和汇总
|
||||
*/
|
||||
async getRegionAccountsSummary(type: 'province' | 'city'): Promise<{
|
||||
accounts: SystemAccountDTO[]
|
||||
summary: {
|
||||
totalBalance: string
|
||||
totalReceived: string
|
||||
count: number
|
||||
}
|
||||
}> {
|
||||
const accountType = type === 'province'
|
||||
? SystemAccountType.SYSTEM_PROVINCE
|
||||
: SystemAccountType.SYSTEM_CITY
|
||||
|
||||
const accounts = await this.systemAccountRepository.findAllRegionAccounts(accountType)
|
||||
|
||||
// 计算汇总
|
||||
let totalBalance = new Decimal(0)
|
||||
let totalReceived = new Decimal(0)
|
||||
|
||||
const accountDTOs = accounts.map((account) => {
|
||||
totalBalance = totalBalance.plus(account.usdtBalance)
|
||||
totalReceived = totalReceived.plus(account.totalReceived)
|
||||
return this.toDTO(account)
|
||||
})
|
||||
|
||||
return {
|
||||
accounts: accountDTOs,
|
||||
summary: {
|
||||
totalBalance: totalBalance.toString(),
|
||||
totalReceived: totalReceived.toString(),
|
||||
count: accounts.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统账户汇总(固定+区域)
|
||||
* 用于系统账户报表概览
|
||||
*/
|
||||
async getAllAccountsSummary(): Promise<{
|
||||
fixedAccounts: SystemAccountDTO[]
|
||||
provinceSummary: {
|
||||
totalBalance: string
|
||||
totalReceived: string
|
||||
count: number
|
||||
}
|
||||
citySummary: {
|
||||
totalBalance: string
|
||||
totalReceived: string
|
||||
count: number
|
||||
}
|
||||
}> {
|
||||
// 获取固定账户
|
||||
const fixedAccounts = await this.getAllFixedAccounts()
|
||||
|
||||
// 获取省区域账户汇总
|
||||
const provinceSummary = await this.getRegionAccountsSummary('province')
|
||||
|
||||
// 获取市区域账户汇总
|
||||
const citySummary = await this.getRegionAccountsSummary('city')
|
||||
|
||||
return {
|
||||
fixedAccounts,
|
||||
provinceSummary: provinceSummary.summary,
|
||||
citySummary: citySummary.summary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
|
|
@ -1651,6 +1652,17 @@
|
|||
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nestjs/axios": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz",
|
||||
"integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"axios": "^1.3.1",
|
||||
"rxjs": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cli": {
|
||||
"version": "10.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz",
|
||||
|
|
@ -3559,6 +3571,7 @@
|
|||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
"prisma:studio": "prisma studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
/**
|
||||
* API 模块
|
||||
* [2026-01-04] 更新:新增 SystemAccountReportController 用于系统账户报表
|
||||
* 回滚方式:删除 SystemAccountReportController 的导入和注册
|
||||
*/
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ApplicationModule } from '../application/application.module';
|
||||
import { HealthController } from './controllers/health.controller';
|
||||
import { ReportController } from './controllers/report.controller';
|
||||
import { ExportController } from './controllers/export.controller';
|
||||
import { DashboardController } from './controllers/dashboard.controller';
|
||||
// [2026-01-04] 新增:系统账户报表控制器
|
||||
import { SystemAccountReportController } from './controllers/system-account-report.controller';
|
||||
|
||||
@Module({
|
||||
imports: [ApplicationModule],
|
||||
|
|
@ -12,6 +19,8 @@ import { DashboardController } from './controllers/dashboard.controller';
|
|||
ReportController,
|
||||
ExportController,
|
||||
DashboardController,
|
||||
// [2026-01-04] 新增:系统账户报表控制器
|
||||
SystemAccountReportController,
|
||||
],
|
||||
})
|
||||
export class ApiModule {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* 系统账户报表控制器
|
||||
* [2026-01-04] 新增:提供系统账户报表相关API
|
||||
* 回滚方式:删除此文件,并从 api.module.ts 中移除注册
|
||||
*/
|
||||
import { Controller, Get, Query, Logger } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
|
||||
import { SystemAccountReportApplicationService } from '../../application/services/system-account-report-application.service';
|
||||
|
||||
@ApiTags('System Account Reports')
|
||||
@Controller('system-account-reports')
|
||||
export class SystemAccountReportController {
|
||||
private readonly logger = new Logger(SystemAccountReportController.name);
|
||||
|
||||
constructor(
|
||||
private readonly systemAccountReportService: SystemAccountReportApplicationService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: '获取完整系统账户报表' })
|
||||
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||
@ApiResponse({ status: 200, description: '系统账户报表数据' })
|
||||
async getFullReport(
|
||||
@Query('startDate') startDate?: string,
|
||||
@Query('endDate') endDate?: string,
|
||||
) {
|
||||
this.logger.log(`[getFullReport] 请求系统账户报表, startDate=${startDate}, endDate=${endDate}`);
|
||||
return this.systemAccountReportService.getFullReport({ startDate, endDate });
|
||||
}
|
||||
|
||||
@Get('fixed-accounts')
|
||||
@ApiOperation({ summary: '获取固定系统账户列表(带余额)' })
|
||||
@ApiResponse({ status: 200, description: '固定系统账户列表' })
|
||||
async getFixedAccounts() {
|
||||
this.logger.log('[getFixedAccounts] 请求固定系统账户列表');
|
||||
return this.systemAccountReportService.getFixedAccountsWithBalances();
|
||||
}
|
||||
|
||||
@Get('province-summary')
|
||||
@ApiOperation({ summary: '获取省区域账户汇总' })
|
||||
@ApiResponse({ status: 200, description: '省区域账户汇总' })
|
||||
async getProvinceSummary() {
|
||||
this.logger.log('[getProvinceSummary] 请求省区域账户汇总');
|
||||
return this.systemAccountReportService.getProvinceAccountsSummary();
|
||||
}
|
||||
|
||||
@Get('city-summary')
|
||||
@ApiOperation({ summary: '获取市区域账户汇总' })
|
||||
@ApiResponse({ status: 200, description: '市区域账户汇总' })
|
||||
async getCitySummary() {
|
||||
this.logger.log('[getCitySummary] 请求市区域账户汇总');
|
||||
return this.systemAccountReportService.getCityAccountsSummary();
|
||||
}
|
||||
|
||||
@Get('offline-settlement')
|
||||
@ApiOperation({ summary: '获取面对面结算统计' })
|
||||
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||
@ApiResponse({ status: 200, description: '面对面结算统计' })
|
||||
async getOfflineSettlement(
|
||||
@Query('startDate') startDate?: string,
|
||||
@Query('endDate') endDate?: string,
|
||||
) {
|
||||
this.logger.log(`[getOfflineSettlement] 请求面对面结算统计, startDate=${startDate}, endDate=${endDate}`);
|
||||
return this.systemAccountReportService.getOfflineSettlementSummary({ startDate, endDate });
|
||||
}
|
||||
|
||||
@Get('expired-rewards')
|
||||
@ApiOperation({ summary: '获取过期收益统计' })
|
||||
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||
@ApiResponse({ status: 200, description: '过期收益统计' })
|
||||
async getExpiredRewards(
|
||||
@Query('startDate') startDate?: string,
|
||||
@Query('endDate') endDate?: string,
|
||||
) {
|
||||
this.logger.log(`[getExpiredRewards] 请求过期收益统计, startDate=${startDate}, endDate=${endDate}`);
|
||||
return this.systemAccountReportService.getExpiredRewardsSummary({ startDate, endDate });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* 应用层模块
|
||||
* [2026-01-04] 更新:新增 SystemAccountReportApplicationService 用于系统账户报表聚合
|
||||
* 回滚方式:删除 SystemAccountReportApplicationService 的导入和注册
|
||||
*/
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { DomainModule } from '../domain/domain.module';
|
||||
|
|
@ -7,6 +12,8 @@ import { ExportReportHandler } from './commands/export-report/export-report.hand
|
|||
import { GetReportSnapshotHandler } from './queries/get-report-snapshot/get-report-snapshot.handler';
|
||||
import { ReportingApplicationService } from './services/reporting-application.service';
|
||||
import { DashboardApplicationService } from './services/dashboard-application.service';
|
||||
// [2026-01-04] 新增:系统账户报表聚合服务
|
||||
import { SystemAccountReportApplicationService } from './services/system-account-report-application.service';
|
||||
import { ReportGenerationScheduler } from './schedulers/report-generation.scheduler';
|
||||
|
||||
@Module({
|
||||
|
|
@ -17,6 +24,8 @@ import { ReportGenerationScheduler } from './schedulers/report-generation.schedu
|
|||
GetReportSnapshotHandler,
|
||||
ReportingApplicationService,
|
||||
DashboardApplicationService,
|
||||
// [2026-01-04] 新增:系统账户报表聚合服务
|
||||
SystemAccountReportApplicationService,
|
||||
ReportGenerationScheduler,
|
||||
],
|
||||
exports: [
|
||||
|
|
@ -25,6 +34,8 @@ import { ReportGenerationScheduler } from './schedulers/report-generation.schedu
|
|||
GetReportSnapshotHandler,
|
||||
ReportingApplicationService,
|
||||
DashboardApplicationService,
|
||||
// [2026-01-04] 新增:系统账户报表聚合服务
|
||||
SystemAccountReportApplicationService,
|
||||
],
|
||||
})
|
||||
export class ApplicationModule {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
/**
|
||||
* 系统账户报表应用服务
|
||||
* [2026-01-04] 新增:用于聚合各服务的系统账户报表数据
|
||||
* 回滚方式:删除此文件,并从 application.module.ts 中移除注册
|
||||
*/
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { WalletServiceClient, OfflineSettlementSummary, SystemAccountBalance } 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 SystemAccountReportResponse {
|
||||
// 固定系统账户
|
||||
fixedAccounts: {
|
||||
costAccount: SystemAccountWithBalance | null; // 成本账户 S0000000001
|
||||
operationAccount: SystemAccountWithBalance | null; // 运营账户 S0000000002
|
||||
hqCommunity: SystemAccountWithBalance | null; // 总部社区 S0000000003
|
||||
rwadPoolPending: SystemAccountWithBalance | null; // RWAD待发放池 S0000000004
|
||||
platformFee: SystemAccountWithBalance | null; // 平台手续费 S0000000005
|
||||
};
|
||||
// 省区域账户汇总
|
||||
provinceSummary: {
|
||||
accounts: SystemAccountDTO[];
|
||||
summary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
// 市区域账户汇总
|
||||
citySummary: {
|
||||
accounts: SystemAccountDTO[];
|
||||
summary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
// 面对面结算统计
|
||||
offlineSettlement: OfflineSettlementSummary;
|
||||
// 过期收益统计
|
||||
expiredRewards: ExpiredRewardsSummary;
|
||||
// 报表生成时间
|
||||
generatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统账户带钱包余额信息
|
||||
*/
|
||||
export interface SystemAccountWithBalance extends SystemAccountDTO {
|
||||
walletBalance?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 固定账户类型映射
|
||||
*/
|
||||
const FIXED_ACCOUNT_TYPES = {
|
||||
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()
|
||||
export class SystemAccountReportApplicationService {
|
||||
private readonly logger = new Logger(SystemAccountReportApplicationService.name);
|
||||
|
||||
constructor(
|
||||
private readonly walletServiceClient: WalletServiceClient,
|
||||
private readonly rewardServiceClient: RewardServiceClient,
|
||||
private readonly authorizationServiceClient: AuthorizationServiceClient,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取完整的系统账户报表
|
||||
*/
|
||||
async getFullReport(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<SystemAccountReportResponse> {
|
||||
this.logger.log('[getFullReport] 开始聚合系统账户报表数据...');
|
||||
|
||||
// 并行获取所有数据
|
||||
const [
|
||||
allAccountsSummary,
|
||||
provinceSummary,
|
||||
citySummary,
|
||||
offlineSettlement,
|
||||
expiredRewards,
|
||||
fixedAccountsBalances,
|
||||
] = await Promise.all([
|
||||
this.authorizationServiceClient.getAllAccountsSummary(),
|
||||
this.authorizationServiceClient.getRegionAccountsList('province'),
|
||||
this.authorizationServiceClient.getRegionAccountsList('city'),
|
||||
this.walletServiceClient.getOfflineSettlementSummary(params),
|
||||
this.rewardServiceClient.getExpiredRewardsSummary(params),
|
||||
this.getFixedAccountsBalances(),
|
||||
]);
|
||||
|
||||
// 组装固定账户数据
|
||||
const fixedAccounts = this.assembleFixedAccounts(
|
||||
allAccountsSummary.fixedAccounts,
|
||||
fixedAccountsBalances,
|
||||
);
|
||||
|
||||
const report: SystemAccountReportResponse = {
|
||||
fixedAccounts,
|
||||
provinceSummary,
|
||||
citySummary,
|
||||
offlineSettlement,
|
||||
expiredRewards,
|
||||
generatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
this.logger.log('[getFullReport] 系统账户报表数据聚合完成');
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取固定账户列表(带余额)
|
||||
*/
|
||||
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 getProvinceAccountsSummary(): Promise<RegionAccountsSummary> {
|
||||
return this.authorizationServiceClient.getRegionAccountsList('province');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取市区域账户汇总
|
||||
*/
|
||||
async getCityAccountsSummary(): Promise<RegionAccountsSummary> {
|
||||
return this.authorizationServiceClient.getRegionAccountsList('city');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取面对面结算统计
|
||||
*/
|
||||
async getOfflineSettlementSummary(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<OfflineSettlementSummary> {
|
||||
return this.walletServiceClient.getOfflineSettlementSummary(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期收益统计
|
||||
*/
|
||||
async getExpiredRewardsSummary(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<ExpiredRewardsSummary> {
|
||||
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[],
|
||||
): SystemAccountReportResponse['fixedAccounts'] {
|
||||
const result: SystemAccountReportResponse['fixedAccounts'] = {
|
||||
costAccount: null,
|
||||
operationAccount: null,
|
||||
hqCommunity: null,
|
||||
rwadPoolPending: null,
|
||||
platformFee: null,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据账户类型获取账户序列号
|
||||
*/
|
||||
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];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Authorization Service HTTP 客户端
|
||||
* [2026-01-04] 更新:新增系统账户报表统计相关方法
|
||||
* 回滚方式:删除 getFixedAccountsList, getRegionAccountsList, getAllAccountsSummary 方法
|
||||
*/
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import axios from 'axios';
|
||||
|
|
@ -14,6 +19,47 @@ export interface CompanyStatsChange {
|
|||
cityCompanyChange: number;
|
||||
}
|
||||
|
||||
// =============== 系统账户报表相关接口 ===============
|
||||
// [2026-01-04] 新增:用于系统账户报表统计
|
||||
|
||||
export interface SystemAccountDTO {
|
||||
id: string;
|
||||
accountType: string;
|
||||
regionCode: string | null;
|
||||
regionName: string | null;
|
||||
walletAddress: string | null;
|
||||
usdtBalance: string;
|
||||
hashpower: string;
|
||||
totalReceived: string;
|
||||
totalTransferred: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface RegionAccountsSummary {
|
||||
accounts: SystemAccountDTO[];
|
||||
summary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AllAccountsSummary {
|
||||
fixedAccounts: SystemAccountDTO[];
|
||||
provinceSummary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
count: number;
|
||||
};
|
||||
citySummary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
count: number;
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuthorizationServiceClient {
|
||||
private readonly logger = new Logger(AuthorizationServiceClient.name);
|
||||
|
|
@ -79,4 +125,78 @@ export class AuthorizationServiceClient {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计方法 ===============
|
||||
// [2026-01-04] 新增:用于系统账户报表统计
|
||||
// 回滚方式:删除以下方法即可
|
||||
|
||||
/**
|
||||
* 获取固定系统账户列表
|
||||
*/
|
||||
async getFixedAccountsList(): Promise<SystemAccountDTO[]> {
|
||||
this.logger.debug(`[getFixedAccountsList] 请求: ${this.baseUrl}/internal/statistics/fixed-accounts`);
|
||||
|
||||
try {
|
||||
const response = await axios.get<SystemAccountDTO[]>(
|
||||
`${this.baseUrl}/internal/statistics/fixed-accounts`,
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getFixedAccountsList] 失败: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域系统账户列表和汇总
|
||||
*/
|
||||
async getRegionAccountsList(type: 'province' | 'city'): Promise<RegionAccountsSummary> {
|
||||
this.logger.debug(`[getRegionAccountsList] 请求: ${this.baseUrl}/internal/statistics/region-accounts?type=${type}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get<RegionAccountsSummary>(
|
||||
`${this.baseUrl}/internal/statistics/region-accounts?type=${type}`,
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getRegionAccountsList] 失败: ${error.message}`);
|
||||
return {
|
||||
accounts: [],
|
||||
summary: {
|
||||
totalBalance: '0',
|
||||
totalReceived: '0',
|
||||
count: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统账户汇总
|
||||
*/
|
||||
async getAllAccountsSummary(): Promise<AllAccountsSummary> {
|
||||
this.logger.debug(`[getAllAccountsSummary] 请求: ${this.baseUrl}/internal/statistics/all-accounts-summary`);
|
||||
|
||||
try {
|
||||
const response = await axios.get<AllAccountsSummary>(
|
||||
`${this.baseUrl}/internal/statistics/all-accounts-summary`,
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getAllAccountsSummary] 失败: ${error.message}`);
|
||||
return {
|
||||
fixedAccounts: [],
|
||||
provinceSummary: {
|
||||
totalBalance: '0',
|
||||
totalReceived: '0',
|
||||
count: 0,
|
||||
},
|
||||
citySummary: {
|
||||
totalBalance: '0',
|
||||
totalReceived: '0',
|
||||
count: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Reward Service HTTP 客户端
|
||||
* [2026-01-04] 新增:用于系统账户报表统计
|
||||
* 回滚方式:删除此文件
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
export interface ExpiredRewardsSummary {
|
||||
totalAmount: number;
|
||||
totalCount: number;
|
||||
byMonth: Array<{
|
||||
month: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
byRightType: Array<{
|
||||
rightType: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RewardServiceClient {
|
||||
private readonly logger = new Logger(RewardServiceClient.name);
|
||||
private readonly baseUrl: string;
|
||||
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
this.baseUrl = this.configService.get<string>(
|
||||
'REWARD_SERVICE_URL',
|
||||
'http://reward-service:3004',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期收益统计汇总
|
||||
*/
|
||||
async getExpiredRewardsSummary(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<ExpiredRewardsSummary> {
|
||||
try {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (params?.startDate) queryParams.append('startDate', params.startDate);
|
||||
if (params?.endDate) queryParams.append('endDate', params.endDate);
|
||||
|
||||
const url = `${this.baseUrl}/internal/statistics/expired-rewards-summary?${queryParams.toString()}`;
|
||||
this.logger.debug(`[getExpiredRewardsSummary] 请求: ${url}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get<ExpiredRewardsSummary>(url),
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getExpiredRewardsSummary] 失败: ${error.message}`);
|
||||
// 返回默认值,不阻塞报表
|
||||
return {
|
||||
totalAmount: 0,
|
||||
totalCount: 0,
|
||||
byMonth: [],
|
||||
byRightType: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Wallet Service HTTP 客户端
|
||||
* [2026-01-04] 新增:用于系统账户报表统计
|
||||
* 回滚方式:删除此文件
|
||||
*/
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
export interface OfflineSettlementSummary {
|
||||
totalAmount: number;
|
||||
totalCount: number;
|
||||
byMonth: Array<{
|
||||
month: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface SystemAccountBalance {
|
||||
accountSequence: string;
|
||||
name: string;
|
||||
balance: number;
|
||||
totalReceived: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WalletServiceClient {
|
||||
private readonly logger = new Logger(WalletServiceClient.name);
|
||||
private readonly baseUrl: string;
|
||||
|
||||
constructor(
|
||||
private readonly httpService: HttpService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
this.baseUrl = this.configService.get<string>(
|
||||
'WALLET_SERVICE_URL',
|
||||
'http://wallet-service:3002',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取面对面结算统计汇总
|
||||
*/
|
||||
async getOfflineSettlementSummary(params?: {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<OfflineSettlementSummary> {
|
||||
try {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (params?.startDate) queryParams.append('startDate', params.startDate);
|
||||
if (params?.endDate) queryParams.append('endDate', params.endDate);
|
||||
|
||||
const url = `${this.baseUrl}/wallets/statistics/offline-settlement-summary?${queryParams.toString()}`;
|
||||
this.logger.debug(`[getOfflineSettlementSummary] 请求: ${url}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get<OfflineSettlementSummary>(url),
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getOfflineSettlementSummary] 失败: ${error.message}`);
|
||||
// 返回默认值,不阻塞报表
|
||||
return {
|
||||
totalAmount: 0,
|
||||
totalCount: 0,
|
||||
byMonth: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统账户余额
|
||||
*/
|
||||
async getSystemAccountsBalances(
|
||||
accountSequences: string[],
|
||||
): Promise<SystemAccountBalance[]> {
|
||||
try {
|
||||
const url = `${this.baseUrl}/wallets/statistics/system-accounts-balances?sequences=${accountSequences.join(',')}`;
|
||||
this.logger.debug(`[getSystemAccountsBalances] 请求: ${url}`);
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.httpService.get<SystemAccountBalance[]>(url),
|
||||
);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error(`[getSystemAccountsBalances] 失败: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
/**
|
||||
* 基础设施模块
|
||||
* [2026-01-04] 更新:新增 WalletServiceClient, RewardServiceClient 用于系统账户报表统计
|
||||
* 回滚方式:删除 HttpModule 导入及 WalletServiceClient, RewardServiceClient 的注册
|
||||
*/
|
||||
import { Module } from '@nestjs/common';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
import { PrismaService } from './persistence/prisma/prisma.service';
|
||||
import { ReportDefinitionRepository } from './persistence/repositories/report-definition.repository.impl';
|
||||
import { ReportSnapshotRepository } from './persistence/repositories/report-snapshot.repository.impl';
|
||||
|
|
@ -22,11 +28,22 @@ import { LeaderboardServiceClient } from './external/leaderboard-service/leaderb
|
|||
import { PlantingServiceClient } from './external/planting-service/planting-service.client';
|
||||
import { AuthorizationServiceClient } from './external/authorization-service/authorization-service.client';
|
||||
import { IdentityServiceClient } from './external/identity-service/identity-service.client';
|
||||
// [2026-01-04] 新增:系统账户报表统计 HTTP 客户端
|
||||
import { WalletServiceClient } from './external/wallet-service/wallet-service.client';
|
||||
import { RewardServiceClient } from './external/reward-service/reward-service.client';
|
||||
import { ExportModule } from './export/export.module';
|
||||
import { RedisModule } from './redis/redis.module';
|
||||
|
||||
@Module({
|
||||
imports: [ExportModule, RedisModule],
|
||||
imports: [
|
||||
// [2026-01-04] 新增:HttpModule 用于系统账户报表统计的 HTTP 调用
|
||||
HttpModule.register({
|
||||
timeout: 30000,
|
||||
maxRedirects: 5,
|
||||
}),
|
||||
ExportModule,
|
||||
RedisModule,
|
||||
],
|
||||
providers: [
|
||||
PrismaService,
|
||||
{
|
||||
|
|
@ -65,6 +82,9 @@ import { RedisModule } from './redis/redis.module';
|
|||
PlantingServiceClient,
|
||||
AuthorizationServiceClient,
|
||||
IdentityServiceClient,
|
||||
// [2026-01-04] 新增:系统账户报表统计 HTTP 客户端
|
||||
WalletServiceClient,
|
||||
RewardServiceClient,
|
||||
],
|
||||
exports: [
|
||||
PrismaService,
|
||||
|
|
@ -80,6 +100,9 @@ import { RedisModule } from './redis/redis.module';
|
|||
PlantingServiceClient,
|
||||
AuthorizationServiceClient,
|
||||
IdentityServiceClient,
|
||||
// [2026-01-04] 新增:系统账户报表统计 HTTP 客户端
|
||||
WalletServiceClient,
|
||||
RewardServiceClient,
|
||||
ExportModule,
|
||||
RedisModule,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Controller, Post, Body, Logger } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Controller, Post, Body, Logger, Get, Query } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
|
||||
import { IsArray, IsString, IsOptional } from 'class-validator';
|
||||
import { RewardApplicationService } from '../../application/services/reward-application.service';
|
||||
|
||||
|
|
@ -71,4 +71,29 @@ export class InternalController {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计 API ===============
|
||||
// [2026-01-04] 新增:用于 reporting-service 聚合系统账户报表数据
|
||||
// 回滚方式:删除以下 API 方法即可
|
||||
|
||||
@Get('statistics/expired-rewards-summary')
|
||||
@ApiOperation({ summary: '过期收益统计汇总(内部接口)- 用于系统账户报表' })
|
||||
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||
@ApiResponse({ status: 200, description: '过期收益统计汇总' })
|
||||
async getExpiredRewardsSummary(
|
||||
@Query('startDate') startDate?: string,
|
||||
@Query('endDate') endDate?: string,
|
||||
) {
|
||||
this.logger.log(`========== statistics/expired-rewards-summary 请求 ==========`);
|
||||
this.logger.log(`startDate: ${startDate}, endDate: ${endDate}`);
|
||||
|
||||
const result = await this.rewardService.getExpiredRewardsSummary({
|
||||
startDate: startDate ? new Date(startDate) : undefined,
|
||||
endDate: endDate ? new Date(endDate) : undefined,
|
||||
});
|
||||
|
||||
this.logger.log(`过期收益统计结果: totalAmount=${result.totalAmount}, totalCount=${result.totalCount}`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import { WalletServiceClient } from '../../infrastructure/external/wallet-servic
|
|||
import { OutboxRepository, OutboxEventData } from '../../infrastructure/persistence/repositories/outbox.repository';
|
||||
import { SettlementRecordRepository, SettlementRecordDto } from '../../infrastructure/persistence/repositories/settlement-record.repository';
|
||||
import { RewardSummary } from '../../domain/aggregates/reward-summary/reward-summary.aggregate';
|
||||
// [2026-01-04] 新增:用于过期收益统计查询
|
||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||
|
||||
// 总部社区账户ID
|
||||
const HEADQUARTERS_COMMUNITY_USER_ID = BigInt(1);
|
||||
|
|
@ -34,6 +36,8 @@ export class RewardApplicationService {
|
|||
private readonly walletService: WalletServiceClient,
|
||||
private readonly outboxRepository: OutboxRepository,
|
||||
private readonly settlementRecordRepository: SettlementRecordRepository,
|
||||
// [2026-01-04] 新增:用于过期收益统计查询
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
@ -80,6 +84,12 @@ export class RewardApplicationService {
|
|||
rightType: reward.rewardSource.rightType,
|
||||
sourceOrderNo: params.sourceOrderNo,
|
||||
sourceUserId: params.sourceUserId.toString(),
|
||||
// [2026-01-04] 新增字段:用于系统账户报表统计,支持追溯每笔收益的来源
|
||||
// 回滚方式:删除以下4行即可恢复原状
|
||||
sourceAccountSequence: params.sourceAccountSequence ?? null, // 来源用户账户序列号
|
||||
treeCount: params.treeCount, // 认种数量
|
||||
provinceCode: params.provinceCode, // 认种省份代码
|
||||
cityCode: params.cityCode, // 认种城市代码
|
||||
memo: reward.memo,
|
||||
},
|
||||
}));
|
||||
|
|
@ -183,6 +193,12 @@ export class RewardApplicationService {
|
|||
rightType: reward.rewardSource.rightType,
|
||||
sourceOrderNo: params.sourceOrderNo,
|
||||
sourceUserId: params.sourceUserId.toString(),
|
||||
// [2026-01-04] 新增字段:用于系统账户报表统计,支持追溯每笔收益的来源
|
||||
// 回滚方式:删除以下4行即可恢复原状
|
||||
sourceAccountSequence: params.sourceAccountSequence ?? null, // 来源用户账户序列号
|
||||
treeCount: params.treeCount, // 认种数量
|
||||
provinceCode: params.provinceCode, // 认种省份代码
|
||||
cityCode: params.cityCode, // 认种城市代码
|
||||
memo: reward.memo,
|
||||
reason: 'CONTRACT_EXPIRED', // 标记为合同超时
|
||||
},
|
||||
|
|
@ -884,4 +900,113 @@ export class RewardApplicationService {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计方法 ===============
|
||||
// [2026-01-04] 新增:用于 reporting-service 聚合系统账户报表数据
|
||||
// 回滚方式:删除以下方法和构造函数中的 prisma 注入即可
|
||||
|
||||
/**
|
||||
* 获取过期收益统计汇总
|
||||
* 用于系统账户报表展示过期收益总额
|
||||
*
|
||||
* 统计 reward_status = 'EXPIRED' 的奖励流水
|
||||
*/
|
||||
async getExpiredRewardsSummary(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<{
|
||||
totalAmount: number;
|
||||
totalCount: number;
|
||||
byMonth: Array<{
|
||||
month: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
byRightType: Array<{
|
||||
rightType: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
}> {
|
||||
this.logger.log(`[getExpiredRewardsSummary] 查询过期收益统计`);
|
||||
|
||||
// 构建日期筛选条件
|
||||
const whereClause: any = {
|
||||
rewardStatus: 'EXPIRED',
|
||||
};
|
||||
if (params.startDate || params.endDate) {
|
||||
whereClause.expiredAt = {};
|
||||
if (params.startDate) {
|
||||
whereClause.expiredAt.gte = params.startDate;
|
||||
}
|
||||
if (params.endDate) {
|
||||
whereClause.expiredAt.lte = params.endDate;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询总计
|
||||
const aggregateResult = await this.prisma.rewardLedgerEntry.aggregate({
|
||||
where: whereClause,
|
||||
_sum: {
|
||||
usdtAmount: true,
|
||||
},
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const totalAmount = aggregateResult._sum.usdtAmount
|
||||
? Number(aggregateResult._sum.usdtAmount)
|
||||
: 0;
|
||||
const totalCount = aggregateResult._count.id || 0;
|
||||
|
||||
// 查询按月统计
|
||||
const byMonth = await this.prisma.$queryRaw<Array<{
|
||||
month: string;
|
||||
amount: any;
|
||||
count: any;
|
||||
}>>`
|
||||
SELECT
|
||||
TO_CHAR(expired_at, 'YYYY-MM') as month,
|
||||
SUM(usdt_amount) as amount,
|
||||
COUNT(*) as count
|
||||
FROM reward_ledger_entries
|
||||
WHERE reward_status = 'EXPIRED'
|
||||
AND expired_at IS NOT NULL
|
||||
GROUP BY TO_CHAR(expired_at, 'YYYY-MM')
|
||||
ORDER BY month DESC
|
||||
LIMIT 12
|
||||
`;
|
||||
|
||||
// 查询按权益类型统计
|
||||
const byRightType = await this.prisma.$queryRaw<Array<{
|
||||
rightType: string;
|
||||
amount: any;
|
||||
count: any;
|
||||
}>>`
|
||||
SELECT
|
||||
right_type as "rightType",
|
||||
SUM(usdt_amount) as amount,
|
||||
COUNT(*) as count
|
||||
FROM reward_ledger_entries
|
||||
WHERE reward_status = 'EXPIRED'
|
||||
GROUP BY right_type
|
||||
ORDER BY amount DESC
|
||||
`;
|
||||
|
||||
return {
|
||||
totalAmount,
|
||||
totalCount,
|
||||
byMonth: byMonth.map(row => ({
|
||||
month: row.month,
|
||||
amount: Number(row.amount) || 0,
|
||||
count: Number(row.count) || 0,
|
||||
})),
|
||||
byRightType: byRightType.map(row => ({
|
||||
rightType: row.rightType,
|
||||
amount: Number(row.amount) || 0,
|
||||
count: Number(row.count) || 0,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,4 +344,48 @@ export class InternalWalletController {
|
|||
this.logger.log(`全额线下结算扣减结果: ${JSON.stringify(result)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计 API ===============
|
||||
// [2026-01-04] 新增:用于 reporting-service 聚合系统账户报表数据
|
||||
// 回滚方式:删除以下 API 方法即可
|
||||
|
||||
@Get('statistics/offline-settlement-summary')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '面对面(线下)结算统计汇总(内部API) - 用于系统账户报表' })
|
||||
@ApiQuery({ name: 'startDate', required: false, description: '开始日期 (YYYY-MM-DD)' })
|
||||
@ApiQuery({ name: 'endDate', required: false, description: '结束日期 (YYYY-MM-DD)' })
|
||||
@ApiResponse({ status: 200, description: '面对面结算统计汇总' })
|
||||
async getOfflineSettlementSummary(
|
||||
@Query('startDate') startDate?: string,
|
||||
@Query('endDate') endDate?: string,
|
||||
) {
|
||||
this.logger.log(`========== statistics/offline-settlement-summary 请求 ==========`);
|
||||
this.logger.log(`startDate: ${startDate}, endDate: ${endDate}`);
|
||||
|
||||
const result = await this.walletService.getOfflineSettlementSummary({
|
||||
startDate: startDate ? new Date(startDate) : undefined,
|
||||
endDate: endDate ? new Date(endDate) : undefined,
|
||||
});
|
||||
|
||||
this.logger.log(`面对面结算统计结果: totalAmount=${result.totalAmount}, totalCount=${result.totalCount}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('statistics/system-accounts-balances')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '批量查询系统账户余额(内部API) - 用于系统账户报表' })
|
||||
@ApiQuery({ name: 'sequences', description: '账户序列号列表,逗号分隔 (如: S0000000001,S0000000002)' })
|
||||
@ApiResponse({ status: 200, description: '系统账户余额列表' })
|
||||
async getSystemAccountsBalances(
|
||||
@Query('sequences') sequences: string,
|
||||
) {
|
||||
this.logger.log(`========== statistics/system-accounts-balances 请求 ==========`);
|
||||
this.logger.log(`sequences: ${sequences}`);
|
||||
|
||||
const accountSequences = sequences.split(',').map(s => s.trim()).filter(s => s);
|
||||
const result = await this.walletService.getSystemAccountsBalances(accountSequences);
|
||||
|
||||
this.logger.log(`系统账户余额查询结果: ${result.length} 个账户`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2787,4 +2787,170 @@ export class WalletApplicationService {
|
|||
};
|
||||
return nameMap[allocationType] || allocationType;
|
||||
}
|
||||
|
||||
// =============== 系统账户报表统计方法 ===============
|
||||
// [2026-01-04] 新增:用于 reporting-service 聚合系统账户报表数据
|
||||
// 回滚方式:删除以下方法即可
|
||||
|
||||
/**
|
||||
* 获取面对面(线下)结算统计汇总
|
||||
* 用于系统账户报表展示面对面结算收益总额
|
||||
*/
|
||||
async getOfflineSettlementSummary(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<{
|
||||
totalAmount: number;
|
||||
totalCount: number;
|
||||
byMonth: Array<{
|
||||
month: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
}> {
|
||||
this.logger.log(`[getOfflineSettlementSummary] 查询面对面结算统计`);
|
||||
|
||||
// 构建日期筛选条件
|
||||
const whereClause: any = {};
|
||||
if (params.startDate || params.endDate) {
|
||||
whereClause.createdAt = {};
|
||||
if (params.startDate) {
|
||||
whereClause.createdAt.gte = params.startDate;
|
||||
}
|
||||
if (params.endDate) {
|
||||
whereClause.createdAt.lte = params.endDate;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询总计
|
||||
const aggregateResult = await this.prisma.offlineSettlementDeduction.aggregate({
|
||||
where: whereClause,
|
||||
_sum: {
|
||||
deductedAmount: true,
|
||||
},
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const totalAmount = aggregateResult._sum.deductedAmount
|
||||
? Number(aggregateResult._sum.deductedAmount)
|
||||
: 0;
|
||||
const totalCount = aggregateResult._count.id || 0;
|
||||
|
||||
// 查询按月统计
|
||||
const monthlyStats = await this.prisma.$queryRaw<Array<{
|
||||
month: string;
|
||||
amount: any;
|
||||
count: any;
|
||||
}>>`
|
||||
SELECT
|
||||
TO_CHAR(created_at, 'YYYY-MM') as month,
|
||||
SUM(deducted_amount) as amount,
|
||||
COUNT(*) as count
|
||||
FROM offline_settlement_deductions
|
||||
${params.startDate ? this.prisma.$queryRaw`WHERE created_at >= ${params.startDate}` : this.prisma.$queryRaw``}
|
||||
${params.endDate ? this.prisma.$queryRaw`AND created_at <= ${params.endDate}` : this.prisma.$queryRaw``}
|
||||
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
|
||||
ORDER BY month DESC
|
||||
LIMIT 12
|
||||
`;
|
||||
|
||||
// 简化查询:不使用条件拼接
|
||||
const byMonth = await this.prisma.$queryRaw<Array<{
|
||||
month: string;
|
||||
amount: any;
|
||||
count: any;
|
||||
}>>`
|
||||
SELECT
|
||||
TO_CHAR(created_at, 'YYYY-MM') as month,
|
||||
SUM(deducted_amount) as amount,
|
||||
COUNT(*) as count
|
||||
FROM offline_settlement_deductions
|
||||
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
|
||||
ORDER BY month DESC
|
||||
LIMIT 12
|
||||
`;
|
||||
|
||||
return {
|
||||
totalAmount,
|
||||
totalCount,
|
||||
byMonth: byMonth.map(row => ({
|
||||
month: row.month,
|
||||
amount: Number(row.amount) || 0,
|
||||
count: Number(row.count) || 0,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询系统账户余额
|
||||
* 用于系统账户报表展示固定账户(总部、成本、运营)的余额
|
||||
*/
|
||||
async getSystemAccountsBalances(accountSequences: string[]): Promise<Array<{
|
||||
accountSequence: string;
|
||||
name: string;
|
||||
balance: number;
|
||||
totalReceived: number;
|
||||
createdAt: string;
|
||||
}>> {
|
||||
this.logger.log(`[getSystemAccountsBalances] 查询系统账户余额: ${accountSequences.join(', ')}`);
|
||||
|
||||
if (accountSequences.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 系统账户名称映射
|
||||
const accountNames: Record<string, string> = {
|
||||
'S0000000001': '总部社区账户',
|
||||
'S0000000002': '成本费账户',
|
||||
'S0000000003': '运营费账户',
|
||||
'S0000000004': 'RWAD底池账户',
|
||||
'S0000000005': '分享权益池账户',
|
||||
};
|
||||
|
||||
// 查询钱包账户
|
||||
const wallets = await this.prisma.walletAccount.findMany({
|
||||
where: {
|
||||
accountSequence: {
|
||||
in: accountSequences,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
accountSequence: true,
|
||||
usdtAvailable: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 查询每个账户的累计收入 (SYSTEM_ALLOCATION 类型的流水总和)
|
||||
const receivedStats = await this.prisma.ledgerEntry.groupBy({
|
||||
by: ['accountSequence'],
|
||||
where: {
|
||||
accountSequence: {
|
||||
in: accountSequences,
|
||||
},
|
||||
entryType: 'SYSTEM_ALLOCATION',
|
||||
amount: {
|
||||
gt: 0,
|
||||
},
|
||||
},
|
||||
_sum: {
|
||||
amount: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 构建结果
|
||||
const receivedMap = new Map(
|
||||
receivedStats.map(stat => [stat.accountSequence, Number(stat._sum.amount) || 0])
|
||||
);
|
||||
|
||||
return wallets.map(wallet => ({
|
||||
accountSequence: wallet.accountSequence,
|
||||
name: accountNames[wallet.accountSequence] || `系统账户 ${wallet.accountSequence}`,
|
||||
balance: Number(wallet.usdtAvailable) || 0,
|
||||
totalReceived: receivedMap.get(wallet.accountSequence) || 0,
|
||||
createdAt: wallet.createdAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
/**
|
||||
* 数据统计页面
|
||||
* [2026-01-04] 更新:新增系统账户报表Tab
|
||||
* 回滚方式:删除 SystemAccountsTab 导入及相关 mainTab state 和切换逻辑
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { PageContainer } from '@/components/layout';
|
||||
import { cn } from '@/utils/helpers';
|
||||
// [2026-01-04] 新增:系统账户报表Tab
|
||||
import { SystemAccountsTab } from '@/components/features/system-account-report';
|
||||
import styles from './statistics.module.scss';
|
||||
|
||||
/**
|
||||
|
|
@ -95,8 +102,11 @@ const metricsData = [
|
|||
/**
|
||||
* 数据统计页面
|
||||
* 基于 UIPro Figma 设计实现
|
||||
* [2026-01-04] 更新:新增系统账户报表Tab
|
||||
*/
|
||||
export default function StatisticsPage() {
|
||||
// [2026-01-04] 新增:主Tab切换 - 数据统计 vs 系统账户
|
||||
const [mainTab, setMainTab] = useState<'statistics' | 'system-accounts'>('statistics');
|
||||
// 趋势图时间维度
|
||||
const [trendPeriod, setTrendPeriod] = useState<'day' | 'week' | 'month' | 'quarter' | 'year'>('day');
|
||||
// 龙虎榜时间维度
|
||||
|
|
@ -110,6 +120,28 @@ export default function StatisticsPage() {
|
|||
{/* 页面标题 */}
|
||||
<h2 className={styles.statistics__title}>数据统计</h2>
|
||||
|
||||
{/* [2026-01-04] 新增:主Tab切换 */}
|
||||
<div className={styles.statistics__mainTabs}>
|
||||
<button
|
||||
className={cn(styles.statistics__mainTab, mainTab === 'statistics' && styles['statistics__mainTab--active'])}
|
||||
onClick={() => setMainTab('statistics')}
|
||||
>
|
||||
认种统计
|
||||
</button>
|
||||
<button
|
||||
className={cn(styles.statistics__mainTab, mainTab === 'system-accounts' && styles['statistics__mainTab--active'])}
|
||||
onClick={() => setMainTab('system-accounts')}
|
||||
>
|
||||
系统账户
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* [2026-01-04] 新增:系统账户报表Tab内容 */}
|
||||
{mainTab === 'system-accounts' && <SystemAccountsTab />}
|
||||
|
||||
{/* 原有统计内容 - 仅在 statistics tab 显示 */}
|
||||
{mainTab === 'statistics' && (
|
||||
<>
|
||||
{/* 统计概览卡片 */}
|
||||
<section className={styles.statistics__overview}>
|
||||
<div className={styles.statistics__overviewCard}>
|
||||
|
|
@ -353,6 +385,8 @@ export default function StatisticsPage() {
|
|||
<button className={styles.statistics__viewDetailLink}>查看时间轴视图</button>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,43 @@
|
|||
color: #0f172a;
|
||||
}
|
||||
|
||||
/* [2026-01-04] 新增:主Tab切换样式 */
|
||||
/* 回滚方式:删除以下 mainTabs 和 mainTab 样式 */
|
||||
.statistics__mainTabs {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 4px;
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 10px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.statistics__mainTab {
|
||||
padding: 10px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: transparent;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
color: #374151;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: #fff;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 统计概览卡片组 */
|
||||
.statistics__overview {
|
||||
align-self: stretch;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* 系统账户报表Tab样式
|
||||
* [2026-01-04] 新增:用于系统账户报表显示
|
||||
* 回滚方式:删除此文件
|
||||
*/
|
||||
@use '@/styles/variables' as *;
|
||||
@use '@/styles/mixins' as *;
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px;
|
||||
gap: 16px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #e5e7eb;
|
||||
border-top-color: #3b82f6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 错误状态 */
|
||||
.error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px;
|
||||
gap: 16px;
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.retryButton {
|
||||
padding: 8px 16px;
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 48px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Tab 切换 */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 8px 16px;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 区块标题 */
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* 卡片网格 */
|
||||
.cardGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 账户卡片 */
|
||||
.accountCard {
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.accountLabel {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.accountSequence {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.cardBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.statRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.statLabel {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.statValue {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
/* 汇总卡片 */
|
||||
.summaryCards {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.summaryCard {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
background-color: #f0f9ff;
|
||||
border: 1px solid #bae6fd;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summaryLabel {
|
||||
font-size: 13px;
|
||||
color: #0369a1;
|
||||
}
|
||||
|
||||
.summaryValue {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #0c4a6e;
|
||||
}
|
||||
|
||||
/* 表格 */
|
||||
.tableWrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f9fafb;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyTable {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
color: #9ca3af;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 状态标签 */
|
||||
.statusBadge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
background-color: #e5e7eb;
|
||||
color: #4b5563;
|
||||
|
||||
&.active {
|
||||
background-color: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
.footer {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
/**
|
||||
* 系统账户报表Tab组件
|
||||
* [2026-01-04] 新增:显示系统账户统计数据
|
||||
* 回滚方式:删除此文件及整个 system-account-report 目录
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { systemAccountReportService } from '@/services/systemAccountReportService';
|
||||
import type {
|
||||
SystemAccountReportResponse,
|
||||
RegionAccountsSummary,
|
||||
} from '@/types';
|
||||
import styles from './SystemAccountsTab.module.scss';
|
||||
|
||||
/**
|
||||
* 格式化金额显示
|
||||
*/
|
||||
const formatAmount = (value: string | number | undefined): string => {
|
||||
if (value === undefined || value === null) return '0.00';
|
||||
const num = typeof value === 'string' ? parseFloat(value) : value;
|
||||
if (isNaN(num)) return '0.00';
|
||||
return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
};
|
||||
|
||||
/**
|
||||
* 权益类型显示名称
|
||||
*/
|
||||
const getRightTypeName = (type: string): string => {
|
||||
const labels: Record<string, string> = {
|
||||
SETTLEABLE: '可结算收益',
|
||||
SHARE: '分享权益',
|
||||
PROVINCE_TEAM: '省团队权益',
|
||||
CITY_TEAM: '市团队权益',
|
||||
PROVINCE_REGION: '省区域权益',
|
||||
CITY_REGION: '市区域权益',
|
||||
COMMUNITY: '社区权益',
|
||||
};
|
||||
return labels[type] || type;
|
||||
};
|
||||
|
||||
/**
|
||||
* 系统账户报表Tab组件
|
||||
*/
|
||||
export default function SystemAccountsTab() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [reportData, setReportData] = useState<SystemAccountReportResponse | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<'fixed' | 'province' | 'city' | 'settlement' | 'expired'>('fixed');
|
||||
|
||||
// 加载报表数据
|
||||
const loadReportData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const response = await systemAccountReportService.getFullReport();
|
||||
if (response.data) {
|
||||
setReportData(response.data);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('加载系统账户报表失败');
|
||||
console.error('Failed to load system account report:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadReportData();
|
||||
}, [loadReportData]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={styles.loading}>
|
||||
<div className={styles.spinner} />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={styles.error}>
|
||||
<span>{error}</span>
|
||||
<button onClick={loadReportData} className={styles.retryButton}>
|
||||
重试
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!reportData) {
|
||||
return <div className={styles.empty}>暂无数据</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{/* Tab 切换 */}
|
||||
<div className={styles.tabs}>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'fixed' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('fixed')}
|
||||
>
|
||||
固定系统账户
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'province' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('province')}
|
||||
>
|
||||
省区域账户汇总
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'city' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('city')}
|
||||
>
|
||||
市区域账户汇总
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'settlement' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('settlement')}
|
||||
>
|
||||
面对面结算
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${activeTab === 'expired' ? styles.active : ''}`}
|
||||
onClick={() => setActiveTab('expired')}
|
||||
>
|
||||
过期收益
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className={styles.content}>
|
||||
{activeTab === 'fixed' && <FixedAccountsSection data={reportData.fixedAccounts} />}
|
||||
{activeTab === 'province' && <RegionAccountsSection data={reportData.provinceSummary} type="province" />}
|
||||
{activeTab === 'city' && <RegionAccountsSection data={reportData.citySummary} type="city" />}
|
||||
{activeTab === 'settlement' && <OfflineSettlementSection data={reportData.offlineSettlement} />}
|
||||
{activeTab === 'expired' && <ExpiredRewardsSection data={reportData.expiredRewards} />}
|
||||
</div>
|
||||
|
||||
{/* 报表生成时间 */}
|
||||
<div className={styles.footer}>
|
||||
报表生成时间: {new Date(reportData.generatedAt).toLocaleString('zh-CN')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 固定系统账户区域
|
||||
*/
|
||||
function FixedAccountsSection({ data }: { data: SystemAccountReportResponse['fixedAccounts'] }) {
|
||||
const accounts = [
|
||||
{ key: 'costAccount', label: '成本账户', sequence: 'S0000000001', data: data.costAccount },
|
||||
{ key: 'operationAccount', label: '运营账户', sequence: 'S0000000002', data: data.operationAccount },
|
||||
{ key: 'hqCommunity', label: '总部社区', sequence: 'S0000000003', data: data.hqCommunity },
|
||||
{ key: 'rwadPoolPending', label: 'RWAD待发放池', sequence: 'S0000000004', data: data.rwadPoolPending },
|
||||
{ key: 'platformFee', label: '平台手续费', sequence: 'S0000000005', data: data.platformFee },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>固定系统账户</h3>
|
||||
<div className={styles.cardGrid}>
|
||||
{accounts.map(({ key, label, sequence, data: accountData }) => (
|
||||
<div key={key} className={styles.accountCard}>
|
||||
<div className={styles.cardHeader}>
|
||||
<span className={styles.accountLabel}>{label}</span>
|
||||
<span className={styles.accountSequence}>{sequence}</span>
|
||||
</div>
|
||||
<div className={styles.cardBody}>
|
||||
<div className={styles.statRow}>
|
||||
<span className={styles.statLabel}>账户余额</span>
|
||||
<span className={styles.statValue}>
|
||||
{accountData ? formatAmount(accountData.usdtBalance) : '0.00'} USDT
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statRow}>
|
||||
<span className={styles.statLabel}>累计收入</span>
|
||||
<span className={styles.statValue}>
|
||||
{accountData ? formatAmount(accountData.totalReceived) : '0.00'} USDT
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statRow}>
|
||||
<span className={styles.statLabel}>累计转出</span>
|
||||
<span className={styles.statValue}>
|
||||
{accountData ? formatAmount(accountData.totalTransferred) : '0.00'} USDT
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域账户汇总区域
|
||||
*/
|
||||
function RegionAccountsSection({ data, type }: { data: RegionAccountsSummary; type: 'province' | 'city' }) {
|
||||
const typeLabel = type === 'province' ? '省' : '市';
|
||||
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>{typeLabel}区域账户汇总</h3>
|
||||
|
||||
{/* 汇总卡片 */}
|
||||
<div className={styles.summaryCards}>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>账户数量</span>
|
||||
<span className={styles.summaryValue}>{data.summary.count}</span>
|
||||
</div>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>总余额</span>
|
||||
<span className={styles.summaryValue}>{formatAmount(data.summary.totalBalance)} USDT</span>
|
||||
</div>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>累计收入</span>
|
||||
<span className={styles.summaryValue}>{formatAmount(data.summary.totalReceived)} USDT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 账户列表 */}
|
||||
{data.accounts.length > 0 ? (
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>区域代码</th>
|
||||
<th>区域名称</th>
|
||||
<th>余额 (USDT)</th>
|
||||
<th>累计收入 (USDT)</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.accounts.map((account) => (
|
||||
<tr key={account.id}>
|
||||
<td>{account.regionCode || '-'}</td>
|
||||
<td>{account.regionName || '-'}</td>
|
||||
<td>{formatAmount(account.usdtBalance)}</td>
|
||||
<td>{formatAmount(account.totalReceived)}</td>
|
||||
<td>
|
||||
<span className={`${styles.statusBadge} ${account.status === 'ACTIVE' ? styles.active : ''}`}>
|
||||
{account.status === 'ACTIVE' ? '正常' : account.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.emptyTable}>暂无{typeLabel}区域账户数据</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 面对面结算统计区域
|
||||
*/
|
||||
function OfflineSettlementSection({ data }: { data: SystemAccountReportResponse['offlineSettlement'] }) {
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>面对面结算统计</h3>
|
||||
|
||||
{/* 汇总卡片 */}
|
||||
<div className={styles.summaryCards}>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>总笔数</span>
|
||||
<span className={styles.summaryValue}>{data.totalCount}</span>
|
||||
</div>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>总金额</span>
|
||||
<span className={styles.summaryValue}>{formatAmount(data.totalAmount)} USDT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 按月统计 */}
|
||||
{data.byMonth.length > 0 && (
|
||||
<>
|
||||
<h4 className={styles.subTitle}>按月统计</h4>
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>月份</th>
|
||||
<th>笔数</th>
|
||||
<th>金额 (USDT)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.byMonth.map((item) => (
|
||||
<tr key={item.month}>
|
||||
<td>{item.month}</td>
|
||||
<td>{item.count}</td>
|
||||
<td>{formatAmount(item.amount)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{data.byMonth.length === 0 && (
|
||||
<div className={styles.emptyTable}>暂无面对面结算数据</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 过期收益统计区域
|
||||
*/
|
||||
function ExpiredRewardsSection({ data }: { data: SystemAccountReportResponse['expiredRewards'] }) {
|
||||
return (
|
||||
<div className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>过期收益统计</h3>
|
||||
|
||||
{/* 汇总卡片 */}
|
||||
<div className={styles.summaryCards}>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>总笔数</span>
|
||||
<span className={styles.summaryValue}>{data.totalCount}</span>
|
||||
</div>
|
||||
<div className={styles.summaryCard}>
|
||||
<span className={styles.summaryLabel}>总金额</span>
|
||||
<span className={styles.summaryValue}>{formatAmount(data.totalAmount)} USDT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 按权益类型统计 */}
|
||||
{data.byRightType.length > 0 && (
|
||||
<>
|
||||
<h4 className={styles.subTitle}>按权益类型统计</h4>
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>权益类型</th>
|
||||
<th>笔数</th>
|
||||
<th>金额 (USDT)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.byRightType.map((item) => (
|
||||
<tr key={item.rightType}>
|
||||
<td>{getRightTypeName(item.rightType)}</td>
|
||||
<td>{item.count}</td>
|
||||
<td>{formatAmount(item.amount)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 按月统计 */}
|
||||
{data.byMonth.length > 0 && (
|
||||
<>
|
||||
<h4 className={styles.subTitle}>按月统计</h4>
|
||||
<div className={styles.tableWrapper}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>月份</th>
|
||||
<th>笔数</th>
|
||||
<th>金额 (USDT)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.byMonth.map((item) => (
|
||||
<tr key={item.month}>
|
||||
<td>{item.month}</td>
|
||||
<td>{item.count}</td>
|
||||
<td>{formatAmount(item.amount)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{data.byRightType.length === 0 && data.byMonth.length === 0 && (
|
||||
<div className={styles.emptyTable}>暂无过期收益数据</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* 系统账户报表组件导出
|
||||
* [2026-01-04] 新增
|
||||
* 回滚方式:删除此文件
|
||||
*/
|
||||
export { default as SystemAccountsTab } from './SystemAccountsTab';
|
||||
|
|
@ -204,4 +204,15 @@ export const API_ENDPOINTS = {
|
|||
START_PAYMENT: (orderNo: string) => `/v1/wallets/fiat-withdrawals/${orderNo}/start-payment`,
|
||||
COMPLETE_PAYMENT: (orderNo: string) => `/v1/wallets/fiat-withdrawals/${orderNo}/complete-payment`,
|
||||
},
|
||||
|
||||
// [2026-01-04] 新增:系统账户报表 (reporting-service)
|
||||
// 回滚方式:删除此部分即可
|
||||
SYSTEM_ACCOUNT_REPORTS: {
|
||||
FULL_REPORT: '/v1/system-account-reports',
|
||||
FIXED_ACCOUNTS: '/v1/system-account-reports/fixed-accounts',
|
||||
PROVINCE_SUMMARY: '/v1/system-account-reports/province-summary',
|
||||
CITY_SUMMARY: '/v1/system-account-reports/city-summary',
|
||||
OFFLINE_SETTLEMENT: '/v1/system-account-reports/offline-settlement',
|
||||
EXPIRED_REWARDS: '/v1/system-account-reports/expired-rewards',
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* 系统账户报表服务
|
||||
* [2026-01-04] 新增:负责系统账户报表数据的API调用
|
||||
* 回滚方式:删除此文件
|
||||
*/
|
||||
|
||||
import apiClient from '@/infrastructure/api/client';
|
||||
import { API_ENDPOINTS } from '@/infrastructure/api/endpoints';
|
||||
import type {
|
||||
ApiResponse,
|
||||
SystemAccountReportResponse,
|
||||
SystemAccountWithBalance,
|
||||
RegionAccountsSummary,
|
||||
OfflineSettlementSummary,
|
||||
ExpiredRewardsSummary,
|
||||
} from '@/types';
|
||||
|
||||
/**
|
||||
* 查询参数
|
||||
*/
|
||||
interface DateRangeParams {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统账户报表服务
|
||||
*/
|
||||
export const systemAccountReportService = {
|
||||
/**
|
||||
* 获取完整系统账户报表
|
||||
*/
|
||||
async getFullReport(params?: DateRangeParams): Promise<ApiResponse<SystemAccountReportResponse>> {
|
||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.FULL_REPORT, { params });
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取固定系统账户列表(带余额)
|
||||
*/
|
||||
async getFixedAccounts(): Promise<ApiResponse<SystemAccountWithBalance[]>> {
|
||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.FIXED_ACCOUNTS);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取省区域账户汇总
|
||||
*/
|
||||
async getProvinceSummary(): Promise<ApiResponse<RegionAccountsSummary>> {
|
||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.PROVINCE_SUMMARY);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取市区域账户汇总
|
||||
*/
|
||||
async getCitySummary(): Promise<ApiResponse<RegionAccountsSummary>> {
|
||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.CITY_SUMMARY);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取面对面结算统计
|
||||
*/
|
||||
async getOfflineSettlement(params?: DateRangeParams): Promise<ApiResponse<OfflineSettlementSummary>> {
|
||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.OFFLINE_SETTLEMENT, { params });
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取过期收益统计
|
||||
*/
|
||||
async getExpiredRewards(params?: DateRangeParams): Promise<ApiResponse<ExpiredRewardsSummary>> {
|
||||
return apiClient.get(API_ENDPOINTS.SYSTEM_ACCOUNT_REPORTS.EXPIRED_REWARDS, { params });
|
||||
},
|
||||
};
|
||||
|
||||
export default systemAccountReportService;
|
||||
|
|
@ -9,3 +9,5 @@ export * from './dashboard.types';
|
|||
export * from './pending-action.types';
|
||||
export * from './withdrawal.types';
|
||||
export * from './authorization.types';
|
||||
// [2026-01-04] 新增:系统账户报表类型
|
||||
export * from './system-account.types';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* 系统账户报表类型定义
|
||||
* [2026-01-04] 新增:用于系统账户报表显示
|
||||
* 回滚方式:删除此文件并从 index.ts 中移除导出
|
||||
*/
|
||||
|
||||
/**
|
||||
* 系统账户 DTO
|
||||
*/
|
||||
export interface SystemAccountDTO {
|
||||
id: string;
|
||||
accountType: string;
|
||||
regionCode: string | null;
|
||||
regionName: string | null;
|
||||
walletAddress: string | null;
|
||||
usdtBalance: string;
|
||||
hashpower: string;
|
||||
totalReceived: string;
|
||||
totalTransferred: string;
|
||||
status: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带钱包余额的系统账户
|
||||
*/
|
||||
export interface SystemAccountWithBalance extends SystemAccountDTO {
|
||||
walletBalance?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域账户汇总
|
||||
*/
|
||||
export interface RegionAccountsSummary {
|
||||
accounts: SystemAccountDTO[];
|
||||
summary: {
|
||||
totalBalance: string;
|
||||
totalReceived: string;
|
||||
count: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 面对面结算统计
|
||||
*/
|
||||
export interface OfflineSettlementSummary {
|
||||
totalAmount: number;
|
||||
totalCount: number;
|
||||
byMonth: Array<{
|
||||
month: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过期收益统计
|
||||
*/
|
||||
export interface ExpiredRewardsSummary {
|
||||
totalAmount: number;
|
||||
totalCount: number;
|
||||
byMonth: Array<{
|
||||
month: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
byRightType: Array<{
|
||||
rightType: string;
|
||||
amount: number;
|
||||
count: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 固定系统账户
|
||||
*/
|
||||
export interface FixedAccounts {
|
||||
costAccount: SystemAccountWithBalance | null; // 成本账户 S0000000001
|
||||
operationAccount: SystemAccountWithBalance | null; // 运营账户 S0000000002
|
||||
hqCommunity: SystemAccountWithBalance | null; // 总部社区 S0000000003
|
||||
rwadPoolPending: SystemAccountWithBalance | null; // RWAD待发放池 S0000000004
|
||||
platformFee: SystemAccountWithBalance | null; // 平台手续费 S0000000005
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整系统账户报表响应
|
||||
*/
|
||||
export interface SystemAccountReportResponse {
|
||||
fixedAccounts: FixedAccounts;
|
||||
provinceSummary: RegionAccountsSummary;
|
||||
citySummary: RegionAccountsSummary;
|
||||
offlineSettlement: OfflineSettlementSummary;
|
||||
expiredRewards: ExpiredRewardsSummary;
|
||||
generatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户类型显示名称映射
|
||||
*/
|
||||
export const ACCOUNT_TYPE_LABELS: Record<string, string> = {
|
||||
COST_ACCOUNT: '成本账户',
|
||||
OPERATION_ACCOUNT: '运营账户',
|
||||
HQ_COMMUNITY: '总部社区',
|
||||
RWAD_POOL_PENDING: 'RWAD待发放池',
|
||||
PLATFORM_FEE: '平台手续费',
|
||||
SYSTEM_PROVINCE: '系统省账户',
|
||||
SYSTEM_CITY: '系统市账户',
|
||||
};
|
||||
|
||||
/**
|
||||
* 权益类型显示名称映射
|
||||
*/
|
||||
export const RIGHT_TYPE_LABELS: Record<string, string> = {
|
||||
SETTLEABLE: '可结算收益',
|
||||
SHARE: '分享权益',
|
||||
PROVINCE_TEAM: '省团队权益',
|
||||
CITY_TEAM: '市团队权益',
|
||||
PROVINCE_REGION: '省区域权益',
|
||||
CITY_REGION: '市区域权益',
|
||||
COMMUNITY: '社区权益',
|
||||
};
|
||||
Loading…
Reference in New Issue