From 19fca05a81eb7657228982b22a5a7d9612b8f3cb Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 28 Feb 2026 19:27:16 -0800 Subject: [PATCH] =?UTF-8?q?fix(pre-planting):=20=E9=A2=84=E7=A7=8D?= =?UTF-8?q?=E6=9D=83=E7=9B=8A=E5=88=86=E9=85=8Dmetadata=E4=B8=8E=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E8=AE=A4=E7=A7=8D=E5=AF=B9=E9=BD=90=20+=20=E5=88=A0?= =?UTF-8?q?=E9=99=A4retry-rewards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. executeAllocations() metadata 修复: - 原:仅传 { source: 'PRE_PLANTING' },wallet流水缺失所有业务信息 - 现:传完整 metadata(rightType, sourceOrderNo, sourceAccountSequence, treeCount, provinceCode, cityCode, memo),与正常认种 reward-service 一致 - wallet-service 的 prePlantingPrefix() 通过 metadata.source 添加[预种]前缀 2. SHARE_RIGHT PENDING 机制说明(无代码变更): - 预种侧只确定收款人,全部标记 SETTLED 发给 wallet-service - wallet-service.allocateToUserWallet() 内部根据收款方 hasPlanted 判断: 已种→SETTLEABLE / 未种→PENDING(24h过期归总部) - 与正常认种走同一套 wallet-service 代码 3. 删除无用的 retry-rewards 端点及其 WalletServiceClient 依赖 不涉及历史数据修改,不影响正常认种流程。 Co-Authored-By: Claude Opus 4.6 --- .../internal-pre-planting.controller.ts | 67 ------------------- .../pre-planting-application.service.ts | 9 ++- .../services/pre-planting-reward.service.ts | 40 ++++++++--- 3 files changed, 39 insertions(+), 77 deletions(-) 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 c651ce3e..977959db 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 @@ -16,7 +16,6 @@ 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') @@ -26,7 +25,6 @@ export class InternalPrePlantingController { constructor( private readonly prePlantingService: PrePlantingApplicationService, private readonly prisma: PrismaService, - private readonly walletClient: WalletServiceClient, ) {} @Get('eligibility/:accountSequence') @@ -238,69 +236,4 @@ export class InternalPrePlantingController { }; } - /** - * [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), - metadata: { source: 'PRE_PLANTING' }, - })), - }); - - 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-application.service.ts b/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts index 79876c32..84df442c 100644 --- a/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts +++ b/backend/services/planting-service/src/pre-planting/application/services/pre-planting-application.service.ts @@ -174,7 +174,14 @@ export class PrePlantingApplicationService { orderId: orderNo, }); - await this.rewardService.executeAllocations(orderNo, rewardAllocations); + await this.rewardService.executeAllocations( + orderNo, + rewardAllocations, + accountSequence, + portionCount, + provinceCode, + cityCode, + ); } catch (error) { // 事务失败,解冻余额 this.logger.error( 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 f262fa2a..b2c92ba9 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 @@ -87,16 +87,27 @@ export class PrePlantingRewardService { /** * Step 5: 事务提交后执行资金转账(HTTP 调用 wallet-service) * - * [2026-02-28] 修复:使用正确的 FundAllocationItem 格式 - * - targetType: 'USER' | 'SYSTEM'(账户序列以 S 开头 → SYSTEM,否则 → USER) - * - targetId: 账户序列号(原错误字段名 targetAccountId 已修正) - * - allocationType: 权益类型字符串(用于 wallet-service ledger 分类) + * [2026-02-28] 修复:与正常认种对齐 + * - 使用正确的 FundAllocationItem 格式(targetType/targetId/allocationType) + * - metadata 包含完整信息(rightType, sourceOrderNo, sourceAccountSequence, + * treeCount, provinceCode, cityCode, memo),与正常认种 reward-service 一致 + * - metadata.source = 'PRE_PLANTING' 供 wallet-service 添加 [预种] 前缀 + * + * SHARE_RIGHT 的 PENDING/SETTLEABLE 判断: + * 预种侧不做 hasPlanted 判断,全部作为 SETTLED 发送给 wallet-service。 + * wallet-service.allocateToUserWallet() 内部会查询收款方钱包的 hasPlanted 字段: + * - hasPlanted=true → 直接 SETTLEABLE(进可结算余额) + * - hasPlanted=false → 创建 pending_rewards(24h 过期归总部 S0000000001) + * 这与正常认种的链路完全一致(reward-service → wallet-service 的同一段代码)。 */ async executeAllocations( orderNo: string, allocations: RewardAllocation[], + sourceAccountSequence: string, + treeCount: number, + provinceCode: string, + cityCode: string, ): Promise { - // 只转 SETTLED 状态的分配 const settledAllocations = allocations.filter( (a) => a.rewardStatus === PrePlantingRewardStatus.SETTLED, ); @@ -104,13 +115,20 @@ export class PrePlantingRewardService { await this.walletClient.allocatePrePlantingFunds({ orderId: orderNo, allocations: settledAllocations.map((a) => ({ - // 账户序列以 S 开头 → 系统账户 SYSTEM,其余(D/9/8/7/6 开头)→ 用户账户 USER targetType: a.recipientAccountSequence.startsWith('S') ? 'SYSTEM' : 'USER', targetId: a.recipientAccountSequence, allocationType: a.rightType, amount: a.amount, - // source 标识供 wallet-service 在流水备注中加"[预种]"前缀,与普通认种权益区分 - metadata: { source: 'PRE_PLANTING' }, + metadata: { + source: 'PRE_PLANTING', + rightType: a.rightType, + sourceOrderNo: orderNo, + sourceAccountSequence, + treeCount, + provinceCode, + cityCode, + memo: a.memo, + }, })), }); @@ -185,7 +203,11 @@ export class PrePlantingRewardService { ]); // 推荐奖励 (SHARE_RIGHT) - // 与现有认种一致:推荐奖励立即发放到推荐人钱包,无论推荐人是否已认种 + // 预种侧只确定收款人(推荐人 or S0000000005),全部标记 SETTLED 发给 wallet-service。 + // wallet-service.allocateToUserWallet() 会根据收款方 hasPlanted 自动决定: + // 已种 → SETTLEABLE(立即可结算) + // 未种 → PENDING(24h,过期归总部 S0000000001) + // 与正常认种走同一套 wallet-service 代码,无需预种侧额外判断。 const referrer = referralInfo.directReferrer; if (referrer) { allocations.push({