150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
||
import { OutboxRepository } from '../../infrastructure/persistence/repositories/outbox.repository';
|
||
import { ContributionDistributionResult } from '../../domain/services/contribution-calculator.service';
|
||
import { SyncedAdoption } from '../../domain/repositories/synced-data.repository.interface';
|
||
|
||
/**
|
||
* 贡献值分配结果发布服务
|
||
* 将计算完成的分配结果发布到 Kafka,供 mining-wallet-service 消费
|
||
*/
|
||
@Injectable()
|
||
export class ContributionDistributionPublisherService {
|
||
private readonly logger = new Logger(
|
||
ContributionDistributionPublisherService.name,
|
||
);
|
||
|
||
constructor(private readonly outboxRepository: OutboxRepository) {}
|
||
|
||
/**
|
||
* 发布分配结果到 Kafka
|
||
*/
|
||
async publishDistributionResult(
|
||
adoption: SyncedAdoption,
|
||
result: ContributionDistributionResult,
|
||
provinceCode: string,
|
||
cityCode: string,
|
||
): Promise<void> {
|
||
const eventId = `dist-${adoption.originalAdoptionId}-${Date.now()}`;
|
||
|
||
const payload = {
|
||
eventType: 'ContributionDistributionCompleted',
|
||
eventId,
|
||
timestamp: new Date().toISOString(),
|
||
payload: {
|
||
// 认种信息
|
||
adoptionId: adoption.originalAdoptionId.toString(),
|
||
adopterAccountSequence: adoption.accountSequence,
|
||
treeCount: adoption.treeCount,
|
||
adoptionDate: adoption.adoptionDate.toISOString(),
|
||
|
||
// 用户贡献值分配
|
||
userContributions: this.mapUserContributions(result),
|
||
|
||
// 系统账户分配
|
||
systemContributions: this.mapSystemContributions(
|
||
result,
|
||
provinceCode,
|
||
cityCode,
|
||
),
|
||
|
||
// 未分配(归总部)
|
||
unallocatedToHeadquarters: this.mapUnallocated(result),
|
||
},
|
||
};
|
||
|
||
await this.outboxRepository.save({
|
||
eventType: 'ContributionDistributionCompleted',
|
||
topic: 'contribution.distribution.completed',
|
||
key: adoption.accountSequence,
|
||
payload,
|
||
aggregateId: adoption.originalAdoptionId.toString(),
|
||
aggregateType: 'Adoption',
|
||
});
|
||
|
||
this.logger.debug(
|
||
`Published distribution result for adoption ${adoption.originalAdoptionId}`,
|
||
);
|
||
}
|
||
|
||
private mapUserContributions(result: ContributionDistributionResult): any[] {
|
||
const contributions: any[] = [];
|
||
|
||
// 个人贡献
|
||
if (result.personalRecord) {
|
||
contributions.push({
|
||
accountSequence: result.personalRecord.accountSequence,
|
||
contributionType: 'PERSONAL',
|
||
amount: result.personalRecord.amount.value.toString(),
|
||
effectiveDate: result.personalRecord.effectiveDate.toISOString(),
|
||
expireDate: result.personalRecord.expireDate.toISOString(),
|
||
sourceAdoptionId: result.personalRecord.sourceAdoptionId.toString(),
|
||
sourceAccountSequence: result.personalRecord.sourceAccountSequence,
|
||
});
|
||
}
|
||
|
||
// 团队层级贡献
|
||
for (const record of result.teamLevelRecords) {
|
||
contributions.push({
|
||
accountSequence: record.accountSequence,
|
||
contributionType: 'TEAM_LEVEL',
|
||
amount: record.amount.value.toString(),
|
||
levelDepth: record.levelDepth,
|
||
effectiveDate: record.effectiveDate.toISOString(),
|
||
expireDate: record.expireDate.toISOString(),
|
||
sourceAdoptionId: record.sourceAdoptionId.toString(),
|
||
sourceAccountSequence: record.sourceAccountSequence,
|
||
});
|
||
}
|
||
|
||
// 团队奖励贡献
|
||
for (const record of result.teamBonusRecords) {
|
||
contributions.push({
|
||
accountSequence: record.accountSequence,
|
||
contributionType: 'TEAM_BONUS',
|
||
amount: record.amount.value.toString(),
|
||
bonusTier: record.bonusTier,
|
||
effectiveDate: record.effectiveDate.toISOString(),
|
||
expireDate: record.expireDate.toISOString(),
|
||
sourceAdoptionId: record.sourceAdoptionId.toString(),
|
||
sourceAccountSequence: record.sourceAccountSequence,
|
||
});
|
||
}
|
||
|
||
return contributions;
|
||
}
|
||
|
||
private mapSystemContributions(
|
||
result: ContributionDistributionResult,
|
||
provinceCode: string,
|
||
cityCode: string,
|
||
): any[] {
|
||
return result.systemContributions.map((sys) => ({
|
||
accountType: sys.accountType,
|
||
amount: sys.amount.value.toString(),
|
||
// 省份代码:PROVINCE 用自己的 regionCode,CITY 需要传递省份代码用于创建省份
|
||
provinceCode:
|
||
sys.accountType === 'PROVINCE'
|
||
? sys.regionCode || provinceCode
|
||
: sys.accountType === 'CITY'
|
||
? provinceCode // CITY 需要省份代码来创建省份(如果省份不存在)
|
||
: undefined,
|
||
// 城市代码:只有 CITY 类型有
|
||
cityCode:
|
||
sys.accountType === 'CITY' ? sys.regionCode || cityCode : undefined,
|
||
neverExpires: sys.accountType === 'OPERATION', // 运营账户永不过期
|
||
}));
|
||
}
|
||
|
||
private mapUnallocated(result: ContributionDistributionResult): any[] {
|
||
return result.unallocatedContributions.map((u) => ({
|
||
reason: u.reason,
|
||
amount: u.amount.value.toString(),
|
||
wouldBeAccountSequence: u.wouldBeAccountSequence,
|
||
levelDepth: u.levelDepth,
|
||
bonusTier: u.type.startsWith('BONUS_TIER_')
|
||
? parseInt(u.type.split('_')[2])
|
||
: undefined,
|
||
}));
|
||
}
|
||
}
|