feat(authorization): add admin APIs for auth-province and auth-city company
- Add POST /admin/authorizations/auth-province-company for 省团队授权 - Add POST /admin/authorizations/auth-city-company for 市团队授权 - Add team uniqueness validation for all province/city authorization types - Add domain events: AuthProvinceCompanyGrantedEvent, AuthCityCompanyGrantedEvent - Add factory methods: createAuthProvinceCompanyByAdmin, createAuthCityCompanyByAdmin 🤖 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
582828b8be
commit
905725fc2d
|
|
@ -1,8 +1,20 @@
|
|||
import { Controller, Post, Body, UseGuards, HttpCode, HttpStatus } from '@nestjs/common'
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'
|
||||
import { AuthorizationApplicationService } from '@/application/services'
|
||||
import { GrantCommunityCommand, GrantProvinceCompanyCommand, GrantCityCompanyCommand } from '@/application/commands'
|
||||
import { GrantCommunityDto, GrantProvinceCompanyDto, GrantCityCompanyDto } from '@/api/dto/request'
|
||||
import {
|
||||
GrantCommunityCommand,
|
||||
GrantProvinceCompanyCommand,
|
||||
GrantCityCompanyCommand,
|
||||
GrantAuthProvinceCompanyCommand,
|
||||
GrantAuthCityCompanyCommand,
|
||||
} from '@/application/commands'
|
||||
import {
|
||||
GrantCommunityDto,
|
||||
GrantProvinceCompanyDto,
|
||||
GrantCityCompanyDto,
|
||||
GrantAuthProvinceCompanyDto,
|
||||
GrantAuthCityCompanyDto,
|
||||
} from '@/api/dto/request'
|
||||
import { CurrentUser } from '@/shared/decorators'
|
||||
import { JwtAuthGuard } from '@/shared/guards'
|
||||
|
||||
|
|
@ -74,4 +86,48 @@ export class AdminAuthorizationController {
|
|||
await this.applicationService.grantCityCompany(command)
|
||||
return { message: '正式市公司授权成功' }
|
||||
}
|
||||
|
||||
@Post('auth-province-company')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: '授权省团队(管理员)' })
|
||||
@ApiResponse({ status: 201, description: '授权成功' })
|
||||
@ApiResponse({ status: 400, description: '验证失败(如团队内已存在相同省份授权)' })
|
||||
async grantAuthProvinceCompany(
|
||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
||||
@Body() dto: GrantAuthProvinceCompanyDto,
|
||||
): Promise<{ message: string }> {
|
||||
const command = new GrantAuthProvinceCompanyCommand(
|
||||
dto.userId,
|
||||
dto.accountSequence,
|
||||
dto.provinceCode,
|
||||
dto.provinceName,
|
||||
user.userId,
|
||||
user.accountSequence,
|
||||
dto.skipAssessment ?? false,
|
||||
)
|
||||
await this.applicationService.grantAuthProvinceCompany(command)
|
||||
return { message: '省团队授权成功' }
|
||||
}
|
||||
|
||||
@Post('auth-city-company')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: '授权市团队(管理员)' })
|
||||
@ApiResponse({ status: 201, description: '授权成功' })
|
||||
@ApiResponse({ status: 400, description: '验证失败(如团队内已存在相同城市授权)' })
|
||||
async grantAuthCityCompany(
|
||||
@CurrentUser() user: { userId: string; accountSequence: number },
|
||||
@Body() dto: GrantAuthCityCompanyDto,
|
||||
): Promise<{ message: string }> {
|
||||
const command = new GrantAuthCityCompanyCommand(
|
||||
dto.userId,
|
||||
dto.accountSequence,
|
||||
dto.cityCode,
|
||||
dto.cityName,
|
||||
user.userId,
|
||||
user.accountSequence,
|
||||
dto.skipAssessment ?? false,
|
||||
)
|
||||
await this.applicationService.grantAuthCityCompany(command)
|
||||
return { message: '市团队授权成功' }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { IsString, IsNotEmpty, MaxLength, IsNumber, IsBoolean, IsOptional } from 'class-validator'
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||
|
||||
export class GrantAuthCityCompanyDto {
|
||||
@ApiProperty({ description: '用户ID' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '用户ID不能为空' })
|
||||
userId: string
|
||||
|
||||
@ApiProperty({ description: '账户序列号' })
|
||||
@IsNumber()
|
||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||
accountSequence: number
|
||||
|
||||
@ApiProperty({ description: '城市代码', example: '430100' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '城市代码不能为空' })
|
||||
@MaxLength(20, { message: '城市代码最大20字符' })
|
||||
cityCode: string
|
||||
|
||||
@ApiProperty({ description: '城市名称', example: '长沙市' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '城市名称不能为空' })
|
||||
@MaxLength(50, { message: '城市名称最大50字符' })
|
||||
cityName: string
|
||||
|
||||
@ApiPropertyOptional({ description: '是否跳过考核直接激活权益', default: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
skipAssessment?: boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { IsString, IsNotEmpty, MaxLength, IsNumber, IsBoolean, IsOptional } from 'class-validator'
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||
|
||||
export class GrantAuthProvinceCompanyDto {
|
||||
@ApiProperty({ description: '用户ID' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '用户ID不能为空' })
|
||||
userId: string
|
||||
|
||||
@ApiProperty({ description: '账户序列号' })
|
||||
@IsNumber()
|
||||
@IsNotEmpty({ message: '账户序列号不能为空' })
|
||||
accountSequence: number
|
||||
|
||||
@ApiProperty({ description: '省份代码', example: '430000' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '省份代码不能为空' })
|
||||
@MaxLength(20, { message: '省份代码最大20字符' })
|
||||
provinceCode: string
|
||||
|
||||
@ApiProperty({ description: '省份名称', example: '湖南省' })
|
||||
@IsString()
|
||||
@IsNotEmpty({ message: '省份名称不能为空' })
|
||||
@MaxLength(50, { message: '省份名称最大50字符' })
|
||||
provinceName: string
|
||||
|
||||
@ApiPropertyOptional({ description: '是否跳过考核直接激活权益', default: false })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
skipAssessment?: boolean
|
||||
}
|
||||
|
|
@ -4,5 +4,7 @@ export * from './apply-auth-city.dto'
|
|||
export * from './grant-community.dto'
|
||||
export * from './grant-province-company.dto'
|
||||
export * from './grant-city-company.dto'
|
||||
export * from './grant-auth-province-company.dto'
|
||||
export * from './grant-auth-city-company.dto'
|
||||
export * from './revoke-authorization.dto'
|
||||
export * from './grant-monthly-bypass.dto'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
export class GrantAuthCityCompanyCommand {
|
||||
constructor(
|
||||
public readonly userId: string,
|
||||
public readonly accountSequence: number,
|
||||
public readonly cityCode: string,
|
||||
public readonly cityName: string,
|
||||
public readonly adminId: string,
|
||||
public readonly adminAccountSequence: number,
|
||||
public readonly skipAssessment: boolean = false,
|
||||
) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export class GrantAuthProvinceCompanyCommand {
|
||||
constructor(
|
||||
public readonly userId: string,
|
||||
public readonly accountSequence: number,
|
||||
public readonly provinceCode: string,
|
||||
public readonly provinceName: string,
|
||||
public readonly adminId: string,
|
||||
public readonly adminAccountSequence: number,
|
||||
public readonly skipAssessment: boolean = false,
|
||||
) {}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ export * from './apply-auth-city-company.command'
|
|||
export * from './grant-community.command'
|
||||
export * from './grant-province-company.command'
|
||||
export * from './grant-city-company.command'
|
||||
export * from './grant-auth-province-company.command'
|
||||
export * from './grant-auth-city-company.command'
|
||||
export * from './revoke-authorization.command'
|
||||
export * from './grant-monthly-bypass.command'
|
||||
export * from './exempt-percentage-check.command'
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import {
|
|||
GrantCommunityCommand,
|
||||
GrantProvinceCompanyCommand,
|
||||
GrantCityCompanyCommand,
|
||||
GrantAuthProvinceCompanyCommand,
|
||||
GrantAuthCityCompanyCommand,
|
||||
RevokeAuthorizationCommand,
|
||||
GrantMonthlyBypassCommand,
|
||||
ExemptLocalPercentageCheckCommand,
|
||||
|
|
@ -241,12 +243,28 @@ export class AuthorizationApplicationService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 管理员授权正式省公司
|
||||
* 管理员授权正式省公司(省区域)
|
||||
* 需要验证团队内唯一性:同一推荐链上不能有重复的相同省份授权
|
||||
*/
|
||||
async grantProvinceCompany(command: GrantProvinceCompanyCommand): Promise<void> {
|
||||
const userId = UserId.create(command.userId, command.accountSequence)
|
||||
const adminId = AdminUserId.create(command.adminId, command.adminAccountSequence)
|
||||
const regionCode = RegionCode.create(command.provinceCode)
|
||||
|
||||
// 1. 验证团队内唯一性(同一推荐链上不能有重复的相同省份授权)
|
||||
const validation = await this.validatorService.validateAuthorizationRequest(
|
||||
userId,
|
||||
RoleType.PROVINCE_COMPANY,
|
||||
regionCode,
|
||||
this.referralRepository,
|
||||
this.authorizationRepository,
|
||||
)
|
||||
|
||||
if (!validation.isValid) {
|
||||
throw new ApplicationError(validation.errorMessage!)
|
||||
}
|
||||
|
||||
// 2. 创建授权
|
||||
const authorization = AuthorizationRole.createProvinceCompany({
|
||||
userId,
|
||||
provinceCode: command.provinceCode,
|
||||
|
|
@ -261,12 +279,28 @@ export class AuthorizationApplicationService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 管理员授权正式市公司
|
||||
* 管理员授权正式市公司(市区域)
|
||||
* 需要验证团队内唯一性:同一推荐链上不能有重复的相同城市授权
|
||||
*/
|
||||
async grantCityCompany(command: GrantCityCompanyCommand): Promise<void> {
|
||||
const userId = UserId.create(command.userId, command.accountSequence)
|
||||
const adminId = AdminUserId.create(command.adminId, command.adminAccountSequence)
|
||||
const regionCode = RegionCode.create(command.cityCode)
|
||||
|
||||
// 1. 验证团队内唯一性(同一推荐链上不能有重复的相同城市授权)
|
||||
const validation = await this.validatorService.validateAuthorizationRequest(
|
||||
userId,
|
||||
RoleType.CITY_COMPANY,
|
||||
regionCode,
|
||||
this.referralRepository,
|
||||
this.authorizationRepository,
|
||||
)
|
||||
|
||||
if (!validation.isValid) {
|
||||
throw new ApplicationError(validation.errorMessage!)
|
||||
}
|
||||
|
||||
// 2. 创建授权
|
||||
const authorization = AuthorizationRole.createCityCompany({
|
||||
userId,
|
||||
cityCode: command.cityCode,
|
||||
|
|
@ -280,6 +314,80 @@ export class AuthorizationApplicationService {
|
|||
authorization.clearDomainEvents()
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员授权授权省公司(省团队)
|
||||
* Admin直接授权,跳过用户申请流程
|
||||
* 需要验证团队内唯一性:同一推荐链上不能有重复的相同省份授权
|
||||
*/
|
||||
async grantAuthProvinceCompany(command: GrantAuthProvinceCompanyCommand): Promise<void> {
|
||||
const userId = UserId.create(command.userId, command.accountSequence)
|
||||
const adminId = AdminUserId.create(command.adminId, command.adminAccountSequence)
|
||||
const regionCode = RegionCode.create(command.provinceCode)
|
||||
|
||||
// 1. 验证团队内唯一性(同一推荐链上不能有重复的相同省份授权)
|
||||
const validation = await this.validatorService.validateAuthorizationRequest(
|
||||
userId,
|
||||
RoleType.AUTH_PROVINCE_COMPANY,
|
||||
regionCode,
|
||||
this.referralRepository,
|
||||
this.authorizationRepository,
|
||||
)
|
||||
|
||||
if (!validation.isValid) {
|
||||
throw new ApplicationError(validation.errorMessage!)
|
||||
}
|
||||
|
||||
// 2. 创建授权
|
||||
const authorization = AuthorizationRole.createAuthProvinceCompanyByAdmin({
|
||||
userId,
|
||||
provinceCode: command.provinceCode,
|
||||
provinceName: command.provinceName,
|
||||
adminId,
|
||||
skipAssessment: command.skipAssessment,
|
||||
})
|
||||
|
||||
await this.authorizationRepository.save(authorization)
|
||||
await this.eventPublisher.publishAll(authorization.domainEvents)
|
||||
authorization.clearDomainEvents()
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员授权授权市公司(市团队)
|
||||
* Admin直接授权,跳过用户申请流程
|
||||
* 需要验证团队内唯一性:同一推荐链上不能有重复的相同城市授权
|
||||
*/
|
||||
async grantAuthCityCompany(command: GrantAuthCityCompanyCommand): Promise<void> {
|
||||
const userId = UserId.create(command.userId, command.accountSequence)
|
||||
const adminId = AdminUserId.create(command.adminId, command.adminAccountSequence)
|
||||
const regionCode = RegionCode.create(command.cityCode)
|
||||
|
||||
// 1. 验证团队内唯一性(同一推荐链上不能有重复的相同城市授权)
|
||||
const validation = await this.validatorService.validateAuthorizationRequest(
|
||||
userId,
|
||||
RoleType.AUTH_CITY_COMPANY,
|
||||
regionCode,
|
||||
this.referralRepository,
|
||||
this.authorizationRepository,
|
||||
)
|
||||
|
||||
if (!validation.isValid) {
|
||||
throw new ApplicationError(validation.errorMessage!)
|
||||
}
|
||||
|
||||
// 2. 创建授权
|
||||
const authorization = AuthorizationRole.createAuthCityCompanyByAdmin({
|
||||
userId,
|
||||
cityCode: command.cityCode,
|
||||
cityName: command.cityName,
|
||||
adminId,
|
||||
skipAssessment: command.skipAssessment,
|
||||
})
|
||||
|
||||
await this.authorizationRepository.save(authorization)
|
||||
await this.eventPublisher.publishAll(authorization.domainEvents)
|
||||
authorization.clearDomainEvents()
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销授权
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import {
|
|||
CommunityAuthRequestedEvent,
|
||||
AuthProvinceCompanyRequestedEvent,
|
||||
AuthCityCompanyRequestedEvent,
|
||||
AuthProvinceCompanyGrantedEvent,
|
||||
AuthCityCompanyGrantedEvent,
|
||||
ProvinceCompanyAuthorizedEvent,
|
||||
CityCompanyAuthorizedEvent,
|
||||
BenefitActivatedEvent,
|
||||
|
|
@ -436,6 +438,98 @@ export class AuthorizationRole extends AggregateRoot {
|
|||
return auth
|
||||
}
|
||||
|
||||
// 工厂方法 - Admin授权省团队
|
||||
static createAuthProvinceCompanyByAdmin(params: {
|
||||
userId: UserId
|
||||
provinceCode: string
|
||||
provinceName: string
|
||||
adminId: AdminUserId
|
||||
skipAssessment?: boolean
|
||||
}): AuthorizationRole {
|
||||
const skipAssessment = params.skipAssessment ?? false
|
||||
const auth = new AuthorizationRole({
|
||||
authorizationId: AuthorizationId.generate(),
|
||||
userId: params.userId,
|
||||
roleType: RoleType.AUTH_PROVINCE_COMPANY,
|
||||
regionCode: RegionCode.create(params.provinceCode),
|
||||
regionName: params.provinceName,
|
||||
status: AuthorizationStatus.AUTHORIZED,
|
||||
displayTitle: `授权${params.provinceName}`,
|
||||
authorizedAt: new Date(),
|
||||
authorizedBy: params.adminId,
|
||||
revokedAt: null,
|
||||
revokedBy: null,
|
||||
revokeReason: null,
|
||||
assessmentConfig: AssessmentConfig.forAuthProvince(),
|
||||
requireLocalPercentage: 5.0,
|
||||
exemptFromPercentageCheck: false,
|
||||
benefitActive: skipAssessment,
|
||||
benefitActivatedAt: skipAssessment ? new Date() : null,
|
||||
benefitDeactivatedAt: null,
|
||||
currentMonthIndex: skipAssessment ? 1 : 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
|
||||
auth.addDomainEvent(
|
||||
new AuthProvinceCompanyGrantedEvent({
|
||||
authorizationId: auth.authorizationId.value,
|
||||
userId: params.userId.value,
|
||||
provinceCode: params.provinceCode,
|
||||
provinceName: params.provinceName,
|
||||
authorizedBy: params.adminId.value,
|
||||
}),
|
||||
)
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
// 工厂方法 - Admin授权市团队
|
||||
static createAuthCityCompanyByAdmin(params: {
|
||||
userId: UserId
|
||||
cityCode: string
|
||||
cityName: string
|
||||
adminId: AdminUserId
|
||||
skipAssessment?: boolean
|
||||
}): AuthorizationRole {
|
||||
const skipAssessment = params.skipAssessment ?? false
|
||||
const auth = new AuthorizationRole({
|
||||
authorizationId: AuthorizationId.generate(),
|
||||
userId: params.userId,
|
||||
roleType: RoleType.AUTH_CITY_COMPANY,
|
||||
regionCode: RegionCode.create(params.cityCode),
|
||||
regionName: params.cityName,
|
||||
status: AuthorizationStatus.AUTHORIZED,
|
||||
displayTitle: `授权${params.cityName}`,
|
||||
authorizedAt: new Date(),
|
||||
authorizedBy: params.adminId,
|
||||
revokedAt: null,
|
||||
revokedBy: null,
|
||||
revokeReason: null,
|
||||
assessmentConfig: AssessmentConfig.forAuthCity(),
|
||||
requireLocalPercentage: 5.0,
|
||||
exemptFromPercentageCheck: false,
|
||||
benefitActive: skipAssessment,
|
||||
benefitActivatedAt: skipAssessment ? new Date() : null,
|
||||
benefitDeactivatedAt: null,
|
||||
currentMonthIndex: skipAssessment ? 1 : 0,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
|
||||
auth.addDomainEvent(
|
||||
new AuthCityCompanyGrantedEvent({
|
||||
authorizationId: auth.authorizationId.value,
|
||||
userId: params.userId.value,
|
||||
cityCode: params.cityCode,
|
||||
cityName: params.cityName,
|
||||
authorizedBy: params.adminId.value,
|
||||
}),
|
||||
)
|
||||
|
||||
return auth
|
||||
}
|
||||
|
||||
// 核心领域行为
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -137,6 +137,56 @@ export class CityCompanyAuthorizedEvent extends DomainEvent {
|
|||
}
|
||||
}
|
||||
|
||||
// Admin授权省团队事件
|
||||
export class AuthProvinceCompanyGrantedEvent extends DomainEvent {
|
||||
readonly eventType = 'authorization.auth_province.granted'
|
||||
readonly aggregateId: string
|
||||
readonly payload: {
|
||||
authorizationId: string
|
||||
userId: string
|
||||
provinceCode: string
|
||||
provinceName: string
|
||||
authorizedBy: string
|
||||
}
|
||||
|
||||
constructor(data: {
|
||||
authorizationId: string
|
||||
userId: string
|
||||
provinceCode: string
|
||||
provinceName: string
|
||||
authorizedBy: string
|
||||
}) {
|
||||
super()
|
||||
this.aggregateId = data.authorizationId
|
||||
this.payload = data
|
||||
}
|
||||
}
|
||||
|
||||
// Admin授权市团队事件
|
||||
export class AuthCityCompanyGrantedEvent extends DomainEvent {
|
||||
readonly eventType = 'authorization.auth_city.granted'
|
||||
readonly aggregateId: string
|
||||
readonly payload: {
|
||||
authorizationId: string
|
||||
userId: string
|
||||
cityCode: string
|
||||
cityName: string
|
||||
authorizedBy: string
|
||||
}
|
||||
|
||||
constructor(data: {
|
||||
authorizationId: string
|
||||
userId: string
|
||||
cityCode: string
|
||||
cityName: string
|
||||
authorizedBy: string
|
||||
}) {
|
||||
super()
|
||||
this.aggregateId = data.authorizationId
|
||||
this.payload = data
|
||||
}
|
||||
}
|
||||
|
||||
// 权益激活事件
|
||||
export class BenefitActivatedEvent extends DomainEvent {
|
||||
readonly eventType = 'authorization.benefit.activated'
|
||||
|
|
|
|||
Loading…
Reference in New Issue