fix(contribution): 使用 upsert 替代 update 避免记录不存在错误
将 addContribution 方法改为 upsert,当系统账户不存在时自动创建, 存在时增加算力余额。 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5ec310124d
commit
1d5e3ebff2
|
|
@ -285,8 +285,6 @@ export class ContributionCalculationService {
|
||||||
(sum, u) => sum.add(u.amount),
|
(sum, u) => sum.add(u.amount),
|
||||||
new ContributionAmount(0),
|
new ContributionAmount(0),
|
||||||
);
|
);
|
||||||
// 确保 HEADQUARTERS 账户存在
|
|
||||||
await this.systemAccountRepository.ensureSystemAccountsExist();
|
|
||||||
await this.systemAccountRepository.addContribution('HEADQUARTERS', totalUnallocatedAmount);
|
await this.systemAccountRepository.addContribution('HEADQUARTERS', totalUnallocatedAmount);
|
||||||
|
|
||||||
// 发布 HEADQUARTERS 账户同步事件
|
// 发布 HEADQUARTERS 账户同步事件
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,21 @@ export class SystemAccountRepository {
|
||||||
accountType: SystemAccountType,
|
accountType: SystemAccountType,
|
||||||
amount: ContributionAmount,
|
amount: ContributionAmount,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.client.systemAccount.update({
|
const accountNames: Record<SystemAccountType, string> = {
|
||||||
|
OPERATION: '运营账户',
|
||||||
|
PROVINCE: '省公司账户',
|
||||||
|
CITY: '市公司账户',
|
||||||
|
HEADQUARTERS: '总部账户',
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.client.systemAccount.upsert({
|
||||||
where: { accountType },
|
where: { accountType },
|
||||||
data: {
|
create: {
|
||||||
|
accountType,
|
||||||
|
name: accountNames[accountType],
|
||||||
|
contributionBalance: amount.value,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
contributionBalance: { increment: amount.value },
|
contributionBalance: { increment: amount.value },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,8 @@ class ContributionPage extends ConsumerWidget {
|
||||||
final user = ref.watch(userNotifierProvider);
|
final user = ref.watch(userNotifierProvider);
|
||||||
final accountSequence = user.accountSequence ?? '';
|
final accountSequence = user.accountSequence ?? '';
|
||||||
final contributionAsync = ref.watch(contributionProvider(accountSequence));
|
final contributionAsync = ref.watch(contributionProvider(accountSequence));
|
||||||
// 获取预估收益
|
// 获取预估收益(基于用户每秒收益计算)
|
||||||
final estimatedEarnings = ref.watch(estimatedEarningsProvider(accountSequence));
|
final estimatedEarnings = ref.watch(estimatedEarningsProvider(accountSequence));
|
||||||
final statsAsync = ref.watch(contributionStatsProvider);
|
|
||||||
// 获取积分股池余量
|
// 获取积分股池余量
|
||||||
final sharePoolAsync = ref.watch(sharePoolBalanceProvider);
|
final sharePoolAsync = ref.watch(sharePoolBalanceProvider);
|
||||||
|
|
||||||
|
|
@ -32,7 +31,6 @@ class ContributionPage extends ConsumerWidget {
|
||||||
final contribution = contributionAsync.valueOrNull;
|
final contribution = contributionAsync.valueOrNull;
|
||||||
final hasError = contributionAsync.hasError;
|
final hasError = contributionAsync.hasError;
|
||||||
final error = contributionAsync.error;
|
final error = contributionAsync.error;
|
||||||
final isStatsLoading = statsAsync.isLoading;
|
|
||||||
final isSharePoolLoading = sharePoolAsync.isLoading;
|
final isSharePoolLoading = sharePoolAsync.isLoading;
|
||||||
final sharePoolBalance = sharePoolAsync.valueOrNull;
|
final sharePoolBalance = sharePoolAsync.valueOrNull;
|
||||||
|
|
||||||
|
|
@ -43,7 +41,6 @@ class ContributionPage extends ConsumerWidget {
|
||||||
child: RefreshIndicator(
|
child: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
ref.invalidate(contributionProvider(accountSequence));
|
ref.invalidate(contributionProvider(accountSequence));
|
||||||
ref.invalidate(contributionStatsProvider);
|
|
||||||
ref.invalidate(sharePoolBalanceProvider);
|
ref.invalidate(sharePoolBalanceProvider);
|
||||||
},
|
},
|
||||||
child: hasError && contribution == null
|
child: hasError && contribution == null
|
||||||
|
|
@ -78,7 +75,7 @@ class ContributionPage extends ConsumerWidget {
|
||||||
_buildThreeColumnStats(context, ref, contribution, isLoading),
|
_buildThreeColumnStats(context, ref, contribution, isLoading),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 今日预估收益
|
// 今日预估收益
|
||||||
_buildTodayEstimateCard(context, ref, estimatedEarnings, isLoading || isStatsLoading),
|
_buildTodayEstimateCard(context, ref, estimatedEarnings, isLoading),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// 贡献值明细(三类汇总)
|
// 贡献值明细(三类汇总)
|
||||||
_buildContributionDetailCard(context, ref, contribution, isLoading),
|
_buildContributionDetailCard(context, ref, contribution, isLoading),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import '../../domain/repositories/contribution_repository.dart';
|
||||||
import '../../core/di/injection.dart';
|
import '../../core/di/injection.dart';
|
||||||
import '../../core/network/api_client.dart';
|
import '../../core/network/api_client.dart';
|
||||||
import '../../core/network/api_endpoints.dart';
|
import '../../core/network/api_endpoints.dart';
|
||||||
|
import 'mining_providers.dart';
|
||||||
|
|
||||||
final getUserContributionUseCaseProvider = Provider<GetUserContribution>((ref) {
|
final getUserContributionUseCaseProvider = Provider<GetUserContribution>((ref) {
|
||||||
return getIt<GetUserContribution>();
|
return getIt<GetUserContribution>();
|
||||||
|
|
@ -118,8 +119,7 @@ final contributionStatsProvider = FutureProvider<ContributionStats?>((ref) async
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 计算用户今日预估收益
|
/// 计算用户今日预估收益
|
||||||
/// 公式: (用户贡献值 / 全网总贡献值) × 每日发放量
|
/// 公式: 用户每秒收益 × 60分钟 × 60秒 × 24小时
|
||||||
/// 每日发放量 = 每秒发放量 × 86400
|
|
||||||
class EstimatedEarnings {
|
class EstimatedEarnings {
|
||||||
final String dailyShares;
|
final String dailyShares;
|
||||||
final String perSecondShares;
|
final String perSecondShares;
|
||||||
|
|
@ -138,40 +138,27 @@ class EstimatedEarnings {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 每日发放总量配置(积分股)
|
|
||||||
/// 第1纪元: 100M / (2年 × 365天) ≈ 136,986 积分股/天
|
|
||||||
const double dailyAllocationTotal = 136986.0;
|
|
||||||
|
|
||||||
final estimatedEarningsProvider = Provider.family<EstimatedEarnings, String>((ref, accountSequence) {
|
final estimatedEarningsProvider = Provider.family<EstimatedEarnings, String>((ref, accountSequence) {
|
||||||
final contributionAsync = ref.watch(contributionProvider(accountSequence));
|
// 从 mining-service 获取用户每秒收益
|
||||||
final statsAsync = ref.watch(contributionStatsProvider);
|
final shareAccountAsync = ref.watch(shareAccountProvider(accountSequence));
|
||||||
|
final shareAccount = shareAccountAsync.valueOrNull;
|
||||||
|
|
||||||
final contribution = contributionAsync.valueOrNull;
|
if (shareAccount == null) {
|
||||||
final stats = statsAsync.valueOrNull;
|
|
||||||
|
|
||||||
if (contribution == null || stats == null) {
|
|
||||||
return EstimatedEarnings.zero;
|
return EstimatedEarnings.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
final userContribution = double.tryParse(contribution.totalContribution) ?? 0;
|
final perSecondEarning = double.tryParse(shareAccount.perSecondEarning) ?? 0;
|
||||||
final totalContribution = double.tryParse(stats.totalContribution) ?? 0;
|
|
||||||
|
|
||||||
if (totalContribution <= 0 || userContribution <= 0) {
|
if (perSecondEarning <= 0) {
|
||||||
return EstimatedEarnings.zero;
|
return EstimatedEarnings.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户占比
|
// 每日预估 = 每秒收益 × 86400(60×60×24)
|
||||||
final ratio = userContribution / totalContribution;
|
final dailyShares = perSecondEarning * 86400;
|
||||||
|
|
||||||
// 每日预估
|
|
||||||
final dailyShares = ratio * dailyAllocationTotal;
|
|
||||||
|
|
||||||
// 每秒预估
|
|
||||||
final perSecondShares = dailyShares / 86400;
|
|
||||||
|
|
||||||
return EstimatedEarnings(
|
return EstimatedEarnings(
|
||||||
dailyShares: dailyShares.toStringAsFixed(4),
|
dailyShares: dailyShares.toStringAsFixed(4),
|
||||||
perSecondShares: perSecondShares.toStringAsFixed(8),
|
perSecondShares: perSecondEarning.toStringAsFixed(8),
|
||||||
isValid: true,
|
isValid: true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue