feat(authorization): 实现市/省团队自动升级为市/省区域机制

- 添加 findAllActiveAuthProvinceCompanies 和 findAllActiveAuthCityCompanies 仓储方法
- 在认种事件处理中添加 checkAllTeamAutoUpgrade 检查所有已激活团队
- 市团队达到1万棵自动升级为市区域(CITY_COMPANY)
- 省团队达到5万棵自动升级为省区域(PROVINCE_COMPANY)
- 保持原有手动授权功能不变

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-23 03:56:38 -08:00
parent 0a64024773
commit bf63852a0f
4 changed files with 243 additions and 1 deletions

View File

@ -116,4 +116,18 @@ export interface IAuthorizationRoleRepository {
*
*/
findByIdIncludeDeleted(authorizationId: AuthorizationId): Promise<AuthorizationRole | null>
// ============ 自动升级检查方法 ============
/**
* (AUTH_PROVINCE_COMPANY)
*
*/
findAllActiveAuthProvinceCompanies(): Promise<AuthorizationRole[]>
/**
* (AUTH_CITY_COMPANY)
*
*/
findAllActiveAuthCityCompanies(): Promise<AuthorizationRole[]>
}

View File

@ -183,9 +183,13 @@ export class EventConsumerController {
}
}
// 4. 检查自动升级条件(省区域/市区域)
// 4. 检查自动升级条件(省区域/市区域)- 仅针对当前用户的祖先链
await this.checkAutoUpgrade(teamStats)
// 5. 检查所有已激活市/省团队的自动升级条件
// 每次认种都可能导致某个市/省团队达到升级阈值
await this.checkAllTeamAutoUpgrade()
this.logger.log(`[PLANTING] Completed processing tree planted event for user ${userId}`)
} catch (error) {
this.logger.error(`[PLANTING] Error processing tree planted for user ${userId}:`, error)
@ -486,4 +490,198 @@ export class EventConsumerController {
this.logger.log(`[AUTO-UPGRADE] Successfully auto upgraded user ${accountSequence} to city company: ${cityName}`)
}
/**
* /
*
* - (AUTH_PROVINCE_COMPANY)5(PROVINCE_COMPANY)
* - (AUTH_CITY_COMPANY)1(CITY_COMPANY)
*/
private async checkAllTeamAutoUpgrade(): Promise<void> {
this.logger.debug('[TEAM-AUTO-UPGRADE] Starting check for all active team authorizations')
// 并行检查省团队和市团队
await Promise.all([
this.checkAllAuthProvinceUpgrade(),
this.checkAllAuthCityUpgrade(),
])
this.logger.debug('[TEAM-AUTO-UPGRADE] Completed check for all active team authorizations')
}
/**
*
*/
private async checkAllAuthProvinceUpgrade(): Promise<void> {
// 1. 获取所有权益已激活的省团队授权
const activeAuthProvinces = await this.authorizationRepository.findAllActiveAuthProvinceCompanies()
if (activeAuthProvinces.length === 0) {
this.logger.debug('[TEAM-AUTO-UPGRADE] No active auth province companies found')
return
}
this.logger.debug(`[TEAM-AUTO-UPGRADE] Found ${activeAuthProvinces.length} active auth province companies`)
// 2. 逐个检查是否达到升级阈值
for (const authProvince of activeAuthProvinces) {
await this.checkAuthProvinceUpgrade(authProvince)
}
}
/**
*
*/
private async checkAuthProvinceUpgrade(authProvince: AuthorizationRole): Promise<void> {
const accountSequence = authProvince.userId.accountSequence
const provinceCode = authProvince.regionCode.value
const provinceName = authProvince.regionName
// 1. 获取该用户的团队统计
const teamStats = await this.statsRepository.findByAccountSequence(accountSequence)
if (!teamStats) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] No team stats found for auth province ${accountSequence}`)
return
}
const totalTeamCount = teamStats.totalTeamPlantingCount
// 2. 检查是否达到省区域升级阈值5万棵
if (totalTeamCount < EventConsumerController.PROVINCE_UPGRADE_THRESHOLD) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] Auth province ${accountSequence} has ${totalTeamCount} trees, not reaching ${EventConsumerController.PROVINCE_UPGRADE_THRESHOLD} threshold`)
return
}
this.logger.log(`[TEAM-AUTO-UPGRADE] Auth province ${accountSequence} reached ${totalTeamCount} trees, checking upgrade eligibility`)
// 3. 检查该用户是否已有省区域授权
const existingProvinceCompany = await this.authorizationRepository.findByAccountSequenceAndRoleType(
accountSequence,
RoleType.PROVINCE_COMPANY,
)
if (existingProvinceCompany && existingProvinceCompany.status !== AuthorizationStatus.REVOKED) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] User ${accountSequence} already has province company authorization`)
return
}
// 4. 检查该用户是否已有市区域授权(互斥)
const existingCityCompany = await this.authorizationRepository.findByAccountSequenceAndRoleType(
accountSequence,
RoleType.CITY_COMPANY,
)
if (existingCityCompany && existingCityCompany.status !== AuthorizationStatus.REVOKED) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] User ${accountSequence} already has city company authorization, cannot upgrade to province`)
return
}
// 5. 检查该省是否已有省区域授权
const existingProvinceRegion = await this.authorizationRepository.findProvinceCompanyByRegion(provinceCode)
if (existingProvinceRegion) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] Province ${provinceName} already has province company authorization`)
return
}
// 6. 执行自动升级:省团队 → 省区域
this.logger.log(`[TEAM-AUTO-UPGRADE] Auto upgrading auth province ${accountSequence} to province company: ${provinceName}`)
const userId = UserId.create(teamStats.userId, accountSequence)
const authorization = AuthorizationRole.createAutoUpgradedProvinceCompany({
userId,
provinceCode,
provinceName,
})
await this.authorizationRepository.save(authorization)
await this.eventPublisher.publishAll(authorization.domainEvents)
authorization.clearDomainEvents()
this.logger.log(`[TEAM-AUTO-UPGRADE] Successfully auto upgraded auth province ${accountSequence} to province company: ${provinceName}`)
}
/**
*
*/
private async checkAllAuthCityUpgrade(): Promise<void> {
// 1. 获取所有权益已激活的市团队授权
const activeAuthCities = await this.authorizationRepository.findAllActiveAuthCityCompanies()
if (activeAuthCities.length === 0) {
this.logger.debug('[TEAM-AUTO-UPGRADE] No active auth city companies found')
return
}
this.logger.debug(`[TEAM-AUTO-UPGRADE] Found ${activeAuthCities.length} active auth city companies`)
// 2. 逐个检查是否达到升级阈值
for (const authCity of activeAuthCities) {
await this.checkAuthCityUpgrade(authCity)
}
}
/**
*
*/
private async checkAuthCityUpgrade(authCity: AuthorizationRole): Promise<void> {
const accountSequence = authCity.userId.accountSequence
const cityCode = authCity.regionCode.value
const cityName = authCity.regionName
// 1. 获取该用户的团队统计
const teamStats = await this.statsRepository.findByAccountSequence(accountSequence)
if (!teamStats) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] No team stats found for auth city ${accountSequence}`)
return
}
const totalTeamCount = teamStats.totalTeamPlantingCount
// 2. 检查是否达到市区域升级阈值1万棵
if (totalTeamCount < EventConsumerController.CITY_UPGRADE_THRESHOLD) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] Auth city ${accountSequence} has ${totalTeamCount} trees, not reaching ${EventConsumerController.CITY_UPGRADE_THRESHOLD} threshold`)
return
}
this.logger.log(`[TEAM-AUTO-UPGRADE] Auth city ${accountSequence} reached ${totalTeamCount} trees, checking upgrade eligibility`)
// 3. 检查该用户是否已有市区域授权
const existingCityCompany = await this.authorizationRepository.findByAccountSequenceAndRoleType(
accountSequence,
RoleType.CITY_COMPANY,
)
if (existingCityCompany && existingCityCompany.status !== AuthorizationStatus.REVOKED) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] User ${accountSequence} already has city company authorization`)
return
}
// 4. 检查该用户是否已有省区域授权(互斥)
const existingProvinceCompany = await this.authorizationRepository.findByAccountSequenceAndRoleType(
accountSequence,
RoleType.PROVINCE_COMPANY,
)
if (existingProvinceCompany && existingProvinceCompany.status !== AuthorizationStatus.REVOKED) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] User ${accountSequence} already has province company authorization, cannot upgrade to city`)
return
}
// 5. 检查该市是否已有市区域授权
const existingCityRegion = await this.authorizationRepository.findCityCompanyByRegion(cityCode)
if (existingCityRegion) {
this.logger.debug(`[TEAM-AUTO-UPGRADE] City ${cityName} already has city company authorization`)
return
}
// 6. 执行自动升级:市团队 → 市区域
this.logger.log(`[TEAM-AUTO-UPGRADE] Auto upgrading auth city ${accountSequence} to city company: ${cityName}`)
const userId = UserId.create(teamStats.userId, accountSequence)
const authorization = AuthorizationRole.createAutoUpgradedCityCompany({
userId,
cityCode,
cityName,
})
await this.authorizationRepository.save(authorization)
await this.eventPublisher.publishAll(authorization.domainEvents)
authorization.clearDomainEvents()
this.logger.log(`[TEAM-AUTO-UPGRADE] Successfully auto upgraded auth city ${accountSequence} to city company: ${cityName}`)
}
}

View File

@ -447,6 +447,34 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
return record ? this.toDomain(record) : null
}
// ============ 自动升级检查方法 ============
async findAllActiveAuthProvinceCompanies(): Promise<AuthorizationRole[]> {
const records = await this.prisma.authorizationRole.findMany({
where: {
roleType: RoleType.AUTH_PROVINCE_COMPANY,
status: AuthorizationStatus.AUTHORIZED,
benefitActive: true,
...this.notDeleted,
},
orderBy: { createdAt: 'asc' },
})
return records.map((record) => this.toDomain(record))
}
async findAllActiveAuthCityCompanies(): Promise<AuthorizationRole[]> {
const records = await this.prisma.authorizationRole.findMany({
where: {
roleType: RoleType.AUTH_CITY_COMPANY,
status: AuthorizationStatus.AUTHORIZED,
benefitActive: true,
...this.notDeleted,
},
orderBy: { createdAt: 'asc' },
})
return records.map((record) => this.toDomain(record))
}
private toDomain(record: any): AuthorizationRole {
const props: AuthorizationRoleProps = {
authorizationId: AuthorizationId.create(record.id),

View File

@ -44,6 +44,8 @@ describe('Domain Services Integration Tests', () => {
findAllByUserIdIncludeDeleted: jest.fn(),
findAllByAccountSequenceIncludeDeleted: jest.fn(),
findByIdIncludeDeleted: jest.fn(),
findAllActiveAuthProvinceCompanies: jest.fn(),
findAllActiveAuthCityCompanies: jest.fn(),
}
const mockMonthlyAssessmentRepository: jest.Mocked<IMonthlyAssessmentRepository> = {