fix(authorization): use subordinate team count for assessment and auto-activate benefit
1. Assessment calculation now uses subordinate team count (excluding self planting)
- Added selfPlantingCount to referral-service API response
- Added subordinateTeamPlantingCount getter to TeamStatistics interface
- Updated all assessment checks to use subordinateTeamPlantingCount
2. Auto-activate benefit when assessment target is reached
- Added tryActivateBenefit helper method
- Community, province team, and city team reward distribution methods
now automatically activate benefit when target is reached
3. Fixed event consumer to support AUTHORIZED status (admin grants)
- Previously only checked PENDING status for activation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f94e7283d5
commit
9819130f98
|
|
@ -85,11 +85,11 @@ export class AuthorizationApplicationService {
|
|||
communityName: command.communityName,
|
||||
})
|
||||
|
||||
// 3. 检查初始考核(10棵)
|
||||
// 3. 检查初始考核(10棵)- 使用下级团队认种数(不含自己)
|
||||
const teamStats = await this.statsRepository.findByAccountSequence(userId.accountSequence)
|
||||
const totalTreeCount = teamStats?.totalTeamPlantingCount || 0
|
||||
const subordinateTreeCount = teamStats?.subordinateTeamPlantingCount || 0
|
||||
|
||||
if (totalTreeCount >= authorization.getInitialTarget()) {
|
||||
if (subordinateTreeCount >= authorization.getInitialTarget()) {
|
||||
// 达标,激活权益
|
||||
authorization.activateBenefit()
|
||||
}
|
||||
|
|
@ -104,8 +104,8 @@ export class AuthorizationApplicationService {
|
|||
benefitActive: authorization.benefitActive,
|
||||
message: authorization.benefitActive
|
||||
? '社区权益已激活'
|
||||
: `需要团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
|
||||
currentTreeCount: totalTreeCount,
|
||||
: `需要下级团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
|
||||
currentTreeCount: subordinateTreeCount,
|
||||
requiredTreeCount: authorization.getInitialTarget(),
|
||||
}
|
||||
}
|
||||
|
|
@ -139,11 +139,11 @@ export class AuthorizationApplicationService {
|
|||
provinceName: command.provinceName,
|
||||
})
|
||||
|
||||
// 3. 检查初始考核(500棵)
|
||||
// 3. 检查初始考核(500棵)- 使用下级团队认种数(不含自己)
|
||||
const teamStats = await this.statsRepository.findByAccountSequence(userId.accountSequence)
|
||||
const totalTreeCount = teamStats?.totalTeamPlantingCount || 0
|
||||
const subordinateTreeCount = teamStats?.subordinateTeamPlantingCount || 0
|
||||
|
||||
if (totalTreeCount >= authorization.getInitialTarget()) {
|
||||
if (subordinateTreeCount >= authorization.getInitialTarget()) {
|
||||
// 达标,激活权益并创建首月考核
|
||||
authorization.activateBenefit()
|
||||
await this.createInitialAssessment(authorization, teamStats!)
|
||||
|
|
@ -160,8 +160,8 @@ export class AuthorizationApplicationService {
|
|||
displayTitle: authorization.displayTitle,
|
||||
message: authorization.benefitActive
|
||||
? '授权省公司权益已激活,开始阶梯考核'
|
||||
: `需要团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
|
||||
currentTreeCount: totalTreeCount,
|
||||
: `需要下级团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
|
||||
currentTreeCount: subordinateTreeCount,
|
||||
requiredTreeCount: authorization.getInitialTarget(),
|
||||
}
|
||||
}
|
||||
|
|
@ -195,11 +195,11 @@ export class AuthorizationApplicationService {
|
|||
cityName: command.cityName,
|
||||
})
|
||||
|
||||
// 3. 检查初始考核(100棵)
|
||||
// 3. 检查初始考核(100棵)- 使用下级团队认种数(不含自己)
|
||||
const teamStats = await this.statsRepository.findByAccountSequence(userId.accountSequence)
|
||||
const totalTreeCount = teamStats?.totalTeamPlantingCount || 0
|
||||
const subordinateTreeCount = teamStats?.subordinateTeamPlantingCount || 0
|
||||
|
||||
if (totalTreeCount >= authorization.getInitialTarget()) {
|
||||
if (subordinateTreeCount >= authorization.getInitialTarget()) {
|
||||
authorization.activateBenefit()
|
||||
await this.createInitialAssessment(authorization, teamStats!)
|
||||
}
|
||||
|
|
@ -215,8 +215,8 @@ export class AuthorizationApplicationService {
|
|||
displayTitle: authorization.displayTitle,
|
||||
message: authorization.benefitActive
|
||||
? '授权市公司权益已激活,开始阶梯考核'
|
||||
: `需要团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
|
||||
currentTreeCount: totalTreeCount,
|
||||
: `需要下级团队累计认种达到${authorization.getInitialTarget()}棵才能激活`,
|
||||
currentTreeCount: subordinateTreeCount,
|
||||
requiredTreeCount: authorization.getInitialTarget(),
|
||||
}
|
||||
}
|
||||
|
|
@ -748,6 +748,26 @@ export class AuthorizationApplicationService {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试激活授权权益
|
||||
* 仅当权益未激活时执行激活操作
|
||||
*/
|
||||
private async tryActivateBenefit(authorization: AuthorizationRole): Promise<void> {
|
||||
if (authorization.benefitActive) {
|
||||
return // 已激活,无需操作
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`[tryActivateBenefit] Activating benefit for authorization ${authorization.authorizationId.value}, ` +
|
||||
`role=${authorization.roleType}, accountSequence=${authorization.userId.accountSequence}`,
|
||||
)
|
||||
|
||||
authorization.activateBenefit()
|
||||
await this.authorizationRepository.save(authorization)
|
||||
await this.eventPublisher.publishAll(authorization.domainEvents)
|
||||
authorization.clearDomainEvents()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取社区权益分配方案
|
||||
* 根据考核规则计算每棵树的社区权益应该分配给谁
|
||||
|
|
@ -855,23 +875,23 @@ export class AuthorizationApplicationService {
|
|||
}
|
||||
|
||||
// 5. 权益未激活,需要计算考核分配
|
||||
// 获取该社区的团队统计数据(当前已完成的认种数量)
|
||||
// 获取该社区的团队统计数据 - 使用下级团队认种数(不含自己)
|
||||
const communityStats = await this.statsRepository.findByAccountSequence(
|
||||
nearestCommunity.userId.accountSequence,
|
||||
)
|
||||
const rawTeamCount = communityStats?.totalTeamPlantingCount ?? 0
|
||||
const rawSubordinateCount = communityStats?.subordinateTeamPlantingCount ?? 0
|
||||
|
||||
// 重要:由于 referral-service 和 reward-service 都消费同一个 Kafka 事件,
|
||||
// 存在竞态条件,此时查询到的 totalTeamPlantingCount 可能已经包含了本次认种。
|
||||
// 因此需要减去本次认种数量来还原"认种前"的团队数。
|
||||
// 注意:如果 referral-service 还没处理完,rawTeamCount 可能还是旧值,
|
||||
// 存在竞态条件,此时查询到的 subordinateTeamPlantingCount 可能已经包含了本次认种。
|
||||
// 因此需要减去本次认种数量来还原"认种前"的下级团队数。
|
||||
// 注意:如果 referral-service 还没处理完,rawSubordinateCount 可能还是旧值,
|
||||
// 此时 currentTeamCount 可能为负数,需要取 max(0, ...)
|
||||
const currentTeamCount = Math.max(0, rawTeamCount - treeCount)
|
||||
const currentTeamCount = Math.max(0, rawSubordinateCount - treeCount)
|
||||
const initialTarget = nearestCommunity.getInitialTarget() // 社区初始考核目标:10棵
|
||||
|
||||
this.logger.debug(
|
||||
`[getCommunityRewardDistribution] Community ${nearestCommunity.userId.accountSequence} ` +
|
||||
`benefitActive=false, rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, ` +
|
||||
`benefitActive=false, rawSubordinateCount=${rawSubordinateCount}, treeCount=${treeCount}, ` +
|
||||
`currentTeamCount(before)=${currentTeamCount}, initialTarget=${initialTarget}`,
|
||||
)
|
||||
|
||||
|
|
@ -907,6 +927,9 @@ export class AuthorizationApplicationService {
|
|||
treeCount,
|
||||
reason: '已达初始考核目标',
|
||||
})
|
||||
|
||||
// 自动激活权益
|
||||
await this.tryActivateBenefit(nearestCommunity)
|
||||
} else {
|
||||
// 未达标,需要拆分
|
||||
const remaining = initialTarget - currentTeamCount // 还差多少棵达标
|
||||
|
|
@ -932,6 +955,9 @@ export class AuthorizationApplicationService {
|
|||
treeCount: treeCount - remaining,
|
||||
reason: `考核达标后权益生效`,
|
||||
})
|
||||
|
||||
// 自动激活权益(本次认种使其达标)
|
||||
await this.tryActivateBenefit(nearestCommunity)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1029,15 +1055,15 @@ export class AuthorizationApplicationService {
|
|||
}
|
||||
}
|
||||
|
||||
// 5. 权益未激活,计算考核分配
|
||||
// 5. 权益未激活,计算考核分配 - 使用下级团队认种数(不含自己)
|
||||
const stats = await this.statsRepository.findByAccountSequence(nearestAuthProvince.userId.accountSequence)
|
||||
const rawTeamCount = stats?.totalTeamPlantingCount ?? 0
|
||||
// 修复竞态条件:减去本次认种数量来还原"认种前"的团队数
|
||||
const currentTeamCount = Math.max(0, rawTeamCount - treeCount)
|
||||
const rawSubordinateCount = stats?.subordinateTeamPlantingCount ?? 0
|
||||
// 修复竞态条件:减去本次认种数量来还原"认种前"的下级团队数
|
||||
const currentTeamCount = Math.max(0, rawSubordinateCount - treeCount)
|
||||
const initialTarget = nearestAuthProvince.getInitialTarget() // 500棵
|
||||
|
||||
this.logger.debug(
|
||||
`[getProvinceTeamRewardDistribution] rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`,
|
||||
`[getProvinceTeamRewardDistribution] rawSubordinateCount=${rawSubordinateCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`,
|
||||
)
|
||||
|
||||
// 6. 查找上级(用于接收考核前的权益)
|
||||
|
|
@ -1065,6 +1091,8 @@ export class AuthorizationApplicationService {
|
|||
treeCount,
|
||||
reason: '已达初始考核目标',
|
||||
})
|
||||
// 自动激活权益
|
||||
await this.tryActivateBenefit(nearestAuthProvince)
|
||||
} else {
|
||||
const remaining = initialTarget - currentTeamCount
|
||||
|
||||
|
|
@ -1085,6 +1113,8 @@ export class AuthorizationApplicationService {
|
|||
treeCount: treeCount - remaining,
|
||||
reason: '考核达标后权益生效',
|
||||
})
|
||||
// 自动激活权益(本次认种使其达标)
|
||||
await this.tryActivateBenefit(nearestAuthProvince)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1239,15 +1269,15 @@ export class AuthorizationApplicationService {
|
|||
}
|
||||
}
|
||||
|
||||
// 5. 权益未激活,计算考核分配
|
||||
// 5. 权益未激活,计算考核分配 - 使用下级团队认种数(不含自己)
|
||||
const stats = await this.statsRepository.findByAccountSequence(nearestAuthCity.userId.accountSequence)
|
||||
const rawTeamCount = stats?.totalTeamPlantingCount ?? 0
|
||||
// 修复竞态条件:减去本次认种数量来还原"认种前"的团队数
|
||||
const currentTeamCount = Math.max(0, rawTeamCount - treeCount)
|
||||
const rawSubordinateCount = stats?.subordinateTeamPlantingCount ?? 0
|
||||
// 修复竞态条件:减去本次认种数量来还原"认种前"的下级团队数
|
||||
const currentTeamCount = Math.max(0, rawSubordinateCount - treeCount)
|
||||
const initialTarget = nearestAuthCity.getInitialTarget() // 100棵
|
||||
|
||||
this.logger.debug(
|
||||
`[getCityTeamRewardDistribution] rawTeamCount=${rawTeamCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`,
|
||||
`[getCityTeamRewardDistribution] rawSubordinateCount=${rawSubordinateCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}`,
|
||||
)
|
||||
|
||||
// 6. 查找上级
|
||||
|
|
@ -1275,6 +1305,8 @@ export class AuthorizationApplicationService {
|
|||
treeCount,
|
||||
reason: '已达初始考核目标',
|
||||
})
|
||||
// 自动激活权益
|
||||
await this.tryActivateBenefit(nearestAuthCity)
|
||||
} else {
|
||||
const remaining = initialTarget - currentTeamCount
|
||||
|
||||
|
|
@ -1295,6 +1327,8 @@ export class AuthorizationApplicationService {
|
|||
treeCount: treeCount - remaining,
|
||||
reason: '考核达标后权益生效',
|
||||
})
|
||||
// 自动激活权益(本次认种使其达标)
|
||||
await this.tryActivateBenefit(nearestAuthCity)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ export interface TeamStatistics {
|
|||
userId: string
|
||||
accountSequence: bigint
|
||||
totalTeamPlantingCount: number
|
||||
selfPlantingCount: number
|
||||
/** 下级团队认种数(不包括自己)= totalTeamPlantingCount - selfPlantingCount */
|
||||
get subordinateTeamPlantingCount(): number
|
||||
getProvinceTeamCount(provinceCode: string): number
|
||||
getCityTeamCount(cityCode: string): number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ interface ReferralTeamStatsResponse {
|
|||
userId: string;
|
||||
accountSequence: string;
|
||||
totalTeamPlantingCount: number;
|
||||
selfPlantingCount: number;
|
||||
provinceCityDistribution: Record<string, Record<string, number>> | null;
|
||||
}
|
||||
|
||||
|
|
@ -42,9 +43,15 @@ class TeamStatisticsAdapter implements TeamStatistics {
|
|||
public readonly userId: string,
|
||||
public readonly accountSequence: bigint,
|
||||
public readonly totalTeamPlantingCount: number,
|
||||
public readonly selfPlantingCount: number,
|
||||
private readonly provinceCityDistribution: Record<string, Record<string, number>> | null,
|
||||
) {}
|
||||
|
||||
/** 下级团队认种数(不包括自己) */
|
||||
get subordinateTeamPlantingCount(): number {
|
||||
return Math.max(0, this.totalTeamPlantingCount - this.selfPlantingCount);
|
||||
}
|
||||
|
||||
getProvinceTeamCount(provinceCode: string): number {
|
||||
if (!this.provinceCityDistribution || !this.provinceCityDistribution[provinceCode]) {
|
||||
return 0;
|
||||
|
|
@ -125,6 +132,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
|||
data.userId,
|
||||
BigInt(data.accountSequence || 0),
|
||||
data.totalTeamPlantingCount,
|
||||
data.selfPlantingCount || 0,
|
||||
data.provinceCityDistribution,
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -156,12 +164,13 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
|||
}
|
||||
|
||||
const data = response.data;
|
||||
this.logger.debug(`[HTTP] Got stats for accountSequence ${accountSequence}: totalTeamPlantingCount=${data.totalTeamPlantingCount}`);
|
||||
this.logger.debug(`[HTTP] Got stats for accountSequence ${accountSequence}: totalTeamPlantingCount=${data.totalTeamPlantingCount}, selfPlantingCount=${data.selfPlantingCount}`);
|
||||
|
||||
return new TeamStatisticsAdapter(
|
||||
data.userId,
|
||||
BigInt(data.accountSequence || accountSequence.toString()),
|
||||
data.totalTeamPlantingCount,
|
||||
data.selfPlantingCount || 0,
|
||||
data.provinceCityDistribution,
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
@ -175,7 +184,7 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
|||
* 创建空的统计数据
|
||||
*/
|
||||
private createEmptyStats(userId: string, accountSequence: bigint): TeamStatistics {
|
||||
return new TeamStatisticsAdapter(userId, accountSequence, 0, null);
|
||||
return new TeamStatisticsAdapter(userId, accountSequence, 0, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -136,8 +136,9 @@ export class EventConsumerController {
|
|||
return
|
||||
}
|
||||
|
||||
const totalTreeCount = teamStats.totalTeamPlantingCount
|
||||
this.logger.debug(`[PLANTING] User ${userId} total team planting count: ${totalTreeCount}`)
|
||||
// 使用下级团队认种数(不含自己)进行考核判断
|
||||
const subordinateTreeCount = teamStats.subordinateTeamPlantingCount
|
||||
this.logger.debug(`[PLANTING] User ${userId} subordinate team planting count: ${subordinateTreeCount}`)
|
||||
|
||||
// 2. 获取用户所有授权
|
||||
const authorizations = await this.authorizationRepository.findByUserId(
|
||||
|
|
@ -151,14 +152,15 @@ export class EventConsumerController {
|
|||
|
||||
// 3. 处理每个授权
|
||||
for (const auth of authorizations) {
|
||||
this.logger.debug(`[PLANTING] Processing authorization: ${auth.authorizationId.value}, role=${auth.roleType}, benefitActive=${auth.benefitActive}`)
|
||||
this.logger.debug(`[PLANTING] Processing authorization: ${auth.authorizationId.value}, role=${auth.roleType}, benefitActive=${auth.benefitActive}, status=${auth.status}`)
|
||||
|
||||
// 3.1 检查初始考核(权益未激活的情况)
|
||||
if (!auth.benefitActive && auth.status === AuthorizationStatus.PENDING) {
|
||||
// 支持 PENDING(用户申请)和 AUTHORIZED(管理员授权)两种状态
|
||||
if (!auth.benefitActive && (auth.status === AuthorizationStatus.PENDING || auth.status === AuthorizationStatus.AUTHORIZED)) {
|
||||
const initialTarget = auth.getInitialTarget()
|
||||
this.logger.debug(`[PLANTING] Checking initial target: ${totalTreeCount}/${initialTarget}`)
|
||||
this.logger.debug(`[PLANTING] Checking initial target: ${subordinateTreeCount}/${initialTarget}`)
|
||||
|
||||
if (totalTreeCount >= initialTarget) {
|
||||
if (subordinateTreeCount >= initialTarget) {
|
||||
this.logger.log(`[PLANTING] User ${userId} reached initial target for ${auth.roleType}, activating benefit`)
|
||||
auth.activateBenefit()
|
||||
await this.authorizationRepository.save(auth)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export class InternalTeamStatisticsController {
|
|||
userId: { type: 'string' },
|
||||
accountSequence: { type: 'string' },
|
||||
totalTeamPlantingCount: { type: 'number' },
|
||||
selfPlantingCount: { type: 'number' },
|
||||
provinceCityDistribution: { type: 'object' },
|
||||
},
|
||||
},
|
||||
|
|
@ -51,7 +52,8 @@ export class InternalTeamStatisticsController {
|
|||
return {
|
||||
userId: stats.userId.toString(),
|
||||
accountSequence: '0', // userId 查询时无法获取 accountSequence
|
||||
totalTeamPlantingCount: stats.teamPlantingCount, // 使用 teamPlantingCount 作为团队总量
|
||||
totalTeamPlantingCount: stats.teamPlantingCount, // 团队总认种(含自己)
|
||||
selfPlantingCount: stats.personalPlantingCount, // 自己的认种数
|
||||
provinceCityDistribution: distribution.toJson(),
|
||||
};
|
||||
} catch (error) {
|
||||
|
|
@ -72,6 +74,7 @@ export class InternalTeamStatisticsController {
|
|||
userId: { type: 'string' },
|
||||
accountSequence: { type: 'string' },
|
||||
totalTeamPlantingCount: { type: 'number' },
|
||||
selfPlantingCount: { type: 'number' },
|
||||
provinceCityDistribution: { type: 'object' },
|
||||
},
|
||||
},
|
||||
|
|
@ -94,7 +97,8 @@ export class InternalTeamStatisticsController {
|
|||
return {
|
||||
userId: stats.userId.toString(),
|
||||
accountSequence: accountSequence,
|
||||
totalTeamPlantingCount: stats.teamPlantingCount,
|
||||
totalTeamPlantingCount: stats.teamPlantingCount, // 团队总认种(含自己)
|
||||
selfPlantingCount: stats.personalPlantingCount, // 自己的认种数
|
||||
provinceCityDistribution: distribution.toJson(),
|
||||
};
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue