From 4bf4efb1f43ad30deb2ed59f397981dd0a4d7d03 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 11 Dec 2025 21:57:00 -0800 Subject: [PATCH] feat(authorization): add assessment rules for province/city area roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Province area (PROVINCE_COMPANY): 50,000 trees initial target - City area (CITY_COMPANY): 10,000 trees initial target - Apply consistent assessment logic: pre-target rewards go to system account, post-target rewards go to company - Auto-activate benefit when target is reached 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../authorization-application.service.ts | 200 +++++++++++++++--- .../value-objects/assessment-config.vo.ts | 4 +- 2 files changed, 178 insertions(+), 26 deletions(-) diff --git a/backend/services/authorization-service/src/application/services/authorization-application.service.ts b/backend/services/authorization-service/src/application/services/authorization-application.service.ts index 15a756ac..c6739859 100644 --- a/backend/services/authorization-service/src/application/services/authorization-application.service.ts +++ b/backend/services/authorization-service/src/application/services/authorization-application.service.ts @@ -1259,7 +1259,11 @@ export class AuthorizationApplicationService { * 规则: * 1. 查找该省份是否有正式省公司(PROVINCE_COMPANY) * 2. 如果有且 benefitActive=true,权益进该省公司自己的账户 - * 3. 如果没有或 benefitActive=false,权益进系统省账户 + * 3. 如果有但 benefitActive=false(考核中): + * - 计算还需要多少棵才能达到初始考核(5万棵) + * - 考核前的部分进系统省账户 + * - 考核后的部分给该省公司 + * 4. 如果没有正式省公司,全部进系统省账户 */ async getProvinceAreaRewardDistribution( provinceCode: string, @@ -1282,7 +1286,21 @@ export class AuthorizationApplicationService { // 查找该省份的正式省公司 const provinceCompany = await this.authorizationRepository.findProvinceCompanyByRegion(provinceCode) - if (provinceCompany && provinceCompany.benefitActive) { + if (!provinceCompany) { + // 无正式省公司,全部进系统省账户 + return { + distributions: [ + { + accountSequence: systemProvinceAccountId, + treeCount, + reason: '无正式省公司授权,进系统省账户', + isSystemAccount: true, + }, + ], + } + } + + if (provinceCompany.benefitActive) { // 正式省公司权益已激活,进该省公司账户 return { distributions: [ @@ -1296,21 +1314,79 @@ export class AuthorizationApplicationService { } } - // 无正式省公司或权益未激活,进系统省账户 - const reason = provinceCompany - ? `省区域权益未激活(考核中),进系统省账户` - : '无正式省公司授权,进系统省账户' + // 权益未激活,计算考核分配 - 使用下级团队认种数(不含自己) + const stats = await this.statsRepository.findByAccountSequence(provinceCompany.userId.accountSequence) + const rawSubordinateCount = stats?.subordinateTeamPlantingCount ?? 0 + // 修复竞态条件:减去本次认种数量来还原"认种前"的下级团队数 + const currentTeamCount = Math.max(0, rawSubordinateCount - treeCount) + const initialTarget = provinceCompany.getInitialTarget() // 5万棵 - return { - distributions: [ - { + this.logger.debug( + `[getProvinceAreaRewardDistribution] rawSubordinateCount=${rawSubordinateCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}, initialTarget=${initialTarget}`, + ) + + const distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + isSystemAccount: boolean + }> = [] + + if (currentTeamCount >= initialTarget) { + // 已达标但权益未激活,全部给该省公司 + distributions.push({ + accountSequence: Number(provinceCompany.userId.accountSequence), + treeCount, + reason: '已达初始考核目标', + isSystemAccount: false, + }) + // 自动激活权益 + await this.tryActivateBenefit(provinceCompany) + } else { + const remaining = initialTarget - currentTeamCount + const afterPlantingCount = currentTeamCount + treeCount + + if (afterPlantingCount < initialTarget) { + // 本次认种后仍未达标,全部进系统省账户 + distributions.push({ accountSequence: systemProvinceAccountId, treeCount, - reason, + reason: `初始考核中(${currentTeamCount}+${treeCount}=${afterPlantingCount}/${initialTarget}),进系统省账户`, isSystemAccount: true, - }, - ], + }) + } else if (afterPlantingCount === initialTarget) { + // 本次认种恰好达标,全部进系统省账户,但需要激活权益 + distributions.push({ + accountSequence: systemProvinceAccountId, + treeCount, + reason: `初始考核达标(${currentTeamCount}+${treeCount}=${initialTarget}),进系统省账户`, + isSystemAccount: true, + }) + // 自动激活权益 + await this.tryActivateBenefit(provinceCompany) + } else { + // 本次认种跨越考核达标点 + // 考核前的部分进系统省账户 + distributions.push({ + accountSequence: systemProvinceAccountId, + treeCount: remaining, + reason: `初始考核(${currentTeamCount}+${remaining}=${initialTarget}),进系统省账户`, + isSystemAccount: true, + }) + // 考核后的部分给该省公司 + distributions.push({ + accountSequence: Number(provinceCompany.userId.accountSequence), + treeCount: treeCount - remaining, + reason: '考核达标后权益生效', + isSystemAccount: false, + }) + // 自动激活权益 + await this.tryActivateBenefit(provinceCompany) + } } + + this.logger.debug(`[getProvinceAreaRewardDistribution] Result: ${JSON.stringify(distributions)}`) + return { distributions } } /** @@ -1485,7 +1561,11 @@ export class AuthorizationApplicationService { * 规则: * 1. 查找该城市是否有正式市公司(CITY_COMPANY) * 2. 如果有且 benefitActive=true,权益进该市公司自己的账户 - * 3. 如果没有或 benefitActive=false,权益进系统市账户 + * 3. 如果有但 benefitActive=false(考核中): + * - 计算还需要多少棵才能达到初始考核(1万棵) + * - 考核前的部分进系统市账户 + * - 考核后的部分给该市公司 + * 4. 如果没有正式市公司,全部进系统市账户 */ async getCityAreaRewardDistribution( cityCode: string, @@ -1508,7 +1588,21 @@ export class AuthorizationApplicationService { // 查找该城市的正式市公司 const cityCompany = await this.authorizationRepository.findCityCompanyByRegion(cityCode) - if (cityCompany && cityCompany.benefitActive) { + if (!cityCompany) { + // 无正式市公司,全部进系统市账户 + return { + distributions: [ + { + accountSequence: systemCityAccountId, + treeCount, + reason: '无正式市公司授权,进系统市账户', + isSystemAccount: true, + }, + ], + } + } + + if (cityCompany.benefitActive) { // 正式市公司权益已激活,进该市公司账户 return { distributions: [ @@ -1522,20 +1616,78 @@ export class AuthorizationApplicationService { } } - // 无正式市公司或权益未激活,进系统市账户 - const reason = cityCompany - ? `市区域权益未激活(考核中),进系统市账户` - : '无正式市公司授权,进系统市账户' + // 权益未激活,计算考核分配 - 使用下级团队认种数(不含自己) + const stats = await this.statsRepository.findByAccountSequence(cityCompany.userId.accountSequence) + const rawSubordinateCount = stats?.subordinateTeamPlantingCount ?? 0 + // 修复竞态条件:减去本次认种数量来还原"认种前"的下级团队数 + const currentTeamCount = Math.max(0, rawSubordinateCount - treeCount) + const initialTarget = cityCompany.getInitialTarget() // 1万棵 - return { - distributions: [ - { + this.logger.debug( + `[getCityAreaRewardDistribution] rawSubordinateCount=${rawSubordinateCount}, treeCount=${treeCount}, currentTeamCount(before)=${currentTeamCount}, initialTarget=${initialTarget}`, + ) + + const distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + isSystemAccount: boolean + }> = [] + + if (currentTeamCount >= initialTarget) { + // 已达标但权益未激活,全部给该市公司 + distributions.push({ + accountSequence: Number(cityCompany.userId.accountSequence), + treeCount, + reason: '已达初始考核目标', + isSystemAccount: false, + }) + // 自动激活权益 + await this.tryActivateBenefit(cityCompany) + } else { + const remaining = initialTarget - currentTeamCount + const afterPlantingCount = currentTeamCount + treeCount + + if (afterPlantingCount < initialTarget) { + // 本次认种后仍未达标,全部进系统市账户 + distributions.push({ accountSequence: systemCityAccountId, treeCount, - reason, + reason: `初始考核中(${currentTeamCount}+${treeCount}=${afterPlantingCount}/${initialTarget}),进系统市账户`, isSystemAccount: true, - }, - ], + }) + } else if (afterPlantingCount === initialTarget) { + // 本次认种恰好达标,全部进系统市账户,但需要激活权益 + distributions.push({ + accountSequence: systemCityAccountId, + treeCount, + reason: `初始考核达标(${currentTeamCount}+${treeCount}=${initialTarget}),进系统市账户`, + isSystemAccount: true, + }) + // 自动激活权益 + await this.tryActivateBenefit(cityCompany) + } else { + // 本次认种跨越考核达标点 + // 考核前的部分进系统市账户 + distributions.push({ + accountSequence: systemCityAccountId, + treeCount: remaining, + reason: `初始考核(${currentTeamCount}+${remaining}=${initialTarget}),进系统市账户`, + isSystemAccount: true, + }) + // 考核后的部分给该市公司 + distributions.push({ + accountSequence: Number(cityCompany.userId.accountSequence), + treeCount: treeCount - remaining, + reason: '考核达标后权益生效', + isSystemAccount: false, + }) + // 自动激活权益 + await this.tryActivateBenefit(cityCompany) + } } + + this.logger.debug(`[getCityAreaRewardDistribution] Result: ${JSON.stringify(distributions)}`) + return { distributions } } } diff --git a/backend/services/authorization-service/src/domain/value-objects/assessment-config.vo.ts b/backend/services/authorization-service/src/domain/value-objects/assessment-config.vo.ts index c08d7866..886b69d8 100644 --- a/backend/services/authorization-service/src/domain/value-objects/assessment-config.vo.ts +++ b/backend/services/authorization-service/src/domain/value-objects/assessment-config.vo.ts @@ -15,7 +15,7 @@ export class AssessmentConfig { } static forProvince(): AssessmentConfig { - return new AssessmentConfig(0, MonthlyTargetType.NONE) + return new AssessmentConfig(50000, MonthlyTargetType.LADDER) // 省区域:5万棵 } static forAuthCity(): AssessmentConfig { @@ -23,6 +23,6 @@ export class AssessmentConfig { } static forCity(): AssessmentConfig { - return new AssessmentConfig(0, MonthlyTargetType.NONE) + return new AssessmentConfig(10000, MonthlyTargetType.LADDER) // 市区域:1万棵 } }