fix(pre-planting): 预种权益分配metadata与正常认种对齐 + 删除retry-rewards

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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-28 19:27:16 -08:00
parent d9c238702e
commit 19fca05a81
3 changed files with 39 additions and 77 deletions

View File

@ -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 targetAccountIdtargetId
* SHARE_RIGHT/COMMUNITY_RIGHT
*
* pre_planting_reward_entries
* wallet-service /allocate-fundswallet-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,
};
}
}

View File

@ -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(

View File

@ -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_rewards24h S0000000001
* reward-service wallet-service
*/
async executeAllocations(
orderNo: string,
allocations: RewardAllocation[],
sourceAccountSequence: string,
treeCount: number,
provinceCode: string,
cityCode: string,
): Promise<void> {
// 只转 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立即可结算
// 未种 → PENDING24h过期归总部 S0000000001
// 与正常认种走同一套 wallet-service 代码,无需预种侧额外判断。
const referrer = referralInfo.directReferrer;
if (referrer) {
allocations.push({