feat(settlement): implement settle-to-balance with detailed source tracking

Add complete settlement-to-balance feature that transfers settleable
earnings directly to wallet USDT balance (no currency swap). Key changes:

Backend (wallet-service):
- Add SettleToBalanceCommand for settlement operations
- Add settleToBalance method to WalletAccountAggregate
- Add settleToBalance application service with ledger recording
- Add internal API endpoint POST /api/v1/wallets/settle-to-balance

Backend (reward-service):
- Add settleToBalance client method for wallet-service communication
- Add settleRewardsToBalance application service method
- Add user-facing API endpoint POST /rewards/settle-to-balance
- Build detailed settlement memo with source user tracking per reward

Frontend (mobile-app):
- Add SettleToBalanceResult model class
- Add settleToBalance() method to RewardService
- Update pending_actions_page to handle SETTLE_REWARDS action
- Add completion detection via settleableUsdt balance check

Settlement memo now includes detailed breakdown by right type with
source user accountSequence for each reward entry, e.g.:
  结算 1000.00 绿积分到钱包余额
  涉及 5 笔奖励
    - SHARE_RIGHT: 500.00 绿积分
        来自 D2512120001: 288.00 绿积分
        来自 D2512120002: 212.00 绿积分

🤖 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-03 04:29:38 -08:00
parent cbbef170e8
commit 036696878f
12 changed files with 613 additions and 9 deletions

View File

@ -573,7 +573,8 @@
"Bash(dir /s /b \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\identity-service\\\\src\\\\infrastructure\\\\persistence\\\\repositories\\\\*.ts\")",
"Bash(head:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(pending-actions\\): add user pending actions system\n\nAdd a fully optional pending actions system that allows admins to configure\nspecific tasks that users must complete after login.\n\nBackend \\(identity-service\\):\n- Add UserPendingAction model to Prisma schema\n- Add migration for user_pending_actions table\n- Add PendingActionService with full CRUD operations\n- Add user-facing API \\(GET list, POST complete\\)\n- Add admin API \\(CRUD, batch create\\)\n\nAdmin Web:\n- Add pending actions management page\n- Support single/batch create, edit, cancel, delete\n- View action details including completion time\n- Filter by userId, actionCode, status\n\nFlutter Mobile App:\n- Add PendingActionService and PendingActionCheckService\n- Add PendingActionsPage for forced task execution\n- Integrate into splash_page login flow\n- Users must complete all pending tasks in priority order\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(npm run type-check:*)"
"Bash(npm run type-check:*)",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(settlement\\): implement settle-to-balance with detailed source tracking\n\nAdd complete settlement-to-balance feature that transfers settleable\nearnings directly to wallet USDT balance \\(no currency swap\\). Key changes:\n\nBackend \\(wallet-service\\):\n- Add SettleToBalanceCommand for settlement operations\n- Add settleToBalance method to WalletAccountAggregate\n- Add settleToBalance application service with ledger recording\n- Add internal API endpoint POST /api/v1/wallets/settle-to-balance\n\nBackend \\(reward-service\\):\n- Add settleToBalance client method for wallet-service communication\n- Add settleRewardsToBalance application service method\n- Add user-facing API endpoint POST /rewards/settle-to-balance\n- Build detailed settlement memo with source user tracking per reward\n\nFrontend \\(mobile-app\\):\n- Add SettleToBalanceResult model class\n- Add settleToBalance\\(\\) method to RewardService\n- Update pending_actions_page to handle SETTLE_REWARDS action\n- Add completion detection via settleableUsdt balance check\n\nSettlement memo now includes detailed breakdown by right type with\nsource user accountSequence for each reward entry, e.g.:\n 结算 1000.00 绿积分到钱包余额\n 涉及 5 笔奖励\n - SHARE_RIGHT: 500.00 绿积分\n 来自 D2512120001: 288.00 绿积分\n 来自 D2512120002: 212.00 绿积分\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
],
"deny": [],
"ask": []

View File

@ -1,4 +1,4 @@
import { Controller, Get, Query, UseGuards, Request, DefaultValuePipe, ParseIntPipe } from '@nestjs/common';
import { Controller, Get, Post, Query, UseGuards, Request, DefaultValuePipe, ParseIntPipe } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../shared/guards/jwt-auth.guard';
import { RewardApplicationService } from '../../application/services/reward-application.service';
@ -66,4 +66,35 @@ export class RewardController {
const accountSequence = req.user.accountSequence;
return this.rewardService.getSettleableRewards(accountSequence);
}
@Post('settle-to-balance')
@ApiOperation({
summary: '结算到余额',
description: '将可结算收益直接转入钱包USDT余额无币种兑换',
})
@ApiResponse({
status: 200,
description: '结算结果',
schema: {
type: 'object',
properties: {
success: { type: 'boolean', description: '是否成功' },
settlementId: { type: 'string', description: '结算ID' },
settledUsdtAmount: { type: 'number', description: '结算的USDT总额' },
settledHashpowerAmount: { type: 'number', description: '结算的算力总额' },
rewardCount: { type: 'number', description: '涉及的奖励数量' },
breakdown: {
type: 'object',
description: '按权益类型分解',
additionalProperties: { type: 'number' },
},
balanceAfter: { type: 'number', description: '结算后USDT余额' },
error: { type: 'string', description: '错误信息(如果失败)' },
},
},
})
async settleToBalance(@Request() req) {
const accountSequence = req.user.accountSequence;
return this.rewardService.settleRewardsToBalance(accountSequence);
}
}

View File

@ -390,6 +390,129 @@ export class RewardApplicationService {
};
}
/**
* (USDT USDT余额)
* USDT
*/
async settleRewardsToBalance(accountSequence: string): Promise<{
success: boolean;
settlementId?: string;
settledUsdtAmount: number;
settledHashpowerAmount: number;
rewardCount: number;
breakdown?: Record<string, number>;
balanceAfter?: number;
error?: string;
}> {
this.logger.log(`Settling rewards to balance for accountSequence ${accountSequence}`);
// 1. 获取可结算奖励
const settleableRewards = await this.rewardLedgerEntryRepository.findSettleableByAccountSequence(accountSequence);
if (settleableRewards.length === 0) {
return {
success: false,
settledUsdtAmount: 0,
settledHashpowerAmount: 0,
rewardCount: 0,
error: '没有可结算的收益',
};
}
// 2. 计算总金额和按权益类型分解
const totalUsdt = settleableRewards.reduce((sum, r) => sum + r.usdtAmount.amount, 0);
const totalHashpower = settleableRewards.reduce((sum, r) => sum + r.hashpowerAmount.value, 0);
const rewardEntryIds = settleableRewards.map(r => r.id?.toString() || '');
// 按权益类型分解
const breakdown: Record<string, number> = {};
for (const reward of settleableRewards) {
const rightType = reward.rewardSource.rightType;
breakdown[rightType] = (breakdown[rightType] || 0) + reward.usdtAmount.amount;
}
// 3. 构建详细的结算备注(按权益类型分组,列出每笔奖励的来源用户)
const memoLines = [
`结算 ${totalUsdt.toFixed(2)} 绿积分到钱包余额`,
`涉及 ${settleableRewards.length} 笔奖励`,
];
// 按权益类型分组,收集每笔奖励的来源用户和金额
const rewardsByType: Record<string, Array<{ sourceUser: string; amount: number }>> = {};
for (const reward of settleableRewards) {
const rightType = reward.rewardSource.rightType;
if (!rewardsByType[rightType]) {
rewardsByType[rightType] = [];
}
// 从 memo 中提取来源用户,格式如 "分享权益来自用户D2512120001的认种"
let sourceUser = reward.rewardSource.sourceUserId.toString();
const memoMatch = reward.memo.match(/来自用户([A-Z]\d+)的认种/);
if (memoMatch) {
sourceUser = memoMatch[1];
}
rewardsByType[rightType].push({
sourceUser,
amount: reward.usdtAmount.amount,
});
}
// 输出按权益类型分组的详细信息
for (const [rightType, rewards] of Object.entries(rewardsByType)) {
const typeTotal = rewards.reduce((sum, r) => sum + r.amount, 0);
memoLines.push(` - ${rightType}: ${typeTotal.toFixed(2)} 绿积分`);
for (const r of rewards) {
memoLines.push(` 来自 ${r.sourceUser}: ${r.amount.toFixed(2)} 绿积分`);
}
}
const memo = memoLines.join('\n');
// 4. 调用钱包服务执行结算
const walletResult = await this.walletService.settleToBalance({
accountSequence,
usdtAmount: totalUsdt,
rewardEntryIds,
breakdown,
memo,
});
if (!walletResult.success) {
return {
success: false,
settledUsdtAmount: totalUsdt,
settledHashpowerAmount: totalHashpower,
rewardCount: settleableRewards.length,
error: walletResult.error,
};
}
// 5. 更新奖励状态为已结算
for (const reward of settleableRewards) {
reward.settle('USDT', reward.usdtAmount.amount);
await this.rewardLedgerEntryRepository.save(reward);
await this.eventPublisher.publishAll(reward.domainEvents);
reward.clearDomainEvents();
}
// 6. 更新汇总数据
const summary = await this.rewardSummaryRepository.findByAccountSequence(accountSequence);
if (summary) {
summary.settle(Money.USDT(totalUsdt), Hashpower.create(totalHashpower));
await this.rewardSummaryRepository.save(summary);
}
this.logger.log(`Settled ${totalUsdt} USDT to balance for accountSequence ${accountSequence}, ${settleableRewards.length} rewards`);
return {
success: true,
settlementId: walletResult.settlementId,
settledUsdtAmount: totalUsdt,
settledHashpowerAmount: totalHashpower,
rewardCount: settleableRewards.length,
breakdown,
balanceAfter: walletResult.balanceAfter,
};
}
/**
* ()
*/

View File

@ -144,6 +144,61 @@ export class WalletServiceClient {
}
}
/**
* (USDT USDT余额)
* USDT
*/
async settleToBalance(params: {
accountSequence: string;
usdtAmount: number;
rewardEntryIds: string[];
breakdown?: Record<string, number>;
memo?: string;
}): Promise<{
success: boolean;
settlementId?: string;
settledAmount?: number;
balanceAfter?: number;
error?: string;
}> {
try {
this.logger.log(`Settling ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
const response = await fetch(`${this.baseUrl}/api/v1/wallets/settle-to-balance`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
this.logger.error(`Failed to settle to balance for ${params.accountSequence}:`, errorData);
return {
success: false,
error: errorData.message || `Settlement failed with status ${response.status}`,
};
}
const data = await response.json();
this.logger.log(`Successfully settled to balance for ${params.accountSequence}`);
return {
success: data.success,
settlementId: data.settlementId,
settledAmount: data.settledAmount,
balanceAfter: data.balanceAfter,
error: data.error,
};
} catch (error) {
this.logger.error(`Error settling to balance for ${params.accountSequence}:`, error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
/**
*
*

View File

@ -166,4 +166,32 @@ export class InternalWalletController {
return result;
}
@Post('settle-to-balance')
@Public()
@ApiOperation({ summary: '结算到余额(内部API) - 可结算USDT转入钱包余额' })
@ApiResponse({ status: 200, description: '结算结果' })
async settleToBalance(
@Body() dto: {
accountSequence: string;
usdtAmount: number;
rewardEntryIds: string[];
breakdown?: Record<string, number>;
memo?: string;
},
) {
this.logger.log(`========== settle-to-balance 请求 ==========`);
this.logger.log(`请求参数: ${JSON.stringify(dto)}`);
const result = await this.walletService.settleToBalance({
accountSequence: dto.accountSequence,
usdtAmount: dto.usdtAmount,
rewardEntryIds: dto.rewardEntryIds,
breakdown: dto.breakdown,
memo: dto.memo,
});
this.logger.log(`结算结果: ${JSON.stringify(result)}`);
return result;
}
}

View File

@ -6,5 +6,6 @@ export * from './unfreeze-for-planting.command';
export * from './add-rewards.command';
export * from './claim-rewards.command';
export * from './settle-rewards.command';
export * from './settle-to-balance.command';
export * from './allocate-funds.command';
export * from './request-withdrawal.command';

View File

@ -0,0 +1,102 @@
/**
*
* USDT
*/
export class SettleToBalanceCommand {
constructor(
/**
* D25122700022
*/
public readonly accountSequence: string,
/**
* ID
*/
public readonly userId: string,
) {}
}
/**
*
*/
export interface SettlementSourceInfo {
/**
* ID
*/
rewardEntryId: string;
/**
*
*/
sourceOrderNo: string;
/**
* ID
*/
sourceUserId: string;
/**
*
*/
sourceAccountSequence?: string;
/**
* SHARE_RIGHT, TEAM_RIGHT
*/
rightType: string;
/**
* USDT
*/
usdtAmount: number;
/**
*
*/
hashpowerAmount: number;
/**
*
*/
memo?: string;
}
/**
*
*/
export interface SettleToBalanceResult {
/**
*
*/
success: boolean;
/**
* ID
*/
settlementId?: string;
/**
* USDT
*/
settledUsdtAmount: number;
/**
*
*/
settledHashpowerAmount: number;
/**
*
*/
rewardCount: number;
/**
*
*/
breakdown?: Record<string, number>;
/**
*
*/
error?: string;
}

View File

@ -886,6 +886,86 @@ export class WalletApplicationService {
return savedOrder.id.toString();
}
/**
* (USDT USDT余额)
* USDT
*
* @param params
* @returns
*/
async settleToBalance(params: {
accountSequence: string;
usdtAmount: number;
rewardEntryIds: string[];
breakdown?: Record<string, number>;
memo?: string;
}): Promise<{
success: boolean;
settlementId: string;
settledAmount: number;
balanceAfter: number;
error?: string;
}> {
this.logger.log(`Settling ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
try {
// 1. 查找钱包
const wallet = await this.walletRepo.findByAccountSequence(params.accountSequence);
if (!wallet) {
throw new WalletNotFoundError(`accountSequence: ${params.accountSequence}`);
}
const usdtAmount = Money.USDT(params.usdtAmount);
const userId = wallet.userId.value;
// 2. 生成结算ID
const settlementId = `STL_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 3. 执行钱包结算
wallet.settleToBalance(usdtAmount, settlementId);
await this.walletRepo.save(wallet);
// 4. 记录账本流水(含详细来源信息)
const ledgerEntry = LedgerEntry.create({
accountSequence: wallet.accountSequence,
userId: UserId.create(userId),
entryType: LedgerEntryType.REWARD_SETTLED,
amount: usdtAmount,
balanceAfter: wallet.balances.usdt.available,
refOrderId: settlementId,
memo: params.memo || `结算 ${params.usdtAmount} 绿积分到钱包余额`,
payloadJson: {
settlementType: 'SETTLE_TO_BALANCE',
rewardEntryIds: params.rewardEntryIds,
rewardCount: params.rewardEntryIds.length,
breakdown: params.breakdown,
},
});
await this.ledgerRepo.save(ledgerEntry);
// 5. 使缓存失效
await this.walletCacheService.invalidateWallet(userId);
this.logger.log(`Successfully settled ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
return {
success: true,
settlementId,
settledAmount: params.usdtAmount,
balanceAfter: wallet.balances.usdt.available.value,
};
} catch (error) {
this.logger.error(`Failed to settle to balance for ${params.accountSequence}: ${error.message}`);
return {
success: false,
settlementId: '',
settledAmount: 0,
balanceAfter: 0,
error: error.message,
};
}
}
/**
* -
*

View File

@ -425,6 +425,44 @@ export class WalletAccount {
}));
}
/**
* (USDT USDT余额)
* USDT
*/
settleToBalance(usdtAmount: Money, settlementId: string): void {
this.ensureActive();
if (this._rewards.settleableUsdt.lessThan(usdtAmount)) {
throw new InsufficientBalanceError(
'USDT (settleable)',
usdtAmount.value.toString(),
this._rewards.settleableUsdt.value.toString(),
);
}
// 扣减可结算USDT
this._rewards = {
...this._rewards,
settleableUsdt: this._rewards.settleableUsdt.subtract(usdtAmount),
settledTotalUsdt: this._rewards.settledTotalUsdt.add(usdtAmount),
};
// 增加 USDT 可用余额
this._balances.usdt = this._balances.usdt.add(usdtAmount);
this._updatedAt = new Date();
// 发布结算完成事件
this.addDomainEvent(new SettlementCompletedEvent({
userId: this._userId.toString(),
walletId: this._walletId.toString(),
settlementOrderId: settlementId,
usdtAmount: usdtAmount.value.toString(),
settleCurrency: 'USDT',
receivedAmount: usdtAmount.value.toString(),
swapTxHash: undefined,
}));
}
// 冻结钱包
freezeWallet(): void {
if (this._status === WalletStatus.FROZEN) {

View File

@ -13,6 +13,7 @@ export const ACTION_CODES = {
SETTLE_REWARDS: 'SETTLE_REWARDS', // 结算奖励
BIND_PHONE: 'BIND_PHONE', // 绑定手机
FORCE_KYC: 'FORCE_KYC', // 强制 KYC
SIGN_CONTRACT: 'SIGN_CONTRACT', // 签订合同
CUSTOM_NOTICE: 'CUSTOM_NOTICE', // 自定义通知
} as const;
@ -105,6 +106,7 @@ export const ACTION_CODE_OPTIONS = [
{ value: ACTION_CODES.SETTLE_REWARDS, label: '结算奖励' },
{ value: ACTION_CODES.BIND_PHONE, label: '绑定手机' },
{ value: ACTION_CODES.FORCE_KYC, label: '强制 KYC' },
{ value: ACTION_CODES.SIGN_CONTRACT, label: '签订合同' },
{ value: ACTION_CODES.CUSTOM_NOTICE, label: '自定义通知' },
] as const;

View File

@ -144,6 +144,49 @@ class ExpiredRewardItem {
String get rightTypeName => allocationTypeName;
}
///
class SettleToBalanceResult {
final bool success;
final String? settlementId;
final double settledUsdtAmount;
final double settledHashpowerAmount;
final int rewardCount;
final Map<String, double>? breakdown;
final double? balanceAfter;
final String? error;
SettleToBalanceResult({
required this.success,
this.settlementId,
required this.settledUsdtAmount,
required this.settledHashpowerAmount,
required this.rewardCount,
this.breakdown,
this.balanceAfter,
this.error,
});
factory SettleToBalanceResult.fromJson(Map<String, dynamic> json) {
Map<String, double>? breakdown;
if (json['breakdown'] != null) {
breakdown = (json['breakdown'] as Map<String, dynamic>).map(
(key, value) => MapEntry(key, (value ?? 0).toDouble()),
);
}
return SettleToBalanceResult(
success: json['success'] ?? false,
settlementId: json['settlementId'],
settledUsdtAmount: (json['settledUsdtAmount'] ?? 0).toDouble(),
settledHashpowerAmount: (json['settledHashpowerAmount'] ?? 0).toDouble(),
rewardCount: (json['rewardCount'] ?? 0).toInt(),
breakdown: breakdown,
balanceAfter: json['balanceAfter']?.toDouble(),
error: json['error'],
);
}
}
/// ( reward-service )
class RewardSummary {
final double pendingUsdt;
@ -357,6 +400,60 @@ class RewardService {
}
}
///
///
/// POST /rewards/settle-to-balance (reward-service)
/// USDT
Future<SettleToBalanceResult> settleToBalance() async {
try {
debugPrint('[RewardService] ========== 结算到余额 ==========');
debugPrint('[RewardService] 请求: POST /rewards/settle-to-balance');
final response = await _apiClient.post('/rewards/settle-to-balance');
debugPrint('[RewardService] 响应状态码: ${response.statusCode}');
debugPrint('[RewardService] 响应数据: ${response.data}');
if (response.statusCode == 200 || response.statusCode == 201) {
final responseData = response.data as Map<String, dynamic>;
// data
final data = responseData['data'] as Map<String, dynamic>? ?? responseData;
final result = SettleToBalanceResult.fromJson(data);
debugPrint('[RewardService] 结算结果:');
debugPrint('[RewardService] - success: ${result.success}');
debugPrint('[RewardService] - settlementId: ${result.settlementId}');
debugPrint('[RewardService] - settledUsdtAmount: ${result.settledUsdtAmount}');
debugPrint('[RewardService] - rewardCount: ${result.rewardCount}');
debugPrint('[RewardService] - balanceAfter: ${result.balanceAfter}');
debugPrint('[RewardService] ================================');
return result;
}
debugPrint('[RewardService] 请求失败,状态码: ${response.statusCode}');
debugPrint('[RewardService] 响应内容: ${response.data}');
return SettleToBalanceResult(
success: false,
settledUsdtAmount: 0,
settledHashpowerAmount: 0,
rewardCount: 0,
error: '结算失败: ${response.statusCode}',
);
} catch (e, stackTrace) {
debugPrint('[RewardService] !!!!!!!!!! 结算异常 !!!!!!!!!!');
debugPrint('[RewardService] 错误: $e');
debugPrint('[RewardService] 堆栈: $stackTrace');
return SettleToBalanceResult(
success: false,
settledUsdtAmount: 0,
settledHashpowerAmount: 0,
rewardCount: 0,
error: e.toString(),
);
}
}
///
///
/// GET /wallet/expired-rewards (wallet-service)

View File

@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/di/injection_container.dart';
import '../../../../core/services/pending_action_service.dart';
import '../../../../core/services/contract_signing_service.dart';
import '../../../../core/services/reward_service.dart';
import '../../../../routes/route_paths.dart';
import '../../../kyc/data/kyc_service.dart';
@ -114,10 +116,15 @@ class _PendingActionsPageState extends ConsumerState<PendingActionsPage> {
return result == true;
case 'SETTLE_REWARDS':
//
// actionParams
final result = await context.push<bool>(RoutePaths.trading);
return result == true;
// API
final rewardService = ref.read(rewardServiceProvider);
final settleResult = await rewardService.settleToBalance();
if (settleResult.success) {
debugPrint('[PendingActionsPage] 结算成功: ${settleResult.settledUsdtAmount} USDT');
return true;
}
//
return await _checkIfAlreadyCompleted(action);
case 'BIND_PHONE':
//
@ -141,14 +148,18 @@ class _PendingActionsPageState extends ConsumerState<PendingActionsPage> {
final result = await context.push<bool>(
'${RoutePaths.contractSigning}/$orderNo',
);
return result == true;
if (result == true) return true;
//
return await _checkIfAlreadyCompleted(action);
}
//
final result = await context.push<bool>(
RoutePaths.pendingContracts,
extra: true, // forceSign = true
);
return result == true;
if (result == true) return true;
//
return await _checkIfAlreadyCompleted(action);
case 'UPDATE_PROFILE':
//
@ -163,7 +174,7 @@ class _PendingActionsPageState extends ConsumerState<PendingActionsPage> {
}
///
/// KYC
/// KYC
Future<bool> _checkIfAlreadyCompleted(PendingAction action) async {
try {
switch (action.actionCode) {
@ -188,6 +199,41 @@ class _PendingActionsPageState extends ConsumerState<PendingActionsPage> {
}
return false;
case 'SIGN_CONTRACT':
//
final contractService = ref.read(contractSigningServiceProvider);
final orderNo = action.actionParams?['orderNo'] as String?;
if (orderNo != null) {
//
try {
final task = await contractService.getTask(orderNo);
if (task.status == ContractSigningStatus.signed) {
debugPrint('[PendingActionsPage] 合同 $orderNo 已签署,跳过 SIGN_CONTRACT 操作');
return true;
}
} catch (e) {
//
debugPrint('[PendingActionsPage] 获取合同任务失败: $e');
}
}
//
final pendingTasks = await contractService.getPendingTasks();
if (pendingTasks.isEmpty) {
debugPrint('[PendingActionsPage] 没有待签署的合同,跳过 SIGN_CONTRACT 操作');
return true;
}
return false;
case 'SETTLE_REWARDS':
//
final rewardService = ref.read(rewardServiceProvider);
final summary = await rewardService.getMyRewardSummary();
if (summary.settleableUsdt <= 0) {
debugPrint('[PendingActionsPage] 没有可结算收益,跳过 SETTLE_REWARDS 操作');
return true;
}
return false;
//
default:
return false;