fix: add accountSequence to all services and fix compilation errors
- reward-service: add accountSequence to aggregates, services, tests - authorization-service: fix UserId/AdminUserId to accept accountSequence, add findByAccountSequence to repositories - referral-service: fix test files for accountSequence changes - Add migration files for reward-service and authorization-service 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
034fb53674
commit
300fe211c8
|
|
@ -0,0 +1,44 @@
|
|||
-- Step 1: Add account_sequence columns to all tables first
|
||||
ALTER TABLE "authorization_roles" ADD COLUMN "account_sequence" BIGINT;
|
||||
ALTER TABLE "monthly_assessments" ADD COLUMN "account_sequence" BIGINT;
|
||||
ALTER TABLE "monthly_bypasses" ADD COLUMN "account_sequence" BIGINT;
|
||||
ALTER TABLE "stickman_rankings" ADD COLUMN "account_sequence" BIGINT;
|
||||
|
||||
-- Step 2: Backfill account_sequence from existing user_id (which is still String at this point)
|
||||
UPDATE "authorization_roles" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
|
||||
UPDATE "monthly_assessments" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
|
||||
UPDATE "monthly_bypasses" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
|
||||
UPDATE "stickman_rankings" SET "account_sequence" = CAST("user_id" AS BIGINT) WHERE "account_sequence" IS NULL;
|
||||
|
||||
-- Step 3: Make account_sequence NOT NULL
|
||||
ALTER TABLE "authorization_roles" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
ALTER TABLE "monthly_assessments" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
ALTER TABLE "monthly_bypasses" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
ALTER TABLE "stickman_rankings" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
|
||||
-- Step 4: Drop existing unique constraints that reference user_id
|
||||
DROP INDEX IF EXISTS "authorization_roles_user_id_role_type_region_code_key";
|
||||
|
||||
-- Step 5: Change user_id column type from String to BigInt
|
||||
ALTER TABLE "authorization_roles" ALTER COLUMN "user_id" TYPE BIGINT USING "user_id"::BIGINT;
|
||||
ALTER TABLE "monthly_assessments" ALTER COLUMN "user_id" TYPE BIGINT USING "user_id"::BIGINT;
|
||||
ALTER TABLE "monthly_bypasses" ALTER COLUMN "user_id" TYPE BIGINT USING "user_id"::BIGINT;
|
||||
ALTER TABLE "stickman_rankings" ALTER COLUMN "user_id" TYPE BIGINT USING "user_id"::BIGINT;
|
||||
|
||||
-- Step 6: Change authorized_by, revoked_by, bypassed_by, grantedBy, approverXId to BigInt
|
||||
ALTER TABLE "authorization_roles" ALTER COLUMN "authorized_by" TYPE BIGINT USING "authorized_by"::BIGINT;
|
||||
ALTER TABLE "authorization_roles" ALTER COLUMN "revoked_by" TYPE BIGINT USING "revoked_by"::BIGINT;
|
||||
ALTER TABLE "monthly_assessments" ALTER COLUMN "bypassed_by" TYPE BIGINT USING "bypassed_by"::BIGINT;
|
||||
ALTER TABLE "monthly_bypasses" ALTER COLUMN "granted_by" TYPE BIGINT USING "granted_by"::BIGINT;
|
||||
ALTER TABLE "monthly_bypasses" ALTER COLUMN "approver1_id" TYPE BIGINT USING "approver1_id"::BIGINT;
|
||||
ALTER TABLE "monthly_bypasses" ALTER COLUMN "approver2_id" TYPE BIGINT USING "approver2_id"::BIGINT;
|
||||
ALTER TABLE "monthly_bypasses" ALTER COLUMN "approver3_id" TYPE BIGINT USING "approver3_id"::BIGINT;
|
||||
|
||||
-- Step 7: Create new unique constraint using account_sequence
|
||||
CREATE UNIQUE INDEX "authorization_roles_account_sequence_role_type_region_code_key" ON "authorization_roles"("account_sequence", "role_type", "region_code");
|
||||
|
||||
-- Step 8: Create indexes for account_sequence
|
||||
CREATE INDEX "idx_authorization_roles_account_sequence" ON "authorization_roles"("account_sequence");
|
||||
CREATE INDEX "idx_monthly_assessments_account_sequence_month" ON "monthly_assessments"("account_sequence", "assessment_month");
|
||||
CREATE INDEX "idx_monthly_bypasses_account_sequence_month" ON "monthly_bypasses"("account_sequence", "bypass_month");
|
||||
CREATE INDEX "idx_stickman_rankings_account_sequence_month" ON "stickman_rankings"("account_sequence", "current_month");
|
||||
|
|
@ -18,14 +18,16 @@ export class AdminAuthorizationController {
|
|||
@ApiOperation({ summary: '授权正式省公司(管理员)' })
|
||||
@ApiResponse({ status: 201, description: '授权成功' })
|
||||
async grantProvinceCompany(
|
||||
@CurrentUser() user: { userId: string },
|
||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
||||
@Body() dto: GrantProvinceCompanyDto,
|
||||
): Promise<{ message: string }> {
|
||||
const command = new GrantProvinceCompanyCommand(
|
||||
dto.userId,
|
||||
dto.accountSequence,
|
||||
dto.provinceCode,
|
||||
dto.provinceName,
|
||||
user.userId,
|
||||
user.accountSequence,
|
||||
)
|
||||
await this.applicationService.grantProvinceCompany(command)
|
||||
return { message: '正式省公司授权成功' }
|
||||
|
|
@ -36,14 +38,16 @@ export class AdminAuthorizationController {
|
|||
@ApiOperation({ summary: '授权正式市公司(管理员)' })
|
||||
@ApiResponse({ status: 201, description: '授权成功' })
|
||||
async grantCityCompany(
|
||||
@CurrentUser() user: { userId: string },
|
||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
||||
@Body() dto: GrantCityCompanyDto,
|
||||
): Promise<{ message: string }> {
|
||||
const command = new GrantCityCompanyCommand(
|
||||
dto.userId,
|
||||
dto.accountSequence,
|
||||
dto.cityCode,
|
||||
dto.cityName,
|
||||
user.userId,
|
||||
user.accountSequence,
|
||||
)
|
||||
await this.applicationService.grantCityCompany(command)
|
||||
return { message: '正式市公司授权成功' }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
||||
import { IsString, IsNotEmpty, MaxLength, IsNumber } from 'class-validator'
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
|
||||
export class GrantCityCompanyDto {
|
||||
|
|
@ -7,6 +7,11 @@ export class GrantCityCompanyDto {
|
|||
@IsNotEmpty({ message: '用户ID不能为空' })
|
||||
userId: string
|
||||
|
||||
@ApiProperty({ description: '账户序列号' })
|
||||
@IsNumber()
|
||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||
accountSequence: number
|
||||
|
||||
@ApiProperty({ description: '城市代码', example: '430100' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '城市代码不能为空' })
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IsString, IsNotEmpty, MaxLength } from 'class-validator'
|
||||
import { IsString, IsNotEmpty, MaxLength, IsNumber } from 'class-validator'
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
|
||||
export class GrantProvinceCompanyDto {
|
||||
|
|
@ -7,6 +7,11 @@ export class GrantProvinceCompanyDto {
|
|||
@IsNotEmpty({ message: '用户ID不能为空' })
|
||||
userId: string
|
||||
|
||||
@ApiProperty({ description: '账户序列号' })
|
||||
@IsNumber()
|
||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||
accountSequence: number
|
||||
|
||||
@ApiProperty({ description: '省份代码', example: '430000' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '省份代码不能为空' })
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('createCommunityAuth', () => {
|
||||
it('should create community authorization', () => {
|
||||
const auth = AuthorizationRole.createCommunityAuth({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
communityName: '量子社区',
|
||||
})
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('createAuthProvinceCompany', () => {
|
||||
it('should create auth province company authorization', () => {
|
||||
const auth = AuthorizationRole.createAuthProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
})
|
||||
|
|
@ -42,7 +42,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('createAuthCityCompany', () => {
|
||||
it('should create auth city company authorization', () => {
|
||||
const auth = AuthorizationRole.createAuthCityCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
cityCode: '430100',
|
||||
cityName: '长沙市',
|
||||
})
|
||||
|
|
@ -57,9 +57,9 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
|
||||
describe('createProvinceCompany', () => {
|
||||
it('should create official province company with active benefits', () => {
|
||||
const adminId = AdminUserId.create('admin-1')
|
||||
const adminId = AdminUserId.create('admin-1', BigInt(101))
|
||||
const auth = AuthorizationRole.createProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
adminId,
|
||||
|
|
@ -76,7 +76,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('activateBenefit', () => {
|
||||
it('should activate benefit and emit event', () => {
|
||||
const auth = AuthorizationRole.createCommunityAuth({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
communityName: '量子社区',
|
||||
})
|
||||
auth.clearDomainEvents()
|
||||
|
|
@ -92,10 +92,10 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
|
||||
it('should throw error if already active', () => {
|
||||
const auth = AuthorizationRole.createProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
adminId: AdminUserId.create('admin-1'),
|
||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
||||
})
|
||||
|
||||
expect(() => auth.activateBenefit()).toThrow(DomainError)
|
||||
|
|
@ -105,10 +105,10 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('deactivateBenefit', () => {
|
||||
it('should deactivate benefit and reset month index', () => {
|
||||
const auth = AuthorizationRole.createProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
adminId: AdminUserId.create('admin-1'),
|
||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
||||
})
|
||||
auth.clearDomainEvents()
|
||||
|
||||
|
|
@ -124,14 +124,14 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('revoke', () => {
|
||||
it('should revoke authorization', () => {
|
||||
const auth = AuthorizationRole.createProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
adminId: AdminUserId.create('admin-1'),
|
||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
||||
})
|
||||
auth.clearDomainEvents()
|
||||
|
||||
auth.revoke(AdminUserId.create('admin-2'), '违规操作')
|
||||
auth.revoke(AdminUserId.create('admin-2', BigInt(102)), '违规操作')
|
||||
|
||||
expect(auth.status).toBe(AuthorizationStatus.REVOKED)
|
||||
expect(auth.benefitActive).toBe(false)
|
||||
|
|
@ -142,14 +142,14 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
|
||||
it('should throw error if already revoked', () => {
|
||||
const auth = AuthorizationRole.createProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
adminId: AdminUserId.create('admin-1'),
|
||||
adminId: AdminUserId.create('admin-1', BigInt(101)),
|
||||
})
|
||||
auth.revoke(AdminUserId.create('admin-2'), '违规操作')
|
||||
auth.revoke(AdminUserId.create('admin-2', BigInt(102)), '违规操作')
|
||||
|
||||
expect(() => auth.revoke(AdminUserId.create('admin-3'), '再次撤销')).toThrow(
|
||||
expect(() => auth.revoke(AdminUserId.create('admin-3', BigInt(103)), '再次撤销')).toThrow(
|
||||
DomainError,
|
||||
)
|
||||
})
|
||||
|
|
@ -158,7 +158,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('exemptLocalPercentageCheck', () => {
|
||||
it('should exempt from percentage check', () => {
|
||||
const auth = AuthorizationRole.createAuthProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
})
|
||||
|
|
@ -166,7 +166,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
expect(auth.exemptFromPercentageCheck).toBe(false)
|
||||
expect(auth.needsLocalPercentageCheck()).toBe(true)
|
||||
|
||||
auth.exemptLocalPercentageCheck(AdminUserId.create('admin-1'))
|
||||
auth.exemptLocalPercentageCheck(AdminUserId.create('admin-1', BigInt(101)))
|
||||
|
||||
expect(auth.exemptFromPercentageCheck).toBe(true)
|
||||
expect(auth.needsLocalPercentageCheck()).toBe(false)
|
||||
|
|
@ -176,7 +176,7 @@ describe('AuthorizationRole Aggregate', () => {
|
|||
describe('incrementMonthIndex', () => {
|
||||
it('should increment month index', () => {
|
||||
const auth = AuthorizationRole.createCommunityAuth({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
communityName: '量子社区',
|
||||
})
|
||||
auth.activateBenefit()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { IMonthlyAssessmentRepository, IAuthorizationRoleRepository } from '@/do
|
|||
|
||||
export interface TeamStatistics {
|
||||
userId: string
|
||||
accountSequence: bigint
|
||||
totalTeamPlantingCount: number
|
||||
getProvinceTeamCount(provinceCode: string): number
|
||||
getCityTeamCount(cityCode: string): number
|
||||
|
|
@ -13,6 +14,7 @@ export interface TeamStatistics {
|
|||
|
||||
export interface ITeamStatisticsRepository {
|
||||
findByUserId(userId: string): Promise<TeamStatistics | null>
|
||||
findByAccountSequence(accountSequence: bigint): Promise<TeamStatistics | null>
|
||||
}
|
||||
|
||||
export class AssessmentCalculatorService {
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export class EventConsumerController {
|
|||
|
||||
// 2. 获取用户所有授权
|
||||
const authorizations = await this.authorizationRepository.findByUserId(
|
||||
UserId.create(userId),
|
||||
UserId.create(userId, teamStats.accountSequence),
|
||||
)
|
||||
|
||||
if (authorizations.length === 0) {
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
|||
): Promise<AuthorizationRole | null> {
|
||||
const record = await this.prisma.authorizationRole.findFirst({
|
||||
where: {
|
||||
userId: userId.value,
|
||||
userId: BigInt(userId.value),
|
||||
roleType: roleType,
|
||||
},
|
||||
})
|
||||
|
|
@ -90,7 +90,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
|||
): Promise<AuthorizationRole | null> {
|
||||
const record = await this.prisma.authorizationRole.findFirst({
|
||||
where: {
|
||||
userId: userId.value,
|
||||
userId: BigInt(userId.value),
|
||||
roleType: roleType,
|
||||
regionCode: regionCode.value,
|
||||
},
|
||||
|
|
@ -113,7 +113,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
|||
|
||||
async findByUserId(userId: UserId): Promise<AuthorizationRole[]> {
|
||||
const records = await this.prisma.authorizationRole.findMany({
|
||||
where: { userId: userId.value },
|
||||
where: { userId: BigInt(userId.value) },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
return records.map((record) => this.toDomain(record))
|
||||
|
|
@ -156,7 +156,7 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
|||
async findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]> {
|
||||
const records = await this.prisma.authorizationRole.findMany({
|
||||
where: {
|
||||
userId: userId.value,
|
||||
userId: BigInt(userId.value),
|
||||
status: AuthorizationStatus.PENDING,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export class MonthlyAssessmentRepositoryImpl implements IMonthlyAssessmentReposi
|
|||
async findByUserAndMonth(userId: UserId, month: Month): Promise<MonthlyAssessment[]> {
|
||||
const records = await this.prisma.monthlyAssessment.findMany({
|
||||
where: {
|
||||
userId: userId.value,
|
||||
userId: BigInt(userId.value),
|
||||
assessmentMonth: month.value,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
|||
|
||||
export interface CurrentUserData {
|
||||
userId: string
|
||||
accountSequence?: number
|
||||
walletAddress?: string
|
||||
roles?: string[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ describe('Domain Services Integration Tests', () => {
|
|||
findPendingByUserId: jest.fn(),
|
||||
findByStatus: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
findByAccountSequenceAndRoleType: jest.fn(),
|
||||
findByAccountSequence: jest.fn(),
|
||||
}
|
||||
|
||||
const mockMonthlyAssessmentRepository: jest.Mocked<IMonthlyAssessmentRepository> = {
|
||||
|
|
@ -52,6 +54,7 @@ describe('Domain Services Integration Tests', () => {
|
|||
// Mock team statistics repository
|
||||
const mockTeamStatisticsRepository: jest.Mocked<ITeamStatisticsRepository> = {
|
||||
findByUserId: jest.fn(),
|
||||
findByAccountSequence: jest.fn(),
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -93,7 +96,7 @@ describe('Domain Services Integration Tests', () => {
|
|||
describe('AuthorizationValidatorService', () => {
|
||||
describe('validateAuthorizationRequest', () => {
|
||||
it('should return success when no conflicts in referral chain', async () => {
|
||||
const userId = UserId.create('user-123')
|
||||
const userId = UserId.create('user-123', BigInt(123))
|
||||
const roleType = RoleType.AUTH_PROVINCE_COMPANY
|
||||
const regionCode = RegionCode.create('430000')
|
||||
|
||||
|
|
@ -112,7 +115,7 @@ describe('Domain Services Integration Tests', () => {
|
|||
})
|
||||
|
||||
it('should return failure when user already has province authorization', async () => {
|
||||
const userId = UserId.create('user-123')
|
||||
const userId = UserId.create('user-123', BigInt(123))
|
||||
const roleType = RoleType.AUTH_PROVINCE_COMPANY
|
||||
const regionCode = RegionCode.create('430000')
|
||||
|
||||
|
|
@ -136,11 +139,11 @@ describe('Domain Services Integration Tests', () => {
|
|||
})
|
||||
|
||||
it('should return failure when ancestor has same region authorization', async () => {
|
||||
const userId = UserId.create('user-123')
|
||||
const userId = UserId.create('user-123', BigInt(123))
|
||||
const roleType = RoleType.AUTH_PROVINCE_COMPANY
|
||||
const regionCode = RegionCode.create('430000')
|
||||
|
||||
const ancestorUserId = UserId.create('ancestor-user')
|
||||
const ancestorUserId = UserId.create('ancestor-user', BigInt(999))
|
||||
|
||||
mockAuthorizationRoleRepository.findByUserIdAndRoleType.mockResolvedValue(null)
|
||||
mockReferralRepository.findByUserId.mockResolvedValue({ parentId: 'ancestor-user' })
|
||||
|
|
@ -177,18 +180,18 @@ describe('Domain Services Integration Tests', () => {
|
|||
|
||||
// Mock two authorizations
|
||||
const auth1 = AuthorizationRole.createAuthProvinceCompany({
|
||||
userId: UserId.create('user-1'),
|
||||
userId: UserId.create('user-1', BigInt(1)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
})
|
||||
auth1.authorize(UserId.create('admin'))
|
||||
auth1.authorize(UserId.create('admin', BigInt(100)))
|
||||
|
||||
const auth2 = AuthorizationRole.createAuthProvinceCompany({
|
||||
userId: UserId.create('user-2'),
|
||||
userId: UserId.create('user-2', BigInt(2)),
|
||||
provinceCode: '430000',
|
||||
provinceName: '湖南省',
|
||||
})
|
||||
auth2.authorize(UserId.create('admin'))
|
||||
auth2.authorize(UserId.create('admin', BigInt(100)))
|
||||
|
||||
mockAuthorizationRoleRepository.findActiveByRoleTypeAndRegion.mockResolvedValue([auth1, auth2])
|
||||
mockMonthlyAssessmentRepository.findByAuthorizationAndMonth.mockResolvedValue(null)
|
||||
|
|
@ -197,12 +200,14 @@ describe('Domain Services Integration Tests', () => {
|
|||
mockTeamStatisticsRepository.findByUserId
|
||||
.mockResolvedValueOnce({
|
||||
userId: 'user-1',
|
||||
accountSequence: BigInt(1),
|
||||
totalTeamPlantingCount: 200,
|
||||
getProvinceTeamCount: () => 70,
|
||||
getCityTeamCount: () => 0,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
userId: 'user-2',
|
||||
accountSequence: BigInt(2),
|
||||
totalTeamPlantingCount: 100,
|
||||
getProvinceTeamCount: () => 35,
|
||||
getCityTeamCount: () => 0,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import { ReferralRelationshipCreatedEvent } from '../../../src/domain/events';
|
|||
describe('ReferralRelationship Aggregate', () => {
|
||||
describe('create', () => {
|
||||
it('should create referral relationship without referrer', () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
|
||||
expect(relationship.userId).toBe(100n);
|
||||
expect(relationship.accountSequence).toBe(12345678);
|
||||
expect(relationship.referrerId).toBeNull();
|
||||
expect(relationship.referralCode).toMatch(/^RWA/);
|
||||
expect(relationship.referralChain).toEqual([]);
|
||||
|
|
@ -14,15 +15,16 @@ describe('ReferralRelationship Aggregate', () => {
|
|||
|
||||
it('should create referral relationship with referrer', () => {
|
||||
const parentChain = [200n, 300n];
|
||||
const relationship = ReferralRelationship.create(100n, 50n, parentChain);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, parentChain);
|
||||
|
||||
expect(relationship.userId).toBe(100n);
|
||||
expect(relationship.accountSequence).toBe(12345678);
|
||||
expect(relationship.referrerId).toBe(50n);
|
||||
expect(relationship.referralChain).toEqual([50n, 200n, 300n]);
|
||||
});
|
||||
|
||||
it('should emit ReferralRelationshipCreatedEvent', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n);
|
||||
|
||||
expect(relationship.domainEvents.length).toBe(1);
|
||||
expect(relationship.domainEvents[0]).toBeInstanceOf(ReferralRelationshipCreatedEvent);
|
||||
|
|
@ -38,6 +40,7 @@ describe('ReferralRelationship Aggregate', () => {
|
|||
const props = {
|
||||
id: 1n,
|
||||
userId: 100n,
|
||||
accountSequence: 12345678,
|
||||
referrerId: 50n,
|
||||
referralCode: 'RWATEST123',
|
||||
referralChain: [50n, 200n],
|
||||
|
|
@ -49,6 +52,7 @@ describe('ReferralRelationship Aggregate', () => {
|
|||
|
||||
expect(relationship.id).toBe(1n);
|
||||
expect(relationship.userId).toBe(100n);
|
||||
expect(relationship.accountSequence).toBe(12345678);
|
||||
expect(relationship.referrerId).toBe(50n);
|
||||
expect(relationship.referralCode).toBe('RWATEST123');
|
||||
expect(relationship.referralChain).toEqual([50n, 200n]);
|
||||
|
|
@ -57,19 +61,19 @@ describe('ReferralRelationship Aggregate', () => {
|
|||
|
||||
describe('getDirectReferrer', () => {
|
||||
it('should return direct referrer', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n, [200n]);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n]);
|
||||
expect(relationship.getDirectReferrer()).toBe(50n);
|
||||
});
|
||||
|
||||
it('should return null when no referrer', () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
expect(relationship.getDirectReferrer()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getReferrerAtLevel', () => {
|
||||
it('should return referrer at specific level', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n, [200n, 300n]);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n, 300n]);
|
||||
|
||||
expect(relationship.getReferrerAtLevel(0)).toBe(50n);
|
||||
expect(relationship.getReferrerAtLevel(1)).toBe(200n);
|
||||
|
|
@ -79,21 +83,21 @@ describe('ReferralRelationship Aggregate', () => {
|
|||
|
||||
describe('getAllAncestorIds', () => {
|
||||
it('should return all ancestor IDs', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n, [200n, 300n]);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n, 300n]);
|
||||
expect(relationship.getAllAncestorIds()).toEqual([50n, 200n, 300n]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChainDepth', () => {
|
||||
it('should return chain depth', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n, [200n, 300n]);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n, 300n]);
|
||||
expect(relationship.getChainDepth()).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearDomainEvents', () => {
|
||||
it('should clear domain events', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n);
|
||||
expect(relationship.domainEvents.length).toBe(1);
|
||||
|
||||
relationship.clearDomainEvents();
|
||||
|
|
@ -103,10 +107,11 @@ describe('ReferralRelationship Aggregate', () => {
|
|||
|
||||
describe('toPersistence', () => {
|
||||
it('should convert to persistence format', () => {
|
||||
const relationship = ReferralRelationship.create(100n, 50n, [200n]);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, [200n]);
|
||||
const data = relationship.toPersistence();
|
||||
|
||||
expect(data.userId).toBe(100n);
|
||||
expect(data.accountSequence).toBe(12345678);
|
||||
expect(data.referrerId).toBe(50n);
|
||||
expect(data.referralCode).toMatch(/^RWA/);
|
||||
expect(data.referralChain).toEqual([50n, 200n]);
|
||||
|
|
|
|||
|
|
@ -30,48 +30,52 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
|
||||
describe('save', () => {
|
||||
it('should save a new referral relationship', async () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
const saved = await repository.save(relationship);
|
||||
|
||||
expect(saved).toBeDefined();
|
||||
expect(saved.userId).toBe(100n);
|
||||
expect(saved.accountSequence).toBe(12345678);
|
||||
expect(saved.referralCode).toBeDefined();
|
||||
expect(saved.referralChain).toEqual([]);
|
||||
});
|
||||
|
||||
it('should save referral relationship with referrer', async () => {
|
||||
// First create the referrer
|
||||
const referrer = ReferralRelationship.create(50n, null);
|
||||
const referrer = ReferralRelationship.create(50n, 11111111, null);
|
||||
await repository.save(referrer);
|
||||
|
||||
// Then create with referrer
|
||||
const relationship = ReferralRelationship.create(100n, 50n, []);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, []);
|
||||
const saved = await repository.save(relationship);
|
||||
|
||||
expect(saved.userId).toBe(100n);
|
||||
expect(saved.accountSequence).toBe(12345678);
|
||||
expect(saved.referrerId).toBe(50n);
|
||||
expect(saved.referralChain).toContain(50n);
|
||||
});
|
||||
|
||||
it('should update existing referral relationship', async () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
await repository.save(relationship);
|
||||
|
||||
// Save again should update
|
||||
const updated = await repository.save(relationship);
|
||||
expect(updated.userId).toBe(100n);
|
||||
expect(updated.accountSequence).toBe(12345678);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByUserId', () => {
|
||||
it('should find relationship by user ID', async () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
await repository.save(relationship);
|
||||
|
||||
const found = await repository.findByUserId(100n);
|
||||
|
||||
expect(found).toBeDefined();
|
||||
expect(found!.userId).toBe(100n);
|
||||
expect(found!.accountSequence).toBe(12345678);
|
||||
});
|
||||
|
||||
it('should return null for non-existent user', async () => {
|
||||
|
|
@ -82,13 +86,14 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
|
||||
describe('findByReferralCode', () => {
|
||||
it('should find relationship by referral code', async () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
const saved = await repository.save(relationship);
|
||||
|
||||
const found = await repository.findByReferralCode(saved.referralCode);
|
||||
|
||||
expect(found).toBeDefined();
|
||||
expect(found!.userId).toBe(100n);
|
||||
expect(found!.accountSequence).toBe(12345678);
|
||||
});
|
||||
|
||||
it('should return null for non-existent code', async () => {
|
||||
|
|
@ -100,12 +105,12 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
describe('findDirectReferrals', () => {
|
||||
it('should find all direct referrals', async () => {
|
||||
// Create referrer
|
||||
const referrer = ReferralRelationship.create(50n, null);
|
||||
const referrer = ReferralRelationship.create(50n, 11111111, null);
|
||||
await repository.save(referrer);
|
||||
|
||||
// Create direct referrals
|
||||
const ref1 = ReferralRelationship.create(100n, 50n, []);
|
||||
const ref2 = ReferralRelationship.create(101n, 50n, []);
|
||||
const ref1 = ReferralRelationship.create(100n, 12345678, 50n, []);
|
||||
const ref2 = ReferralRelationship.create(101n, 12345679, 50n, []);
|
||||
await repository.save(ref1);
|
||||
await repository.save(ref2);
|
||||
|
||||
|
|
@ -117,7 +122,7 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
});
|
||||
|
||||
it('should return empty array for user with no referrals', async () => {
|
||||
const referrer = ReferralRelationship.create(50n, null);
|
||||
const referrer = ReferralRelationship.create(50n, 11111111, null);
|
||||
await repository.save(referrer);
|
||||
|
||||
const directReferrals = await repository.findDirectReferrals(50n);
|
||||
|
|
@ -127,7 +132,7 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
|
||||
describe('existsByReferralCode', () => {
|
||||
it('should return true for existing code', async () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
const saved = await repository.save(relationship);
|
||||
|
||||
const exists = await repository.existsByReferralCode(saved.referralCode);
|
||||
|
|
@ -142,7 +147,7 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
|
||||
describe('existsByUserId', () => {
|
||||
it('should return true for existing user', async () => {
|
||||
const relationship = ReferralRelationship.create(100n, null);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, null);
|
||||
await repository.save(relationship);
|
||||
|
||||
const exists = await repository.existsByUserId(100n);
|
||||
|
|
@ -158,7 +163,7 @@ describe('ReferralRelationshipRepository (Integration)', () => {
|
|||
describe('getReferralChain', () => {
|
||||
it('should return referral chain', async () => {
|
||||
const parentChain = [200n, 300n];
|
||||
const relationship = ReferralRelationship.create(100n, 50n, parentChain);
|
||||
const relationship = ReferralRelationship.create(100n, 12345678, 50n, parentChain);
|
||||
await repository.save(relationship);
|
||||
|
||||
const chain = await repository.getReferralChain(100n);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
ReferralChainService,
|
||||
ReferralRelationship,
|
||||
} from '../../../src/domain';
|
||||
import { EventPublisherService, LeaderboardCacheService } from '../../../src/infrastructure';
|
||||
import { EventPublisherService } from '../../../src/infrastructure';
|
||||
import { CreateReferralRelationshipCommand } from '../../../src/application/commands';
|
||||
import { GetUserReferralInfoQuery, GetDirectReferralsQuery } from '../../../src/application/queries';
|
||||
|
||||
|
|
@ -91,10 +91,6 @@ class MockEventPublisher {
|
|||
async publishEvent() {}
|
||||
}
|
||||
|
||||
class MockLeaderboardCache {
|
||||
async getUserRank() { return 1; }
|
||||
async updateScore() {}
|
||||
}
|
||||
|
||||
describe('ReferralService (Integration)', () => {
|
||||
let service: ReferralService;
|
||||
|
|
@ -121,10 +117,6 @@ describe('ReferralService (Integration)', () => {
|
|||
provide: EventPublisherService,
|
||||
useClass: MockEventPublisher,
|
||||
},
|
||||
{
|
||||
provide: LeaderboardCacheService,
|
||||
useClass: MockLeaderboardCache,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
|
@ -133,7 +125,7 @@ describe('ReferralService (Integration)', () => {
|
|||
|
||||
describe('createReferralRelationship', () => {
|
||||
it('should create referral relationship without referrer', async () => {
|
||||
const command = new CreateReferralRelationshipCommand(100n, null);
|
||||
const command = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
||||
const result = await service.createReferralRelationship(command);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
|
@ -143,11 +135,11 @@ describe('ReferralService (Integration)', () => {
|
|||
|
||||
it('should create referral relationship with valid referrer code', async () => {
|
||||
// First create a referrer
|
||||
const referrerCommand = new CreateReferralRelationshipCommand(50n, null);
|
||||
const referrerCommand = new CreateReferralRelationshipCommand(50n, 11111111, null);
|
||||
const referrerResult = await service.createReferralRelationship(referrerCommand);
|
||||
|
||||
// Then create with referrer code
|
||||
const command = new CreateReferralRelationshipCommand(100n, referrerResult.referralCode);
|
||||
const command = new CreateReferralRelationshipCommand(100n, 12345678, referrerResult.referralCode);
|
||||
const result = await service.createReferralRelationship(command);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
|
@ -155,14 +147,14 @@ describe('ReferralService (Integration)', () => {
|
|||
});
|
||||
|
||||
it('should throw error if user already has referral relationship', async () => {
|
||||
const command = new CreateReferralRelationshipCommand(100n, null);
|
||||
const command = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
||||
await service.createReferralRelationship(command);
|
||||
|
||||
await expect(service.createReferralRelationship(command)).rejects.toThrow('用户已存在推荐关系');
|
||||
});
|
||||
|
||||
it('should throw error for invalid referral code', async () => {
|
||||
const command = new CreateReferralRelationshipCommand(100n, 'INVALID_CODE');
|
||||
const command = new CreateReferralRelationshipCommand(100n, 12345678, 'INVALID_CODE');
|
||||
|
||||
await expect(service.createReferralRelationship(command)).rejects.toThrow('推荐码不存在');
|
||||
});
|
||||
|
|
@ -171,10 +163,10 @@ describe('ReferralService (Integration)', () => {
|
|||
describe('getUserReferralInfo', () => {
|
||||
it('should return user referral info', async () => {
|
||||
// Create user first
|
||||
const createCommand = new CreateReferralRelationshipCommand(100n, null);
|
||||
const createCommand = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
||||
await service.createReferralRelationship(createCommand);
|
||||
|
||||
const query = new GetUserReferralInfoQuery(100n);
|
||||
const query = new GetUserReferralInfoQuery(12345678);
|
||||
const result = await service.getUserReferralInfo(query);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
|
|
@ -184,7 +176,7 @@ describe('ReferralService (Integration)', () => {
|
|||
});
|
||||
|
||||
it('should throw error for non-existent user', async () => {
|
||||
const query = new GetUserReferralInfoQuery(999n);
|
||||
const query = new GetUserReferralInfoQuery(99999999);
|
||||
|
||||
await expect(service.getUserReferralInfo(query)).rejects.toThrow('用户推荐关系不存在');
|
||||
});
|
||||
|
|
@ -193,12 +185,12 @@ describe('ReferralService (Integration)', () => {
|
|||
describe('getDirectReferrals', () => {
|
||||
it('should return direct referrals list', async () => {
|
||||
// Create referrer
|
||||
const referrerCommand = new CreateReferralRelationshipCommand(50n, null);
|
||||
const referrerCommand = new CreateReferralRelationshipCommand(50n, 11111111, null);
|
||||
const referrerResult = await service.createReferralRelationship(referrerCommand);
|
||||
|
||||
// Create direct referrals
|
||||
await service.createReferralRelationship(new CreateReferralRelationshipCommand(100n, referrerResult.referralCode));
|
||||
await service.createReferralRelationship(new CreateReferralRelationshipCommand(101n, referrerResult.referralCode));
|
||||
await service.createReferralRelationship(new CreateReferralRelationshipCommand(100n, 12345678, referrerResult.referralCode));
|
||||
await service.createReferralRelationship(new CreateReferralRelationshipCommand(101n, 12345679, referrerResult.referralCode));
|
||||
|
||||
const query = new GetDirectReferralsQuery(50n);
|
||||
const result = await service.getDirectReferrals(query);
|
||||
|
|
@ -210,7 +202,7 @@ describe('ReferralService (Integration)', () => {
|
|||
|
||||
describe('validateReferralCode', () => {
|
||||
it('should return true for valid code', async () => {
|
||||
const createCommand = new CreateReferralRelationshipCommand(100n, null);
|
||||
const createCommand = new CreateReferralRelationshipCommand(100n, 12345678, null);
|
||||
const { referralCode } = await service.createReferralRelationship(createCommand);
|
||||
|
||||
const isValid = await service.validateReferralCode(referralCode);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
-- Add account_sequence column to reward_ledger_entries
|
||||
ALTER TABLE "reward_ledger_entries" ADD COLUMN "account_sequence" BIGINT;
|
||||
|
||||
-- Add indexes for account_sequence on reward_ledger_entries
|
||||
CREATE INDEX "idx_account_status" ON "reward_ledger_entries"("account_sequence", "reward_status");
|
||||
CREATE INDEX "idx_account_created" ON "reward_ledger_entries"("account_sequence", "created_at" DESC);
|
||||
|
||||
-- Add account_sequence column to reward_summaries
|
||||
ALTER TABLE "reward_summaries" ADD COLUMN "account_sequence" BIGINT;
|
||||
|
||||
-- Add unique constraint and index for account_sequence on reward_summaries
|
||||
CREATE UNIQUE INDEX "reward_summaries_account_sequence_key" ON "reward_summaries"("account_sequence");
|
||||
CREATE INDEX "idx_summary_account" ON "reward_summaries"("account_sequence");
|
||||
|
||||
-- Add account_sequence column to settlement_records
|
||||
ALTER TABLE "settlement_records" ADD COLUMN "account_sequence" BIGINT;
|
||||
|
||||
-- Add index for account_sequence on settlement_records
|
||||
CREATE INDEX "idx_settlement_account" ON "settlement_records"("account_sequence");
|
||||
|
||||
-- Backfill: set account_sequence = user_id for existing records
|
||||
UPDATE "reward_ledger_entries" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
|
||||
UPDATE "reward_summaries" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
|
||||
UPDATE "settlement_records" SET "account_sequence" = "user_id" WHERE "account_sequence" IS NULL;
|
||||
|
||||
-- Make account_sequence NOT NULL after backfill
|
||||
ALTER TABLE "reward_ledger_entries" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
ALTER TABLE "reward_summaries" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
ALTER TABLE "settlement_records" ALTER COLUMN "account_sequence" SET NOT NULL;
|
||||
|
|
@ -53,7 +53,8 @@ export class RewardApplicationService {
|
|||
const userIds = [...new Set(rewards.map(r => r.userId))];
|
||||
for (const userId of userIds) {
|
||||
const userRewards = rewards.filter(r => r.userId === userId);
|
||||
const summary = await this.rewardSummaryRepository.getOrCreate(userId);
|
||||
const accountSequence = userRewards[0].accountSequence;
|
||||
const summary = await this.rewardSummaryRepository.getOrCreate(userId, accountSequence);
|
||||
|
||||
for (const reward of userRewards) {
|
||||
if (reward.isPending) {
|
||||
|
|
@ -89,7 +90,8 @@ export class RewardApplicationService {
|
|||
this.logger.log(`Claiming pending rewards for user ${userId}`);
|
||||
|
||||
const pendingRewards = await this.rewardLedgerEntryRepository.findPendingByUserId(userId);
|
||||
const summary = await this.rewardSummaryRepository.getOrCreate(userId);
|
||||
// 使用 userId 作为 accountSequence (系统账户场景)
|
||||
const summary = await this.rewardSummaryRepository.getOrCreate(userId, userId);
|
||||
|
||||
let claimedCount = 0;
|
||||
let totalUsdtClaimed = 0;
|
||||
|
|
@ -222,7 +224,9 @@ export class RewardApplicationService {
|
|||
|
||||
// 更新每个用户的汇总数据
|
||||
for (const [userId, rewards] of userRewardsMap) {
|
||||
const summary = await this.rewardSummaryRepository.getOrCreate(BigInt(userId));
|
||||
const userIdBigInt = BigInt(userId);
|
||||
// 使用 userId 作为 accountSequence (过期任务中无法获取真实 accountSequence)
|
||||
const summary = await this.rewardSummaryRepository.getOrCreate(userIdBigInt, userIdBigInt);
|
||||
|
||||
for (const reward of rewards) {
|
||||
await this.rewardLedgerEntryRepository.save(reward);
|
||||
|
|
@ -236,7 +240,7 @@ export class RewardApplicationService {
|
|||
|
||||
// 将过期奖励转入总部社区
|
||||
if (expiredRewards.length > 0) {
|
||||
const hqSummary = await this.rewardSummaryRepository.getOrCreate(HEADQUARTERS_COMMUNITY_USER_ID);
|
||||
const hqSummary = await this.rewardSummaryRepository.getOrCreate(HEADQUARTERS_COMMUNITY_USER_ID, HEADQUARTERS_COMMUNITY_USER_ID);
|
||||
const totalHqUsdt = expiredRewards.reduce((sum, r) => sum + r.usdtAmount.amount, 0);
|
||||
const totalHqHashpower = expiredRewards.reduce((sum, r) => sum + r.hashpowerAmount.value, 0);
|
||||
hqSummary.addSettleable(Money.USDT(totalHqUsdt), Hashpower.create(totalHqHashpower));
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import { Hashpower } from '../../value-objects/hashpower.vo';
|
|||
|
||||
describe('RewardLedgerEntry', () => {
|
||||
const createRewardSource = () =>
|
||||
RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(2));
|
||||
RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(2));
|
||||
|
||||
describe('createPending', () => {
|
||||
it('should create a pending reward with 24h expiration', () => {
|
||||
const entry = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -31,6 +32,7 @@ describe('RewardLedgerEntry', () => {
|
|||
const before = Date.now();
|
||||
const entry = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -49,6 +51,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should create a settleable reward without expiration', () => {
|
||||
const entry = RewardLedgerEntry.createSettleable({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.create(5),
|
||||
|
|
@ -66,6 +69,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should transition pending to settleable', () => {
|
||||
const entry = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -84,6 +88,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should throw error when not pending', () => {
|
||||
const entry = RewardLedgerEntry.createSettleable({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -97,6 +102,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should transition pending to expired', () => {
|
||||
const entry = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -114,6 +120,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should throw error when not pending', () => {
|
||||
const entry = RewardLedgerEntry.createSettleable({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -127,6 +134,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should transition settleable to settled', () => {
|
||||
const entry = RewardLedgerEntry.createSettleable({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -144,6 +152,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should throw error when not settleable', () => {
|
||||
const entry = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -157,6 +166,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should return remaining time for pending rewards', () => {
|
||||
const entry = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -172,6 +182,7 @@ describe('RewardLedgerEntry', () => {
|
|||
it('should return 0 for settleable rewards', () => {
|
||||
const entry = RewardLedgerEntry.createSettleable({
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
|
|
@ -186,6 +197,7 @@ describe('RewardLedgerEntry', () => {
|
|||
const data = {
|
||||
id: BigInt(1),
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: createRewardSource(),
|
||||
usdtAmount: 500,
|
||||
hashpowerAmount: 5,
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import { Hashpower } from '../../value-objects/hashpower.vo';
|
|||
describe('RewardSummary', () => {
|
||||
describe('create', () => {
|
||||
it('should create a new summary with zero values', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
|
||||
expect(summary.userId).toBe(BigInt(100));
|
||||
expect(summary.accountSequence).toBe(BigInt(100));
|
||||
expect(summary.pendingUsdt.amount).toBe(0);
|
||||
expect(summary.settleableUsdt.amount).toBe(0);
|
||||
expect(summary.settledTotalUsdt.amount).toBe(0);
|
||||
|
|
@ -17,7 +18,7 @@ describe('RewardSummary', () => {
|
|||
|
||||
describe('addPending', () => {
|
||||
it('should add pending rewards and update expire time', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
|
||||
summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt);
|
||||
|
|
@ -28,7 +29,7 @@ describe('RewardSummary', () => {
|
|||
});
|
||||
|
||||
it('should keep earliest expire time', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
const earlyExpire = new Date(Date.now() + 12 * 60 * 60 * 1000);
|
||||
const lateExpire = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ describe('RewardSummary', () => {
|
|||
|
||||
describe('movePendingToSettleable', () => {
|
||||
it('should move amounts from pending to settleable', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
|
||||
summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt);
|
||||
|
|
@ -56,7 +57,7 @@ describe('RewardSummary', () => {
|
|||
});
|
||||
|
||||
it('should partially move pending amounts', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
|
||||
summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt);
|
||||
|
|
@ -71,7 +72,7 @@ describe('RewardSummary', () => {
|
|||
|
||||
describe('movePendingToExpired', () => {
|
||||
it('should move amounts from pending to expired', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
const expireAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
|
||||
summary.addPending(Money.USDT(500), Hashpower.create(5), expireAt);
|
||||
|
|
@ -85,7 +86,7 @@ describe('RewardSummary', () => {
|
|||
|
||||
describe('addSettleable', () => {
|
||||
it('should add directly to settleable', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
|
||||
summary.addSettleable(Money.USDT(1000), Hashpower.create(10));
|
||||
|
||||
|
|
@ -96,7 +97,7 @@ describe('RewardSummary', () => {
|
|||
|
||||
describe('settle', () => {
|
||||
it('should move settleable to settled total', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
|
||||
summary.addSettleable(Money.USDT(1000), Hashpower.create(10));
|
||||
summary.settle(Money.USDT(1000), Hashpower.create(10));
|
||||
|
|
@ -107,7 +108,7 @@ describe('RewardSummary', () => {
|
|||
});
|
||||
|
||||
it('should accumulate settled totals', () => {
|
||||
const summary = RewardSummary.create(BigInt(100));
|
||||
const summary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
|
||||
summary.addSettleable(Money.USDT(1000), Hashpower.create(10));
|
||||
summary.settle(Money.USDT(500), Hashpower.create(5));
|
||||
|
|
@ -124,6 +125,7 @@ describe('RewardSummary', () => {
|
|||
const data = {
|
||||
id: BigInt(1),
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
pendingUsdt: 500,
|
||||
pendingHashpower: 5,
|
||||
pendingExpireAt: new Date(),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export interface IRewardSummaryRepository {
|
|||
save(summary: RewardSummary): Promise<void>;
|
||||
findByUserId(userId: bigint): Promise<RewardSummary | null>;
|
||||
findByAccountSequence(accountSequence: bigint): Promise<RewardSummary | null>;
|
||||
getOrCreate(userId: bigint): Promise<RewardSummary>;
|
||||
getOrCreate(userId: bigint, accountSequence: bigint): Promise<RewardSummary>;
|
||||
getOrCreateByAccountSequence(accountSequence: bigint): Promise<RewardSummary>;
|
||||
findByUserIds(userIds: bigint[]): Promise<Map<string, RewardSummary>>;
|
||||
findTopSettleableUsers(limit: number): Promise<RewardSummary[]>;
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ export class RewardCalculationService {
|
|||
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: COST_FEE_ACCOUNT_ID,
|
||||
accountSequence: COST_FEE_ACCOUNT_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -207,6 +208,7 @@ export class RewardCalculationService {
|
|||
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: OPERATION_FEE_ACCOUNT_ID,
|
||||
accountSequence: OPERATION_FEE_ACCOUNT_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -235,6 +237,7 @@ export class RewardCalculationService {
|
|||
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
accountSequence: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -263,6 +266,7 @@ export class RewardCalculationService {
|
|||
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: RWAD_POOL_ACCOUNT_ID,
|
||||
accountSequence: RWAD_POOL_ACCOUNT_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -302,6 +306,7 @@ export class RewardCalculationService {
|
|||
// 推荐人已认种,直接可结算
|
||||
return [RewardLedgerEntry.createSettleable({
|
||||
userId: directReferrer.userId,
|
||||
accountSequence: directReferrer.userId,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -311,6 +316,7 @@ export class RewardCalculationService {
|
|||
// 推荐人未认种,进入待领取(24h倒计时)
|
||||
return [RewardLedgerEntry.createPending({
|
||||
userId: directReferrer.userId,
|
||||
accountSequence: directReferrer.userId,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -321,6 +327,7 @@ export class RewardCalculationService {
|
|||
// 无推荐人,进总部社区
|
||||
return [RewardLedgerEntry.createSettleable({
|
||||
userId: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
accountSequence: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -357,6 +364,7 @@ export class RewardCalculationService {
|
|||
if (nearestProvince) {
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: nearestProvince,
|
||||
accountSequence: nearestProvince,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -365,6 +373,7 @@ export class RewardCalculationService {
|
|||
} else {
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
accountSequence: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -397,6 +406,7 @@ export class RewardCalculationService {
|
|||
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: systemProvinceAccountId,
|
||||
accountSequence: systemProvinceAccountId,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -432,6 +442,7 @@ export class RewardCalculationService {
|
|||
if (nearestCity) {
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: nearestCity,
|
||||
accountSequence: nearestCity,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -440,6 +451,7 @@ export class RewardCalculationService {
|
|||
} else {
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
accountSequence: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -472,6 +484,7 @@ export class RewardCalculationService {
|
|||
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: systemCityAccountId,
|
||||
accountSequence: systemCityAccountId,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -503,6 +516,7 @@ export class RewardCalculationService {
|
|||
if (nearestCommunity) {
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: nearestCommunity,
|
||||
accountSequence: nearestCommunity,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
@ -511,6 +525,7 @@ export class RewardCalculationService {
|
|||
} else {
|
||||
return RewardLedgerEntry.createSettleable({
|
||||
userId: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
accountSequence: HEADQUARTERS_COMMUNITY_USER_ID,
|
||||
rewardSource,
|
||||
usdtAmount,
|
||||
hashpowerAmount: hashpower,
|
||||
|
|
|
|||
|
|
@ -53,13 +53,13 @@ export class RewardSummaryRepositoryImpl implements IRewardSummaryRepository {
|
|||
return raw ? RewardSummaryMapper.toDomain(raw) : null;
|
||||
}
|
||||
|
||||
async getOrCreate(userId: bigint): Promise<RewardSummary> {
|
||||
async getOrCreate(userId: bigint, accountSequence: bigint): Promise<RewardSummary> {
|
||||
const existing = await this.findByUserId(userId);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const newSummary = RewardSummary.create(userId);
|
||||
const newSummary = RewardSummary.create(userId, accountSequence);
|
||||
await this.save(newSummary);
|
||||
return newSummary;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
describe('distributeRewards', () => {
|
||||
it('should distribute rewards and update summaries', async () => {
|
||||
const params = {
|
||||
sourceOrderId: BigInt(1),
|
||||
sourceOrderNo: 'ORDER001',
|
||||
sourceUserId: BigInt(100),
|
||||
treeCount: 10,
|
||||
provinceCode: '440000',
|
||||
|
|
@ -96,13 +96,14 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
|
||||
const mockReward = RewardLedgerEntry.createPending({
|
||||
userId: BigInt(200),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(100)),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(100)),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
memo: 'Test',
|
||||
});
|
||||
|
||||
const mockSummary = RewardSummary.create(BigInt(200));
|
||||
const mockSummary = RewardSummary.create(BigInt(200), BigInt(100));
|
||||
|
||||
mockCalculationService.calculateRewards.mockResolvedValue([mockReward]);
|
||||
mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);
|
||||
|
|
@ -122,13 +123,14 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
|
||||
const pendingReward = RewardLedgerEntry.createPending({
|
||||
userId,
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
memo: 'Test reward',
|
||||
});
|
||||
|
||||
const mockSummary = RewardSummary.create(userId);
|
||||
const mockSummary = RewardSummary.create(userId, BigInt(100));
|
||||
mockSummary.addPending(
|
||||
Money.USDT(500),
|
||||
Hashpower.zero(),
|
||||
|
|
@ -152,7 +154,8 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
const expiredReward = RewardLedgerEntry.reconstitute({
|
||||
id: BigInt(1),
|
||||
userId,
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
|
||||
usdtAmount: 500,
|
||||
hashpowerAmount: 0,
|
||||
rewardStatus: RewardStatus.PENDING,
|
||||
|
|
@ -164,7 +167,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
memo: 'Expired reward',
|
||||
});
|
||||
|
||||
const mockSummary = RewardSummary.create(userId);
|
||||
const mockSummary = RewardSummary.create(userId, BigInt(100));
|
||||
|
||||
mockLedgerRepository.findPendingByUserId.mockResolvedValue([expiredReward]);
|
||||
mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);
|
||||
|
|
@ -178,12 +181,13 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
|
||||
describe('settleRewards', () => {
|
||||
it('should settle rewards and call wallet service', async () => {
|
||||
const userId = BigInt(100);
|
||||
const accountSequence = BigInt(100);
|
||||
|
||||
const settleableReward = RewardLedgerEntry.reconstitute({
|
||||
id: BigInt(1),
|
||||
userId,
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)),
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
|
||||
usdtAmount: 500,
|
||||
hashpowerAmount: 0,
|
||||
rewardStatus: RewardStatus.SETTLEABLE,
|
||||
|
|
@ -195,7 +199,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
memo: 'Test',
|
||||
});
|
||||
|
||||
const mockSummary = RewardSummary.create(userId);
|
||||
const mockSummary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
mockSummary.addSettleable(Money.USDT(500), Hashpower.zero());
|
||||
|
||||
mockLedgerRepository.findSettleableByUserId.mockResolvedValue([settleableReward]);
|
||||
|
|
@ -207,7 +211,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
});
|
||||
|
||||
const result = await service.settleRewards({
|
||||
userId,
|
||||
accountSequence,
|
||||
settleCurrency: 'BNB',
|
||||
});
|
||||
|
||||
|
|
@ -216,19 +220,19 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
expect(result.receivedAmount).toBe(0.25);
|
||||
expect(result.settleCurrency).toBe('BNB');
|
||||
expect(mockWalletService.executeSwap).toHaveBeenCalledWith({
|
||||
userId,
|
||||
userId: BigInt(100),
|
||||
usdtAmount: 500,
|
||||
targetCurrency: 'BNB',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error when no settleable rewards', async () => {
|
||||
const userId = BigInt(100);
|
||||
const accountSequence = BigInt(100);
|
||||
|
||||
mockLedgerRepository.findSettleableByUserId.mockResolvedValue([]);
|
||||
|
||||
const result = await service.settleRewards({
|
||||
userId,
|
||||
accountSequence,
|
||||
settleCurrency: 'BNB',
|
||||
});
|
||||
|
||||
|
|
@ -237,12 +241,13 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
});
|
||||
|
||||
it('should handle wallet service failure', async () => {
|
||||
const userId = BigInt(100);
|
||||
const accountSequence = BigInt(100);
|
||||
|
||||
const settleableReward = RewardLedgerEntry.reconstitute({
|
||||
id: BigInt(1),
|
||||
userId,
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)),
|
||||
userId: BigInt(100),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
|
||||
usdtAmount: 500,
|
||||
hashpowerAmount: 0,
|
||||
rewardStatus: RewardStatus.SETTLEABLE,
|
||||
|
|
@ -254,7 +259,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
memo: 'Test',
|
||||
});
|
||||
|
||||
const mockSummary = RewardSummary.create(userId);
|
||||
const mockSummary = RewardSummary.create(BigInt(100), BigInt(100));
|
||||
mockSummary.addSettleable(Money.USDT(500), Hashpower.zero());
|
||||
|
||||
mockLedgerRepository.findSettleableByUserId.mockResolvedValue([settleableReward]);
|
||||
|
|
@ -265,7 +270,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
});
|
||||
|
||||
const result = await service.settleRewards({
|
||||
userId,
|
||||
accountSequence,
|
||||
settleCurrency: 'BNB',
|
||||
});
|
||||
|
||||
|
|
@ -277,7 +282,7 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
describe('getRewardSummary', () => {
|
||||
it('should return reward summary for user', async () => {
|
||||
const userId = BigInt(100);
|
||||
const mockSummary = RewardSummary.create(userId);
|
||||
const mockSummary = RewardSummary.create(userId, BigInt(100));
|
||||
|
||||
mockSummaryRepository.findByUserId.mockResolvedValue(mockSummary);
|
||||
|
||||
|
|
@ -307,7 +312,8 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
const mockReward = RewardLedgerEntry.reconstitute({
|
||||
id: BigInt(1),
|
||||
userId,
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
|
||||
usdtAmount: 500,
|
||||
hashpowerAmount: 0,
|
||||
rewardStatus: RewardStatus.PENDING,
|
||||
|
|
@ -336,7 +342,8 @@ describe('RewardApplicationService (Integration)', () => {
|
|||
|
||||
const pendingReward = RewardLedgerEntry.createPending({
|
||||
userId,
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, BigInt(1), BigInt(50)),
|
||||
accountSequence: BigInt(100),
|
||||
rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
|
||||
usdtAmount: Money.USDT(500),
|
||||
hashpowerAmount: Hashpower.zero(),
|
||||
memo: 'Test',
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ describe('RewardCalculationService (Integration)', () => {
|
|||
|
||||
describe('calculateRewards', () => {
|
||||
const baseParams = {
|
||||
sourceOrderId: BigInt(1),
|
||||
sourceOrderNo: 'ORDER001',
|
||||
sourceUserId: BigInt(100),
|
||||
treeCount: 10,
|
||||
provinceCode: '440000',
|
||||
|
|
|
|||
Loading…
Reference in New Issue