fix(reward): change sourceOrderId to sourceOrderNo string type

Fixes BigInt conversion error when consuming planting.order.paid events.
The orderId from planting-service is a string format like PLT1765391584505Q0Q6QD,
not a numeric value.

Changes:
- reward-source.vo.ts: sourceOrderId (bigint) → sourceOrderNo (string)
- reward-calculation.service.ts: updated all method signatures
- reward-application.service.ts: updated distributeRewards params
- event-consumer.controller.ts: removed BigInt() conversion
- prisma schema: changed column type to VarChar(50)
- mapper/repository: updated field names accordingly
- Removed incompatible prisma.config.ts (Prisma 7.x format)

🤖 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 2025-12-10 11:11:36 -08:00
parent 6b10b15492
commit 8cea0e7998
12 changed files with 65 additions and 64 deletions

View File

@ -1,14 +0,0 @@
// This file was generated by Prisma and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});

View File

@ -0,0 +1,15 @@
-- AlterColumn: Change source_order_id from BIGINT to VARCHAR(50)
-- This is needed because planting-service sends orderNo as string (e.g., PLT1765391584505Q0Q6QD)
-- Step 1: Drop the existing index
DROP INDEX IF EXISTS "idx_source_order";
-- Step 2: Rename the old column
ALTER TABLE "reward_ledger_entries" RENAME COLUMN "source_order_id" TO "source_order_no";
-- Step 3: Change the column type from BIGINT to VARCHAR(50)
ALTER TABLE "reward_ledger_entries"
ALTER COLUMN "source_order_no" TYPE VARCHAR(50) USING "source_order_no"::VARCHAR(50);
-- Step 4: Recreate the index on the new column
CREATE INDEX "idx_source_order" ON "reward_ledger_entries"("source_order_no");

View File

@ -16,7 +16,7 @@ model RewardLedgerEntry {
userId BigInt @map("user_id") // 接收奖励的用户ID userId BigInt @map("user_id") // 接收奖励的用户ID
// === 奖励来源 === // === 奖励来源 ===
sourceOrderId BigInt @map("source_order_id") // 来源认种订单ID sourceOrderNo String @map("source_order_no") @db.VarChar(50) // 来源认种订单号(字符串格式如PLT1765391584505Q0Q6QD)
sourceUserId BigInt @map("source_user_id") // 触发奖励的用户ID(认种者) sourceUserId BigInt @map("source_user_id") // 触发奖励的用户ID(认种者)
rightType String @map("right_type") @db.VarChar(50) // 权益类型 rightType String @map("right_type") @db.VarChar(50) // 权益类型
@ -40,7 +40,7 @@ model RewardLedgerEntry {
@@map("reward_ledger_entries") @@map("reward_ledger_entries")
@@index([userId, rewardStatus], name: "idx_user_status") @@index([userId, rewardStatus], name: "idx_user_status")
@@index([userId, createdAt(sort: Desc)], name: "idx_user_created") @@index([userId, createdAt(sort: Desc)], name: "idx_user_created")
@@index([sourceOrderId], name: "idx_source_order") @@index([sourceOrderNo], name: "idx_source_order")
@@index([sourceUserId], name: "idx_source_user") @@index([sourceUserId], name: "idx_source_user")
@@index([rightType], name: "idx_right_type") @@index([rightType], name: "idx_right_type")
@@index([rewardStatus], name: "idx_status") @@index([rewardStatus], name: "idx_status")

View File

@ -35,13 +35,13 @@ export class RewardApplicationService {
* () * ()
*/ */
async distributeRewards(params: { async distributeRewards(params: {
sourceOrderId: bigint; sourceOrderNo: string; // 订单号是字符串格式如 PLT1765391584505Q0Q6QD
sourceUserId: bigint; sourceUserId: bigint;
treeCount: number; treeCount: number;
provinceCode: string; provinceCode: string;
cityCode: string; cityCode: string;
}): Promise<void> { }): Promise<void> {
this.logger.log(`Distributing rewards for order ${params.sourceOrderId}`); this.logger.log(`Distributing rewards for order ${params.sourceOrderNo}`);
// 1. 计算所有奖励 // 1. 计算所有奖励
const rewards = await this.rewardCalculationService.calculateRewards(params); const rewards = await this.rewardCalculationService.calculateRewards(params);
@ -76,7 +76,7 @@ export class RewardApplicationService {
reward.clearDomainEvents(); reward.clearDomainEvents();
} }
this.logger.log(`Distributed ${rewards.length} rewards for order ${params.sourceOrderId}`); this.logger.log(`Distributed ${rewards.length} rewards for order ${params.sourceOrderNo}`);
} }
/** /**

View File

@ -106,7 +106,7 @@ export class RewardLedgerEntry {
entry._domainEvents.push(new RewardCreatedEvent({ entry._domainEvents.push(new RewardCreatedEvent({
entryId: entry._id?.toString() || 'temp', entryId: entry._id?.toString() || 'temp',
userId: entry._userId.toString(), userId: entry._userId.toString(),
sourceOrderId: entry._rewardSource.sourceOrderId.toString(), sourceOrderNo: entry._rewardSource.sourceOrderNo,
sourceUserId: entry._rewardSource.sourceUserId.toString(), sourceUserId: entry._rewardSource.sourceUserId.toString(),
rightType: entry._rewardSource.rightType, rightType: entry._rewardSource.rightType,
usdtAmount: entry._usdtAmount.amount, usdtAmount: entry._usdtAmount.amount,
@ -143,7 +143,7 @@ export class RewardLedgerEntry {
entry._domainEvents.push(new RewardCreatedEvent({ entry._domainEvents.push(new RewardCreatedEvent({
entryId: entry._id?.toString() || 'temp', entryId: entry._id?.toString() || 'temp',
userId: entry._userId.toString(), userId: entry._userId.toString(),
sourceOrderId: entry._rewardSource.sourceOrderId.toString(), sourceOrderNo: entry._rewardSource.sourceOrderNo,
sourceUserId: entry._rewardSource.sourceUserId.toString(), sourceUserId: entry._rewardSource.sourceUserId.toString(),
rightType: entry._rewardSource.rightType, rightType: entry._rewardSource.rightType,
usdtAmount: entry._usdtAmount.amount, usdtAmount: entry._usdtAmount.amount,

View File

@ -5,7 +5,7 @@ import { RewardStatus } from '../value-objects/reward-status.enum';
export interface RewardCreatedPayload { export interface RewardCreatedPayload {
entryId: string; entryId: string;
userId: string; userId: string;
sourceOrderId: string; sourceOrderNo: string;
sourceUserId: string; sourceUserId: string;
rightType: RightType; rightType: RightType;
usdtAmount: number; usdtAmount: number;

View File

@ -19,7 +19,7 @@ export interface IRewardLedgerEntryRepository {
findPendingByUserId(userId: bigint): Promise<RewardLedgerEntry[]>; findPendingByUserId(userId: bigint): Promise<RewardLedgerEntry[]>;
findSettleableByUserId(userId: bigint): Promise<RewardLedgerEntry[]>; findSettleableByUserId(userId: bigint): Promise<RewardLedgerEntry[]>;
findExpiredPending(beforeDate: Date): Promise<RewardLedgerEntry[]>; findExpiredPending(beforeDate: Date): Promise<RewardLedgerEntry[]>;
findBySourceOrderId(sourceOrderId: bigint): Promise<RewardLedgerEntry[]>; findBySourceOrderNo(sourceOrderNo: string): Promise<RewardLedgerEntry[]>;
countByUserId(userId: bigint, status?: RewardStatus): Promise<number>; countByUserId(userId: bigint, status?: RewardStatus): Promise<number>;
} }

View File

@ -51,7 +51,7 @@ export class RewardCalculationService {
* 2199 USDT = 400 + 300 + 9 + 800 + 500 + 15 + 20 + 35 + 40 + 80 * 2199 USDT = 400 + 300 + 9 + 800 + 500 + 15 + 20 + 35 + 40 + 80
*/ */
async calculateRewards(params: { async calculateRewards(params: {
sourceOrderId: bigint; sourceOrderNo: string; // 订单号是字符串格式
sourceUserId: bigint; sourceUserId: bigint;
treeCount: number; treeCount: number;
provinceCode: string; provinceCode: string;
@ -65,7 +65,7 @@ export class RewardCalculationService {
// 1. 成本费 (400 USDT) // 1. 成本费 (400 USDT)
const costFeeReward = this.calculateCostFee( const costFeeReward = this.calculateCostFee(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.treeCount, params.treeCount,
); );
@ -73,7 +73,7 @@ export class RewardCalculationService {
// 2. 运营费 (300 USDT) // 2. 运营费 (300 USDT)
const operationFeeReward = this.calculateOperationFee( const operationFeeReward = this.calculateOperationFee(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.treeCount, params.treeCount,
); );
@ -81,7 +81,7 @@ export class RewardCalculationService {
// 3. 总部社区基础费 (9 USDT) // 3. 总部社区基础费 (9 USDT)
const headquartersBaseFeeReward = this.calculateHeadquartersBaseFee( const headquartersBaseFeeReward = this.calculateHeadquartersBaseFee(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.treeCount, params.treeCount,
); );
@ -89,7 +89,7 @@ export class RewardCalculationService {
// 4. RWAD底池注入 (800 USDT) // 4. RWAD底池注入 (800 USDT)
const rwadPoolReward = this.calculateRwadPoolInjection( const rwadPoolReward = this.calculateRwadPoolInjection(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.treeCount, params.treeCount,
); );
@ -101,7 +101,7 @@ export class RewardCalculationService {
// 5. 分享权益 (500 USDT) // 5. 分享权益 (500 USDT)
const shareRewards = await this.calculateShareRights( const shareRewards = await this.calculateShareRights(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.treeCount, params.treeCount,
); );
@ -109,7 +109,7 @@ export class RewardCalculationService {
// 6. 省团队权益 (20 USDT) // 6. 省团队权益 (20 USDT)
const provinceTeamReward = await this.calculateProvinceTeamRight( const provinceTeamReward = await this.calculateProvinceTeamRight(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.provinceCode, params.provinceCode,
params.treeCount, params.treeCount,
@ -118,7 +118,7 @@ export class RewardCalculationService {
// 7. 省区域权益 (15 USDT + 1%算力) // 7. 省区域权益 (15 USDT + 1%算力)
const provinceAreaReward = this.calculateProvinceAreaRight( const provinceAreaReward = this.calculateProvinceAreaRight(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.provinceCode, params.provinceCode,
params.treeCount, params.treeCount,
@ -127,7 +127,7 @@ export class RewardCalculationService {
// 8. 市团队权益 (40 USDT) // 8. 市团队权益 (40 USDT)
const cityTeamReward = await this.calculateCityTeamRight( const cityTeamReward = await this.calculateCityTeamRight(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.cityCode, params.cityCode,
params.treeCount, params.treeCount,
@ -136,7 +136,7 @@ export class RewardCalculationService {
// 9. 市区域权益 (35 USDT + 2%算力) // 9. 市区域权益 (35 USDT + 2%算力)
const cityAreaReward = this.calculateCityAreaRight( const cityAreaReward = this.calculateCityAreaRight(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.cityCode, params.cityCode,
params.treeCount, params.treeCount,
@ -145,7 +145,7 @@ export class RewardCalculationService {
// 10. 社区权益 (80 USDT) // 10. 社区权益 (80 USDT)
const communityReward = await this.calculateCommunityRight( const communityReward = await this.calculateCommunityRight(
params.sourceOrderId, params.sourceOrderNo,
params.sourceUserId, params.sourceUserId,
params.treeCount, params.treeCount,
); );
@ -163,7 +163,7 @@ export class RewardCalculationService {
* *
*/ */
private calculateCostFee( private calculateCostFee(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
treeCount: number, treeCount: number,
): RewardLedgerEntry { ): RewardLedgerEntry {
@ -173,7 +173,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.COST_FEE, RightType.COST_FEE,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -191,7 +191,7 @@ export class RewardCalculationService {
* *
*/ */
private calculateOperationFee( private calculateOperationFee(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
treeCount: number, treeCount: number,
): RewardLedgerEntry { ): RewardLedgerEntry {
@ -201,7 +201,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.OPERATION_FEE, RightType.OPERATION_FEE,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -219,7 +219,7 @@ export class RewardCalculationService {
* *
*/ */
private calculateHeadquartersBaseFee( private calculateHeadquartersBaseFee(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
treeCount: number, treeCount: number,
): RewardLedgerEntry { ): RewardLedgerEntry {
@ -229,7 +229,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.HEADQUARTERS_BASE_FEE, RightType.HEADQUARTERS_BASE_FEE,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -247,7 +247,7 @@ export class RewardCalculationService {
* RWAD 1 * RWAD 1
*/ */
private calculateRwadPoolInjection( private calculateRwadPoolInjection(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
treeCount: number, treeCount: number,
): RewardLedgerEntry { ): RewardLedgerEntry {
@ -257,7 +257,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.RWAD_POOL_INJECTION, RightType.RWAD_POOL_INJECTION,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -278,7 +278,7 @@ export class RewardCalculationService {
* (500 USDT) * (500 USDT)
*/ */
private async calculateShareRights( private async calculateShareRights(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
treeCount: number, treeCount: number,
): Promise<RewardLedgerEntry[]> { ): Promise<RewardLedgerEntry[]> {
@ -288,7 +288,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.SHARE_RIGHT, RightType.SHARE_RIGHT,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -333,7 +333,7 @@ export class RewardCalculationService {
* (20 USDT) * (20 USDT)
*/ */
private async calculateProvinceTeamRight( private async calculateProvinceTeamRight(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
provinceCode: string, provinceCode: string,
treeCount: number, treeCount: number,
@ -344,7 +344,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.PROVINCE_TEAM_RIGHT, RightType.PROVINCE_TEAM_RIGHT,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -377,7 +377,7 @@ export class RewardCalculationService {
* (15 USDT + 1%) * (15 USDT + 1%)
*/ */
private calculateProvinceAreaRight( private calculateProvinceAreaRight(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
provinceCode: string, provinceCode: string,
treeCount: number, treeCount: number,
@ -388,7 +388,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.PROVINCE_AREA_RIGHT, RightType.PROVINCE_AREA_RIGHT,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -408,7 +408,7 @@ export class RewardCalculationService {
* (40 USDT) * (40 USDT)
*/ */
private async calculateCityTeamRight( private async calculateCityTeamRight(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
cityCode: string, cityCode: string,
treeCount: number, treeCount: number,
@ -419,7 +419,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.CITY_TEAM_RIGHT, RightType.CITY_TEAM_RIGHT,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -452,7 +452,7 @@ export class RewardCalculationService {
* (35 USDT + 2%) * (35 USDT + 2%)
*/ */
private calculateCityAreaRight( private calculateCityAreaRight(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
cityCode: string, cityCode: string,
treeCount: number, treeCount: number,
@ -463,7 +463,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.CITY_AREA_RIGHT, RightType.CITY_AREA_RIGHT,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );
@ -483,7 +483,7 @@ export class RewardCalculationService {
* (80 USDT) * (80 USDT)
*/ */
private async calculateCommunityRight( private async calculateCommunityRight(
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
treeCount: number, treeCount: number,
): Promise<RewardLedgerEntry> { ): Promise<RewardLedgerEntry> {
@ -493,7 +493,7 @@ export class RewardCalculationService {
const rewardSource = RewardSource.create( const rewardSource = RewardSource.create(
RightType.COMMUNITY_RIGHT, RightType.COMMUNITY_RIGHT,
sourceOrderId, sourceOrderNo,
sourceUserId, sourceUserId,
); );

View File

@ -3,22 +3,22 @@ import { RightType } from './right-type.enum';
export class RewardSource { export class RewardSource {
private constructor( private constructor(
public readonly rightType: RightType, public readonly rightType: RightType,
public readonly sourceOrderId: bigint, public readonly sourceOrderNo: string, // 订单号是字符串格式如 PLT1765391584505Q0Q6QD
public readonly sourceUserId: bigint, public readonly sourceUserId: bigint,
) {} ) {}
static create( static create(
rightType: RightType, rightType: RightType,
sourceOrderId: bigint, sourceOrderNo: string,
sourceUserId: bigint, sourceUserId: bigint,
): RewardSource { ): RewardSource {
return new RewardSource(rightType, sourceOrderId, sourceUserId); return new RewardSource(rightType, sourceOrderNo, sourceUserId);
} }
equals(other: RewardSource): boolean { equals(other: RewardSource): boolean {
return ( return (
this.rightType === other.rightType && this.rightType === other.rightType &&
this.sourceOrderId === other.sourceOrderId && this.sourceOrderNo === other.sourceOrderNo &&
this.sourceUserId === other.sourceUserId this.sourceUserId === other.sourceUserId
); );
} }

View File

@ -61,7 +61,7 @@ export class EventConsumerController {
try { try {
// 1. 计算并分配奖励 // 1. 计算并分配奖励
await this.rewardService.distributeRewards({ await this.rewardService.distributeRewards({
sourceOrderId: BigInt(eventData.orderId), sourceOrderNo: eventData.orderId, // orderId 实际是 orderNo 字符串格式
sourceUserId: BigInt(eventData.userId), sourceUserId: BigInt(eventData.userId),
treeCount: eventData.treeCount, treeCount: eventData.treeCount,
provinceCode: eventData.provinceCode, provinceCode: eventData.provinceCode,

View File

@ -11,7 +11,7 @@ export class RewardLedgerEntryMapper {
userId: raw.userId, userId: raw.userId,
rewardSource: RewardSource.create( rewardSource: RewardSource.create(
raw.rightType as RightType, raw.rightType as RightType,
raw.sourceOrderId, raw.sourceOrderNo,
raw.sourceUserId, raw.sourceUserId,
), ),
usdtAmount: Number(raw.usdtAmount), usdtAmount: Number(raw.usdtAmount),
@ -30,7 +30,7 @@ export class RewardLedgerEntryMapper {
return { return {
id: entry.id || undefined, id: entry.id || undefined,
userId: entry.userId, userId: entry.userId,
sourceOrderId: entry.rewardSource.sourceOrderId, sourceOrderNo: entry.rewardSource.sourceOrderNo,
sourceUserId: entry.rewardSource.sourceUserId, sourceUserId: entry.rewardSource.sourceUserId,
rightType: entry.rewardSource.rightType, rightType: entry.rewardSource.rightType,
usdtAmount: new Prisma.Decimal(entry.usdtAmount.amount), usdtAmount: new Prisma.Decimal(entry.usdtAmount.amount),

View File

@ -29,7 +29,7 @@ export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryReposi
const created = await this.prisma.rewardLedgerEntry.create({ const created = await this.prisma.rewardLedgerEntry.create({
data: { data: {
userId: data.userId, userId: data.userId,
sourceOrderId: data.sourceOrderId, sourceOrderNo: data.sourceOrderNo,
sourceUserId: data.sourceUserId, sourceUserId: data.sourceUserId,
rightType: data.rightType, rightType: data.rightType,
usdtAmount: data.usdtAmount, usdtAmount: data.usdtAmount,
@ -137,9 +137,9 @@ export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryReposi
return rawList.map(RewardLedgerEntryMapper.toDomain); return rawList.map(RewardLedgerEntryMapper.toDomain);
} }
async findBySourceOrderId(sourceOrderId: bigint): Promise<RewardLedgerEntry[]> { async findBySourceOrderNo(sourceOrderNo: string): Promise<RewardLedgerEntry[]> {
const rawList = await this.prisma.rewardLedgerEntry.findMany({ const rawList = await this.prisma.rewardLedgerEntry.findMany({
where: { sourceOrderId }, where: { sourceOrderNo },
}); });
return rawList.map(RewardLedgerEntryMapper.toDomain); return rawList.map(RewardLedgerEntryMapper.toDomain);