From 16d95999de6dc781df0da4fe005ee2b7afbb5982 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 11 Dec 2025 07:54:55 -0800 Subject: [PATCH] feat(backend): implement assessment rules for reward distribution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add proper assessment/考核 logic for all 5 benefit types: - Community (80 USDT): 10-tree initial assessment, splits rewards between current community and parent/headquarters based on progress - Province Team (20 USDT): 500-tree initial assessment for AUTH_PROVINCE_COMPANY - Province Area (15 USDT + 1%): 50000-tree target for PROVINCE_COMPANY, routes to system account until target reached - City Team (40 USDT): 100-tree initial assessment for AUTH_CITY_COMPANY - City Area (35 USDT + 2%): 10000-tree target for CITY_COMPANY, routes to system account until target reached Changes: - authorization-service: Add 4 new distribution API endpoints and application service methods for province/city team/area reward distribution - authorization-service: Add repository methods for querying authorizations including benefitActive=false records - reward-service: Update client to call new distribution APIs - reward-service: Modify calculation methods to return multiple reward entries based on assessment distribution plan 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../internal-authorization.controller.ts | 165 +++++ .../authorization-application.service.ts | 594 ++++++++++++++++++ .../authorization-role.repository.ts | 29 + .../authorization-role.repository.impl.ts | 83 +++ .../services/reward-calculation.service.ts | 331 ++++++---- .../authorization-service.client.ts | 229 ++++++- 6 files changed, 1295 insertions(+), 136 deletions(-) diff --git a/backend/services/authorization-service/src/api/controllers/internal-authorization.controller.ts b/backend/services/authorization-service/src/api/controllers/internal-authorization.controller.ts index 859ab295..505cbe80 100644 --- a/backend/services/authorization-service/src/api/controllers/internal-authorization.controller.ts +++ b/backend/services/authorization-service/src/api/controllers/internal-authorization.controller.ts @@ -115,4 +115,169 @@ export class InternalAuthorizationController { accountSequence: result ? Number(result) : null, } } + + /** + * 获取社区权益分配方案 + * 根据考核规则计算每棵树的社区权益应该分配给谁 + * 用于 reward-service 分配社区权益 + */ + @Get('community-reward-distribution') + @ApiOperation({ summary: '获取社区权益分配方案(内部 API)' }) + @ApiQuery({ name: 'accountSequence', description: '认种用户的 accountSequence' }) + @ApiQuery({ name: 'treeCount', description: '认种棵数' }) + @ApiResponse({ + status: 200, + description: '返回社区权益分配方案', + schema: { + type: 'object', + properties: { + distributions: { + type: 'array', + items: { + type: 'object', + properties: { + accountSequence: { type: 'number', description: '接收者账号' }, + treeCount: { type: 'number', description: '分配棵数' }, + reason: { type: 'string', description: '分配原因' }, + }, + }, + }, + }, + }, + }) + async getCommunityRewardDistribution( + @Query('accountSequence') accountSequence: string, + @Query('treeCount') treeCount: string, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> + }> { + this.logger.debug( + `[INTERNAL] getCommunityRewardDistribution: accountSequence=${accountSequence}, treeCount=${treeCount}`, + ) + + return this.applicationService.getCommunityRewardDistribution( + Number(accountSequence), + Number(treeCount), + ) + } + + /** + * 获取省团队权益分配方案 (20 USDT) + */ + @Get('province-team-reward-distribution') + @ApiOperation({ summary: '获取省团队权益分配方案(内部 API)' }) + @ApiQuery({ name: 'accountSequence', description: '认种用户的 accountSequence' }) + @ApiQuery({ name: 'provinceCode', description: '省份代码' }) + @ApiQuery({ name: 'treeCount', description: '认种棵数' }) + async getProvinceTeamRewardDistribution( + @Query('accountSequence') accountSequence: string, + @Query('provinceCode') provinceCode: string, + @Query('treeCount') treeCount: string, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> + }> { + this.logger.debug( + `[INTERNAL] getProvinceTeamRewardDistribution: accountSequence=${accountSequence}, provinceCode=${provinceCode}, treeCount=${treeCount}`, + ) + + return this.applicationService.getProvinceTeamRewardDistribution( + Number(accountSequence), + provinceCode, + Number(treeCount), + ) + } + + /** + * 获取省区域权益分配方案 (15 USDT + 1%算力) + */ + @Get('province-area-reward-distribution') + @ApiOperation({ summary: '获取省区域权益分配方案(内部 API)' }) + @ApiQuery({ name: 'provinceCode', description: '省份代码' }) + @ApiQuery({ name: 'treeCount', description: '认种棵数' }) + async getProvinceAreaRewardDistribution( + @Query('provinceCode') provinceCode: string, + @Query('treeCount') treeCount: string, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + isSystemAccount: boolean + }> + }> { + this.logger.debug( + `[INTERNAL] getProvinceAreaRewardDistribution: provinceCode=${provinceCode}, treeCount=${treeCount}`, + ) + + return this.applicationService.getProvinceAreaRewardDistribution( + provinceCode, + Number(treeCount), + ) + } + + /** + * 获取市团队权益分配方案 (40 USDT) + */ + @Get('city-team-reward-distribution') + @ApiOperation({ summary: '获取市团队权益分配方案(内部 API)' }) + @ApiQuery({ name: 'accountSequence', description: '认种用户的 accountSequence' }) + @ApiQuery({ name: 'cityCode', description: '城市代码' }) + @ApiQuery({ name: 'treeCount', description: '认种棵数' }) + async getCityTeamRewardDistribution( + @Query('accountSequence') accountSequence: string, + @Query('cityCode') cityCode: string, + @Query('treeCount') treeCount: string, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> + }> { + this.logger.debug( + `[INTERNAL] getCityTeamRewardDistribution: accountSequence=${accountSequence}, cityCode=${cityCode}, treeCount=${treeCount}`, + ) + + return this.applicationService.getCityTeamRewardDistribution( + Number(accountSequence), + cityCode, + Number(treeCount), + ) + } + + /** + * 获取市区域权益分配方案 (35 USDT + 2%算力) + */ + @Get('city-area-reward-distribution') + @ApiOperation({ summary: '获取市区域权益分配方案(内部 API)' }) + @ApiQuery({ name: 'cityCode', description: '城市代码' }) + @ApiQuery({ name: 'treeCount', description: '认种棵数' }) + async getCityAreaRewardDistribution( + @Query('cityCode') cityCode: string, + @Query('treeCount') treeCount: string, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + isSystemAccount: boolean + }> + }> { + this.logger.debug( + `[INTERNAL] getCityAreaRewardDistribution: cityCode=${cityCode}, treeCount=${treeCount}`, + ) + + return this.applicationService.getCityAreaRewardDistribution( + cityCode, + Number(treeCount), + ) + } } 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 a0360184..8cb857fe 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 @@ -744,4 +744,598 @@ export class AuthorizationApplicationService { return null } + + /** + * 获取社区权益分配方案 + * 根据考核规则计算每棵树的社区权益应该分配给谁 + * + * 规则: + * 1. 找到认种用户推荐链上最近的社区 + * 2. 如果该社区 benefitActive=true,全部权益给该社区 + * 3. 如果该社区 benefitActive=false: + * - 计算该社区还需要多少棵才能达到初始考核(10棵) + * - 考核前的部分给上级社区或总部 + * - 考核后的部分给该社区(同时激活权益) + * 4. 如果没有社区,全部给总部 + */ + async getCommunityRewardDistribution( + accountSequence: number, + treeCount: number, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> + }> { + this.logger.debug( + `[getCommunityRewardDistribution] accountSequence=${accountSequence}, treeCount=${treeCount}`, + ) + + const HEADQUARTERS_ACCOUNT_SEQUENCE = 1 // 总部社区账号 + + // 1. 获取用户的祖先链(推荐链) + const ancestorAccountSequences = await this.referralServiceClient.getReferralChain(BigInt(accountSequence)) + + if (ancestorAccountSequences.length === 0) { + // 无推荐链,全部给总部 + return { + distributions: [ + { + accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, + treeCount, + reason: '无推荐链,进总部社区', + }, + ], + } + } + + // 2. 查找祖先链中所有社区授权(包括 benefitActive=false 的) + const ancestorCommunities = await this.authorizationRepository.findCommunityByAccountSequences( + ancestorAccountSequences.map((seq) => BigInt(seq)), + ) + + if (ancestorCommunities.length === 0) { + // 推荐链上没有社区,全部给总部 + return { + distributions: [ + { + accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, + treeCount, + reason: '推荐链上无社区授权,进总部社区', + }, + ], + } + } + + // 3. 按祖先链顺序找最近的社区 + let nearestCommunity: typeof ancestorCommunities[0] | null = null + let nearestCommunityIndex = -1 + + for (let i = 0; i < ancestorAccountSequences.length; i++) { + const ancestorSeq = ancestorAccountSequences[i] + const found = ancestorCommunities.find( + (auth) => Number(auth.userId.accountSequence) === ancestorSeq, + ) + if (found) { + nearestCommunity = found + nearestCommunityIndex = i + break + } + } + + if (!nearestCommunity) { + // 这种情况理论上不应该发生,但作为兜底 + return { + distributions: [ + { + accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, + treeCount, + reason: '未找到匹配的社区,进总部社区', + }, + ], + } + } + + // 4. 检查最近社区的权益状态 + if (nearestCommunity.benefitActive) { + // 权益已激活,全部给该社区 + return { + distributions: [ + { + accountSequence: Number(nearestCommunity.userId.accountSequence), + treeCount, + reason: '社区权益已激活', + }, + ], + } + } + + // 5. 权益未激活,需要计算考核分配 + // 获取该社区的团队统计数据(当前已完成的认种数量) + const communityStats = await this.statsRepository.findByAccountSequence( + nearestCommunity.userId.accountSequence, + ) + const currentTeamCount = communityStats?.totalTeamPlantingCount ?? 0 + const initialTarget = nearestCommunity.getInitialTarget() // 社区初始考核目标:10棵 + + this.logger.debug( + `[getCommunityRewardDistribution] Community ${nearestCommunity.userId.accountSequence} ` + + `benefitActive=false, currentTeamCount=${currentTeamCount}, initialTarget=${initialTarget}`, + ) + + // 6. 查找上级社区(用于接收考核前的权益) + let parentCommunityAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE + let parentCommunityReason = '上级为总部社区' + + // 从最近社区之后继续查找上级社区 + for (let i = nearestCommunityIndex + 1; i < ancestorAccountSequences.length; i++) { + const ancestorSeq = ancestorAccountSequences[i] + const found = ancestorCommunities.find( + (auth) => Number(auth.userId.accountSequence) === ancestorSeq && auth.benefitActive, + ) + if (found) { + parentCommunityAccountSequence = Number(found.userId.accountSequence) + parentCommunityReason = '上级社区权益已激活' + break + } + } + + // 7. 计算分配方案 + const distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> = [] + + if (currentTeamCount >= initialTarget) { + // 已达标但权益未激活(可能是月度考核失败),全部给该社区 + // 注:这种情况下应该由系统自动激活权益,但这里作为兜底处理 + distributions.push({ + accountSequence: Number(nearestCommunity.userId.accountSequence), + treeCount, + reason: '已达初始考核目标', + }) + } else { + // 未达标,需要拆分 + const remaining = initialTarget - currentTeamCount // 还差多少棵达标 + + if (treeCount <= remaining) { + // 本次认种全部用于考核,给上级/总部 + distributions.push({ + accountSequence: parentCommunityAccountSequence, + treeCount, + reason: `初始考核中(${currentTeamCount}/${initialTarget}),${parentCommunityReason}`, + }) + } else { + // 本次认种跨越考核达标点 + // 考核前的部分给上级/总部 + distributions.push({ + accountSequence: parentCommunityAccountSequence, + treeCount: remaining, + reason: `初始考核(${currentTeamCount}+${remaining}=${initialTarget}),${parentCommunityReason}`, + }) + // 考核后的部分给该社区 + distributions.push({ + accountSequence: Number(nearestCommunity.userId.accountSequence), + treeCount: treeCount - remaining, + reason: `考核达标后权益生效`, + }) + } + } + + this.logger.debug( + `[getCommunityRewardDistribution] Result: ${JSON.stringify(distributions)}`, + ) + + return { distributions } + } + + /** + * 获取省团队权益分配方案 (20 USDT) + * + * 规则: + * 1. 找到认种用户推荐链上最近的授权省公司(AUTH_PROVINCE_COMPANY) + * 2. 如果该授权省公司 benefitActive=true,全部权益给该用户 + * 3. 如果该授权省公司 benefitActive=false: + * - 计算还需要多少棵才能达到初始考核(500棵) + * - 考核前的部分给上级/总部 + * - 考核后的部分给该用户 + * 4. 如果没有授权省公司,全部给总部 + */ + async getProvinceTeamRewardDistribution( + accountSequence: number, + provinceCode: string, + treeCount: number, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> + }> { + this.logger.debug( + `[getProvinceTeamRewardDistribution] accountSequence=${accountSequence}, provinceCode=${provinceCode}, treeCount=${treeCount}`, + ) + + const HEADQUARTERS_ACCOUNT_SEQUENCE = 1 + + // 1. 获取用户的祖先链 + const ancestorAccountSequences = await this.referralServiceClient.getReferralChain(BigInt(accountSequence)) + + if (ancestorAccountSequences.length === 0) { + return { + distributions: [ + { accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, treeCount, reason: '无推荐链,进总部社区' }, + ], + } + } + + // 2. 查找祖先链中所有授权省公司(包括 benefitActive=false) + const ancestorAuthProvinces = await this.authorizationRepository.findAuthProvinceByAccountSequencesAndRegion( + ancestorAccountSequences.map((seq) => BigInt(seq)), + provinceCode, + ) + + if (ancestorAuthProvinces.length === 0) { + return { + distributions: [ + { accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, treeCount, reason: '推荐链上无授权省公司,进总部社区' }, + ], + } + } + + // 3. 按祖先链顺序找最近的授权省公司 + let nearestAuthProvince: typeof ancestorAuthProvinces[0] | null = null + let nearestIndex = -1 + + for (let i = 0; i < ancestorAccountSequences.length; i++) { + const ancestorSeq = ancestorAccountSequences[i] + const found = ancestorAuthProvinces.find( + (auth) => Number(auth.userId.accountSequence) === ancestorSeq, + ) + if (found) { + nearestAuthProvince = found + nearestIndex = i + break + } + } + + if (!nearestAuthProvince) { + return { + distributions: [ + { accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, treeCount, reason: '未找到匹配的授权省公司,进总部社区' }, + ], + } + } + + // 4. 检查权益状态 + if (nearestAuthProvince.benefitActive) { + return { + distributions: [ + { accountSequence: Number(nearestAuthProvince.userId.accountSequence), treeCount, reason: '省团队权益已激活' }, + ], + } + } + + // 5. 权益未激活,计算考核分配 + const stats = await this.statsRepository.findByAccountSequence(nearestAuthProvince.userId.accountSequence) + const currentTeamCount = stats?.totalTeamPlantingCount ?? 0 + const initialTarget = nearestAuthProvince.getInitialTarget() // 500棵 + + // 6. 查找上级(用于接收考核前的权益) + let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE + let parentReason = '上级为总部社区' + + for (let i = nearestIndex + 1; i < ancestorAccountSequences.length; i++) { + const ancestorSeq = ancestorAccountSequences[i] + const found = ancestorAuthProvinces.find( + (auth) => Number(auth.userId.accountSequence) === ancestorSeq && auth.benefitActive, + ) + if (found) { + parentAccountSequence = Number(found.userId.accountSequence) + parentReason = '上级授权省公司权益已激活' + break + } + } + + // 7. 计算分配 + const distributions: Array<{ accountSequence: number; treeCount: number; reason: string }> = [] + + if (currentTeamCount >= initialTarget) { + distributions.push({ + accountSequence: Number(nearestAuthProvince.userId.accountSequence), + treeCount, + reason: '已达初始考核目标', + }) + } else { + const remaining = initialTarget - currentTeamCount + + if (treeCount <= remaining) { + distributions.push({ + accountSequence: parentAccountSequence, + treeCount, + reason: `初始考核中(${currentTeamCount}/${initialTarget}),${parentReason}`, + }) + } else { + distributions.push({ + accountSequence: parentAccountSequence, + treeCount: remaining, + reason: `初始考核(${currentTeamCount}+${remaining}=${initialTarget}),${parentReason}`, + }) + distributions.push({ + accountSequence: Number(nearestAuthProvince.userId.accountSequence), + treeCount: treeCount - remaining, + reason: '考核达标后权益生效', + }) + } + } + + this.logger.debug(`[getProvinceTeamRewardDistribution] Result: ${JSON.stringify(distributions)}`) + return { distributions } + } + + /** + * 获取省区域权益分配方案 (15 USDT + 1%算力) + * + * 规则: + * 1. 查找该省份是否有正式省公司(PROVINCE_COMPANY) + * 2. 如果有且 benefitActive=true,权益进该省公司自己的账户 + * 3. 如果没有或 benefitActive=false,权益进系统省账户 + */ + async getProvinceAreaRewardDistribution( + provinceCode: string, + treeCount: number, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + isSystemAccount: boolean + }> + }> { + this.logger.debug( + `[getProvinceAreaRewardDistribution] provinceCode=${provinceCode}, treeCount=${treeCount}`, + ) + + // 系统省账户ID格式: 9 + 省份代码 + const systemProvinceAccountId = Number(`9${provinceCode.padStart(6, '0')}`) + + // 查找该省份的正式省公司 + const provinceCompany = await this.authorizationRepository.findProvinceCompanyByRegion(provinceCode) + + if (provinceCompany && provinceCompany.benefitActive) { + // 正式省公司权益已激活,进该省公司账户 + return { + distributions: [ + { + accountSequence: Number(provinceCompany.userId.accountSequence), + treeCount, + reason: '省区域权益已激活', + isSystemAccount: false, + }, + ], + } + } + + // 无正式省公司或权益未激活,进系统省账户 + const reason = provinceCompany + ? `省区域权益未激活(考核中),进系统省账户` + : '无正式省公司授权,进系统省账户' + + return { + distributions: [ + { + accountSequence: systemProvinceAccountId, + treeCount, + reason, + isSystemAccount: true, + }, + ], + } + } + + /** + * 获取市团队权益分配方案 (40 USDT) + * + * 规则: + * 1. 找到认种用户推荐链上最近的授权市公司(AUTH_CITY_COMPANY) + * 2. 如果该授权市公司 benefitActive=true,全部权益给该用户 + * 3. 如果该授权市公司 benefitActive=false: + * - 计算还需要多少棵才能达到初始考核(100棵) + * - 考核前的部分给上级/总部 + * - 考核后的部分给该用户 + * 4. 如果没有授权市公司,全部给总部 + */ + async getCityTeamRewardDistribution( + accountSequence: number, + cityCode: string, + treeCount: number, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + }> + }> { + this.logger.debug( + `[getCityTeamRewardDistribution] accountSequence=${accountSequence}, cityCode=${cityCode}, treeCount=${treeCount}`, + ) + + const HEADQUARTERS_ACCOUNT_SEQUENCE = 1 + + // 1. 获取用户的祖先链 + const ancestorAccountSequences = await this.referralServiceClient.getReferralChain(BigInt(accountSequence)) + + if (ancestorAccountSequences.length === 0) { + return { + distributions: [ + { accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, treeCount, reason: '无推荐链,进总部社区' }, + ], + } + } + + // 2. 查找祖先链中所有授权市公司(包括 benefitActive=false) + const ancestorAuthCities = await this.authorizationRepository.findAuthCityByAccountSequencesAndRegion( + ancestorAccountSequences.map((seq) => BigInt(seq)), + cityCode, + ) + + if (ancestorAuthCities.length === 0) { + return { + distributions: [ + { accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, treeCount, reason: '推荐链上无授权市公司,进总部社区' }, + ], + } + } + + // 3. 按祖先链顺序找最近的授权市公司 + let nearestAuthCity: typeof ancestorAuthCities[0] | null = null + let nearestIndex = -1 + + for (let i = 0; i < ancestorAccountSequences.length; i++) { + const ancestorSeq = ancestorAccountSequences[i] + const found = ancestorAuthCities.find( + (auth) => Number(auth.userId.accountSequence) === ancestorSeq, + ) + if (found) { + nearestAuthCity = found + nearestIndex = i + break + } + } + + if (!nearestAuthCity) { + return { + distributions: [ + { accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, treeCount, reason: '未找到匹配的授权市公司,进总部社区' }, + ], + } + } + + // 4. 检查权益状态 + if (nearestAuthCity.benefitActive) { + return { + distributions: [ + { accountSequence: Number(nearestAuthCity.userId.accountSequence), treeCount, reason: '市团队权益已激活' }, + ], + } + } + + // 5. 权益未激活,计算考核分配 + const stats = await this.statsRepository.findByAccountSequence(nearestAuthCity.userId.accountSequence) + const currentTeamCount = stats?.totalTeamPlantingCount ?? 0 + const initialTarget = nearestAuthCity.getInitialTarget() // 100棵 + + // 6. 查找上级 + let parentAccountSequence: number = HEADQUARTERS_ACCOUNT_SEQUENCE + let parentReason = '上级为总部社区' + + for (let i = nearestIndex + 1; i < ancestorAccountSequences.length; i++) { + const ancestorSeq = ancestorAccountSequences[i] + const found = ancestorAuthCities.find( + (auth) => Number(auth.userId.accountSequence) === ancestorSeq && auth.benefitActive, + ) + if (found) { + parentAccountSequence = Number(found.userId.accountSequence) + parentReason = '上级授权市公司权益已激活' + break + } + } + + // 7. 计算分配 + const distributions: Array<{ accountSequence: number; treeCount: number; reason: string }> = [] + + if (currentTeamCount >= initialTarget) { + distributions.push({ + accountSequence: Number(nearestAuthCity.userId.accountSequence), + treeCount, + reason: '已达初始考核目标', + }) + } else { + const remaining = initialTarget - currentTeamCount + + if (treeCount <= remaining) { + distributions.push({ + accountSequence: parentAccountSequence, + treeCount, + reason: `初始考核中(${currentTeamCount}/${initialTarget}),${parentReason}`, + }) + } else { + distributions.push({ + accountSequence: parentAccountSequence, + treeCount: remaining, + reason: `初始考核(${currentTeamCount}+${remaining}=${initialTarget}),${parentReason}`, + }) + distributions.push({ + accountSequence: Number(nearestAuthCity.userId.accountSequence), + treeCount: treeCount - remaining, + reason: '考核达标后权益生效', + }) + } + } + + this.logger.debug(`[getCityTeamRewardDistribution] Result: ${JSON.stringify(distributions)}`) + return { distributions } + } + + /** + * 获取市区域权益分配方案 (35 USDT + 2%算力) + * + * 规则: + * 1. 查找该城市是否有正式市公司(CITY_COMPANY) + * 2. 如果有且 benefitActive=true,权益进该市公司自己的账户 + * 3. 如果没有或 benefitActive=false,权益进系统市账户 + */ + async getCityAreaRewardDistribution( + cityCode: string, + treeCount: number, + ): Promise<{ + distributions: Array<{ + accountSequence: number + treeCount: number + reason: string + isSystemAccount: boolean + }> + }> { + this.logger.debug( + `[getCityAreaRewardDistribution] cityCode=${cityCode}, treeCount=${treeCount}`, + ) + + // 系统市账户ID格式: 8 + 城市代码 + const systemCityAccountId = Number(`8${cityCode.padStart(6, '0')}`) + + // 查找该城市的正式市公司 + const cityCompany = await this.authorizationRepository.findCityCompanyByRegion(cityCode) + + if (cityCompany && cityCompany.benefitActive) { + // 正式市公司权益已激活,进该市公司账户 + return { + distributions: [ + { + accountSequence: Number(cityCompany.userId.accountSequence), + treeCount, + reason: '市区域权益已激活', + isSystemAccount: false, + }, + ], + } + } + + // 无正式市公司或权益未激活,进系统市账户 + const reason = cityCompany + ? `市区域权益未激活(考核中),进系统市账户` + : '无正式市公司授权,进系统市账户' + + return { + distributions: [ + { + accountSequence: systemCityAccountId, + treeCount, + reason, + isSystemAccount: true, + }, + ], + } + } } diff --git a/backend/services/authorization-service/src/domain/repositories/authorization-role.repository.ts b/backend/services/authorization-service/src/domain/repositories/authorization-role.repository.ts index 582054f3..a5db54dd 100644 --- a/backend/services/authorization-service/src/domain/repositories/authorization-role.repository.ts +++ b/backend/services/authorization-service/src/domain/repositories/authorization-role.repository.ts @@ -42,4 +42,33 @@ export interface IAuthorizationRoleRepository { accountSequences: bigint[], cityCode: string, ): Promise + /** + * 批量查询指定 accountSequence 列表中具有社区授权的用户(包括 benefitActive=false) + * 用于社区权益分配计算 + */ + findCommunityByAccountSequences(accountSequences: bigint[]): Promise + /** + * 批量查询指定 accountSequence 列表中具有授权省公司授权的用户(包括 benefitActive=false) + * 用于省团队权益分配计算 + */ + findAuthProvinceByAccountSequencesAndRegion( + accountSequences: bigint[], + provinceCode: string, + ): Promise + /** + * 批量查询指定 accountSequence 列表中具有授权市公司授权的用户(包括 benefitActive=false) + * 用于市团队权益分配计算 + */ + findAuthCityByAccountSequencesAndRegion( + accountSequences: bigint[], + cityCode: string, + ): Promise + /** + * 查找指定省份的正式省公司授权(用于省区域权益分配) + */ + findProvinceCompanyByRegion(provinceCode: string): Promise + /** + * 查找指定城市的正式市公司授权(用于市区域权益分配) + */ + findCityCompanyByRegion(cityCode: string): Promise } diff --git a/backend/services/authorization-service/src/infrastructure/persistence/repositories/authorization-role.repository.impl.ts b/backend/services/authorization-service/src/infrastructure/persistence/repositories/authorization-role.repository.impl.ts index 143927b2..b58a8e46 100644 --- a/backend/services/authorization-service/src/infrastructure/persistence/repositories/authorization-role.repository.impl.ts +++ b/backend/services/authorization-service/src/infrastructure/persistence/repositories/authorization-role.repository.impl.ts @@ -237,6 +237,89 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi return records.map((record) => this.toDomain(record)) } + async findCommunityByAccountSequences( + accountSequences: bigint[], + ): Promise { + if (accountSequences.length === 0) { + return [] + } + + const records = await this.prisma.authorizationRole.findMany({ + where: { + accountSequence: { in: accountSequences }, + roleType: RoleType.COMMUNITY, + status: AuthorizationStatus.AUTHORIZED, + // 注意:不过滤 benefitActive,用于计算社区权益分配 + }, + orderBy: { accountSequence: 'asc' }, + }) + return records.map((record) => this.toDomain(record)) + } + + async findAuthProvinceByAccountSequencesAndRegion( + accountSequences: bigint[], + provinceCode: string, + ): Promise { + if (accountSequences.length === 0) { + return [] + } + + const records = await this.prisma.authorizationRole.findMany({ + where: { + accountSequence: { in: accountSequences }, + roleType: RoleType.AUTH_PROVINCE_COMPANY, + regionCode: provinceCode, + status: AuthorizationStatus.AUTHORIZED, + // 注意:不过滤 benefitActive,用于计算省团队权益分配 + }, + orderBy: { accountSequence: 'asc' }, + }) + return records.map((record) => this.toDomain(record)) + } + + async findAuthCityByAccountSequencesAndRegion( + accountSequences: bigint[], + cityCode: string, + ): Promise { + if (accountSequences.length === 0) { + return [] + } + + const records = await this.prisma.authorizationRole.findMany({ + where: { + accountSequence: { in: accountSequences }, + roleType: RoleType.AUTH_CITY_COMPANY, + regionCode: cityCode, + status: AuthorizationStatus.AUTHORIZED, + // 注意:不过滤 benefitActive,用于计算市团队权益分配 + }, + orderBy: { accountSequence: 'asc' }, + }) + return records.map((record) => this.toDomain(record)) + } + + async findProvinceCompanyByRegion(provinceCode: string): Promise { + const record = await this.prisma.authorizationRole.findFirst({ + where: { + roleType: RoleType.PROVINCE_COMPANY, + regionCode: provinceCode, + status: AuthorizationStatus.AUTHORIZED, + }, + }) + return record ? this.toDomain(record) : null + } + + async findCityCompanyByRegion(cityCode: string): Promise { + const record = await this.prisma.authorizationRole.findFirst({ + where: { + roleType: RoleType.CITY_COMPANY, + regionCode: cityCode, + status: AuthorizationStatus.AUTHORIZED, + }, + }) + return record ? this.toDomain(record) : null + } + private toDomain(record: any): AuthorizationRole { const props: AuthorizationRoleProps = { authorizationId: AuthorizationId.create(record.id), diff --git a/backend/services/reward-service/src/domain/services/reward-calculation.service.ts b/backend/services/reward-service/src/domain/services/reward-calculation.service.ts index 829de27e..74cf100a 100644 --- a/backend/services/reward-service/src/domain/services/reward-calculation.service.ts +++ b/backend/services/reward-service/src/domain/services/reward-calculation.service.ts @@ -12,10 +12,32 @@ export interface IReferralServiceClient { }>; } +export interface RewardDistribution { + distributions: Array<{ + accountSequence: number; + treeCount: number; + reason: string; + }>; +} + +export interface AreaRewardDistribution { + distributions: Array<{ + accountSequence: number; + treeCount: number; + reason: string; + isSystemAccount: boolean; + }>; +} + export interface IAuthorizationServiceClient { findNearestAuthorizedProvince(userId: bigint, provinceCode: string): Promise; findNearestAuthorizedCity(userId: bigint, cityCode: string): Promise; findNearestCommunity(userId: bigint): Promise; + getCommunityRewardDistribution(userId: bigint, treeCount: number): Promise; + getProvinceTeamRewardDistribution(userId: bigint, provinceCode: string, treeCount: number): Promise; + getProvinceAreaRewardDistribution(provinceCode: string, treeCount: number): Promise; + getCityTeamRewardDistribution(userId: bigint, cityCode: string, treeCount: number): Promise; + getCityAreaRewardDistribution(cityCode: string, treeCount: number): Promise; } export const REFERRAL_SERVICE_CLIENT = Symbol('IReferralServiceClient'); @@ -107,49 +129,49 @@ export class RewardCalculationService { ); rewards.push(...shareRewards); - // 6. 省团队权益 (20 USDT) - const provinceTeamReward = await this.calculateProvinceTeamRight( + // 6. 省团队权益 (20 USDT) - 可能返回多条记录(考核分配) + const provinceTeamRewards = await this.calculateProvinceTeamRight( params.sourceOrderNo, params.sourceUserId, params.provinceCode, params.treeCount, ); - rewards.push(provinceTeamReward); + rewards.push(...provinceTeamRewards); - // 7. 省区域权益 (15 USDT + 1%算力) - const provinceAreaReward = this.calculateProvinceAreaRight( + // 7. 省区域权益 (15 USDT + 1%算力) - 可能返回多条记录(考核分配) + const provinceAreaRewards = await this.calculateProvinceAreaRight( params.sourceOrderNo, params.sourceUserId, params.provinceCode, params.treeCount, ); - rewards.push(provinceAreaReward); + rewards.push(...provinceAreaRewards); - // 8. 市团队权益 (40 USDT) - const cityTeamReward = await this.calculateCityTeamRight( + // 8. 市团队权益 (40 USDT) - 可能返回多条记录(考核分配) + const cityTeamRewards = await this.calculateCityTeamRight( params.sourceOrderNo, params.sourceUserId, params.cityCode, params.treeCount, ); - rewards.push(cityTeamReward); + rewards.push(...cityTeamRewards); - // 9. 市区域权益 (35 USDT + 2%算力) - const cityAreaReward = this.calculateCityAreaRight( + // 9. 市区域权益 (35 USDT + 2%算力) - 可能返回多条记录(考核分配) + const cityAreaRewards = await this.calculateCityAreaRight( params.sourceOrderNo, params.sourceUserId, params.cityCode, params.treeCount, ); - rewards.push(cityAreaReward); + rewards.push(...cityAreaRewards); - // 10. 社区权益 (80 USDT) - const communityReward = await this.calculateCommunityRight( + // 10. 社区权益 (80 USDT) - 可能返回多条记录(考核分配) + const communityRewards = await this.calculateCommunityRight( params.sourceOrderNo, params.sourceUserId, params.treeCount, ); - rewards.push(communityReward); + rewards.push(...communityRewards); return rewards; } @@ -338,199 +360,238 @@ export class RewardCalculationService { /** * 计算省团队权益 (20 USDT) + * 根据考核规则(500棵初审),可能返回多条分配记录 */ private async calculateProvinceTeamRight( sourceOrderNo: string, sourceUserId: bigint, provinceCode: string, treeCount: number, - ): Promise { + ): Promise { const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.PROVINCE_TEAM_RIGHT]; - const usdtAmount = Money.USDT(usdt * treeCount); - const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); - const rewardSource = RewardSource.create( - RightType.PROVINCE_TEAM_RIGHT, - sourceOrderNo, - sourceUserId, - ); - - // 查找最近的授权省公司 - const nearestProvince = await this.authorizationService.findNearestAuthorizedProvince( + // 调用 authorization-service 获取省团队权益分配方案 + const distribution = await this.authorizationService.getProvinceTeamRewardDistribution( sourceUserId, provinceCode, + treeCount, ); - if (nearestProvince) { - return RewardLedgerEntry.createSettleable({ - userId: nearestProvince, - accountSequence: nearestProvince, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: `省团队权益:来自${provinceCode}省的认种`, - }); - } else { - return RewardLedgerEntry.createSettleable({ - userId: HEADQUARTERS_COMMUNITY_USER_ID, - accountSequence: HEADQUARTERS_COMMUNITY_USER_ID, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: '省团队权益:无达标授权省公司,进总部社区', - }); + const rewards: RewardLedgerEntry[] = []; + + // 根据分配方案创建奖励记录 + for (const item of distribution.distributions) { + const itemUsdtAmount = Money.USDT(usdt * item.treeCount); + const itemHashpower = Hashpower.fromTreeCount(item.treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.PROVINCE_TEAM_RIGHT, + sourceOrderNo, + sourceUserId, + ); + + rewards.push( + RewardLedgerEntry.createSettleable({ + userId: BigInt(item.accountSequence), + accountSequence: BigInt(item.accountSequence), + rewardSource, + usdtAmount: itemUsdtAmount, + hashpowerAmount: itemHashpower, + memo: `省团队权益(${provinceCode}):${item.reason}`, + }), + ); } + + return rewards; } /** * 计算省区域权益 (15 USDT + 1%算力) + * 根据考核规则(50000棵),可能返回多条分配记录 + * - 未达标:进系统省账户 + * - 已达标:进正式省公司账户 */ - private calculateProvinceAreaRight( + private async calculateProvinceAreaRight( sourceOrderNo: string, sourceUserId: bigint, provinceCode: string, treeCount: number, - ): RewardLedgerEntry { + ): Promise { const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.PROVINCE_AREA_RIGHT]; - const usdtAmount = Money.USDT(usdt * treeCount); - const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); - const rewardSource = RewardSource.create( - RightType.PROVINCE_AREA_RIGHT, - sourceOrderNo, - sourceUserId, + // 调用 authorization-service 获取省区域权益分配方案 + const distribution = await this.authorizationService.getProvinceAreaRewardDistribution( + provinceCode, + treeCount, ); - // 进系统省公司账户 (使用特殊账户ID格式) - const systemProvinceAccountId = BigInt(`9${provinceCode.padStart(6, '0')}`); + const rewards: RewardLedgerEntry[] = []; - return RewardLedgerEntry.createSettleable({ - userId: systemProvinceAccountId, - accountSequence: systemProvinceAccountId, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: `省区域权益:${provinceCode}省,15U + 1%算力`, - }); + // 根据分配方案创建奖励记录 + for (const item of distribution.distributions) { + const itemUsdtAmount = Money.USDT(usdt * item.treeCount); + const itemHashpower = Hashpower.fromTreeCount(item.treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.PROVINCE_AREA_RIGHT, + sourceOrderNo, + sourceUserId, + ); + + rewards.push( + RewardLedgerEntry.createSettleable({ + userId: BigInt(item.accountSequence), + accountSequence: BigInt(item.accountSequence), + rewardSource, + usdtAmount: itemUsdtAmount, + hashpowerAmount: itemHashpower, + memo: `省区域权益(${provinceCode}):${item.reason}`, + }), + ); + } + + return rewards; } /** * 计算市团队权益 (40 USDT) + * 根据考核规则(100棵初审),可能返回多条分配记录 */ private async calculateCityTeamRight( sourceOrderNo: string, sourceUserId: bigint, cityCode: string, treeCount: number, - ): Promise { + ): Promise { const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.CITY_TEAM_RIGHT]; - const usdtAmount = Money.USDT(usdt * treeCount); - const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); - const rewardSource = RewardSource.create( - RightType.CITY_TEAM_RIGHT, - sourceOrderNo, - sourceUserId, - ); - - // 查找最近的授权市公司 - const nearestCity = await this.authorizationService.findNearestAuthorizedCity( + // 调用 authorization-service 获取市团队权益分配方案 + const distribution = await this.authorizationService.getCityTeamRewardDistribution( sourceUserId, cityCode, + treeCount, ); - if (nearestCity) { - return RewardLedgerEntry.createSettleable({ - userId: nearestCity, - accountSequence: nearestCity, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: `市团队权益:来自${cityCode}市的认种`, - }); - } else { - return RewardLedgerEntry.createSettleable({ - userId: HEADQUARTERS_COMMUNITY_USER_ID, - accountSequence: HEADQUARTERS_COMMUNITY_USER_ID, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: '市团队权益:无达标授权市公司,进总部社区', - }); + const rewards: RewardLedgerEntry[] = []; + + // 根据分配方案创建奖励记录 + for (const item of distribution.distributions) { + const itemUsdtAmount = Money.USDT(usdt * item.treeCount); + const itemHashpower = Hashpower.fromTreeCount(item.treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.CITY_TEAM_RIGHT, + sourceOrderNo, + sourceUserId, + ); + + rewards.push( + RewardLedgerEntry.createSettleable({ + userId: BigInt(item.accountSequence), + accountSequence: BigInt(item.accountSequence), + rewardSource, + usdtAmount: itemUsdtAmount, + hashpowerAmount: itemHashpower, + memo: `市团队权益(${cityCode}):${item.reason}`, + }), + ); } + + return rewards; } /** * 计算市区域权益 (35 USDT + 2%算力) + * 根据考核规则(10000棵),可能返回多条分配记录 + * - 未达标:进系统市账户 + * - 已达标:进正式市公司账户 */ - private calculateCityAreaRight( + private async calculateCityAreaRight( sourceOrderNo: string, sourceUserId: bigint, cityCode: string, treeCount: number, - ): RewardLedgerEntry { + ): Promise { const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.CITY_AREA_RIGHT]; - const usdtAmount = Money.USDT(usdt * treeCount); - const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); - const rewardSource = RewardSource.create( - RightType.CITY_AREA_RIGHT, - sourceOrderNo, - sourceUserId, + // 调用 authorization-service 获取市区域权益分配方案 + const distribution = await this.authorizationService.getCityAreaRewardDistribution( + cityCode, + treeCount, ); - // 进系统市公司账户 - const systemCityAccountId = BigInt(`8${cityCode.padStart(6, '0')}`); + const rewards: RewardLedgerEntry[] = []; - return RewardLedgerEntry.createSettleable({ - userId: systemCityAccountId, - accountSequence: systemCityAccountId, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: `市区域权益:${cityCode}市,35U + 2%算力`, - }); + // 根据分配方案创建奖励记录 + for (const item of distribution.distributions) { + const itemUsdtAmount = Money.USDT(usdt * item.treeCount); + const itemHashpower = Hashpower.fromTreeCount(item.treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.CITY_AREA_RIGHT, + sourceOrderNo, + sourceUserId, + ); + + rewards.push( + RewardLedgerEntry.createSettleable({ + userId: BigInt(item.accountSequence), + accountSequence: BigInt(item.accountSequence), + rewardSource, + usdtAmount: itemUsdtAmount, + hashpowerAmount: itemHashpower, + memo: `市区域权益(${cityCode}):${item.reason}`, + }), + ); + } + + return rewards; } /** * 计算社区权益 (80 USDT) + * 根据考核规则,可能返回多条分配记录: + * - 权益已激活:全部给该社区 + * - 权益未激活:考核前的部分给上级社区/总部,考核后的部分给该社区 */ private async calculateCommunityRight( sourceOrderNo: string, sourceUserId: bigint, treeCount: number, - ): Promise { + ): Promise { const { usdt, hashpowerPercent } = RIGHT_AMOUNTS[RightType.COMMUNITY_RIGHT]; - const usdtAmount = Money.USDT(usdt * treeCount); - const hashpower = Hashpower.fromTreeCount(treeCount, hashpowerPercent); - const rewardSource = RewardSource.create( - RightType.COMMUNITY_RIGHT, - sourceOrderNo, + // 调用 authorization-service 获取社区权益分配方案 + const distribution = await this.authorizationService.getCommunityRewardDistribution( sourceUserId, + treeCount, ); - // 查找最近的社区 - const nearestCommunity = await this.authorizationService.findNearestCommunity(sourceUserId); + const rewards: RewardLedgerEntry[] = []; - if (nearestCommunity) { - return RewardLedgerEntry.createSettleable({ - userId: nearestCommunity, - accountSequence: nearestCommunity, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: '社区权益:来自社区成员的认种', - }); - } else { - return RewardLedgerEntry.createSettleable({ - userId: HEADQUARTERS_COMMUNITY_USER_ID, - accountSequence: HEADQUARTERS_COMMUNITY_USER_ID, - rewardSource, - usdtAmount, - hashpowerAmount: hashpower, - memo: '社区权益:无归属社区,进总部社区', - }); + // 根据分配方案创建奖励记录 + for (const item of distribution.distributions) { + const itemUsdtAmount = Money.USDT(usdt * item.treeCount); + const itemHashpower = Hashpower.fromTreeCount(item.treeCount, hashpowerPercent); + + const rewardSource = RewardSource.create( + RightType.COMMUNITY_RIGHT, + sourceOrderNo, + sourceUserId, + ); + + rewards.push( + RewardLedgerEntry.createSettleable({ + userId: BigInt(item.accountSequence), + accountSequence: BigInt(item.accountSequence), + rewardSource, + usdtAmount: itemUsdtAmount, + hashpowerAmount: itemHashpower, + memo: `社区权益:${item.reason}`, + }), + ); } + + return rewards; } } diff --git a/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts b/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts index cf56df35..9b3b0cbc 100644 --- a/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts +++ b/backend/services/reward-service/src/infrastructure/external/authorization-service/authorization-service.client.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { IAuthorizationServiceClient } from '../../../domain/services/reward-calculation.service'; +import { IAuthorizationServiceClient, RewardDistribution, AreaRewardDistribution } from '../../../domain/services/reward-calculation.service'; // authorization-service 返回格式(经过 TransformInterceptor 包装) interface AuthorizationServiceResponse { @@ -13,6 +13,23 @@ interface NearestAuthorizationResult { accountSequence: number | null; } +interface RewardDistributionResult { + distributions: Array<{ + accountSequence: number; + treeCount: number; + reason: string; + }>; +} + +interface AreaRewardDistributionResult { + distributions: Array<{ + accountSequence: number; + treeCount: number; + reason: string; + isSystemAccount: boolean; + }>; +} + @Injectable() export class AuthorizationServiceClient implements IAuthorizationServiceClient { private readonly logger = new Logger(AuthorizationServiceClient.name); @@ -85,4 +102,214 @@ export class AuthorizationServiceClient implements IAuthorizationServiceClient { return null; } } + + async getCommunityRewardDistribution(userId: bigint, treeCount: number): Promise { + const HEADQUARTERS_ACCOUNT_SEQUENCE = 1; + const defaultDistribution: RewardDistribution = { + distributions: [ + { + accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, + treeCount, + reason: '服务调用失败,默认进总部社区', + }, + ], + }; + + try { + const response = await fetch( + `${this.baseUrl}/api/v1/authorization/community-reward-distribution?accountSequence=${userId}&treeCount=${treeCount}`, + ); + + if (!response.ok) { + this.logger.warn(`Failed to get community reward distribution for user ${userId}, treeCount ${treeCount}`); + return defaultDistribution; + } + + // authorization-service 返回格式: { success, data: { distributions }, timestamp } + const result: AuthorizationServiceResponse = await response.json(); + + if (!result.data?.distributions || result.data.distributions.length === 0) { + this.logger.warn(`Empty distributions returned for user ${userId}`); + return defaultDistribution; + } + + this.logger.debug( + `getCommunityRewardDistribution for userId=${userId}, treeCount=${treeCount}: ` + + `distributions=${JSON.stringify(result.data.distributions)}`, + ); + + return result.data; + } catch (error) { + this.logger.error(`Error getting community reward distribution:`, error); + return defaultDistribution; + } + } + + async getProvinceTeamRewardDistribution(userId: bigint, provinceCode: string, treeCount: number): Promise { + const HEADQUARTERS_ACCOUNT_SEQUENCE = 1; + const defaultDistribution: RewardDistribution = { + distributions: [ + { + accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, + treeCount, + reason: '服务调用失败,默认进总部社区', + }, + ], + }; + + try { + const response = await fetch( + `${this.baseUrl}/api/v1/authorization/province-team-reward-distribution?accountSequence=${userId}&provinceCode=${provinceCode}&treeCount=${treeCount}`, + ); + + if (!response.ok) { + this.logger.warn(`Failed to get province team reward distribution for user ${userId}, province ${provinceCode}`); + return defaultDistribution; + } + + const result: AuthorizationServiceResponse = await response.json(); + + if (!result.data?.distributions || result.data.distributions.length === 0) { + this.logger.warn(`Empty province team distributions returned for user ${userId}`); + return defaultDistribution; + } + + this.logger.debug( + `getProvinceTeamRewardDistribution for userId=${userId}, provinceCode=${provinceCode}, treeCount=${treeCount}: ` + + `distributions=${JSON.stringify(result.data.distributions)}`, + ); + + return result.data; + } catch (error) { + this.logger.error(`Error getting province team reward distribution:`, error); + return defaultDistribution; + } + } + + async getProvinceAreaRewardDistribution(provinceCode: string, treeCount: number): Promise { + // 系统省账户ID: 9 + provinceCode (6位) + const systemProvinceAccountId = Number(`9${provinceCode.padStart(6, '0')}`); + const defaultDistribution: AreaRewardDistribution = { + distributions: [ + { + accountSequence: systemProvinceAccountId, + treeCount, + reason: '服务调用失败,默认进系统省账户', + isSystemAccount: true, + }, + ], + }; + + try { + const response = await fetch( + `${this.baseUrl}/api/v1/authorization/province-area-reward-distribution?provinceCode=${provinceCode}&treeCount=${treeCount}`, + ); + + if (!response.ok) { + this.logger.warn(`Failed to get province area reward distribution for province ${provinceCode}`); + return defaultDistribution; + } + + const result: AuthorizationServiceResponse = await response.json(); + + if (!result.data?.distributions || result.data.distributions.length === 0) { + this.logger.warn(`Empty province area distributions returned for province ${provinceCode}`); + return defaultDistribution; + } + + this.logger.debug( + `getProvinceAreaRewardDistribution for provinceCode=${provinceCode}, treeCount=${treeCount}: ` + + `distributions=${JSON.stringify(result.data.distributions)}`, + ); + + return result.data; + } catch (error) { + this.logger.error(`Error getting province area reward distribution:`, error); + return defaultDistribution; + } + } + + async getCityTeamRewardDistribution(userId: bigint, cityCode: string, treeCount: number): Promise { + const HEADQUARTERS_ACCOUNT_SEQUENCE = 1; + const defaultDistribution: RewardDistribution = { + distributions: [ + { + accountSequence: HEADQUARTERS_ACCOUNT_SEQUENCE, + treeCount, + reason: '服务调用失败,默认进总部社区', + }, + ], + }; + + try { + const response = await fetch( + `${this.baseUrl}/api/v1/authorization/city-team-reward-distribution?accountSequence=${userId}&cityCode=${cityCode}&treeCount=${treeCount}`, + ); + + if (!response.ok) { + this.logger.warn(`Failed to get city team reward distribution for user ${userId}, city ${cityCode}`); + return defaultDistribution; + } + + const result: AuthorizationServiceResponse = await response.json(); + + if (!result.data?.distributions || result.data.distributions.length === 0) { + this.logger.warn(`Empty city team distributions returned for user ${userId}`); + return defaultDistribution; + } + + this.logger.debug( + `getCityTeamRewardDistribution for userId=${userId}, cityCode=${cityCode}, treeCount=${treeCount}: ` + + `distributions=${JSON.stringify(result.data.distributions)}`, + ); + + return result.data; + } catch (error) { + this.logger.error(`Error getting city team reward distribution:`, error); + return defaultDistribution; + } + } + + async getCityAreaRewardDistribution(cityCode: string, treeCount: number): Promise { + // 系统市账户ID: 8 + cityCode (6位) + const systemCityAccountId = Number(`8${cityCode.padStart(6, '0')}`); + const defaultDistribution: AreaRewardDistribution = { + distributions: [ + { + accountSequence: systemCityAccountId, + treeCount, + reason: '服务调用失败,默认进系统市账户', + isSystemAccount: true, + }, + ], + }; + + try { + const response = await fetch( + `${this.baseUrl}/api/v1/authorization/city-area-reward-distribution?cityCode=${cityCode}&treeCount=${treeCount}`, + ); + + if (!response.ok) { + this.logger.warn(`Failed to get city area reward distribution for city ${cityCode}`); + return defaultDistribution; + } + + const result: AuthorizationServiceResponse = await response.json(); + + if (!result.data?.distributions || result.data.distributions.length === 0) { + this.logger.warn(`Empty city area distributions returned for city ${cityCode}`); + return defaultDistribution; + } + + this.logger.debug( + `getCityAreaRewardDistribution for cityCode=${cityCode}, treeCount=${treeCount}: ` + + `distributions=${JSON.stringify(result.data.distributions)}`, + ); + + return result.data; + } catch (error) { + this.logger.error(`Error getting city area reward distribution:`, error); + return defaultDistribution; + } + } }