rwadurian/backend/services/admin-service/src/pre-planting/pre-planting-config.control...

231 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Controller,
Get,
Post,
Put,
Body,
Query,
HttpCode,
HttpStatus,
Logger,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { PrePlantingConfigService } from './pre-planting-config.service';
import { PrePlantingProxyService } from './pre-planting-proxy.service';
import { PrismaService } from '../infrastructure/persistence/prisma/prisma.service';
class UpdatePrePlantingConfigDto {
@IsBoolean()
isActive: boolean;
@IsOptional()
@IsString()
updatedBy?: string;
}
class TogglePrePlantingConfigDto {
@IsBoolean()
isActive: boolean;
}
class UpdatePrePlantingAgreementDto {
@IsString()
text: string;
}
@ApiTags('预种计划配置')
@Controller('admin/pre-planting')
export class PrePlantingConfigController {
private readonly logger = new Logger(PrePlantingConfigController.name);
constructor(
private readonly configService: PrePlantingConfigService,
private readonly proxyService: PrePlantingProxyService,
private readonly prisma: PrismaService,
) {}
/**
* 根据 accountSequence 查找该用户的团队成员 accountSequence 列表
* 逻辑:找到该用户的 userId然后查找 ancestorPath 包含该 userId 的所有用户
* 返回的列表包含该用户本人
*/
private async resolveTeamAccountSequences(teamOfAccountSeq: string): Promise<string[]> {
// 1. 找到 teamOf 用户的 userId
const leader = await this.prisma.referralQueryView.findUnique({
where: { accountSequence: teamOfAccountSeq },
select: { userId: true, accountSequence: true },
});
if (!leader) {
this.logger.warn(`[resolveTeamAccountSequences] 未找到用户: ${teamOfAccountSeq}`);
return [];
}
// 2. 查找 ancestorPath 包含该 userId 的所有下级用户PostgreSQL array contains
const teamMembers = await this.prisma.referralQueryView.findMany({
where: {
ancestorPath: { has: leader.userId },
},
select: { accountSequence: true },
});
// 3. 包含团队领导本人
const sequences = [leader.accountSequence, ...teamMembers.map((m) => m.accountSequence)];
this.logger.debug(`[resolveTeamAccountSequences] ${teamOfAccountSeq} 团队成员数: ${sequences.length}`);
return sequences;
}
@Get('config')
@ApiOperation({ summary: '获取预种计划开关状态(含协议文本)' })
@ApiResponse({ status: HttpStatus.OK, description: '开关状态' })
async getConfig() {
const config = await this.configService.getConfig();
const agreementText = await this.configService.getAgreement();
return { ...config, agreementText };
}
@Post('config')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '更新预种计划开关状态' })
@ApiResponse({ status: HttpStatus.OK, description: '更新成功' })
async updateConfig(@Body() dto: UpdatePrePlantingConfigDto) {
return this.configService.updateConfig(dto.isActive, dto.updatedBy);
}
// ============================================
// [2026-02-27] 新增预种管理端点toggle + 数据查询代理)
// ============================================
@Put('config/toggle')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '切换预种计划开关' })
@ApiResponse({ status: HttpStatus.OK, description: '切换成功' })
async toggleConfig(@Body() dto: TogglePrePlantingConfigDto) {
return this.configService.updateConfig(dto.isActive);
}
@Get('orders')
@ApiOperation({ summary: '预种订单列表(管理员视角)' })
@ApiQuery({ name: 'page', required: false })
@ApiQuery({ name: 'pageSize', required: false })
@ApiQuery({ name: 'keyword', required: false })
@ApiQuery({ name: 'status', required: false })
@ApiQuery({ name: 'teamOf', required: false, description: '团队筛选:指定用户 accountSequence只显示其团队成员的订单' })
async getOrders(
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query('keyword') keyword?: string,
@Query('status') status?: string,
@Query('teamOf') teamOf?: string,
) {
let accountSequences: string[] | undefined;
if (teamOf) {
accountSequences = await this.resolveTeamAccountSequences(teamOf);
if (accountSequences.length === 0) {
return { items: [], total: 0, page: page ? parseInt(page, 10) : 1, pageSize: pageSize ? parseInt(pageSize, 10) : 20 };
}
}
return this.proxyService.getOrders({
page: page ? parseInt(page, 10) : undefined,
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
keyword: keyword || undefined,
status: status || undefined,
accountSequences,
});
}
@Get('positions')
@ApiOperation({ summary: '预种持仓列表(管理员视角)' })
@ApiQuery({ name: 'page', required: false })
@ApiQuery({ name: 'pageSize', required: false })
@ApiQuery({ name: 'keyword', required: false })
@ApiQuery({ name: 'teamOf', required: false, description: '团队筛选:指定用户 accountSequence只显示其团队成员的持仓' })
async getPositions(
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query('keyword') keyword?: string,
@Query('teamOf') teamOf?: string,
) {
let accountSequences: string[] | undefined;
if (teamOf) {
accountSequences = await this.resolveTeamAccountSequences(teamOf);
if (accountSequences.length === 0) {
return { items: [], total: 0, page: page ? parseInt(page, 10) : 1, pageSize: pageSize ? parseInt(pageSize, 10) : 20 };
}
}
return this.proxyService.getPositions({
page: page ? parseInt(page, 10) : undefined,
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
keyword: keyword || undefined,
accountSequences,
});
}
@Get('merges')
@ApiOperation({ summary: '预种合并记录列表(管理员视角)' })
@ApiQuery({ name: 'page', required: false })
@ApiQuery({ name: 'pageSize', required: false })
@ApiQuery({ name: 'keyword', required: false })
@ApiQuery({ name: 'status', required: false })
async getMerges(
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@Query('keyword') keyword?: string,
@Query('status') status?: string,
) {
return this.proxyService.getMerges({
page: page ? parseInt(page, 10) : undefined,
pageSize: pageSize ? parseInt(pageSize, 10) : undefined,
keyword: keyword || undefined,
status: status || undefined,
});
}
@Get('stats')
@ApiOperation({ summary: '预种统计汇总' })
async getStats() {
return this.proxyService.getStats();
}
// ============================================
// [2026-02-28] 新增:预种协议管理
// ============================================
@Get('agreement')
@ApiOperation({ summary: '获取预种协议文本' })
@ApiResponse({ status: HttpStatus.OK, description: '协议文本' })
async getAgreement() {
const text = await this.configService.getAgreement();
return { text };
}
@Put('agreement')
@HttpCode(HttpStatus.OK)
@ApiOperation({ summary: '更新预种协议文本' })
@ApiResponse({ status: HttpStatus.OK, description: '更新成功' })
async updateAgreement(@Body() dto: UpdatePrePlantingAgreementDto) {
return this.configService.updateAgreement(dto.text);
}
}
/**
* 公开 API供 planting-service 调用)
*/
@ApiTags('预种计划配置-内部API')
@Controller('api/v1/admin/pre-planting')
export class PublicPrePlantingConfigController {
constructor(
private readonly configService: PrePlantingConfigService,
) {}
@Get('config')
@ApiOperation({ summary: '获取预种计划开关状态内部API含协议文本' })
async getConfig() {
const config = await this.configService.getConfig();
const agreementText = await this.configService.getAgreement();
return { ...config, agreementText };
}
}