feat(authorization-service): 实现社区权益月度考核及级联激活/停用功能

- 新增 benefitValidUntil、lastAssessmentMonth、monthlyTreesAdded 字段追踪月度考核
- 实现级联激活:当社区权益激活时,自动激活所有上级社区的权益
- 实现级联停用:当月度考核不达标时,级联停用该社区及所有上级社区
- 新增定时任务:每天凌晨3点检查过期社区权益,每月1号凌晨重置月度树数
- 权益有效期为当前月+下月末

🤖 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-13 07:23:46 -08:00
parent 846915badc
commit 2af44e5854
7 changed files with 491 additions and 30 deletions

View File

@ -0,0 +1,33 @@
-- AlterTable
ALTER TABLE "authorization_roles" ADD COLUMN "benefit_valid_until" TIMESTAMP(3),
ADD COLUMN "last_assessment_month" TEXT,
ADD COLUMN "monthly_trees_added" INTEGER NOT NULL DEFAULT 0,
ALTER COLUMN "user_id" SET DATA TYPE TEXT,
ALTER COLUMN "authorized_by" SET DATA TYPE TEXT,
ALTER COLUMN "revoked_by" SET DATA TYPE TEXT;
-- AlterTable
ALTER TABLE "monthly_assessments" ALTER COLUMN "user_id" SET DATA TYPE TEXT,
ALTER COLUMN "bypassed_by" SET DATA TYPE TEXT;
-- AlterTable
ALTER TABLE "monthly_bypasses" ALTER COLUMN "user_id" SET DATA TYPE TEXT,
ALTER COLUMN "granted_by" SET DATA TYPE TEXT,
ALTER COLUMN "approver1_id" SET DATA TYPE TEXT,
ALTER COLUMN "approver2_id" SET DATA TYPE TEXT,
ALTER COLUMN "approver3_id" SET DATA TYPE TEXT;
-- AlterTable
ALTER TABLE "stickman_rankings" ALTER COLUMN "user_id" SET DATA TYPE TEXT;
-- RenameIndex
ALTER INDEX "idx_authorization_roles_account_sequence" RENAME TO "authorization_roles_account_sequence_idx";
-- RenameIndex
ALTER INDEX "idx_monthly_assessments_account_sequence_month" RENAME TO "monthly_assessments_account_sequence_assessment_month_idx";
-- RenameIndex
ALTER INDEX "idx_monthly_bypasses_account_sequence_month" RENAME TO "monthly_bypasses_account_sequence_bypass_month_idx";
-- RenameIndex
ALTER INDEX "idx_stickman_rankings_account_sequence_month" RENAME TO "stickman_rankings_account_sequence_current_month_idx";

View File

@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
provider = "postgresql"

View File

@ -40,6 +40,11 @@ model AuthorizationRole {
benefitActive Boolean @default(false) @map("benefit_active")
benefitActivatedAt DateTime? @map("benefit_activated_at")
benefitDeactivatedAt DateTime? @map("benefit_deactivated_at")
benefitValidUntil DateTime? @map("benefit_valid_until") // 权益有效期截止日期(当前月+下月末)
// 月度考核追踪
lastAssessmentMonth String? @map("last_assessment_month") // 上次考核月份 YYYY-MM
monthlyTreesAdded Int @default(0) @map("monthly_trees_added") // 当月新增树数
// 当前考核月份索引
currentMonthIndex Int @default(0) @map("current_month_index")

View File

@ -11,7 +11,7 @@ import {
} from '@/domain/repositories'
import { AssessmentCalculatorService, ITeamStatisticsRepository } from '@/domain/services'
import { EventPublisherService } from '@/infrastructure/kafka'
import { TEAM_STATISTICS_REPOSITORY } from '@/application/services'
import { TEAM_STATISTICS_REPOSITORY, AuthorizationApplicationService } from '@/application/services'
@Injectable()
export class MonthlyAssessmentScheduler {
@ -26,6 +26,7 @@ export class MonthlyAssessmentScheduler {
@Inject(TEAM_STATISTICS_REPOSITORY)
private readonly statsRepository: ITeamStatisticsRepository,
private readonly eventPublisher: EventPublisherService,
private readonly authorizationAppService: AuthorizationApplicationService,
) {}
/**
@ -182,4 +183,57 @@ export class MonthlyAssessmentScheduler {
return map
}
/**
* 3
*
*
* - benefitValidUntil < benefitActive=true
* - >= 10
* -
*/
@Cron('0 3 * * *')
async processExpiredCommunityBenefits(): Promise<void> {
this.logger.log('[processExpiredCommunityBenefits] 开始检查社区权益过期情况...')
try {
const result = await this.authorizationAppService.processExpiredCommunityBenefits(100)
this.logger.log(
`[processExpiredCommunityBenefits] 处理完成: ` +
`已处理=${result.processedCount}, 已续期=${result.renewedCount}, 已停用=${result.deactivatedCount}`,
)
} catch (error) {
this.logger.error('[processExpiredCommunityBenefits] 社区权益过期检查失败', error)
}
}
/**
* 10
*
* processExpiredCommunityBenefits
*
*/
@Cron('0 0 1 * *')
async resetMonthlyTreeCounts(): Promise<void> {
this.logger.log('[resetMonthlyTreeCounts] 开始重置月度新增树数...')
try {
// 获取所有激活的社区
const activeCommunities = await this.authorizationRepository.findAllActive(RoleType.COMMUNITY)
let resetCount = 0
for (const community of activeCommunities) {
if (community.benefitActive && community.monthlyTreesAdded > 0) {
community.resetMonthlyTrees()
await this.authorizationRepository.save(community)
resetCount++
}
}
this.logger.log(`[resetMonthlyTreeCounts] 重置完成: 已重置 ${resetCount} 个社区的月度计数`)
} catch (error) {
this.logger.error('[resetMonthlyTreeCounts] 月度树数重置失败', error)
}
}
}

View File

@ -859,6 +859,8 @@ export class AuthorizationApplicationService {
/**
*
*
*
*
*/
private async tryActivateBenefit(authorization: AuthorizationRole): Promise<void> {
if (authorization.benefitActive) {
@ -874,6 +876,232 @@ export class AuthorizationApplicationService {
await this.authorizationRepository.save(authorization)
await this.eventPublisher.publishAll(authorization.domainEvents)
authorization.clearDomainEvents()
// 如果是社区权益,需要级联激活上级社区
if (authorization.roleType === RoleType.COMMUNITY) {
await this.cascadeActivateParentCommunities(authorization.userId.accountSequence)
}
}
/**
*
*
*
*
* -
* -
* -
*/
private async cascadeActivateParentCommunities(accountSequence: string): Promise<void> {
this.logger.log(
`[cascadeActivateParentCommunities] Starting cascade activation for communities above ${accountSequence}`,
)
// 1. 获取推荐链(不包括当前用户)
const ancestorAccountSequences = await this.referralServiceClient.getReferralChain(accountSequence)
if (ancestorAccountSequences.length === 0) {
return
}
// 2. 查找推荐链上所有社区授权(包括 benefitActive=false
const ancestorCommunities = await this.authorizationRepository.findCommunityByAccountSequences(
ancestorAccountSequences,
)
// 3. 筛选出已授权但权益未激活的社区
const inactiveCommunities = ancestorCommunities.filter(
(auth) => auth.status === AuthorizationStatus.AUTHORIZED && !auth.benefitActive,
)
if (inactiveCommunities.length === 0) {
this.logger.debug('[cascadeActivateParentCommunities] No inactive parent communities to activate')
return
}
// 4. 激活这些社区的权益
for (const community of inactiveCommunities) {
this.logger.log(
`[cascadeActivateParentCommunities] Cascade activating community benefit: ` +
`authorizationId=${community.authorizationId.value}, accountSequence=${community.userId.accountSequence}`,
)
community.activateBenefit()
await this.authorizationRepository.save(community)
await this.eventPublisher.publishAll(community.domainEvents)
community.clearDomainEvents()
}
this.logger.log(
`[cascadeActivateParentCommunities] Cascade activated ${inactiveCommunities.length} parent communities`,
)
}
/**
*
*
*
*
* -
* - 10
* -
*
* @param accountSequence accountSequence
* @param reason
*/
async cascadeDeactivateCommunityBenefits(
accountSequence: string,
reason: string,
): Promise<{ deactivatedCount: number }> {
this.logger.log(
`[cascadeDeactivateCommunityBenefits] Starting cascade deactivation from ${accountSequence}, reason=${reason}`,
)
// 1. 获取当前社区的授权
const currentCommunity = await this.authorizationRepository.findByAccountSequenceAndRoleType(
accountSequence,
RoleType.COMMUNITY,
)
if (!currentCommunity) {
this.logger.warn(`[cascadeDeactivateCommunityBenefits] Community not found for ${accountSequence}`)
return { deactivatedCount: 0 }
}
// 2. 收集需要停用的社区列表
const communitiesToDeactivate: AuthorizationRole[] = []
// 如果当前社区权益已激活,加入停用列表
if (currentCommunity.benefitActive) {
communitiesToDeactivate.push(currentCommunity)
}
// 3. 获取推荐链上的所有父级社区
const ancestorAccountSequences = await this.referralServiceClient.getReferralChain(accountSequence)
if (ancestorAccountSequences.length > 0) {
const ancestorCommunities = await this.authorizationRepository.findCommunityByAccountSequences(
ancestorAccountSequences,
)
// 筛选出已授权且权益已激活的社区
const activeCommunities = ancestorCommunities.filter(
(auth) => auth.status === AuthorizationStatus.AUTHORIZED && auth.benefitActive,
)
communitiesToDeactivate.push(...activeCommunities)
}
if (communitiesToDeactivate.length === 0) {
this.logger.debug('[cascadeDeactivateCommunityBenefits] No active communities to deactivate')
return { deactivatedCount: 0 }
}
// 4. 停用这些社区的权益
for (const community of communitiesToDeactivate) {
this.logger.log(
`[cascadeDeactivateCommunityBenefits] Deactivating community benefit: ` +
`authorizationId=${community.authorizationId.value}, accountSequence=${community.userId.accountSequence}`,
)
community.deactivateBenefit(reason)
await this.authorizationRepository.save(community)
await this.eventPublisher.publishAll(community.domainEvents)
community.clearDomainEvents()
}
this.logger.log(
`[cascadeDeactivateCommunityBenefits] Cascade deactivated ${communitiesToDeactivate.length} communities`,
)
return { deactivatedCount: communitiesToDeactivate.length }
}
/**
*
*
*
*
* - benefitValidUntil < benefitActive=true
* - 10
* -
*
* @param limit
*/
async processExpiredCommunityBenefits(limit = 100): Promise<{
processedCount: number
renewedCount: number
deactivatedCount: number
}> {
const now = new Date()
this.logger.log(`[processExpiredCommunityBenefits] Starting at ${now.toISOString()}, limit=${limit}`)
// 查找过期但仍激活的社区
// 需要在 repository 中添加此查询方法
const expiredCommunities = await this.findExpiredActiveCommunities(now, limit)
if (expiredCommunities.length === 0) {
this.logger.debug('[processExpiredCommunityBenefits] No expired communities found')
return { processedCount: 0, renewedCount: 0, deactivatedCount: 0 }
}
let renewedCount = 0
let deactivatedCount = 0
for (const community of expiredCommunities) {
const accountSequence = community.userId.accountSequence
// 获取团队统计数据,检查月度新增树数
const teamStats = await this.statsRepository.findByAccountSequence(accountSequence)
const monthlyTreesAdded = community.monthlyTreesAdded
this.logger.debug(
`[processExpiredCommunityBenefits] Checking community ${accountSequence}: ` +
`monthlyTreesAdded=${monthlyTreesAdded}, target=10`,
)
if (monthlyTreesAdded >= 10) {
// 达标,续期
community.renewBenefit(monthlyTreesAdded)
await this.authorizationRepository.save(community)
renewedCount++
this.logger.log(
`[processExpiredCommunityBenefits] Community ${accountSequence} renewed, ` +
`new validUntil=${community.benefitValidUntil?.toISOString()}`,
)
} else {
// 不达标,级联停用
const result = await this.cascadeDeactivateCommunityBenefits(
accountSequence,
`月度考核不达标:本月新增${monthlyTreesAdded}未达到10棵目标`,
)
deactivatedCount += result.deactivatedCount
}
}
this.logger.log(
`[processExpiredCommunityBenefits] Completed: processed=${expiredCommunities.length}, ` +
`renewed=${renewedCount}, deactivated=${deactivatedCount}`,
)
return {
processedCount: expiredCommunities.length,
renewedCount,
deactivatedCount,
}
}
/**
*
* TODO: 后续优化可以移到 repository
*/
private async findExpiredActiveCommunities(now: Date, limit: number): Promise<AuthorizationRole[]> {
// 获取所有激活的社区授权
const activeCommunities = await this.authorizationRepository.findAllActive(RoleType.COMMUNITY)
// 筛选出已过期的
return activeCommunities
.filter((auth) => auth.benefitActive && auth.isBenefitExpired(now))
.slice(0, limit)
}
/**

View File

@ -43,6 +43,9 @@ export interface AuthorizationRoleProps {
benefitActive: boolean
benefitActivatedAt: Date | null
benefitDeactivatedAt: Date | null
benefitValidUntil: Date | null // 权益有效期截止日期
lastAssessmentMonth: string | null // 上次考核月份 YYYY-MM
monthlyTreesAdded: number // 当月新增树数
currentMonthIndex: number
createdAt: Date
updatedAt: Date
@ -75,6 +78,11 @@ export class AuthorizationRole extends AggregateRoot {
private _benefitActive: boolean
private _benefitActivatedAt: Date | null
private _benefitDeactivatedAt: Date | null
private _benefitValidUntil: Date | null
// 月度考核追踪
private _lastAssessmentMonth: string | null
private _monthlyTreesAdded: number
// 当前考核月份索引
private _currentMonthIndex: number
@ -137,6 +145,15 @@ export class AuthorizationRole extends AggregateRoot {
get benefitDeactivatedAt(): Date | null {
return this._benefitDeactivatedAt
}
get benefitValidUntil(): Date | null {
return this._benefitValidUntil
}
get lastAssessmentMonth(): string | null {
return this._lastAssessmentMonth
}
get monthlyTreesAdded(): number {
return this._monthlyTreesAdded
}
get currentMonthIndex(): number {
return this._currentMonthIndex
}
@ -171,6 +188,9 @@ export class AuthorizationRole extends AggregateRoot {
this._benefitActive = props.benefitActive
this._benefitActivatedAt = props.benefitActivatedAt
this._benefitDeactivatedAt = props.benefitDeactivatedAt
this._benefitValidUntil = props.benefitValidUntil
this._lastAssessmentMonth = props.lastAssessmentMonth
this._monthlyTreesAdded = props.monthlyTreesAdded
this._currentMonthIndex = props.currentMonthIndex
this._createdAt = props.createdAt
this._updatedAt = props.updatedAt
@ -181,6 +201,32 @@ export class AuthorizationRole extends AggregateRoot {
return new AuthorizationRole(props)
}
/**
*
* +
* 113 -> 12月31日23:59:59
*/
private static calculateBenefitValidUntil(activatedAt: Date): Date {
const year = activatedAt.getFullYear()
const month = activatedAt.getMonth() // 0-based
// 下下个月的第一天的前一天 = 下个月的最后一天
// 例如11月 -> month=10 -> month+2=12 (1月) -> 12月31日
const nextNextMonth = month + 2
const validUntil = new Date(year, nextNextMonth, 0, 23, 59, 59, 999)
return validUntil
}
/**
* YYYY-MM
*/
private static getCurrentMonthString(date: Date = new Date()): string {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
return `${year}-${month}`
}
// 工厂方法 - 创建社区授权
static createCommunityAuth(params: { userId: UserId; communityName: string }): AuthorizationRole {
const auth = new AuthorizationRole({
@ -202,6 +248,9 @@ export class AuthorizationRole extends AggregateRoot {
benefitActive: false,
benefitActivatedAt: null,
benefitDeactivatedAt: null,
benefitValidUntil: null,
lastAssessmentMonth: null,
monthlyTreesAdded: 0,
currentMonthIndex: 0,
createdAt: new Date(),
updatedAt: new Date(),
@ -226,6 +275,7 @@ export class AuthorizationRole extends AggregateRoot {
skipAssessment?: boolean
}): AuthorizationRole {
const skipAssessment = params.skipAssessment ?? false
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -234,7 +284,7 @@ export class AuthorizationRole extends AggregateRoot {
regionName: params.communityName,
status: AuthorizationStatus.AUTHORIZED,
displayTitle: params.communityName,
authorizedAt: new Date(),
authorizedAt: now,
authorizedBy: params.adminId,
revokedAt: null,
revokedBy: null,
@ -243,11 +293,14 @@ export class AuthorizationRole extends AggregateRoot {
requireLocalPercentage: 0,
exemptFromPercentageCheck: true,
benefitActive: skipAssessment,
benefitActivatedAt: skipAssessment ? new Date() : null,
benefitActivatedAt: skipAssessment ? now : null,
benefitDeactivatedAt: null,
benefitValidUntil: skipAssessment ? AuthorizationRole.calculateBenefitValidUntil(now) : null,
lastAssessmentMonth: skipAssessment ? AuthorizationRole.getCurrentMonthString(now) : null,
monthlyTreesAdded: 0,
currentMonthIndex: skipAssessment ? 1 : 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -268,6 +321,7 @@ export class AuthorizationRole extends AggregateRoot {
provinceCode: string
provinceName: string
}): AuthorizationRole {
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -287,9 +341,12 @@ export class AuthorizationRole extends AggregateRoot {
benefitActive: false,
benefitActivatedAt: null,
benefitDeactivatedAt: null,
benefitValidUntil: null,
lastAssessmentMonth: null,
monthlyTreesAdded: 0,
currentMonthIndex: 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -313,6 +370,7 @@ export class AuthorizationRole extends AggregateRoot {
skipAssessment?: boolean
}): AuthorizationRole {
const skipAssessment = params.skipAssessment ?? false
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -321,7 +379,7 @@ export class AuthorizationRole extends AggregateRoot {
regionName: params.provinceName,
status: AuthorizationStatus.AUTHORIZED,
displayTitle: params.provinceName,
authorizedAt: new Date(),
authorizedAt: now,
authorizedBy: params.adminId,
revokedAt: null,
revokedBy: null,
@ -330,11 +388,14 @@ export class AuthorizationRole extends AggregateRoot {
requireLocalPercentage: 0,
exemptFromPercentageCheck: true,
benefitActive: skipAssessment,
benefitActivatedAt: skipAssessment ? new Date() : null,
benefitActivatedAt: skipAssessment ? now : null,
benefitDeactivatedAt: null,
benefitValidUntil: skipAssessment ? AuthorizationRole.calculateBenefitValidUntil(now) : null,
lastAssessmentMonth: skipAssessment ? AuthorizationRole.getCurrentMonthString(now) : null,
monthlyTreesAdded: 0,
currentMonthIndex: skipAssessment ? 1 : 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -356,6 +417,7 @@ export class AuthorizationRole extends AggregateRoot {
cityCode: string
cityName: string
}): AuthorizationRole {
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -375,9 +437,12 @@ export class AuthorizationRole extends AggregateRoot {
benefitActive: false,
benefitActivatedAt: null,
benefitDeactivatedAt: null,
benefitValidUntil: null,
lastAssessmentMonth: null,
monthlyTreesAdded: 0,
currentMonthIndex: 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -401,6 +466,7 @@ export class AuthorizationRole extends AggregateRoot {
skipAssessment?: boolean
}): AuthorizationRole {
const skipAssessment = params.skipAssessment ?? false
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -409,7 +475,7 @@ export class AuthorizationRole extends AggregateRoot {
regionName: params.cityName,
status: AuthorizationStatus.AUTHORIZED,
displayTitle: params.cityName,
authorizedAt: new Date(),
authorizedAt: now,
authorizedBy: params.adminId,
revokedAt: null,
revokedBy: null,
@ -418,11 +484,14 @@ export class AuthorizationRole extends AggregateRoot {
requireLocalPercentage: 0,
exemptFromPercentageCheck: true,
benefitActive: skipAssessment,
benefitActivatedAt: skipAssessment ? new Date() : null,
benefitActivatedAt: skipAssessment ? now : null,
benefitDeactivatedAt: null,
benefitValidUntil: skipAssessment ? AuthorizationRole.calculateBenefitValidUntil(now) : null,
lastAssessmentMonth: skipAssessment ? AuthorizationRole.getCurrentMonthString(now) : null,
monthlyTreesAdded: 0,
currentMonthIndex: skipAssessment ? 1 : 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -447,6 +516,7 @@ export class AuthorizationRole extends AggregateRoot {
skipAssessment?: boolean
}): AuthorizationRole {
const skipAssessment = params.skipAssessment ?? false
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -455,7 +525,7 @@ export class AuthorizationRole extends AggregateRoot {
regionName: params.provinceName,
status: AuthorizationStatus.AUTHORIZED,
displayTitle: `授权${params.provinceName}`,
authorizedAt: new Date(),
authorizedAt: now,
authorizedBy: params.adminId,
revokedAt: null,
revokedBy: null,
@ -464,11 +534,14 @@ export class AuthorizationRole extends AggregateRoot {
requireLocalPercentage: 5.0,
exemptFromPercentageCheck: false,
benefitActive: skipAssessment,
benefitActivatedAt: skipAssessment ? new Date() : null,
benefitActivatedAt: skipAssessment ? now : null,
benefitDeactivatedAt: null,
benefitValidUntil: skipAssessment ? AuthorizationRole.calculateBenefitValidUntil(now) : null,
lastAssessmentMonth: skipAssessment ? AuthorizationRole.getCurrentMonthString(now) : null,
monthlyTreesAdded: 0,
currentMonthIndex: skipAssessment ? 1 : 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -493,6 +566,7 @@ export class AuthorizationRole extends AggregateRoot {
skipAssessment?: boolean
}): AuthorizationRole {
const skipAssessment = params.skipAssessment ?? false
const now = new Date()
const auth = new AuthorizationRole({
authorizationId: AuthorizationId.generate(),
userId: params.userId,
@ -501,7 +575,7 @@ export class AuthorizationRole extends AggregateRoot {
regionName: params.cityName,
status: AuthorizationStatus.AUTHORIZED,
displayTitle: `授权${params.cityName}`,
authorizedAt: new Date(),
authorizedAt: now,
authorizedBy: params.adminId,
revokedAt: null,
revokedBy: null,
@ -510,11 +584,14 @@ export class AuthorizationRole extends AggregateRoot {
requireLocalPercentage: 5.0,
exemptFromPercentageCheck: false,
benefitActive: skipAssessment,
benefitActivatedAt: skipAssessment ? new Date() : null,
benefitActivatedAt: skipAssessment ? now : null,
benefitDeactivatedAt: null,
benefitValidUntil: skipAssessment ? AuthorizationRole.calculateBenefitValidUntil(now) : null,
lastAssessmentMonth: skipAssessment ? AuthorizationRole.getCurrentMonthString(now) : null,
monthlyTreesAdded: 0,
currentMonthIndex: skipAssessment ? 1 : 0,
createdAt: new Date(),
updatedAt: new Date(),
createdAt: now,
updatedAt: now,
})
auth.addDomainEvent(
@ -534,17 +611,22 @@ export class AuthorizationRole extends AggregateRoot {
/**
*
* +
*/
activateBenefit(): void {
if (this._benefitActive) {
throw new DomainError('权益已激活')
}
const now = new Date()
this._status = AuthorizationStatus.AUTHORIZED
this._benefitActive = true
this._benefitActivatedAt = new Date()
this._benefitActivatedAt = now
this._benefitValidUntil = AuthorizationRole.calculateBenefitValidUntil(now)
this._lastAssessmentMonth = AuthorizationRole.getCurrentMonthString(now)
this._monthlyTreesAdded = 0
this._currentMonthIndex = 1
this._updatedAt = new Date()
this._updatedAt = now
this.addDomainEvent(
new BenefitActivatedEvent({
@ -558,16 +640,21 @@ export class AuthorizationRole extends AggregateRoot {
/**
*
*
*/
deactivateBenefit(reason: string): void {
if (!this._benefitActive) {
return
}
const now = new Date()
this._benefitActive = false
this._benefitDeactivatedAt = new Date()
this._benefitDeactivatedAt = now
this._benefitValidUntil = null
this._lastAssessmentMonth = null
this._monthlyTreesAdded = 0
this._currentMonthIndex = 0 // 重置月份索引
this._updatedAt = new Date()
this._updatedAt = now
this.addDomainEvent(
new BenefitDeactivatedEvent({
@ -579,6 +666,48 @@ export class AuthorizationRole extends AggregateRoot {
)
}
/**
*
*
*/
renewBenefit(treesAdded: number): void {
if (!this._benefitActive) {
throw new DomainError('权益未激活,无法续期')
}
const now = new Date()
this._benefitValidUntil = AuthorizationRole.calculateBenefitValidUntil(now)
this._lastAssessmentMonth = AuthorizationRole.getCurrentMonthString(now)
this._monthlyTreesAdded = treesAdded
this._updatedAt = now
}
/**
*
*/
addMonthlyTrees(treeCount: number): void {
this._monthlyTreesAdded += treeCount
this._updatedAt = new Date()
}
/**
*
*/
resetMonthlyTrees(): void {
this._monthlyTreesAdded = 0
this._updatedAt = new Date()
}
/**
*
*/
isBenefitExpired(checkDate: Date = new Date()): boolean {
if (!this._benefitActive || !this._benefitValidUntil) {
return false
}
return checkDate > this._benefitValidUntil
}
/**
*
*/
@ -721,6 +850,9 @@ export class AuthorizationRole extends AggregateRoot {
benefitActive: this._benefitActive,
benefitActivatedAt: this._benefitActivatedAt,
benefitDeactivatedAt: this._benefitDeactivatedAt,
benefitValidUntil: this._benefitValidUntil,
lastAssessmentMonth: this._lastAssessmentMonth,
monthlyTreesAdded: this._monthlyTreesAdded,
currentMonthIndex: this._currentMonthIndex,
createdAt: this._createdAt,
updatedAt: this._updatedAt,

View File

@ -43,6 +43,9 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
benefitActive: data.benefitActive,
benefitActivatedAt: data.benefitActivatedAt,
benefitDeactivatedAt: data.benefitDeactivatedAt,
benefitValidUntil: data.benefitValidUntil,
lastAssessmentMonth: data.lastAssessmentMonth,
monthlyTreesAdded: data.monthlyTreesAdded,
currentMonthIndex: data.currentMonthIndex,
},
update: {
@ -58,6 +61,9 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
benefitActive: data.benefitActive,
benefitActivatedAt: data.benefitActivatedAt,
benefitDeactivatedAt: data.benefitDeactivatedAt,
benefitValidUntil: data.benefitValidUntil,
lastAssessmentMonth: data.lastAssessmentMonth,
monthlyTreesAdded: data.monthlyTreesAdded,
currentMonthIndex: data.currentMonthIndex,
},
})
@ -384,6 +390,9 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
benefitActive: record.benefitActive,
benefitActivatedAt: record.benefitActivatedAt,
benefitDeactivatedAt: record.benefitDeactivatedAt,
benefitValidUntil: record.benefitValidUntil,
lastAssessmentMonth: record.lastAssessmentMonth,
monthlyTreesAdded: record.monthlyTreesAdded ?? 0,
currentMonthIndex: record.currentMonthIndex,
createdAt: record.createdAt,
updatedAt: record.updatedAt,