fix(contribution-service): calculate totalContribution correctly in CDC event
Previously, totalContribution was incorrectly set to effectiveContribution. Now correctly calculated as: personal + teamLevel + teamBonus Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
debc8605df
commit
81a58edaca
|
|
@ -1,8 +1,9 @@
|
|||
import { Controller, Get, Param, Query, NotFoundException } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||
import { GetContributionAccountQuery } from '../../application/queries/get-contribution-account.query';
|
||||
import { GetContributionStatsQuery } from '../../application/queries/get-contribution-stats.query';
|
||||
import { GetContributionRankingQuery } from '../../application/queries/get-contribution-ranking.query';
|
||||
import { GetPlantingLedgerQuery, PlantingLedgerDto } from '../../application/queries/get-planting-ledger.query';
|
||||
import {
|
||||
ContributionAccountResponse,
|
||||
ContributionRecordsResponse,
|
||||
|
|
@ -19,6 +20,7 @@ export class ContributionController {
|
|||
private readonly getAccountQuery: GetContributionAccountQuery,
|
||||
private readonly getStatsQuery: GetContributionStatsQuery,
|
||||
private readonly getRankingQuery: GetContributionRankingQuery,
|
||||
private readonly getPlantingLedgerQuery: GetPlantingLedgerQuery,
|
||||
) {}
|
||||
|
||||
@Get('stats')
|
||||
|
|
@ -95,4 +97,22 @@ export class ContributionController {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('accounts/:accountSequence/planting-ledger')
|
||||
@ApiOperation({ summary: '获取账户认种分类账' })
|
||||
@ApiParam({ name: 'accountSequence', description: '账户序号' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number, description: '页码' })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number, description: '每页数量' })
|
||||
@ApiResponse({ status: 200, description: '认种分类账' })
|
||||
async getPlantingLedger(
|
||||
@Param('accountSequence') accountSequence: string,
|
||||
@Query('page') page?: number,
|
||||
@Query('pageSize') pageSize?: number,
|
||||
): Promise<PlantingLedgerDto> {
|
||||
return this.getPlantingLedgerQuery.execute(
|
||||
accountSequence,
|
||||
page ?? 1,
|
||||
pageSize ?? 20,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { SyncedDataRepository } from '../../infrastructure/persistence/repositories/synced-data.repository';
|
||||
|
||||
export interface PlantingRecordDto {
|
||||
orderId: string;
|
||||
orderNo: string;
|
||||
originalAdoptionId: string;
|
||||
treeCount: number;
|
||||
contributionPerTree: string;
|
||||
totalContribution: string;
|
||||
status: string;
|
||||
adoptionDate: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface PlantingSummaryDto {
|
||||
totalOrders: number;
|
||||
totalTreeCount: number;
|
||||
totalAmount: string;
|
||||
effectiveTreeCount: number;
|
||||
firstPlantingAt: string | null;
|
||||
lastPlantingAt: string | null;
|
||||
}
|
||||
|
||||
export interface PlantingLedgerDto {
|
||||
summary: PlantingSummaryDto;
|
||||
items: PlantingRecordDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GetPlantingLedgerQuery {
|
||||
constructor(private readonly syncedDataRepository: SyncedDataRepository) {}
|
||||
|
||||
async execute(
|
||||
accountSequence: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
): Promise<PlantingLedgerDto> {
|
||||
const [summary, ledger] = await Promise.all([
|
||||
this.syncedDataRepository.getPlantingSummary(accountSequence),
|
||||
this.syncedDataRepository.getPlantingLedger(accountSequence, page, pageSize),
|
||||
]);
|
||||
|
||||
return {
|
||||
summary: {
|
||||
totalOrders: summary.totalOrders,
|
||||
totalTreeCount: summary.totalTreeCount,
|
||||
totalAmount: summary.totalAmount,
|
||||
effectiveTreeCount: summary.effectiveTreeCount,
|
||||
firstPlantingAt: summary.firstPlantingAt?.toISOString() || null,
|
||||
lastPlantingAt: summary.lastPlantingAt?.toISOString() || null,
|
||||
},
|
||||
items: ledger.items.map((item) => ({
|
||||
orderId: item.id.toString(),
|
||||
orderNo: `ORD-${item.originalAdoptionId}`,
|
||||
originalAdoptionId: item.originalAdoptionId.toString(),
|
||||
treeCount: item.treeCount,
|
||||
contributionPerTree: item.contributionPerTree.toString(),
|
||||
totalContribution: item.contributionPerTree.mul(item.treeCount).toString(),
|
||||
status: item.status || 'UNKNOWN',
|
||||
adoptionDate: item.adoptionDate?.toISOString() || null,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
})),
|
||||
total: ledger.total,
|
||||
page: ledger.page,
|
||||
pageSize: ledger.pageSize,
|
||||
totalPages: ledger.totalPages,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -439,12 +439,17 @@ export class ContributionCalculationService {
|
|||
private async publishContributionAccountUpdatedEvent(
|
||||
account: ContributionAccountAggregate,
|
||||
): Promise<void> {
|
||||
// 总算力 = 个人算力 + 层级待解锁 + 加成待解锁
|
||||
const totalContribution = account.personalContribution.value
|
||||
.plus(account.totalLevelPending.value)
|
||||
.plus(account.totalBonusPending.value);
|
||||
|
||||
const event = new ContributionAccountUpdatedEvent(
|
||||
account.accountSequence,
|
||||
account.personalContribution.value.toString(),
|
||||
account.totalLevelPending.value.toString(),
|
||||
account.totalBonusPending.value.toString(),
|
||||
account.effectiveContribution.value.toString(),
|
||||
totalContribution.toString(),
|
||||
account.effectiveContribution.value.toString(),
|
||||
account.hasAdopted,
|
||||
account.directReferralAdoptedCount,
|
||||
|
|
|
|||
|
|
@ -359,6 +359,89 @@ export class SyncedDataRepository implements ISyncedDataRepository {
|
|||
return result;
|
||||
}
|
||||
|
||||
// ========== 认种分类账查询 ==========
|
||||
|
||||
async getPlantingLedger(
|
||||
accountSequence: string,
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
): Promise<{
|
||||
items: SyncedAdoption[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
}> {
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.client.syncedAdoption.findMany({
|
||||
where: { accountSequence },
|
||||
orderBy: { adoptionDate: 'desc' },
|
||||
skip,
|
||||
take: pageSize,
|
||||
}),
|
||||
this.client.syncedAdoption.count({
|
||||
where: { accountSequence },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
items: items.map((r) => this.toSyncedAdoption(r)),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
totalPages: Math.ceil(total / pageSize),
|
||||
};
|
||||
}
|
||||
|
||||
async getPlantingSummary(accountSequence: string): Promise<{
|
||||
totalOrders: number;
|
||||
totalTreeCount: number;
|
||||
totalAmount: string;
|
||||
effectiveTreeCount: number;
|
||||
firstPlantingAt: Date | null;
|
||||
lastPlantingAt: Date | null;
|
||||
}> {
|
||||
const adoptions = await this.client.syncedAdoption.findMany({
|
||||
where: { accountSequence },
|
||||
orderBy: { adoptionDate: 'asc' },
|
||||
});
|
||||
|
||||
if (adoptions.length === 0) {
|
||||
return {
|
||||
totalOrders: 0,
|
||||
totalTreeCount: 0,
|
||||
totalAmount: '0',
|
||||
effectiveTreeCount: 0,
|
||||
firstPlantingAt: null,
|
||||
lastPlantingAt: null,
|
||||
};
|
||||
}
|
||||
|
||||
const totalOrders = adoptions.length;
|
||||
const totalTreeCount = adoptions.reduce((sum, a) => sum + a.treeCount, 0);
|
||||
const effectiveTreeCount = adoptions
|
||||
.filter((a) => a.status === 'MINING_ENABLED')
|
||||
.reduce((sum, a) => sum + a.treeCount, 0);
|
||||
|
||||
// 计算总金额:treeCount * contributionPerTree (假设每棵树价格等于算力值)
|
||||
let totalAmount = new Decimal(0);
|
||||
for (const adoption of adoptions) {
|
||||
const amount = new Decimal(adoption.contributionPerTree).mul(adoption.treeCount);
|
||||
totalAmount = totalAmount.add(amount);
|
||||
}
|
||||
|
||||
return {
|
||||
totalOrders,
|
||||
totalTreeCount,
|
||||
totalAmount: totalAmount.toString(),
|
||||
effectiveTreeCount,
|
||||
firstPlantingAt: adoptions[0]?.adoptionDate || null,
|
||||
lastPlantingAt: adoptions[adoptions.length - 1]?.adoptionDate || null,
|
||||
};
|
||||
}
|
||||
|
||||
// ========== 统计方法(用于查询服务)==========
|
||||
|
||||
async countUsers(): Promise<number> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue