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 { 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 { GetContributionAccountQuery } from '../../application/queries/get-contribution-account.query';
|
||||||
import { GetContributionStatsQuery } from '../../application/queries/get-contribution-stats.query';
|
import { GetContributionStatsQuery } from '../../application/queries/get-contribution-stats.query';
|
||||||
import { GetContributionRankingQuery } from '../../application/queries/get-contribution-ranking.query';
|
import { GetContributionRankingQuery } from '../../application/queries/get-contribution-ranking.query';
|
||||||
|
import { GetPlantingLedgerQuery, PlantingLedgerDto } from '../../application/queries/get-planting-ledger.query';
|
||||||
import {
|
import {
|
||||||
ContributionAccountResponse,
|
ContributionAccountResponse,
|
||||||
ContributionRecordsResponse,
|
ContributionRecordsResponse,
|
||||||
|
|
@ -19,6 +20,7 @@ export class ContributionController {
|
||||||
private readonly getAccountQuery: GetContributionAccountQuery,
|
private readonly getAccountQuery: GetContributionAccountQuery,
|
||||||
private readonly getStatsQuery: GetContributionStatsQuery,
|
private readonly getStatsQuery: GetContributionStatsQuery,
|
||||||
private readonly getRankingQuery: GetContributionRankingQuery,
|
private readonly getRankingQuery: GetContributionRankingQuery,
|
||||||
|
private readonly getPlantingLedgerQuery: GetPlantingLedgerQuery,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('stats')
|
@Get('stats')
|
||||||
|
|
@ -95,4 +97,22 @@ export class ContributionController {
|
||||||
}
|
}
|
||||||
return result;
|
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(
|
private async publishContributionAccountUpdatedEvent(
|
||||||
account: ContributionAccountAggregate,
|
account: ContributionAccountAggregate,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
// 总算力 = 个人算力 + 层级待解锁 + 加成待解锁
|
||||||
|
const totalContribution = account.personalContribution.value
|
||||||
|
.plus(account.totalLevelPending.value)
|
||||||
|
.plus(account.totalBonusPending.value);
|
||||||
|
|
||||||
const event = new ContributionAccountUpdatedEvent(
|
const event = new ContributionAccountUpdatedEvent(
|
||||||
account.accountSequence,
|
account.accountSequence,
|
||||||
account.personalContribution.value.toString(),
|
account.personalContribution.value.toString(),
|
||||||
account.totalLevelPending.value.toString(),
|
account.totalLevelPending.value.toString(),
|
||||||
account.totalBonusPending.value.toString(),
|
account.totalBonusPending.value.toString(),
|
||||||
account.effectiveContribution.value.toString(),
|
totalContribution.toString(),
|
||||||
account.effectiveContribution.value.toString(),
|
account.effectiveContribution.value.toString(),
|
||||||
account.hasAdopted,
|
account.hasAdopted,
|
||||||
account.directReferralAdoptedCount,
|
account.directReferralAdoptedCount,
|
||||||
|
|
|
||||||
|
|
@ -359,6 +359,89 @@ export class SyncedDataRepository implements ISyncedDataRepository {
|
||||||
return result;
|
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> {
|
async countUsers(): Promise<number> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue