diff --git a/backend/services/planting-service/src/infrastructure/external/wallet-service.client.ts b/backend/services/planting-service/src/infrastructure/external/wallet-service.client.ts index b62832a2..b09e9027 100644 --- a/backend/services/planting-service/src/infrastructure/external/wallet-service.client.ts +++ b/backend/services/planting-service/src/infrastructure/external/wallet-service.client.ts @@ -357,6 +357,55 @@ export class WalletServiceClient { } } + /** + * 预种资金分配(使用与 reward-service 一致的 FundAllocationItem 格式) + * + * 与现有 allocateFunds 的区别: + * - targetType: 'USER' | 'SYSTEM'(而非 FundAllocationTargetType 枚举) + * - targetId: 账户序列号(而非 targetAccountId) + * - allocationType: 权益类型字符串(用于 wallet-service ledger 分类) + * + * 规则:账户序列号以 S 开头 → SYSTEM,否则 → USER + */ + async allocatePrePlantingFunds(request: { + orderId: string; + allocations: Array<{ + targetType: 'USER' | 'SYSTEM'; + targetId: string; + allocationType: string; + amount: number; + metadata?: Record; + }>; + }): Promise { + try { + return await this.withRetry( + `allocatePrePlantingFunds(${request.orderId})`, + async () => { + const response = await firstValueFrom( + this.httpService.post( + `${this.baseUrl}/api/v1/wallets/allocate-funds`, + request, + ), + ); + // wallet-service 使用 TransformInterceptor,响应格式为 + // { success: true, data: { success: bool, allocatedCount: N, ... }, timestamp: "..." } + const data = response.data?.data ?? response.data; + return data?.success === true; + }, + ); + } catch (error) { + this.logger.error( + `Failed to allocate pre-planting funds for order: ${request.orderId}`, + error, + ); + if (this.configService.get('NODE_ENV') === 'development') { + this.logger.warn('Development mode: simulating successful pre-planting allocation'); + return true; + } + throw error; + } + } + /** * 注入底池 */ diff --git a/backend/services/planting-service/src/pre-planting/api/controllers/internal-pre-planting.controller.ts b/backend/services/planting-service/src/pre-planting/api/controllers/internal-pre-planting.controller.ts index 588398d5..9549b943 100644 --- a/backend/services/planting-service/src/pre-planting/api/controllers/internal-pre-planting.controller.ts +++ b/backend/services/planting-service/src/pre-planting/api/controllers/internal-pre-planting.controller.ts @@ -1,6 +1,7 @@ import { Controller, Get, + Post, Param, Query, HttpStatus, @@ -15,6 +16,7 @@ import { } from '@nestjs/swagger'; import { PrePlantingApplicationService } from '../../application/services/pre-planting-application.service'; import { PrismaService } from '../../../infrastructure/persistence/prisma/prisma.service'; +import { WalletServiceClient } from '../../../infrastructure/external/wallet-service.client'; @ApiTags('预种计划-内部API') @Controller('internal/pre-planting') @@ -24,6 +26,7 @@ export class InternalPrePlantingController { constructor( private readonly prePlantingService: PrePlantingApplicationService, private readonly prisma: PrismaService, + private readonly walletClient: WalletServiceClient, ) {} @Get('eligibility/:accountSequence') @@ -234,4 +237,69 @@ export class InternalPrePlantingController { pendingContracts, }; } + + /** + * [2026-02-28] 历史数据修复:重新执行预种权益资金分配 + * + * 背景:原 executeAllocations 使用了错误的 FundAllocationItem 字段名(targetAccountId→targetId) + * 导致所有预种订单的权益分配(SHARE_RIGHT/COMMUNITY_RIGHT 等)静默失败。 + * + * 本接口读取 pre_planting_reward_entries 表中已持久化的分配记录, + * 逐笔重新调用 wallet-service /allocate-funds(wallet-service 内置幂等检查,不会重复入账)。 + */ + @Post('admin/retry-rewards') + @ApiOperation({ summary: '重新执行预种权益资金分配(历史数据修复)' }) + @ApiResponse({ status: HttpStatus.OK, description: '修复结果' }) + async retryRewards() { + this.logger.log('[PRE-PLANTING RETRY] Starting rewards retry for all historical orders...'); + + // 1. 查询所有有 SETTLED 分配记录的订单号 + const distinctOrders = await this.prisma.prePlantingRewardEntry.findMany({ + where: { rewardStatus: 'SETTLED' }, + select: { sourceOrderNo: true }, + distinct: ['sourceOrderNo'], + }); + + const orderNos = distinctOrders.map((r) => r.sourceOrderNo); + this.logger.log(`[PRE-PLANTING RETRY] Found ${orderNos.length} orders to retry`); + + let successCount = 0; + let failCount = 0; + const failedOrders: string[] = []; + + // 2. 逐笔订单重新调用 wallet-service allocate-funds + for (const orderNo of orderNos) { + try { + const entries = await this.prisma.prePlantingRewardEntry.findMany({ + where: { sourceOrderNo: orderNo, rewardStatus: 'SETTLED' }, + }); + + if (entries.length === 0) continue; + + await this.walletClient.allocatePrePlantingFunds({ + orderId: orderNo, + allocations: entries.map((e) => ({ + targetType: e.recipientAccountSequence.startsWith('S') ? 'SYSTEM' : 'USER', + targetId: e.recipientAccountSequence, + allocationType: e.rightType, + amount: Number(e.usdtAmount), + })), + }); + + successCount++; + this.logger.log(`[PRE-PLANTING RETRY] OK: ${orderNo} (${entries.length} entries)`); + } catch (err) { + failCount++; + failedOrders.push(orderNo); + this.logger.error(`[PRE-PLANTING RETRY] FAILED: ${orderNo}`, err); + } + } + + return { + total: orderNos.length, + success: successCount, + failed: failCount, + failedOrders, + }; + } } diff --git a/backend/services/planting-service/src/pre-planting/application/services/pre-planting-reward.service.ts b/backend/services/planting-service/src/pre-planting/application/services/pre-planting-reward.service.ts index b071acac..d8aaefed 100644 --- a/backend/services/planting-service/src/pre-planting/application/services/pre-planting-reward.service.ts +++ b/backend/services/planting-service/src/pre-planting/application/services/pre-planting-reward.service.ts @@ -86,6 +86,11 @@ export class PrePlantingRewardService { /** * Step 5: 事务提交后执行资金转账(HTTP 调用 wallet-service) + * + * [2026-02-28] 修复:使用正确的 FundAllocationItem 格式 + * - targetType: 'USER' | 'SYSTEM'(账户序列以 S 开头 → SYSTEM,否则 → USER) + * - targetId: 账户序列号(原错误字段名 targetAccountId 已修正) + * - allocationType: 权益类型字符串(用于 wallet-service ledger 分类) */ async executeAllocations( orderNo: string, @@ -96,12 +101,14 @@ export class PrePlantingRewardService { (a) => a.rewardStatus === PrePlantingRewardStatus.SETTLED, ); - await this.walletClient.allocateFunds({ + await this.walletClient.allocatePrePlantingFunds({ orderId: orderNo, allocations: settledAllocations.map((a) => ({ - targetType: a.rightType as unknown as import('../../../domain/value-objects/fund-allocation-target-type.enum').FundAllocationTargetType, + // 账户序列以 S 开头 → 系统账户 SYSTEM,其余(D/9/8/7/6 开头)→ 用户账户 USER + targetType: a.recipientAccountSequence.startsWith('S') ? 'SYSTEM' : 'USER', + targetId: a.recipientAccountSequence, + allocationType: a.rightType, amount: a.amount, - targetAccountId: a.recipientAccountSequence, })), });