feat(pre-planting+mobile): 预种奖励在"我"页面展示
## 问题 预种奖励直接走 planting-service → wallet-service,绕过 reward-service, 导致前端"我"页面读 reward-service 的 summary/settleable 数据时看不到预种奖励。 ## 方案 前端同时读 reward-service(正常认种)和 planting-service(预种),合并展示。 ## 后端(planting-service) - PrePlantingRewardEntryRepository: 新增 findByRecipientAccountSequence() 方法, 按收款方账户查询预种奖励记录(注入 PrismaService 替代事务 client) - PrePlantingController: 新增 GET /pre-planting/my-rewards 端点, 返回当前用户作为收款方收到的预种奖励汇总+明细列表 格式与 reward-service 的 settleable 对齐(id, rightType, usdtAmount, sourceOrderNo 等) ## 前端(Flutter mobile-app) - PrePlantingService: 新增 getMyRewards() 方法 + PrePlantingMyRewards/PrePlantingRewardItem 数据类 - profile_page.dart: 并行调用 prePlantingService.getMyRewards(), 将预种奖励转为 SettleableRewardItem 合并到可结算列表, summary.settleableUsdt 也加上预种金额 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f32748c1d5
commit
3be7b47678
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { PrePlantingApplicationService } from '../../application/services/pre-planting-application.service';
|
||||
import { PrePlantingRewardEntryRepository } from '../../infrastructure/repositories/pre-planting-reward-entry.repository';
|
||||
import { PurchasePrePlantingDto } from '../dto/request/purchase-pre-planting.dto';
|
||||
import { SignPrePlantingContractDto } from '../dto/request/sign-pre-planting-contract.dto';
|
||||
import { JwtAuthGuard } from '../../../api/guards/jwt-auth.guard';
|
||||
|
|
@ -34,6 +35,7 @@ interface AuthenticatedRequest {
|
|||
export class PrePlantingController {
|
||||
constructor(
|
||||
private readonly prePlantingService: PrePlantingApplicationService,
|
||||
private readonly rewardEntryRepo: PrePlantingRewardEntryRepository,
|
||||
) {}
|
||||
|
||||
@Post('purchase')
|
||||
|
|
@ -103,6 +105,40 @@ export class PrePlantingController {
|
|||
return this.prePlantingService.getMerges(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取"我收到的"预种奖励(作为收款方)
|
||||
* 返回格式与 reward-service 的 summary + settleable 对齐,方便前端合并
|
||||
*/
|
||||
@Get('my-rewards')
|
||||
@ApiOperation({ summary: '获取我收到的预种奖励' })
|
||||
@ApiResponse({ status: HttpStatus.OK, description: '预种奖励汇总+明细' })
|
||||
async getMyRewards(@Req() req: AuthenticatedRequest) {
|
||||
const accountSequence = req.user.accountSequence;
|
||||
const entries = await this.rewardEntryRepo.findByRecipientAccountSequence(accountSequence);
|
||||
|
||||
// 只返回用户账户收到的奖励(D 开头),排除系统账户
|
||||
const userEntries = entries.filter(e => e.recipientAccountSequence.startsWith('D'));
|
||||
|
||||
const settleableUsdt = userEntries.reduce((sum, e) => sum + e.usdtAmount, 0);
|
||||
|
||||
return {
|
||||
summary: {
|
||||
settleableUsdt,
|
||||
},
|
||||
settleableRewards: userEntries.map(e => ({
|
||||
id: `pp-${e.sourceOrderNo}-${e.rightType}`,
|
||||
rightType: e.rightType,
|
||||
usdtAmount: e.usdtAmount,
|
||||
hashpowerAmount: 0,
|
||||
createdAt: e.createdAt.toISOString(),
|
||||
claimedAt: null,
|
||||
sourceOrderNo: e.sourceOrderNo,
|
||||
sourceAccountSequence: e.sourceAccountSequence,
|
||||
memo: e.memo || '',
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@Get('merges/:mergeNo/contract-pdf')
|
||||
@ApiOperation({ summary: '下载合并合同预览 PDF' })
|
||||
@ApiResponse({ status: HttpStatus.OK, description: 'PDF 文件' })
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrePlantingRewardStatus } from '../../domain/value-objects/pre-planting-reward-status.enum';
|
||||
import { PrismaService } from '../../../infrastructure/persistence/prisma/prisma.service';
|
||||
|
||||
export interface PrePlantingRewardEntryData {
|
||||
sourceOrderNo: string;
|
||||
|
|
@ -16,6 +17,8 @@ export interface PrePlantingRewardEntryData {
|
|||
export class PrePlantingRewardEntryRepository {
|
||||
private readonly logger = new Logger(PrePlantingRewardEntryRepository.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async saveMany(
|
||||
tx: Prisma.TransactionClient,
|
||||
entries: PrePlantingRewardEntryData[],
|
||||
|
|
@ -75,4 +78,26 @@ export class PrePlantingRewardEntryRepository {
|
|||
memo: r.memo || undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按收款方账户查询(用于用户查看"我收到的预种奖励")
|
||||
*/
|
||||
async findByRecipientAccountSequence(
|
||||
accountSequence: string,
|
||||
): Promise<(PrePlantingRewardEntryData & { createdAt: Date })[]> {
|
||||
const records = await this.prisma.prePlantingRewardEntry.findMany({
|
||||
where: { recipientAccountSequence: accountSequence },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
return records.map((r) => ({
|
||||
sourceOrderNo: r.sourceOrderNo,
|
||||
sourceAccountSequence: r.sourceAccountSequence,
|
||||
recipientAccountSequence: r.recipientAccountSequence,
|
||||
rightType: r.rightType,
|
||||
usdtAmount: Number(r.usdtAmount),
|
||||
rewardStatus: r.rewardStatus as PrePlantingRewardStatus,
|
||||
memo: r.memo || undefined,
|
||||
createdAt: r.createdAt,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -545,4 +545,89 @@ class PrePlantingService {
|
|||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取"我收到的"预种奖励
|
||||
///
|
||||
/// 返回 { summary: { settleableUsdt }, settleableRewards: [...] }
|
||||
Future<PrePlantingMyRewards> getMyRewards() async {
|
||||
try {
|
||||
debugPrint('[PrePlantingService] 获取我的预种奖励');
|
||||
final response = await _apiClient.get('/pre-planting/my-rewards');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
return PrePlantingMyRewards.fromJson(data);
|
||||
}
|
||||
|
||||
debugPrint('[PrePlantingService] 获取预种奖励失败: ${response.statusCode}');
|
||||
return PrePlantingMyRewards.empty();
|
||||
} catch (e) {
|
||||
debugPrint('[PrePlantingService] 获取预种奖励异常: $e');
|
||||
return PrePlantingMyRewards.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 预种奖励返回数据
|
||||
class PrePlantingMyRewards {
|
||||
final double settleableUsdt;
|
||||
final List<PrePlantingRewardItem> settleableRewards;
|
||||
|
||||
PrePlantingMyRewards({
|
||||
required this.settleableUsdt,
|
||||
required this.settleableRewards,
|
||||
});
|
||||
|
||||
factory PrePlantingMyRewards.empty() => PrePlantingMyRewards(
|
||||
settleableUsdt: 0,
|
||||
settleableRewards: [],
|
||||
);
|
||||
|
||||
factory PrePlantingMyRewards.fromJson(Map<String, dynamic> json) {
|
||||
final summary = json['summary'] as Map<String, dynamic>? ?? {};
|
||||
final rewards = json['settleableRewards'] as List<dynamic>? ?? [];
|
||||
|
||||
return PrePlantingMyRewards(
|
||||
settleableUsdt: (summary['settleableUsdt'] ?? 0).toDouble(),
|
||||
settleableRewards: rewards
|
||||
.map((r) => PrePlantingRewardItem.fromJson(r as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 预种奖励条目
|
||||
class PrePlantingRewardItem {
|
||||
final String id;
|
||||
final String rightType;
|
||||
final double usdtAmount;
|
||||
final double hashpowerAmount;
|
||||
final DateTime createdAt;
|
||||
final String sourceOrderNo;
|
||||
final String? sourceAccountSequence;
|
||||
final String memo;
|
||||
|
||||
PrePlantingRewardItem({
|
||||
required this.id,
|
||||
required this.rightType,
|
||||
required this.usdtAmount,
|
||||
required this.hashpowerAmount,
|
||||
required this.createdAt,
|
||||
required this.sourceOrderNo,
|
||||
this.sourceAccountSequence,
|
||||
required this.memo,
|
||||
});
|
||||
|
||||
factory PrePlantingRewardItem.fromJson(Map<String, dynamic> json) {
|
||||
return PrePlantingRewardItem(
|
||||
id: json['id']?.toString() ?? '',
|
||||
rightType: json['rightType'] ?? '',
|
||||
usdtAmount: (json['usdtAmount'] ?? 0).toDouble(),
|
||||
hashpowerAmount: (json['hashpowerAmount'] ?? 0).toDouble(),
|
||||
createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(),
|
||||
sourceOrderNo: json['sourceOrderNo'] ?? '',
|
||||
sourceAccountSequence: json['sourceAccountSequence'],
|
||||
memo: json['memo'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import '../../../../core/di/injection_container.dart';
|
|||
import '../../../../core/storage/storage_keys.dart';
|
||||
import '../../../../core/services/referral_service.dart';
|
||||
import '../../../../core/services/reward_service.dart';
|
||||
import '../../../../core/services/pre_planting_service.dart';
|
||||
import '../../../../core/services/wallet_service.dart';
|
||||
import '../../../../core/services/notification_service.dart';
|
||||
import '../../../../core/providers/notification_badge_provider.dart';
|
||||
|
|
@ -771,25 +772,46 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
});
|
||||
}
|
||||
|
||||
debugPrint('[ProfilePage] 获取 rewardServiceProvider 和 walletServiceProvider...');
|
||||
debugPrint('[ProfilePage] 获取 rewardServiceProvider / prePlantingServiceProvider / walletServiceProvider...');
|
||||
final rewardService = ref.read(rewardServiceProvider);
|
||||
final prePlantingService = ref.read(prePlantingServiceProvider);
|
||||
final walletService = ref.read(walletServiceProvider);
|
||||
|
||||
// 并行加载汇总数据、待领取列表、可结算列表、已过期列表、流水统计
|
||||
debugPrint('[ProfilePage] 调用 getMyRewardSummary()、getPendingRewards()、getSettleableRewards()、getExpiredRewards()、getLedgerStatistics()...');
|
||||
// 并行加载:正常认种奖励 + 预种奖励 + 流水统计
|
||||
debugPrint('[ProfilePage] 并行调用 reward-service + planting-service(预种) + wallet-service...');
|
||||
final results = await Future.wait([
|
||||
rewardService.getMyRewardSummary(),
|
||||
rewardService.getPendingRewards(),
|
||||
rewardService.getSettleableRewards(),
|
||||
rewardService.getExpiredRewards(),
|
||||
walletService.getLedgerStatistics(),
|
||||
prePlantingService.getMyRewards(), // 预种奖励
|
||||
]);
|
||||
|
||||
final summary = results[0] as RewardSummary;
|
||||
final pendingRewards = results[1] as List<PendingRewardItem>;
|
||||
final settleableRewards = results[2] as List<SettleableRewardItem>;
|
||||
var settleableRewards = results[2] as List<SettleableRewardItem>;
|
||||
final expiredRewards = results[3] as List<ExpiredRewardItem>;
|
||||
final ledgerStats = results[4] as LedgerStatistics;
|
||||
final prePlantingRewards = results[5] as PrePlantingMyRewards;
|
||||
|
||||
// 合并预种可结算奖励到列表中
|
||||
// 预种奖励转为 SettleableRewardItem 格式,与正常认种统一展示
|
||||
final prePlantingSettleable = prePlantingRewards.settleableRewards.map((r) =>
|
||||
SettleableRewardItem(
|
||||
id: r.id,
|
||||
rightType: r.rightType,
|
||||
usdtAmount: r.usdtAmount,
|
||||
hashpowerAmount: r.hashpowerAmount,
|
||||
createdAt: r.createdAt,
|
||||
claimedAt: null,
|
||||
sourceOrderNo: r.sourceOrderNo,
|
||||
sourceAccountSequence: r.sourceAccountSequence,
|
||||
memo: '[预种] ${r.memo}',
|
||||
),
|
||||
).toList();
|
||||
settleableRewards = [...settleableRewards, ...prePlantingSettleable];
|
||||
debugPrint('[ProfilePage] 预种可结算奖励: ${prePlantingSettleable.length} 条, 金额: ${prePlantingRewards.settleableUsdt}');
|
||||
|
||||
// 从流水统计中获取 REWARD_SETTLED 类型的总金额作为"已结算"数据
|
||||
// 这比 summary.settledTotalUsdt 更准确,因为直接来自交易流水
|
||||
|
|
@ -820,7 +842,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
|||
setState(() {
|
||||
_pendingUsdt = summary.pendingUsdt;
|
||||
_pendingPower = summary.pendingHashpower;
|
||||
_settleableUsdt = summary.settleableUsdt;
|
||||
_settleableUsdt = summary.settleableUsdt + prePlantingRewards.settleableUsdt;
|
||||
// 使用流水统计的数据,更准确
|
||||
_settledUsdt = settledFromLedger;
|
||||
_expiredUsdt = summary.expiredTotalUsdt;
|
||||
|
|
|
|||
Loading…
Reference in New Issue