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:
parent
2c8263754f
commit
4df9895863
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@ export class RewardApplicationService {
|
|||
|
||||
/**
|
||||
* 获取按权益类型的收益统计汇总
|
||||
* 统计所有已发放(SETTLEABLE + SETTLED)的奖励按权益类型分组
|
||||
* [2026-01-06] 更新:统计所有状态的奖励,包括 PENDING、SETTLEABLE、SETTLED、EXPIRED
|
||||
*
|
||||
* @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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页脚 */
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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: '已过期',
|
||||
|
|
|
|||
Loading…
Reference in New Issue