fix(contribution): correct TEAM_BONUS distribution to adopter instead of referrer

TEAM_BONUS (7.5% = 2.5% × 3 tiers) should be given to the adopter themselves,
not to their direct referrer. The unlock conditions are:
- T1 (2.5%): Self adoption (always unlocked when adopting)
- T2 (2.5%): 2+ direct referrals adopted
- T3 (2.5%): 4+ direct referrals adopted

Also fixed findAncestorChain to correctly include the first ancestor
in the chain instead of skipping it.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-12 09:13:13 -08:00
parent 01ff873264
commit 8d97daa524
3 changed files with 49 additions and 46 deletions

View File

@ -61,12 +61,15 @@ export class ContributionCalculationService {
ancestorChain = await this.buildAncestorChain(userReferral.referrerAccountSequence);
}
// 获取上线的算力账户(用于判断解锁状态)
// 获取认种人的算力账户(用于判断 TEAM_BONUS 解锁状态)
const adopterAccount = await this.contributionAccountRepository.findByAccountSequence(adoption.accountSequence);
// 获取上线的算力账户(用于判断 TEAM_LEVEL 解锁状态)
const ancestorAccountSequences = ancestorChain.map((a) => a.accountSequence);
const ancestorAccounts = await this.contributionAccountRepository.findByAccountSequences(ancestorAccountSequences);
// 执行算力计算
const result = this.domainCalculator.calculateAdoptionContribution(adoption, ancestorChain, ancestorAccounts);
const result = this.domainCalculator.calculateAdoptionContribution(adoption, adopterAccount, ancestorChain, ancestorAccounts);
// 在事务中保存所有结果
await this.unitOfWork.executeInTransaction(async () => {

View File

@ -15,7 +15,7 @@ export interface ContributionDistributionResult {
// 团队层级贡献值记录(给上线们的)
teamLevelRecords: ContributionRecordAggregate[];
// 团队额外奖励贡献值记录(给直接上线的)
// 团队额外奖励贡献值记录(给认种人自己的)
teamBonusRecords: ContributionRecordAggregate[];
// 未分配的贡献值(归总部)
@ -47,12 +47,14 @@ export class ContributionCalculatorService {
*
*
* @param adoption
* @param adopterAccount TEAM_BONUS
* @param ancestorChain 线线15
* @param ancestorAccounts 线
* @param ancestorAccounts 线 TEAM_LEVEL
* @returns
*/
calculateAdoptionContribution(
adoption: SyncedAdoption,
adopterAccount: ContributionAccountAggregate | null,
ancestorChain: SyncedReferral[],
ancestorAccounts: Map<string, ContributionAccountAggregate>,
): ContributionDistributionResult {
@ -104,6 +106,7 @@ export class ContributionCalculatorService {
// 3. 团队贡献值 (15%)
this.distributeTeamContribution(
adoption,
adopterAccount,
baseContribution,
treeCount,
ancestorChain,
@ -121,6 +124,7 @@ export class ContributionCalculatorService {
*/
private distributeTeamContribution(
adoption: SyncedAdoption,
adopterAccount: ContributionAccountAggregate | null,
baseContribution: ContributionAmount,
treeCount: number,
ancestorChain: SyncedReferral[],
@ -176,49 +180,41 @@ export class ContributionCalculatorService {
}
}
// 3.2 额外奖励部分 (7.5% = 2.5% × 3档) - 只给直接上线
if (ancestorChain.length > 0) {
const directReferrer = ancestorChain[0];
const directReferrerAccount = ancestorAccounts.get(directReferrer.accountSequence);
const unlockedBonusTiers = directReferrerAccount?.unlockedBonusTiers ?? 0;
// 3.2 额外奖励部分 (7.5% = 2.5% × 3档) - 给认种人自己
// T1: 自己认种即解锁(无条件,当前正在认种所以一定解锁)
// T2: 直推≥2人认种解锁
// T3: 直推≥4人认种解锁
const directReferralAdoptedCount = adopterAccount?.directReferralAdoptedCount ?? 0;
for (let tier = 1; tier <= 3; tier++) {
if (unlockedBonusTiers >= tier) {
// 上线已解锁该档位
result.teamBonusRecords.push(
ContributionRecordAggregate.createTeamBonus({
accountSequence: directReferrer.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: directReferrer.accountSequence,
levelDepth: null,
amount: bonusAmount,
reason: `上线${directReferrer.accountSequence}未解锁第${tier}档奖励(已解锁${unlockedBonusTiers}档)`,
});
}
}
} else {
// 没有上线三个2.5%全部归总部
for (let tier = 1; tier <= 3; tier++) {
// 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: null,
wouldBeAccountSequence: adoption.accountSequence,
levelDepth: null,
amount: bonusAmount,
reason: `认种人无上线`,
reason: `认种人${adoption.accountSequence}未解锁第${tier}档奖励(需直推${tier === 2 ? '2人' : '4人'}认种)`,
});
}
}

View File

@ -247,18 +247,22 @@ export class SyncedDataRepository implements ISyncedDataRepository {
let currentSequence = accountSequence;
for (let i = 0; i < maxLevel; i++) {
// 获取当前账户的 referral 记录(包含该账户作为上线的信息)
const referral = await this.findSyncedReferralByAccountSequence(currentSequence);
if (!referral || !referral.referrerAccountSequence) {
if (!referral) {
break;
}
const referrer = await this.findSyncedReferralByAccountSequence(referral.referrerAccountSequence);
if (!referrer) {
// 把当前账户加入上线链条
ancestors.push(referral);
// 如果没有更上级的推荐人,终止
if (!referral.referrerAccountSequence) {
break;
}
ancestors.push(referrer);
currentSequence = referrer.accountSequence;
// 继续向上追溯
currentSequence = referral.referrerAccountSequence;
}
return ancestors;