fix(contribution): 使用 upsert 替代 update 避免记录不存在错误

将 addContribution 方法改为 upsert,当系统账户不存在时自动创建,
存在时增加算力余额。

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-20 08:43:37 -08:00
parent 5ec310124d
commit 1d5e3ebff2
4 changed files with 27 additions and 33 deletions

View File

@ -285,8 +285,6 @@ export class ContributionCalculationService {
(sum, u) => sum.add(u.amount),
new ContributionAmount(0),
);
// 确保 HEADQUARTERS 账户存在
await this.systemAccountRepository.ensureSystemAccountsExist();
await this.systemAccountRepository.addContribution('HEADQUARTERS', totalUnallocatedAmount);
// 发布 HEADQUARTERS 账户同步事件

View File

@ -81,9 +81,21 @@ export class SystemAccountRepository {
accountType: SystemAccountType,
amount: ContributionAmount,
): Promise<void> {
await this.client.systemAccount.update({
const accountNames: Record<SystemAccountType, string> = {
OPERATION: '运营账户',
PROVINCE: '省公司账户',
CITY: '市公司账户',
HEADQUARTERS: '总部账户',
};
await this.client.systemAccount.upsert({
where: { accountType },
data: {
create: {
accountType,
name: accountNames[accountType],
contributionBalance: amount.value,
},
update: {
contributionBalance: { increment: amount.value },
},
});

View File

@ -21,9 +21,8 @@ class ContributionPage extends ConsumerWidget {
final user = ref.watch(userNotifierProvider);
final accountSequence = user.accountSequence ?? '';
final contributionAsync = ref.watch(contributionProvider(accountSequence));
//
//
final estimatedEarnings = ref.watch(estimatedEarningsProvider(accountSequence));
final statsAsync = ref.watch(contributionStatsProvider);
//
final sharePoolAsync = ref.watch(sharePoolBalanceProvider);
@ -32,7 +31,6 @@ class ContributionPage extends ConsumerWidget {
final contribution = contributionAsync.valueOrNull;
final hasError = contributionAsync.hasError;
final error = contributionAsync.error;
final isStatsLoading = statsAsync.isLoading;
final isSharePoolLoading = sharePoolAsync.isLoading;
final sharePoolBalance = sharePoolAsync.valueOrNull;
@ -43,7 +41,6 @@ class ContributionPage extends ConsumerWidget {
child: RefreshIndicator(
onRefresh: () async {
ref.invalidate(contributionProvider(accountSequence));
ref.invalidate(contributionStatsProvider);
ref.invalidate(sharePoolBalanceProvider);
},
child: hasError && contribution == null
@ -78,7 +75,7 @@ class ContributionPage extends ConsumerWidget {
_buildThreeColumnStats(context, ref, contribution, isLoading),
const SizedBox(height: 16),
//
_buildTodayEstimateCard(context, ref, estimatedEarnings, isLoading || isStatsLoading),
_buildTodayEstimateCard(context, ref, estimatedEarnings, isLoading),
const SizedBox(height: 16),
//
_buildContributionDetailCard(context, ref, contribution, isLoading),

View File

@ -8,6 +8,7 @@ import '../../domain/repositories/contribution_repository.dart';
import '../../core/di/injection.dart';
import '../../core/network/api_client.dart';
import '../../core/network/api_endpoints.dart';
import 'mining_providers.dart';
final getUserContributionUseCaseProvider = Provider<GetUserContribution>((ref) {
return getIt<GetUserContribution>();
@ -118,8 +119,7 @@ final contributionStatsProvider = FutureProvider<ContributionStats?>((ref) async
});
///
/// : ( / ) ×
/// = × 86400
/// : × 60 × 60 × 24
class EstimatedEarnings {
final String dailyShares;
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 contributionAsync = ref.watch(contributionProvider(accountSequence));
final statsAsync = ref.watch(contributionStatsProvider);
// mining-service
final shareAccountAsync = ref.watch(shareAccountProvider(accountSequence));
final shareAccount = shareAccountAsync.valueOrNull;
final contribution = contributionAsync.valueOrNull;
final stats = statsAsync.valueOrNull;
if (contribution == null || stats == null) {
if (shareAccount == null) {
return EstimatedEarnings.zero;
}
final userContribution = double.tryParse(contribution.totalContribution) ?? 0;
final totalContribution = double.tryParse(stats.totalContribution) ?? 0;
final perSecondEarning = double.tryParse(shareAccount.perSecondEarning) ?? 0;
if (totalContribution <= 0 || userContribution <= 0) {
if (perSecondEarning <= 0) {
return EstimatedEarnings.zero;
}
//
final ratio = userContribution / totalContribution;
//
final dailyShares = ratio * dailyAllocationTotal;
//
final perSecondShares = dailyShares / 86400;
// = × 8640060×60×24
final dailyShares = perSecondEarning * 86400;
return EstimatedEarnings(
dailyShares: dailyShares.toStringAsFixed(4),
perSecondShares: perSecondShares.toStringAsFixed(8),
perSecondShares: perSecondEarning.toStringAsFixed(8),
isValid: true,
);
});