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:
parent
846915badc
commit
2af44e5854
|
|
@ -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";
|
||||
|
|
@ -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"
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每月1号凌晨0点重置所有社区的月度新增树数
|
||||
*
|
||||
* 注意:此任务必须在 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算权益有效期截止日期
|
||||
* 规则:激活当月 + 下月末
|
||||
* 例如:11月3日激活 -> 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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue