feat(admin-web): 完善系统账户报表收益统计显示

- 分享引荐收益汇总:显示所有状态(PENDING/SETTLEABLE/SETTLED/EXPIRED)的完整数据
- 面对面结算:改为从 wallet_ledger_entries 表查询 SPECIAL_DEDUCTION 类型
- 新增按状态分组统计表格和详细分类卡片

🤖 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-06 20:11:09 -08:00
parent 2c8263754f
commit 4df9895863
6 changed files with 221 additions and 23 deletions

View File

@ -26,11 +26,24 @@ export interface ExpiredRewardsSummary {
}
// [2026-01-06] 新增:收益类型汇总接口类型
// [2026-01-06] 更新:添加全量统计字段(包含所有状态)
export interface RewardTypeSummary {
totalAmount: number;
totalCount: number;
totalSettleableAmount: number;
totalSettledAmount: number;
// [2026-01-06] 新增:全量统计(包含所有状态)
allStatusTotalAmount: number;
allStatusTotalCount: number;
pendingAmount: number;
pendingCount: number;
expiredAmount: number;
expiredCount: number;
byStatus: Array<{
status: string;
amount: number;
count: number;
}>;
byMonth: Array<{
month: string;
amount: number;
@ -149,6 +162,13 @@ export class RewardServiceClient {
totalCount: 0,
totalSettleableAmount: 0,
totalSettledAmount: 0,
allStatusTotalAmount: 0,
allStatusTotalCount: 0,
pendingAmount: 0,
pendingCount: 0,
expiredAmount: 0,
expiredCount: 0,
byStatus: [],
byMonth: [],
};
return {

View File

@ -911,7 +911,7 @@ export class RewardApplicationService {
/**
*
* SETTLEABLE + SETTLED
* [2026-01-06] PENDINGSETTLEABLESETTLEDEXPIRED
*
* @param rightType
*/
@ -920,6 +920,18 @@ export class RewardApplicationService {
totalCount: number;
totalSettleableAmount: number;
totalSettledAmount: number;
// [2026-01-06] 新增:全量统计(包含所有状态)
allStatusTotalAmount: number;
allStatusTotalCount: number;
pendingAmount: number;
pendingCount: number;
expiredAmount: number;
expiredCount: number;
byStatus: Array<{
status: string;
amount: number;
count: number;
}>;
byMonth: Array<{
month: string;
amount: number;
@ -928,7 +940,20 @@ export class RewardApplicationService {
}> {
this.logger.log(`[getRewardsSummaryByType] 查询权益类型收益统计: ${rightType}`);
// 查询总计SETTLEABLE + SETTLED 状态)
// [2026-01-06] 新增:查询所有状态的总计
const allStatusResult = await this.prisma.rewardLedgerEntry.aggregate({
where: {
rightType: rightType,
},
_sum: {
usdtAmount: true,
},
_count: {
id: true,
},
});
// 查询总计SETTLEABLE + SETTLED 状态)- 有效收益
const aggregateResult = await this.prisma.rewardLedgerEntry.aggregate({
where: {
rightType: rightType,
@ -951,6 +976,9 @@ export class RewardApplicationService {
_sum: {
usdtAmount: true,
},
_count: {
id: true,
},
});
// 查询已结算金额
@ -962,8 +990,43 @@ export class RewardApplicationService {
_sum: {
usdtAmount: true,
},
_count: {
id: true,
},
});
// [2026-01-06] 新增:查询待领取金额
const pendingResult = await this.prisma.rewardLedgerEntry.aggregate({
where: {
rightType: rightType,
rewardStatus: 'PENDING',
},
_sum: {
usdtAmount: true,
},
_count: {
id: true,
},
});
// [2026-01-06] 新增:查询已过期金额
const expiredResult = await this.prisma.rewardLedgerEntry.aggregate({
where: {
rightType: rightType,
rewardStatus: 'EXPIRED',
},
_sum: {
usdtAmount: true,
},
_count: {
id: true,
},
});
const allStatusTotalAmount = allStatusResult._sum.usdtAmount
? Number(allStatusResult._sum.usdtAmount)
: 0;
const allStatusTotalCount = allStatusResult._count.id || 0;
const totalAmount = aggregateResult._sum.usdtAmount
? Number(aggregateResult._sum.usdtAmount)
: 0;
@ -971,11 +1034,21 @@ export class RewardApplicationService {
const totalSettleableAmount = settleableResult._sum.usdtAmount
? Number(settleableResult._sum.usdtAmount)
: 0;
const settleableCount = settleableResult._count.id || 0;
const totalSettledAmount = settledResult._sum.usdtAmount
? Number(settledResult._sum.usdtAmount)
: 0;
const settledCount = settledResult._count.id || 0;
const pendingAmount = pendingResult._sum.usdtAmount
? Number(pendingResult._sum.usdtAmount)
: 0;
const pendingCount = pendingResult._count.id || 0;
const expiredAmount = expiredResult._sum.usdtAmount
? Number(expiredResult._sum.usdtAmount)
: 0;
const expiredCount = expiredResult._count.id || 0;
// 查询按月统计
// [2026-01-06] 修改:查询按月统计(所有状态)
const byMonth = await this.prisma.$queryRaw<Array<{
month: string;
amount: any;
@ -987,17 +1060,31 @@ export class RewardApplicationService {
COUNT(*) as count
FROM reward_ledger_entries
WHERE right_type = ${rightType}
AND reward_status IN ('SETTLEABLE', 'SETTLED')
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month DESC
LIMIT 12
`;
// [2026-01-06] 新增:按状态分组统计
const byStatus = [
{ status: 'PENDING', amount: pendingAmount, count: pendingCount },
{ status: 'SETTLEABLE', amount: totalSettleableAmount, count: settleableCount },
{ status: 'SETTLED', amount: totalSettledAmount, count: settledCount },
{ status: 'EXPIRED', amount: expiredAmount, count: expiredCount },
];
return {
totalAmount,
totalCount,
totalSettleableAmount,
totalSettledAmount,
allStatusTotalAmount,
allStatusTotalCount,
pendingAmount,
pendingCount,
expiredAmount,
expiredCount,
byStatus,
byMonth: byMonth.map(row => ({
month: row.month,
amount: Number(row.amount) || 0,

View File

@ -3060,6 +3060,8 @@ export class WalletApplicationService {
/**
* (线)
*
* [2026-01-06] wallet_ledger_entries SPECIAL_DEDUCTION
* offline_settlement_deductions
*/
async getOfflineSettlementSummary(params: {
startDate?: Date;
@ -3073,10 +3075,12 @@ export class WalletApplicationService {
count: number;
}>;
}> {
this.logger.log(`[getOfflineSettlementSummary] 查询面对面结算统计`);
this.logger.log(`[getOfflineSettlementSummary] 查询面对面结算统计(从 SPECIAL_DEDUCTION 流水)`);
// 构建日期筛选条件
const whereClause: any = {};
// 构建日期筛选条件,包含 entryType 过滤
const whereClause: any = {
entryType: 'SPECIAL_DEDUCTION',
};
if (params.startDate || params.endDate) {
whereClause.createdAt = {};
if (params.startDate) {
@ -3087,23 +3091,24 @@ export class WalletApplicationService {
}
}
// 查询总计
const aggregateResult = await this.prisma.offlineSettlementDeduction.aggregate({
// 查询总计(从 wallet_ledger_entries 表)
const aggregateResult = await this.prisma.ledgerEntry.aggregate({
where: whereClause,
_sum: {
deductedAmount: true,
amount: true,
},
_count: {
id: true,
},
});
const totalAmount = aggregateResult._sum.deductedAmount
? Number(aggregateResult._sum.deductedAmount)
// SPECIAL_DEDUCTION 的 amount 是负数,取绝对值
const totalAmount = aggregateResult._sum.amount
? Math.abs(Number(aggregateResult._sum.amount))
: 0;
const totalCount = aggregateResult._count.id || 0;
// 查询按月统计(简化版本,不使用日期筛选
// 查询按月统计(从 wallet_ledger_entries 表SPECIAL_DEDUCTION 类型
const byMonth = await this.prisma.$queryRaw<Array<{
month: string;
amount: any;
@ -3111,14 +3116,17 @@ export class WalletApplicationService {
}>>`
SELECT
TO_CHAR(created_at, 'YYYY-MM') as month,
SUM(deducted_amount) as amount,
ABS(SUM(amount)) as amount,
COUNT(*) as count
FROM offline_settlement_deductions
FROM wallet_ledger_entries
WHERE entry_type = 'SPECIAL_DEDUCTION'
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month DESC
LIMIT 12
`;
this.logger.log(`[getOfflineSettlementSummary] 查询结果: totalAmount=${totalAmount}, totalCount=${totalCount}`);
return {
totalAmount,
totalCount,

View File

@ -275,6 +275,22 @@
background-color: #dcfce7;
color: #166534;
}
/* [2026-01-06] 新增:收益状态样式 */
&.settled {
background-color: #dcfce7;
color: #166534;
}
&.expired {
background-color: #fee2e2;
color: #991b1b;
}
&.pending {
background-color: #fef3c7;
color: #92400e;
}
}
/* 页脚 */

View File

@ -1053,30 +1053,82 @@ function RewardTypeSummarySection({
</button>
</div>
{/* 汇总卡片 */}
{/* [2026-01-06] 更新:全量汇总卡片(所有状态) */}
<div className={styles.summaryCards}>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.allStatusTotalAmount ?? data.totalAmount)} 绿</span>
</div>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{data.allStatusTotalCount ?? data.totalCount}</span>
</div>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.totalAmount)} 绿</span>
</div>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{data.totalCount}</span>
</div>
</div>
{/* [2026-01-06] 新增:按状态分组统计 */}
{data.byStatus && data.byStatus.length > 0 && (
<>
<h4 className={styles.subTitle}></h4>
<div className={styles.tableWrapper}>
<table className={styles.table}>
<thead>
<tr>
<th></th>
<th> (绿)</th>
<th></th>
</tr>
</thead>
<tbody>
{data.byStatus.map((item) => (
<tr key={item.status}>
<td>
<span className={`${styles.statusBadge} ${item.status === 'SETTLED' ? styles.settled : ''} ${item.status === 'EXPIRED' ? styles.expired : ''} ${item.status === 'PENDING' ? styles.pending : ''}`}>
{REWARD_STATUS_LABELS[item.status] || item.status}
</span>
</td>
<td>{formatAmount(item.amount)}</td>
<td>{item.count}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
{/* 详细分类卡片 */}
<h4 className={styles.subTitle}></h4>
<div className={styles.summaryCards}>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.totalSettleableAmount)} 绿</span>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.pendingAmount ?? 0)} ({data.pendingCount ?? 0})</span>
</div>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.totalSettledAmount)} 绿</span>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.totalSettleableAmount)} ({(data.byStatus?.find(s => s.status === 'SETTLEABLE')?.count) ?? 0})</span>
</div>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.totalSettledAmount)} ({(data.byStatus?.find(s => s.status === 'SETTLED')?.count) ?? 0})</span>
</div>
<div className={styles.summaryCard}>
<span className={styles.summaryLabel}></span>
<span className={styles.summaryValue}>{formatAmount(data.expiredAmount ?? 0)} ({data.expiredCount ?? 0})</span>
</div>
</div>
{/* 按月统计 */}
{data.byMonth && data.byMonth.length > 0 ? (
<>
<h4 className={styles.subTitle}></h4>
<h4 className={styles.subTitle}></h4>
<div className={styles.tableWrapper}>
<table className={styles.table}>
<thead>

View File

@ -188,6 +188,7 @@ export const ENTRY_TYPE_LABELS: Record<string, string> = {
};
// [2026-01-06] 新增:收益类型汇总统计类型
// [2026-01-06] 更新:添加全量统计字段(包含所有状态)
/**
*
*/
@ -196,6 +197,18 @@ export interface RewardTypeSummary {
totalCount: number;
totalSettleableAmount: number;
totalSettledAmount: number;
// [2026-01-06] 新增:全量统计(包含所有状态)
allStatusTotalAmount: number;
allStatusTotalCount: number;
pendingAmount: number;
pendingCount: number;
expiredAmount: number;
expiredCount: number;
byStatus: Array<{
status: string;
amount: number;
count: number;
}>;
byMonth: Array<{
month: string;
amount: number;
@ -281,8 +294,10 @@ export const REWARD_RIGHT_TYPE_LABELS: Record<string, string> = {
/**
*
* [2026-01-06] PENDING
*/
export const REWARD_STATUS_LABELS: Record<string, string> = {
PENDING: '待领取',
SETTLEABLE: '可结算',
SETTLED: '已结算',
EXPIRED: '已过期',