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

View File

@ -35,13 +35,13 @@ export class RewardApplicationService {
* ()
*/
async distributeRewards(params: {
sourceOrderId: bigint;
sourceOrderNo: string; // 订单号是字符串格式如 PLT1765391584505Q0Q6QD
sourceUserId: bigint;
treeCount: number;
provinceCode: string;
cityCode: string;
}): Promise<void> {
this.logger.log(`Distributing rewards for order ${params.sourceOrderId}`);
this.logger.log(`Distributing rewards for order ${params.sourceOrderNo}`);
// 1. 计算所有奖励
const rewards = await this.rewardCalculationService.calculateRewards(params);
@ -76,7 +76,7 @@ export class RewardApplicationService {
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({
entryId: entry._id?.toString() || 'temp',
userId: entry._userId.toString(),
sourceOrderId: entry._rewardSource.sourceOrderId.toString(),
sourceOrderNo: entry._rewardSource.sourceOrderNo,
sourceUserId: entry._rewardSource.sourceUserId.toString(),
rightType: entry._rewardSource.rightType,
usdtAmount: entry._usdtAmount.amount,
@ -143,7 +143,7 @@ export class RewardLedgerEntry {
entry._domainEvents.push(new RewardCreatedEvent({
entryId: entry._id?.toString() || 'temp',
userId: entry._userId.toString(),
sourceOrderId: entry._rewardSource.sourceOrderId.toString(),
sourceOrderNo: entry._rewardSource.sourceOrderNo,
sourceUserId: entry._rewardSource.sourceUserId.toString(),
rightType: entry._rewardSource.rightType,
usdtAmount: entry._usdtAmount.amount,

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ export class RewardLedgerEntryMapper {
userId: raw.userId,
rewardSource: RewardSource.create(
raw.rightType as RightType,
raw.sourceOrderId,
raw.sourceOrderNo,
raw.sourceUserId,
),
usdtAmount: Number(raw.usdtAmount),
@ -30,7 +30,7 @@ export class RewardLedgerEntryMapper {
return {
id: entry.id || undefined,
userId: entry.userId,
sourceOrderId: entry.rewardSource.sourceOrderId,
sourceOrderNo: entry.rewardSource.sourceOrderNo,
sourceUserId: entry.rewardSource.sourceUserId,
rightType: entry.rewardSource.rightType,
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({
data: {
userId: data.userId,
sourceOrderId: data.sourceOrderId,
sourceOrderNo: data.sourceOrderNo,
sourceUserId: data.sourceUserId,
rightType: data.rightType,
usdtAmount: data.usdtAmount,
@ -137,9 +137,9 @@ export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryReposi
return rawList.map(RewardLedgerEntryMapper.toDomain);
}
async findBySourceOrderId(sourceOrderId: bigint): Promise<RewardLedgerEntry[]> {
async findBySourceOrderNo(sourceOrderNo: string): Promise<RewardLedgerEntry[]> {
const rawList = await this.prisma.rewardLedgerEntry.findMany({
where: { sourceOrderId },
where: { sourceOrderNo },
});
return rawList.map(RewardLedgerEntryMapper.toDomain);