feat(authorization): add community hierarchy API
- Add internal referral-chain API in referral-service for getting ancestor path and team members - Extend ReferralServiceClient to call referral-chain API - Add findActiveCommunityByAccountSequences repository method - Add getCommunityHierarchy application service method - Add GET /authorizations/my/community-hierarchy endpoint - Update frontend with CommunityHierarchy model and getMyCommunityHierarchy method API returns: - myCommunity: user's own community authorization (if any) - parentCommunity: nearest parent community (defaults to 总部社区 if none) - childCommunities: nearest child communities in user's team 🤖 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
c1d30b0a65
commit
9cf8b5305b
|
|
@ -38,6 +38,7 @@ import {
|
|||
AuthorizationResponse,
|
||||
ApplyAuthorizationResponse,
|
||||
StickmanRankingResponse,
|
||||
CommunityHierarchyResponse,
|
||||
} from '@/api/dto/response'
|
||||
import { CurrentUser } from '@/shared/decorators'
|
||||
import { JwtAuthGuard } from '@/shared/guards'
|
||||
|
|
@ -97,6 +98,15 @@ export class AuthorizationController {
|
|||
return await this.applicationService.getUserAuthorizations(user.accountSequence)
|
||||
}
|
||||
|
||||
@Get('my/community-hierarchy')
|
||||
@ApiOperation({ summary: '获取我的社区层级(上级社区和下级社区)' })
|
||||
@ApiResponse({ status: 200, type: CommunityHierarchyResponse })
|
||||
async getMyCommunityHierarchy(
|
||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
||||
): Promise<CommunityHierarchyResponse> {
|
||||
return await this.applicationService.getCommunityHierarchy(user.accountSequence)
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: '获取授权详情' })
|
||||
@ApiParam({ name: 'id', description: '授权ID' })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||
|
||||
/**
|
||||
* 社区简要信息
|
||||
*/
|
||||
export class CommunityInfo {
|
||||
@ApiProperty({ description: '授权ID' })
|
||||
authorizationId: string
|
||||
|
||||
@ApiProperty({ description: '账户序列号' })
|
||||
accountSequence: number
|
||||
|
||||
@ApiProperty({ description: '社区名称' })
|
||||
communityName: string
|
||||
|
||||
@ApiPropertyOptional({ description: '用户ID' })
|
||||
userId?: string
|
||||
|
||||
@ApiProperty({ description: '是否为总部社区' })
|
||||
isHeadquarters: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 社区层级响应
|
||||
*/
|
||||
export class CommunityHierarchyResponse {
|
||||
@ApiPropertyOptional({ description: '我的社区授权(如果有)', type: CommunityInfo })
|
||||
myCommunity: CommunityInfo | null
|
||||
|
||||
@ApiProperty({ description: '上级社区(最近的,如果没有则为总部社区)', type: CommunityInfo })
|
||||
parentCommunity: CommunityInfo
|
||||
|
||||
@ApiProperty({ description: '下级社区列表(最近的,按accountSequence排序)', type: [CommunityInfo] })
|
||||
childCommunities: CommunityInfo[]
|
||||
|
||||
@ApiProperty({ description: '是否有上级社区(非总部)' })
|
||||
hasParentCommunity: boolean
|
||||
|
||||
@ApiProperty({ description: '下级社区数量' })
|
||||
childCommunityCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 总部社区常量
|
||||
*/
|
||||
export const HEADQUARTERS_COMMUNITY: CommunityInfo = {
|
||||
authorizationId: 'headquarters',
|
||||
accountSequence: 0,
|
||||
communityName: '总部社区',
|
||||
userId: undefined,
|
||||
isHeadquarters: true,
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from './authorization.response'
|
||||
export * from './community-hierarchy.response'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* 社区简要信息
|
||||
*/
|
||||
export interface CommunityInfoDTO {
|
||||
authorizationId: string
|
||||
accountSequence: number
|
||||
communityName: string
|
||||
userId?: string
|
||||
isHeadquarters: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 社区层级响应 DTO
|
||||
*/
|
||||
export interface CommunityHierarchyDTO {
|
||||
/** 我的社区授权(如果有) */
|
||||
myCommunity: CommunityInfoDTO | null
|
||||
/** 上级社区(最近的,如果没有则为总部社区) */
|
||||
parentCommunity: CommunityInfoDTO
|
||||
/** 下级社区列表(最近的) */
|
||||
childCommunities: CommunityInfoDTO[]
|
||||
/** 是否有上级社区(非总部) */
|
||||
hasParentCommunity: boolean
|
||||
/** 下级社区数量 */
|
||||
childCommunityCount: number
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from './authorization.dto'
|
||||
export * from './community-hierarchy.dto'
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
TeamStatistics,
|
||||
} from '@/domain/services'
|
||||
import { EventPublisherService } from '@/infrastructure/kafka'
|
||||
import { ReferralServiceClient } from '@/infrastructure/external'
|
||||
import { ApplicationError, NotFoundError } from '@/shared/exceptions'
|
||||
import {
|
||||
ApplyCommunityAuthCommand,
|
||||
|
|
@ -37,7 +38,7 @@ import {
|
|||
GrantMonthlyBypassCommand,
|
||||
ExemptLocalPercentageCheckCommand,
|
||||
} from '@/application/commands'
|
||||
import { AuthorizationDTO, StickmanRankingDTO } from '@/application/dto'
|
||||
import { AuthorizationDTO, StickmanRankingDTO, CommunityHierarchyDTO } from '@/application/dto'
|
||||
|
||||
export const REFERRAL_REPOSITORY = Symbol('IReferralRepository')
|
||||
export const TEAM_STATISTICS_REPOSITORY = Symbol('ITeamStatisticsRepository')
|
||||
|
|
@ -57,6 +58,7 @@ export class AuthorizationApplicationService {
|
|||
@Inject(TEAM_STATISTICS_REPOSITORY)
|
||||
private readonly statsRepository: ITeamStatisticsRepository,
|
||||
private readonly eventPublisher: EventPublisherService,
|
||||
private readonly referralServiceClient: ReferralServiceClient,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
@ -493,4 +495,130 @@ export class AuthorizationApplicationService {
|
|||
updatedAt: auth.updatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的社区层级信息
|
||||
* - myCommunity: 我的社区授权(如果有)
|
||||
* - parentCommunity: 上级社区(沿推荐链往上找最近的,如果没有则返回总部社区)
|
||||
* - childCommunities: 下级社区(在我的团队中找最近的社区)
|
||||
*/
|
||||
async getCommunityHierarchy(accountSequence: number): Promise<CommunityHierarchyDTO> {
|
||||
this.logger.debug(`[getCommunityHierarchy] accountSequence=${accountSequence}`)
|
||||
|
||||
// 1. 查询我的社区授权
|
||||
const myCommunity = await this.authorizationRepository.findByAccountSequenceAndRoleType(
|
||||
BigInt(accountSequence),
|
||||
RoleType.COMMUNITY,
|
||||
)
|
||||
|
||||
// 2. 获取我的祖先链(推荐链)
|
||||
const ancestorAccountSequences = await this.referralServiceClient.getReferralChain(BigInt(accountSequence))
|
||||
this.logger.debug(`[getCommunityHierarchy] ancestorPath: ${ancestorAccountSequences.join(',')}`)
|
||||
|
||||
// 3. 查找上级社区(在祖先链中找最近的有社区授权的用户)
|
||||
let parentCommunityAuth: AuthorizationRole | null = null
|
||||
if (ancestorAccountSequences.length > 0) {
|
||||
const ancestorCommunities = await this.authorizationRepository.findActiveCommunityByAccountSequences(
|
||||
ancestorAccountSequences.map((seq) => BigInt(seq)),
|
||||
)
|
||||
|
||||
// 找最近的(ancestorAccountSequences 是从直接推荐人到根节点的顺序)
|
||||
if (ancestorCommunities.length > 0) {
|
||||
// 按祖先链顺序找第一个匹配的
|
||||
for (const ancestorSeq of ancestorAccountSequences) {
|
||||
const found = ancestorCommunities.find(
|
||||
(auth) => Number(auth.userId.accountSequence) === ancestorSeq,
|
||||
)
|
||||
if (found) {
|
||||
parentCommunityAuth = found
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 获取我的团队成员
|
||||
const teamMemberAccountSequences = await this.referralServiceClient.getTeamMembers(BigInt(accountSequence))
|
||||
this.logger.debug(`[getCommunityHierarchy] teamMembers count: ${teamMemberAccountSequences.length}`)
|
||||
|
||||
// 5. 查找下级社区(在团队成员中找最近的有社区授权的用户)
|
||||
// "最近" 的定义:直接下级优先,然后是下级的下级,以此类推
|
||||
// 由于 getTeamMembers 返回的是广度优先遍历结果,可以直接使用顺序
|
||||
let childCommunityAuths: AuthorizationRole[] = []
|
||||
if (teamMemberAccountSequences.length > 0) {
|
||||
const teamCommunities = await this.authorizationRepository.findActiveCommunityByAccountSequences(
|
||||
teamMemberAccountSequences.map((seq) => BigInt(seq)),
|
||||
)
|
||||
|
||||
// 只保留"最近的"下级社区
|
||||
// 如果一个社区的上级不在我的直接团队成员中,或者其上级就是我,则它是"最近的"
|
||||
// 简化实现:返回所有团队中的社区,前端可以根据需要过滤
|
||||
// 但按用户要求"只计算最近的那个",这里需要做过滤
|
||||
// 算法:如果某个社区 A 的祖先中有另一个社区 B 也在团队中,则 A 不是最近的
|
||||
|
||||
const communityAccountSeqs = new Set(teamCommunities.map((c) => Number(c.userId.accountSequence)))
|
||||
|
||||
for (const comm of teamCommunities) {
|
||||
// 获取这个社区成员的祖先链
|
||||
const commAncestors = await this.referralServiceClient.getReferralChain(comm.userId.accountSequence)
|
||||
|
||||
// 检查这个社区是否有"更近"的祖先社区
|
||||
let hasCloserAncestorCommunity = false
|
||||
for (const ancestorSeq of commAncestors) {
|
||||
// 如果祖先是我,停止检查
|
||||
if (ancestorSeq === accountSequence) {
|
||||
break
|
||||
}
|
||||
// 如果祖先也是社区且在我的团队中,则当前社区不是最近的
|
||||
if (communityAccountSeqs.has(ancestorSeq)) {
|
||||
hasCloserAncestorCommunity = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasCloserAncestorCommunity) {
|
||||
childCommunityAuths.push(comm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 构建响应
|
||||
const HEADQUARTERS_COMMUNITY = {
|
||||
authorizationId: 'headquarters',
|
||||
accountSequence: 0,
|
||||
communityName: '总部社区',
|
||||
userId: undefined,
|
||||
isHeadquarters: true,
|
||||
}
|
||||
|
||||
return {
|
||||
myCommunity: myCommunity && myCommunity.status === AuthorizationStatus.AUTHORIZED
|
||||
? {
|
||||
authorizationId: myCommunity.authorizationId.value,
|
||||
accountSequence: Number(myCommunity.userId.accountSequence),
|
||||
communityName: myCommunity.displayTitle,
|
||||
userId: myCommunity.userId.value,
|
||||
isHeadquarters: false,
|
||||
}
|
||||
: null,
|
||||
parentCommunity: parentCommunityAuth
|
||||
? {
|
||||
authorizationId: parentCommunityAuth.authorizationId.value,
|
||||
accountSequence: Number(parentCommunityAuth.userId.accountSequence),
|
||||
communityName: parentCommunityAuth.displayTitle,
|
||||
userId: parentCommunityAuth.userId.value,
|
||||
isHeadquarters: false,
|
||||
}
|
||||
: HEADQUARTERS_COMMUNITY,
|
||||
childCommunities: childCommunityAuths.map((auth) => ({
|
||||
authorizationId: auth.authorizationId.value,
|
||||
accountSequence: Number(auth.userId.accountSequence),
|
||||
communityName: auth.displayTitle,
|
||||
userId: auth.userId.value,
|
||||
isHeadquarters: false,
|
||||
})),
|
||||
hasParentCommunity: parentCommunityAuth !== null,
|
||||
childCommunityCount: childCommunityAuths.length,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,4 +24,8 @@ export interface IAuthorizationRoleRepository {
|
|||
findPendingByUserId(userId: UserId): Promise<AuthorizationRole[]>
|
||||
findByStatus(status: AuthorizationStatus): Promise<AuthorizationRole[]>
|
||||
delete(authorizationId: AuthorizationId): Promise<void>
|
||||
/**
|
||||
* 批量查询指定 accountSequence 列表中具有活跃社区授权的用户
|
||||
*/
|
||||
findActiveCommunityByAccountSequences(accountSequences: bigint[]): Promise<AuthorizationRole[]>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,24 @@ interface ReferralTeamStatsResponse {
|
|||
provinceCityDistribution: Record<string, Record<string, number>> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推荐链数据接口
|
||||
*/
|
||||
interface ReferralChainResponse {
|
||||
accountSequence: number;
|
||||
userId: string | null;
|
||||
ancestorPath: string[];
|
||||
referrerId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 团队成员数据接口
|
||||
*/
|
||||
interface TeamMembersResponse {
|
||||
accountSequence: number;
|
||||
teamMembers: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 适配器类:将 referral-service 返回的数据转换为 authorization-service 需要的格式
|
||||
*/
|
||||
|
|
@ -159,4 +177,61 @@ export class ReferralServiceClient implements ITeamStatisticsRepository, OnModul
|
|||
private createEmptyStats(userId: string, accountSequence: bigint): TeamStatistics {
|
||||
return new TeamStatisticsAdapter(userId, accountSequence, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的祖先链(推荐链)
|
||||
* 返回从直接推荐人到根节点的 accountSequence 列表
|
||||
*/
|
||||
async getReferralChain(accountSequence: bigint): Promise<number[]> {
|
||||
if (!this.enabled) {
|
||||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.debug(`[HTTP] GET /internal/referral-chain/${accountSequence}`);
|
||||
|
||||
const response = await this.httpClient.get<ReferralChainResponse>(
|
||||
`/api/v1/internal/referral-chain/${accountSequence}`,
|
||||
);
|
||||
|
||||
if (!response.data || !response.data.ancestorPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// ancestorPath 存储的是 userId (bigint string),我们需要映射到 accountSequence
|
||||
// 由于 referral-service 中 userId = BigInt(accountSequence),可以直接转换
|
||||
return response.data.ancestorPath.map((id) => Number(id));
|
||||
} catch (error) {
|
||||
this.logger.error(`[HTTP] Failed to get referral chain for accountSequence ${accountSequence}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的团队成员 accountSequence 列表
|
||||
*/
|
||||
async getTeamMembers(accountSequence: bigint): Promise<number[]> {
|
||||
if (!this.enabled) {
|
||||
this.logger.debug('[DISABLED] Referral service integration is disabled');
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.debug(`[HTTP] GET /internal/referral-chain/${accountSequence}/team-members`);
|
||||
|
||||
const response = await this.httpClient.get<TeamMembersResponse>(
|
||||
`/api/v1/internal/referral-chain/${accountSequence}/team-members`,
|
||||
);
|
||||
|
||||
if (!response.data || !response.data.teamMembers) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.data.teamMembers;
|
||||
} catch (error) {
|
||||
this.logger.error(`[HTTP] Failed to get team members for accountSequence ${accountSequence}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,6 +176,25 @@ export class AuthorizationRoleRepositoryImpl implements IAuthorizationRoleReposi
|
|||
})
|
||||
}
|
||||
|
||||
async findActiveCommunityByAccountSequences(
|
||||
accountSequences: bigint[],
|
||||
): Promise<AuthorizationRole[]> {
|
||||
if (accountSequences.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const records = await this.prisma.authorizationRole.findMany({
|
||||
where: {
|
||||
accountSequence: { in: accountSequences },
|
||||
roleType: RoleType.COMMUNITY,
|
||||
status: AuthorizationStatus.AUTHORIZED,
|
||||
benefitActive: true,
|
||||
},
|
||||
orderBy: { accountSequence: 'asc' },
|
||||
})
|
||||
return records.map((record) => this.toDomain(record))
|
||||
}
|
||||
|
||||
private toDomain(record: any): AuthorizationRole {
|
||||
const props: AuthorizationRoleProps = {
|
||||
authorizationId: AuthorizationId.create(record.id),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ describe('Domain Services Integration Tests', () => {
|
|||
delete: jest.fn(),
|
||||
findByAccountSequenceAndRoleType: jest.fn(),
|
||||
findByAccountSequence: jest.fn(),
|
||||
findActiveCommunityByAccountSequences: jest.fn(),
|
||||
}
|
||||
|
||||
const mockMonthlyAssessmentRepository: jest.Mocked<IMonthlyAssessmentRepository> = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export * from './referral.controller';
|
||||
export * from './team-statistics.controller';
|
||||
export * from './internal-team-statistics.controller';
|
||||
export * from './internal-referral-chain.controller';
|
||||
export * from './health.controller';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
import { Controller, Get, Param, Logger, Inject, NotFoundException } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
|
||||
import {
|
||||
REFERRAL_RELATIONSHIP_REPOSITORY,
|
||||
IReferralRelationshipRepository,
|
||||
} from '../../domain';
|
||||
|
||||
/**
|
||||
* 内部推荐链API - 供其他微服务调用
|
||||
* 无需JWT认证(服务间内部通信)
|
||||
*/
|
||||
@ApiTags('Internal Referral Chain API')
|
||||
@Controller('internal/referral-chain')
|
||||
export class InternalReferralChainController {
|
||||
private readonly logger = new Logger(InternalReferralChainController.name);
|
||||
|
||||
constructor(
|
||||
@Inject(REFERRAL_RELATIONSHIP_REPOSITORY)
|
||||
private readonly referralRepo: IReferralRelationshipRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取用户的祖先链(从直接推荐人到根节点)
|
||||
*/
|
||||
@Get(':accountSequence')
|
||||
@ApiOperation({ summary: '获取用户推荐链(内部API)' })
|
||||
@ApiParam({ name: 'accountSequence', description: '账户序列号' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '推荐链数据',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
accountSequence: { type: 'number' },
|
||||
userId: { type: 'string' },
|
||||
ancestorPath: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: '祖先链(从直接推荐人到根节点的accountSequence列表)',
|
||||
},
|
||||
referrerId: { type: 'string', nullable: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
async getReferralChain(@Param('accountSequence') accountSequence: string) {
|
||||
this.logger.debug(`[INTERNAL] getReferralChain: accountSequence=${accountSequence}`);
|
||||
|
||||
const relationship = await this.referralRepo.findByAccountSequence(Number(accountSequence));
|
||||
|
||||
if (!relationship) {
|
||||
this.logger.debug(`[INTERNAL] No referral found for accountSequence: ${accountSequence}`);
|
||||
// 返回空的祖先链而不是抛出错误
|
||||
return {
|
||||
accountSequence: Number(accountSequence),
|
||||
userId: null,
|
||||
ancestorPath: [],
|
||||
referrerId: null,
|
||||
};
|
||||
}
|
||||
|
||||
// ancestorPath 存储的是 userId (bigint),需要转换为字符串
|
||||
const ancestorPath = relationship.referralChain.map((id) => id.toString());
|
||||
|
||||
return {
|
||||
accountSequence: relationship.accountSequence,
|
||||
userId: relationship.userId.toString(),
|
||||
ancestorPath,
|
||||
referrerId: relationship.referrerId?.toString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取用户的祖先链
|
||||
*/
|
||||
@Get('batch/:accountSequences')
|
||||
@ApiOperation({ summary: '批量获取用户推荐链(内部API)' })
|
||||
@ApiParam({ name: 'accountSequences', description: '账户序列号列表,逗号分隔' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '批量推荐链数据',
|
||||
})
|
||||
async getBatchReferralChains(@Param('accountSequences') accountSequences: string) {
|
||||
const sequences = accountSequences.split(',').map((s) => Number(s.trim()));
|
||||
this.logger.debug(`[INTERNAL] getBatchReferralChains: ${sequences.length} accounts`);
|
||||
|
||||
const results: Record<number, { userId: string | null; ancestorPath: string[]; referrerId: string | null }> = {};
|
||||
|
||||
for (const seq of sequences) {
|
||||
const relationship = await this.referralRepo.findByAccountSequence(seq);
|
||||
if (relationship) {
|
||||
results[seq] = {
|
||||
userId: relationship.userId.toString(),
|
||||
ancestorPath: relationship.referralChain.map((id) => id.toString()),
|
||||
referrerId: relationship.referrerId?.toString() ?? null,
|
||||
};
|
||||
} else {
|
||||
results[seq] = {
|
||||
userId: null,
|
||||
ancestorPath: [],
|
||||
referrerId: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的团队成员(下级用户)的accountSequence列表
|
||||
* 用于查找下级社区
|
||||
*/
|
||||
@Get(':accountSequence/team-members')
|
||||
@ApiOperation({ summary: '获取团队成员accountSequence列表(内部API)' })
|
||||
@ApiParam({ name: 'accountSequence', description: '账户序列号' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: '团队成员列表',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
accountSequence: { type: 'number' },
|
||||
teamMembers: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
description: '团队成员accountSequence列表(直接和间接下级)',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
async getTeamMembers(@Param('accountSequence') accountSequence: string) {
|
||||
this.logger.debug(`[INTERNAL] getTeamMembers: accountSequence=${accountSequence}`);
|
||||
|
||||
const relationship = await this.referralRepo.findByAccountSequence(Number(accountSequence));
|
||||
|
||||
if (!relationship) {
|
||||
return {
|
||||
accountSequence: Number(accountSequence),
|
||||
teamMembers: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 获取所有直推用户
|
||||
const directReferrals = await this.referralRepo.findDirectReferrals(relationship.userId);
|
||||
|
||||
// 递归获取所有下级成员的accountSequence
|
||||
const teamMembers: number[] = [];
|
||||
const queue = [...directReferrals];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!;
|
||||
teamMembers.push(current.accountSequence);
|
||||
|
||||
// 获取当前用户的直推
|
||||
const subReferrals = await this.referralRepo.findDirectReferrals(current.userId);
|
||||
queue.push(...subReferrals);
|
||||
}
|
||||
|
||||
return {
|
||||
accountSequence: Number(accountSequence),
|
||||
teamMembers,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import {
|
|||
ReferralController,
|
||||
TeamStatisticsController,
|
||||
InternalTeamStatisticsController,
|
||||
InternalReferralChainController,
|
||||
HealthController,
|
||||
} from '../api';
|
||||
import { InternalReferralController } from '../api/controllers/referral.controller';
|
||||
|
|
@ -16,6 +17,7 @@ import { InternalReferralController } from '../api/controllers/referral.controll
|
|||
ReferralController,
|
||||
TeamStatisticsController,
|
||||
InternalTeamStatisticsController,
|
||||
InternalReferralChainController,
|
||||
HealthController,
|
||||
InternalReferralController,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class ApiEndpoints {
|
|||
// Authorization (-> Authorization Service)
|
||||
static const String authorizations = '/authorizations';
|
||||
static const String myAuthorizations = '$authorizations/my'; // 获取我的授权列表
|
||||
static const String myCommunityHierarchy = '$authorizations/my/community-hierarchy'; // 获取社区层级
|
||||
|
||||
// Telemetry (-> Reporting Service)
|
||||
static const String telemetry = '/telemetry';
|
||||
|
|
|
|||
|
|
@ -208,6 +208,69 @@ class UserAuthorizationSummary {
|
|||
}
|
||||
}
|
||||
|
||||
/// 社区简要信息
|
||||
class CommunityInfo {
|
||||
final String authorizationId;
|
||||
final int accountSequence;
|
||||
final String communityName;
|
||||
final String? userId;
|
||||
final bool isHeadquarters;
|
||||
|
||||
CommunityInfo({
|
||||
required this.authorizationId,
|
||||
required this.accountSequence,
|
||||
required this.communityName,
|
||||
this.userId,
|
||||
required this.isHeadquarters,
|
||||
});
|
||||
|
||||
factory CommunityInfo.fromJson(Map<String, dynamic> json) {
|
||||
return CommunityInfo(
|
||||
authorizationId: json['authorizationId'] ?? '',
|
||||
accountSequence: json['accountSequence'] ?? 0,
|
||||
communityName: json['communityName'] ?? '',
|
||||
userId: json['userId'],
|
||||
isHeadquarters: json['isHeadquarters'] ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 社区层级信息
|
||||
class CommunityHierarchy {
|
||||
/// 我的社区授权(如果有)
|
||||
final CommunityInfo? myCommunity;
|
||||
/// 上级社区(最近的,如果没有则为总部社区)
|
||||
final CommunityInfo parentCommunity;
|
||||
/// 下级社区列表
|
||||
final List<CommunityInfo> childCommunities;
|
||||
/// 是否有上级社区(非总部)
|
||||
final bool hasParentCommunity;
|
||||
/// 下级社区数量
|
||||
final int childCommunityCount;
|
||||
|
||||
CommunityHierarchy({
|
||||
this.myCommunity,
|
||||
required this.parentCommunity,
|
||||
required this.childCommunities,
|
||||
required this.hasParentCommunity,
|
||||
required this.childCommunityCount,
|
||||
});
|
||||
|
||||
factory CommunityHierarchy.fromJson(Map<String, dynamic> json) {
|
||||
return CommunityHierarchy(
|
||||
myCommunity: json['myCommunity'] != null
|
||||
? CommunityInfo.fromJson(json['myCommunity'])
|
||||
: null,
|
||||
parentCommunity: CommunityInfo.fromJson(json['parentCommunity']),
|
||||
childCommunities: (json['childCommunities'] as List<dynamic>?)
|
||||
?.map((e) => CommunityInfo.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ?? [],
|
||||
hasParentCommunity: json['hasParentCommunity'] ?? false,
|
||||
childCommunityCount: json['childCommunityCount'] ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 授权服务
|
||||
///
|
||||
/// 处理用户授权相关功能:
|
||||
|
|
@ -261,4 +324,40 @@ class AuthorizationService {
|
|||
final authorizations = await getMyAuthorizations();
|
||||
return UserAuthorizationSummary.fromList(authorizations);
|
||||
}
|
||||
|
||||
/// 获取我的社区层级信息
|
||||
///
|
||||
/// 返回上级社区(如果没有则为总部社区)和下级社区列表
|
||||
/// 调用 GET /authorizations/my/community-hierarchy
|
||||
Future<CommunityHierarchy> getMyCommunityHierarchy() async {
|
||||
try {
|
||||
debugPrint('获取社区层级...');
|
||||
final response = await _apiClient.get(ApiEndpoints.myCommunityHierarchy);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = response.data;
|
||||
// API 返回格式: {"success": true, "data": {...}}
|
||||
Map<String, dynamic>? data;
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
if (responseData.containsKey('data')) {
|
||||
data = responseData['data'] as Map<String, dynamic>?;
|
||||
} else {
|
||||
data = responseData;
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
final hierarchy = CommunityHierarchy.fromJson(data);
|
||||
debugPrint('社区层级获取成功: 上级=${hierarchy.parentCommunity.communityName}, 下级数量=${hierarchy.childCommunityCount}');
|
||||
return hierarchy;
|
||||
}
|
||||
throw Exception('社区层级数据格式错误');
|
||||
}
|
||||
|
||||
throw Exception('获取社区层级失败');
|
||||
} catch (e) {
|
||||
debugPrint('获取社区层级失败: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue