rwadurian/backend/services/contribution-service/src/domain/services/contribution-calculator.ser...

277 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Decimal from 'decimal.js';
import { ContributionAmount } from '../value-objects/contribution-amount.vo';
import { DistributionRate } from '../value-objects/distribution-rate.vo';
import { ContributionAccountAggregate, ContributionSourceType } from '../aggregates/contribution-account.aggregate';
import { ContributionRecordAggregate } from '../aggregates/contribution-record.aggregate';
import { SyncedAdoption, SyncedReferral } from '../repositories/synced-data.repository.interface';
/**
* 系统账户贡献值分配
*/
export interface SystemContributionAllocation {
accountType: 'OPERATION' | 'PROVINCE' | 'CITY' | 'HEADQUARTERS';
regionCode: string | null; // 省市代码,如 440000、440100
rate: DistributionRate;
amount: ContributionAmount;
}
/**
* 算力分配结果
*/
export interface ContributionDistributionResult {
// 个人贡献值记录
personalRecord: ContributionRecordAggregate;
// 团队层级贡献值记录(给上线们的)
teamLevelRecords: ContributionRecordAggregate[];
// 团队额外奖励贡献值记录(给认种人自己的)
teamBonusRecords: ContributionRecordAggregate[];
// 未分配的贡献值(归总部)
unallocatedContributions: {
type: string;
wouldBeAccountSequence: string | null;
levelDepth: number | null;
amount: ContributionAmount;
reason: string;
}[];
// 系统账户贡献值(支持按省市细分)
systemContributions: SystemContributionAllocation[];
}
/**
* 贡献值计算领域服务
* 负责核心的算力计算逻辑
*/
export class ContributionCalculatorService {
// 贡献值有效期2年
private static readonly CONTRIBUTION_VALIDITY_YEARS = 2;
/**
* 计算认种产生的贡献值分配
*
* @param adoption 认种记录
* @param adopterAccount 认种人的算力账户(用于判断 TEAM_BONUS 解锁状态)
* @param ancestorChain 上线链条从直接上线开始最多15级
* @param ancestorAccounts 上线的算力账户(用于判断 TEAM_LEVEL 解锁状态)
* @returns 分配结果
*/
calculateAdoptionContribution(
adoption: SyncedAdoption,
adopterAccount: ContributionAccountAggregate | null,
ancestorChain: SyncedReferral[],
ancestorAccounts: Map<string, ContributionAccountAggregate>,
): ContributionDistributionResult {
const baseContribution = new ContributionAmount(adoption.contributionPerTree);
const treeCount = adoption.treeCount;
const totalContribution = baseContribution.multiply(treeCount);
// 计算生效日期次日和过期日期2年后
const effectiveDate = this.getNextDay(adoption.adoptionDate);
const expireDate = this.addYears(effectiveDate, ContributionCalculatorService.CONTRIBUTION_VALIDITY_YEARS);
const result: ContributionDistributionResult = {
personalRecord: null as any,
teamLevelRecords: [],
teamBonusRecords: [],
unallocatedContributions: [],
systemContributions: [],
};
// 1. 个人贡献值 (70%)
result.personalRecord = ContributionRecordAggregate.createPersonal({
accountSequence: adoption.accountSequence,
sourceAdoptionId: adoption.originalAdoptionId,
treeCount,
baseContribution,
effectiveDate,
expireDate,
});
// 2. 系统账户贡献值 (15%)
// 运营账户(全国)- 12%
result.systemContributions.push({
accountType: 'OPERATION',
regionCode: null,
rate: DistributionRate.OPERATION,
amount: totalContribution.multiply(DistributionRate.OPERATION.value),
});
// 省公司账户 - 1%(按认种选择的省份)
const provinceCode = adoption.selectedProvince;
result.systemContributions.push({
accountType: 'PROVINCE',
regionCode: provinceCode || null,
rate: DistributionRate.PROVINCE,
amount: totalContribution.multiply(DistributionRate.PROVINCE.value),
});
// 市公司账户 - 2%(按认种选择的城市)
const cityCode = adoption.selectedCity;
result.systemContributions.push({
accountType: 'CITY',
regionCode: cityCode || null,
rate: DistributionRate.CITY,
amount: totalContribution.multiply(DistributionRate.CITY.value),
});
// 3. 团队贡献值 (15%)
this.distributeTeamContribution(
adoption,
adopterAccount,
baseContribution,
treeCount,
ancestorChain,
ancestorAccounts,
effectiveDate,
expireDate,
result,
);
return result;
}
/**
* 分配团队贡献值
*/
private distributeTeamContribution(
adoption: SyncedAdoption,
adopterAccount: ContributionAccountAggregate | null,
baseContribution: ContributionAmount,
treeCount: number,
ancestorChain: SyncedReferral[],
ancestorAccounts: Map<string, ContributionAccountAggregate>,
effectiveDate: Date,
expireDate: Date,
result: ContributionDistributionResult,
): void {
// 3.1 层级部分 (7.5% = 0.5% × 15级)
for (let level = 1; level <= 15; level++) {
const ancestor = ancestorChain[level - 1];
if (!ancestor) {
// 没有这一级的上线,归总部
const levelAmount = baseContribution.multiply(treeCount).multiply(DistributionRate.LEVEL_PER.value);
result.unallocatedContributions.push({
type: 'LEVEL_NO_ANCESTOR',
wouldBeAccountSequence: null,
levelDepth: level,
amount: levelAmount,
reason: `${level}级无上线`,
});
continue;
}
const ancestorAccount = ancestorAccounts.get(ancestor.accountSequence);
const unlockedLevelDepth = ancestorAccount?.unlockedLevelDepth ?? 0;
if (unlockedLevelDepth >= level) {
// 上线已解锁该级别
result.teamLevelRecords.push(
ContributionRecordAggregate.createTeamLevel({
accountSequence: ancestor.accountSequence,
sourceAdoptionId: adoption.originalAdoptionId,
sourceAccountSequence: adoption.accountSequence,
treeCount,
baseContribution,
levelDepth: level,
effectiveDate,
expireDate,
}),
);
} else {
// 上线未解锁该级别,归总部
const levelAmount = baseContribution.multiply(treeCount).multiply(DistributionRate.LEVEL_PER.value);
result.unallocatedContributions.push({
type: 'LEVEL_OVERFLOW',
wouldBeAccountSequence: ancestor.accountSequence,
levelDepth: level,
amount: levelAmount,
reason: `上线${ancestor.accountSequence}未解锁第${level}级(已解锁${unlockedLevelDepth}级)`,
});
}
}
// 3.2 额外奖励部分 (7.5% = 2.5% × 3档) - 给认种人自己
// T1: 自己认种即解锁(无条件,当前正在认种所以一定解锁)
// T2: 直推≥2人认种解锁
// T3: 直推≥4人认种解锁
const directReferralAdoptedCount = adopterAccount?.directReferralAdoptedCount ?? 0;
// T1 始终解锁(因为当前正在认种)
// T2 需要直推≥2人认种
// T3 需要直推≥4人认种
const effectiveUnlockedBonusTiers = this.calculateUnlockedBonusTiers(true, directReferralAdoptedCount);
for (let tier = 1; tier <= 3; tier++) {
if (effectiveUnlockedBonusTiers >= tier) {
// 认种人已解锁该档位,给认种人自己
result.teamBonusRecords.push(
ContributionRecordAggregate.createTeamBonus({
accountSequence: adoption.accountSequence,
sourceAdoptionId: adoption.originalAdoptionId,
sourceAccountSequence: adoption.accountSequence,
treeCount,
baseContribution,
bonusTier: tier,
effectiveDate,
expireDate,
}),
);
} else {
// 认种人未解锁该档位,归总部(冻结/待领取)
const bonusAmount = baseContribution.multiply(treeCount).multiply(DistributionRate.BONUS_PER.value);
result.unallocatedContributions.push({
type: `BONUS_TIER_${tier}`,
wouldBeAccountSequence: adoption.accountSequence,
levelDepth: null,
amount: bonusAmount,
reason: `认种人${adoption.accountSequence}未解锁第${tier}档奖励(需直推${tier === 2 ? '2人' : '4人'}认种)`,
});
}
}
}
/**
* 根据直推认种用户数计算解锁层级
*/
calculateUnlockedLevelDepth(directReferralAdoptedCount: number): number {
if (directReferralAdoptedCount >= 5) return 15;
if (directReferralAdoptedCount >= 3) return 10;
if (directReferralAdoptedCount >= 1) return 5;
return 0;
}
/**
* 根据条件计算解锁的额外奖励档位数
*/
calculateUnlockedBonusTiers(hasAdopted: boolean, directReferralAdoptedCount: number): number {
let tiers = 0;
if (hasAdopted) tiers++;
if (directReferralAdoptedCount >= 2) tiers++;
if (directReferralAdoptedCount >= 4) tiers++;
return tiers;
}
/**
* 获取次日日期
*/
private getNextDay(date: Date): Date {
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1);
nextDay.setHours(0, 0, 0, 0);
return nextDay;
}
/**
* 添加年数
*/
private addYears(date: Date, years: number): Date {
const result = new Date(date);
result.setFullYear(result.getFullYear() + years);
return result;
}
}