feat(admin): Complete tenant management and module access control system
Major Features: - Tenant management CRUD (list, create, edit, delete, module configuration) - Dynamic module management system (modules table with 8 modules) - Multi-tenant module permission merging (ModuleService) - Module access control middleware (requireModule) - User module permission API (GET /api/v1/auth/me/modules) - Frontend module permission filtering (HomePage + TopNavigation) Module Integration: - RVW module integrated with PromptService (editorial + methodology) - All modules (RVW/PKB/ASL/DC) added authenticate + requireModule middleware - Fixed ReviewTask foreign key constraint (cross-schema issue) - Removed all MOCK_USER_ID, unified to request.user?.userId Prompt Management Enhancements: - Module names displayed in Chinese (RVW -> 智能审稿) - Enhanced version history with view content and rollback features - List page shows both activeVersion and draftVersion columns Database Changes: - Added platform_schema.modules table - Modified tenant_modules table (added index and UUID) - Removed ReviewTask foreign key to public.users (cross-schema fix) - Seeded 8 modules: RVW, PKB, ASL, DC, IIT, AIA, SSA, ST Documentation Updates: - Updated ADMIN module development status - Updated TODO checklist (89% progress) - Updated Prompt management plan (Phase 3.5.5 completed) - Added module authentication specification Files Changed: 80+ Status: All features tested and verified locally Next: User management module development
This commit is contained in:
275
backend/src/modules/admin/controllers/tenantController.ts
Normal file
275
backend/src/modules/admin/controllers/tenantController.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 租户管理控制器
|
||||
*/
|
||||
|
||||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { tenantService } from '../services/tenantService.js';
|
||||
import { logger } from '../../../common/logging/index.js';
|
||||
import type {
|
||||
CreateTenantRequest,
|
||||
UpdateTenantRequest,
|
||||
UpdateTenantStatusRequest,
|
||||
ConfigureModulesRequest,
|
||||
TenantListQuery,
|
||||
} from '../types/tenant.types.js';
|
||||
|
||||
/**
|
||||
* 获取租户列表
|
||||
* GET /api/admin/tenants
|
||||
*/
|
||||
export async function listTenants(
|
||||
request: FastifyRequest<{ Querystring: TenantListQuery }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const result = await tenantService.listTenants(request.query);
|
||||
return reply.send({
|
||||
success: true,
|
||||
...result,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 获取租户列表失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '获取租户列表失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户详情
|
||||
* GET /api/admin/tenants/:id
|
||||
*/
|
||||
export async function getTenant(
|
||||
request: FastifyRequest<{ Params: { id: string } }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const tenant = await tenantService.getTenantDetail(id);
|
||||
|
||||
if (!tenant) {
|
||||
return reply.status(404).send({
|
||||
success: false,
|
||||
message: '租户不存在',
|
||||
});
|
||||
}
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: tenant,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 获取租户详情失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '获取租户详情失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建租户
|
||||
* POST /api/admin/tenants
|
||||
*/
|
||||
export async function createTenant(
|
||||
request: FastifyRequest<{ Body: CreateTenantRequest }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const tenant = await tenantService.createTenant(request.body);
|
||||
return reply.status(201).send({
|
||||
success: true,
|
||||
data: tenant,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 创建租户失败', { error: error.message });
|
||||
|
||||
if (error.message.includes('已存在')) {
|
||||
return reply.status(400).send({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '创建租户失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租户信息
|
||||
* PUT /api/admin/tenants/:id
|
||||
*/
|
||||
export async function updateTenant(
|
||||
request: FastifyRequest<{ Params: { id: string }; Body: UpdateTenantRequest }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const tenant = await tenantService.updateTenant(id, request.body);
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: tenant,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 更新租户失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '更新租户失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租户状态
|
||||
* PUT /api/admin/tenants/:id/status
|
||||
*/
|
||||
export async function updateTenantStatus(
|
||||
request: FastifyRequest<{ Params: { id: string }; Body: UpdateTenantStatusRequest }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { status } = request.body;
|
||||
await tenantService.updateTenantStatus(id, status);
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: '状态更新成功',
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 更新租户状态失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '更新租户状态失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户
|
||||
* DELETE /api/admin/tenants/:id
|
||||
*/
|
||||
export async function deleteTenant(
|
||||
request: FastifyRequest<{ Params: { id: string } }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
await tenantService.deleteTenant(id);
|
||||
return reply.send({
|
||||
success: true,
|
||||
message: '租户已删除',
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 删除租户失败', { error: error.message });
|
||||
|
||||
if (error.message.includes('无法删除')) {
|
||||
return reply.status(400).send({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '删除租户失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置租户模块
|
||||
* PUT /api/admin/tenants/:id/modules
|
||||
*/
|
||||
export async function configureModules(
|
||||
request: FastifyRequest<{ Params: { id: string }; Body: ConfigureModulesRequest }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { modules } = request.body;
|
||||
|
||||
// 转换日期格式
|
||||
const moduleConfigs = modules.map(m => ({
|
||||
code: m.code,
|
||||
enabled: m.enabled,
|
||||
expiresAt: m.expiresAt ? new Date(m.expiresAt) : null,
|
||||
}));
|
||||
|
||||
await tenantService.configureModules(id, moduleConfigs);
|
||||
|
||||
// 返回更新后的模块配置
|
||||
const updatedModules = await tenantService.getTenantModules(id);
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: updatedModules,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 配置租户模块失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '配置租户模块失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用模块列表
|
||||
* GET /api/admin/modules
|
||||
*/
|
||||
export async function listModules(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const { moduleService } = await import('../../../common/auth/module.service.js');
|
||||
const modules = await moduleService.getAllModules();
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: modules,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 获取模块列表失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '获取模块列表失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户可访问的模块
|
||||
* GET /api/auth/me/modules
|
||||
*/
|
||||
export async function getUserModules(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
if (!request.user) {
|
||||
return reply.status(401).send({
|
||||
success: false,
|
||||
message: '未认证',
|
||||
});
|
||||
}
|
||||
|
||||
const { moduleService } = await import('../../../common/auth/module.service.js');
|
||||
const result = await moduleService.getUserModules(request.user.userId);
|
||||
|
||||
return reply.send({
|
||||
success: true,
|
||||
data: result.moduleDetails.map(m => m.code),
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[TenantController] 获取用户模块失败', { error: error.message });
|
||||
return reply.status(500).send({
|
||||
success: false,
|
||||
message: error.message || '获取用户模块失败',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
77
backend/src/modules/admin/routes/tenantRoutes.ts
Normal file
77
backend/src/modules/admin/routes/tenantRoutes.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 租户管理路由
|
||||
*
|
||||
* API前缀: /api/admin/tenants
|
||||
*/
|
||||
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import * as tenantController from '../controllers/tenantController.js';
|
||||
import { authenticate, requirePermission } from '../../../common/auth/auth.middleware.js';
|
||||
|
||||
export async function tenantRoutes(fastify: FastifyInstance) {
|
||||
// ==================== 租户管理 ====================
|
||||
|
||||
// 获取租户列表
|
||||
// GET /api/admin/tenants?type=&status=&search=&page=1&limit=20
|
||||
fastify.get('/', {
|
||||
preHandler: [authenticate, requirePermission('tenant:view')],
|
||||
handler: tenantController.listTenants,
|
||||
});
|
||||
|
||||
// 获取租户详情
|
||||
// GET /api/admin/tenants/:id
|
||||
fastify.get('/:id', {
|
||||
preHandler: [authenticate, requirePermission('tenant:view')],
|
||||
handler: tenantController.getTenant,
|
||||
});
|
||||
|
||||
// 创建租户
|
||||
// POST /api/admin/tenants
|
||||
fastify.post('/', {
|
||||
preHandler: [authenticate, requirePermission('tenant:create')],
|
||||
handler: tenantController.createTenant,
|
||||
});
|
||||
|
||||
// 更新租户信息
|
||||
// PUT /api/admin/tenants/:id
|
||||
fastify.put('/:id', {
|
||||
preHandler: [authenticate, requirePermission('tenant:edit')],
|
||||
handler: tenantController.updateTenant,
|
||||
});
|
||||
|
||||
// 更新租户状态
|
||||
// PUT /api/admin/tenants/:id/status
|
||||
fastify.put('/:id/status', {
|
||||
preHandler: [authenticate, requirePermission('tenant:edit')],
|
||||
handler: tenantController.updateTenantStatus,
|
||||
});
|
||||
|
||||
// 删除租户
|
||||
// DELETE /api/admin/tenants/:id
|
||||
fastify.delete('/:id', {
|
||||
preHandler: [authenticate, requirePermission('tenant:delete')],
|
||||
handler: tenantController.deleteTenant,
|
||||
});
|
||||
|
||||
// 配置租户模块
|
||||
// PUT /api/admin/tenants/:id/modules
|
||||
fastify.put('/:id/modules', {
|
||||
preHandler: [authenticate, requirePermission('tenant:edit')],
|
||||
handler: tenantController.configureModules,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 模块管理路由
|
||||
*
|
||||
* API前缀: /api/admin/modules
|
||||
*/
|
||||
export async function moduleRoutes(fastify: FastifyInstance) {
|
||||
// 获取所有可用模块列表
|
||||
// GET /api/admin/modules
|
||||
fastify.get('/', {
|
||||
preHandler: [authenticate, requirePermission('tenant:view')],
|
||||
handler: tenantController.listModules,
|
||||
});
|
||||
}
|
||||
|
||||
307
backend/src/modules/admin/services/tenantService.ts
Normal file
307
backend/src/modules/admin/services/tenantService.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* 租户管理服务
|
||||
*/
|
||||
|
||||
import { prisma } from '../../../config/database.js';
|
||||
import { logger } from '../../../common/logging/index.js';
|
||||
import { moduleService } from '../../../common/auth/module.service.js';
|
||||
import type {
|
||||
TenantInfo,
|
||||
TenantDetail,
|
||||
TenantModuleConfig,
|
||||
CreateTenantRequest,
|
||||
UpdateTenantRequest,
|
||||
TenantListQuery,
|
||||
PaginatedResponse,
|
||||
TenantStatus,
|
||||
} from '../types/tenant.types.js';
|
||||
|
||||
class TenantService {
|
||||
/**
|
||||
* 获取租户列表(分页)
|
||||
*/
|
||||
async listTenants(query: TenantListQuery): Promise<PaginatedResponse<TenantInfo>> {
|
||||
const { type, status, search } = query;
|
||||
const page = Number(query.page) || 1;
|
||||
const limit = Number(query.limit) || 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// 构建查询条件
|
||||
const where: any = {};
|
||||
if (type) where.type = type;
|
||||
if (status) where.status = status;
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ code: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
// 查询总数
|
||||
const total = await prisma.tenants.count({ where });
|
||||
|
||||
// 查询数据
|
||||
const tenants = await prisma.tenants.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { created_at: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
type: true,
|
||||
status: true,
|
||||
contact_name: true,
|
||||
contact_phone: true,
|
||||
contact_email: true,
|
||||
expires_at: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
_count: {
|
||||
select: { users: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: tenants.map(t => ({
|
||||
id: t.id,
|
||||
code: t.code,
|
||||
name: t.name,
|
||||
type: t.type as any,
|
||||
status: t.status as any,
|
||||
contactName: t.contact_name,
|
||||
contactPhone: t.contact_phone,
|
||||
contactEmail: t.contact_email,
|
||||
expiresAt: t.expires_at,
|
||||
createdAt: t.created_at,
|
||||
updatedAt: t.updated_at,
|
||||
})),
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户详情(含模块配置)
|
||||
*/
|
||||
async getTenantDetail(tenantId: string): Promise<TenantDetail | null> {
|
||||
const tenant = await prisma.tenants.findUnique({
|
||||
where: { id: tenantId },
|
||||
include: {
|
||||
tenant_modules: true,
|
||||
_count: {
|
||||
select: { users: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!tenant) return null;
|
||||
|
||||
// 获取所有可用模块
|
||||
const allModules = await moduleService.getAllModules();
|
||||
|
||||
// 构建模块配置列表
|
||||
const modules: TenantModuleConfig[] = allModules.map(m => {
|
||||
const tenantModule = tenant.tenant_modules.find(tm => tm.module_code === m.code);
|
||||
return {
|
||||
code: m.code,
|
||||
name: m.name,
|
||||
enabled: tenantModule?.is_enabled ?? false,
|
||||
expiresAt: tenantModule?.expires_at,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: tenant.id,
|
||||
code: tenant.code,
|
||||
name: tenant.name,
|
||||
type: tenant.type as any,
|
||||
status: tenant.status as any,
|
||||
contactName: tenant.contact_name,
|
||||
contactPhone: tenant.contact_phone,
|
||||
contactEmail: tenant.contact_email,
|
||||
expiresAt: tenant.expires_at,
|
||||
createdAt: tenant.created_at,
|
||||
updatedAt: tenant.updated_at,
|
||||
modules,
|
||||
userCount: tenant._count.users,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建租户
|
||||
*/
|
||||
async createTenant(data: CreateTenantRequest): Promise<TenantInfo> {
|
||||
const { randomUUID } = await import('crypto');
|
||||
const tenantId = randomUUID();
|
||||
|
||||
// 检查 code 是否已存在
|
||||
const existing = await prisma.tenants.findUnique({
|
||||
where: { code: data.code },
|
||||
});
|
||||
if (existing) {
|
||||
throw new Error(`租户代码 "${data.code}" 已存在`);
|
||||
}
|
||||
|
||||
// 创建租户
|
||||
const tenant = await prisma.tenants.create({
|
||||
data: {
|
||||
id: tenantId,
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
type: data.type as any,
|
||||
status: 'ACTIVE',
|
||||
contact_name: data.contactName,
|
||||
contact_phone: data.contactPhone,
|
||||
contact_email: data.contactEmail,
|
||||
expires_at: data.expiresAt ? new Date(data.expiresAt) : null,
|
||||
updated_at: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
// 如果指定了初始模块,创建模块配置
|
||||
if (data.modules && data.modules.length > 0) {
|
||||
for (const moduleCode of data.modules) {
|
||||
await prisma.tenant_modules.create({
|
||||
data: {
|
||||
id: randomUUID(),
|
||||
tenant_id: tenantId,
|
||||
module_code: moduleCode,
|
||||
is_enabled: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('[TenantService] 创建租户', {
|
||||
tenantId,
|
||||
code: data.code,
|
||||
name: data.name,
|
||||
modules: data.modules,
|
||||
});
|
||||
|
||||
return {
|
||||
id: tenant.id,
|
||||
code: tenant.code,
|
||||
name: tenant.name,
|
||||
type: tenant.type as any,
|
||||
status: tenant.status as any,
|
||||
contactName: tenant.contact_name,
|
||||
contactPhone: tenant.contact_phone,
|
||||
contactEmail: tenant.contact_email,
|
||||
expiresAt: tenant.expires_at,
|
||||
createdAt: tenant.created_at,
|
||||
updatedAt: tenant.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租户信息
|
||||
*/
|
||||
async updateTenant(tenantId: string, data: UpdateTenantRequest): Promise<TenantInfo> {
|
||||
const tenant = await prisma.tenants.update({
|
||||
where: { id: tenantId },
|
||||
data: {
|
||||
name: data.name,
|
||||
contact_name: data.contactName,
|
||||
contact_phone: data.contactPhone,
|
||||
contact_email: data.contactEmail,
|
||||
expires_at: data.expiresAt === null ? null : (data.expiresAt ? new Date(data.expiresAt) : undefined),
|
||||
updated_at: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('[TenantService] 更新租户', { tenantId, data });
|
||||
|
||||
return {
|
||||
id: tenant.id,
|
||||
code: tenant.code,
|
||||
name: tenant.name,
|
||||
type: tenant.type as any,
|
||||
status: tenant.status as any,
|
||||
contactName: tenant.contact_name,
|
||||
contactPhone: tenant.contact_phone,
|
||||
contactEmail: tenant.contact_email,
|
||||
expiresAt: tenant.expires_at,
|
||||
createdAt: tenant.created_at,
|
||||
updatedAt: tenant.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租户状态
|
||||
*/
|
||||
async updateTenantStatus(tenantId: string, status: TenantStatus): Promise<void> {
|
||||
await prisma.tenants.update({
|
||||
where: { id: tenantId },
|
||||
data: {
|
||||
status: status as any,
|
||||
updated_at: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('[TenantService] 更新租户状态', { tenantId, status });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除租户(软删除 - 标记为 SUSPENDED)
|
||||
*/
|
||||
async deleteTenant(tenantId: string): Promise<void> {
|
||||
// 检查是否有用户
|
||||
const userCount = await prisma.user.count({
|
||||
where: { tenant_id: tenantId },
|
||||
});
|
||||
|
||||
if (userCount > 0) {
|
||||
throw new Error(`无法删除租户:该租户下还有 ${userCount} 个用户`);
|
||||
}
|
||||
|
||||
// 软删除:标记为 SUSPENDED
|
||||
await prisma.tenants.update({
|
||||
where: { id: tenantId },
|
||||
data: {
|
||||
status: 'SUSPENDED',
|
||||
updated_at: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('[TenantService] 删除租户(软删除)', { tenantId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置租户模块
|
||||
*/
|
||||
async configureModules(
|
||||
tenantId: string,
|
||||
modules: { code: string; enabled: boolean; expiresAt?: Date | null }[]
|
||||
): Promise<void> {
|
||||
await moduleService.setTenantModules(tenantId, modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户的模块配置
|
||||
*/
|
||||
async getTenantModules(tenantId: string): Promise<TenantModuleConfig[]> {
|
||||
const allModules = await moduleService.getAllModules();
|
||||
const tenantModules = await prisma.tenant_modules.findMany({
|
||||
where: { tenant_id: tenantId },
|
||||
});
|
||||
|
||||
return allModules.map(m => {
|
||||
const tm = tenantModules.find(t => t.module_code === m.code);
|
||||
return {
|
||||
code: m.code,
|
||||
name: m.name,
|
||||
enabled: tm?.is_enabled ?? false,
|
||||
expiresAt: tm?.expires_at,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const tenantService = new TenantService();
|
||||
|
||||
107
backend/src/modules/admin/types/tenant.types.ts
Normal file
107
backend/src/modules/admin/types/tenant.types.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 租户管理类型定义
|
||||
*/
|
||||
|
||||
export type TenantType = 'HOSPITAL' | 'PHARMA' | 'INTERNAL' | 'PUBLIC';
|
||||
export type TenantStatus = 'ACTIVE' | 'SUSPENDED' | 'EXPIRED';
|
||||
|
||||
/**
|
||||
* 租户基本信息
|
||||
*/
|
||||
export interface TenantInfo {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
type: TenantType;
|
||||
status: TenantStatus;
|
||||
contactName?: string | null;
|
||||
contactPhone?: string | null;
|
||||
contactEmail?: string | null;
|
||||
expiresAt?: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户详情(含模块配置)
|
||||
*/
|
||||
export interface TenantDetail extends TenantInfo {
|
||||
modules: TenantModuleConfig[];
|
||||
userCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户模块配置
|
||||
*/
|
||||
export interface TenantModuleConfig {
|
||||
code: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
expiresAt?: Date | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建租户请求
|
||||
*/
|
||||
export interface CreateTenantRequest {
|
||||
code: string;
|
||||
name: string;
|
||||
type: TenantType;
|
||||
contactName?: string;
|
||||
contactPhone?: string;
|
||||
contactEmail?: string;
|
||||
expiresAt?: string;
|
||||
modules?: string[]; // 初始开通的模块代码列表
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租户请求
|
||||
*/
|
||||
export interface UpdateTenantRequest {
|
||||
name?: string;
|
||||
contactName?: string;
|
||||
contactPhone?: string;
|
||||
contactEmail?: string;
|
||||
expiresAt?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新租户状态请求
|
||||
*/
|
||||
export interface UpdateTenantStatusRequest {
|
||||
status: TenantStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置租户模块请求
|
||||
*/
|
||||
export interface ConfigureModulesRequest {
|
||||
modules: {
|
||||
code: string;
|
||||
enabled: boolean;
|
||||
expiresAt?: string | null;
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户列表查询参数
|
||||
*/
|
||||
export interface TenantListQuery {
|
||||
type?: TenantType;
|
||||
status?: TenantStatus;
|
||||
search?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页响应
|
||||
*/
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user