erp-core/backend/src/modules/tenants/tenants.controller.ts
rckrdmrd 4c4e27d9ba feat: Documentation and orchestration updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:35:20 -06:00

335 lines
8.7 KiB
TypeScript

import { Response, NextFunction } from 'express';
import { tenantsService } from './tenants.service.js';
import { TenantStatus } from '../auth/entities/index.js';
import { TenantPlan } from './entities/index.js';
import {
safeValidateCreateTenant,
safeValidateUpdateTenant,
safeValidateUpdateTenantSettings,
} from './dto/index.js';
import {
ApiResponse,
AuthenticatedRequest,
ValidationError,
PaginationParams
} from '../../shared/types/index.js';
/**
* TenantsController
* Handles HTTP requests for tenant management.
* Most endpoints require super_admin role.
*/
export class TenantsController {
/**
* GET /tenants - List all tenants (super_admin only)
* Supports pagination, filtering by status/plan, and search
*/
async findAll(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const page = parseInt(req.query.page as string) || 1;
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const sortBy = req.query.sortBy as string || 'name';
const sortOrder = (req.query.sortOrder as 'asc' | 'desc') || 'asc';
const params: PaginationParams = { page, limit, sortBy, sortOrder };
// Build filter
const filter: { status?: TenantStatus; plan?: TenantPlan; search?: string } = {};
if (req.query.status) {
filter.status = req.query.status as TenantStatus;
}
if (req.query.plan) {
filter.plan = req.query.plan as TenantPlan;
}
if (req.query.search) {
filter.search = req.query.search as string;
}
const result = await tenantsService.findAll(params, filter);
const response: ApiResponse = {
success: true,
data: result.tenants,
meta: {
page,
limit,
total: result.total,
totalPages: Math.ceil(result.total / limit),
},
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* GET /tenants/current - Get current user's tenant
* Available to any authenticated user
*/
async getCurrent(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.user!.tenantId;
const tenant = await tenantsService.findById(tenantId);
const response: ApiResponse = {
success: true,
data: tenant,
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* GET /tenants/:id - Get tenant by ID (super_admin only)
*/
async findById(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const includeSettings = req.query.includeSettings === 'true';
const tenant = await tenantsService.findById(tenantId, includeSettings);
const response: ApiResponse = {
success: true,
data: tenant,
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* GET /tenants/:id/stats - Get tenant statistics
*/
async getStats(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const stats = await tenantsService.getTenantStats(tenantId);
const response: ApiResponse = {
success: true,
data: stats,
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* POST /tenants - Create new tenant (super_admin only)
*/
async create(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const validation = safeValidateCreateTenant(req.body);
if (!validation.success) {
throw new ValidationError('Datos invalidos', validation.error.errors);
}
const createdBy = req.user!.userId;
const tenant = await tenantsService.create(validation.data, createdBy);
const response: ApiResponse = {
success: true,
data: tenant,
message: 'Tenant creado exitosamente',
};
res.status(201).json(response);
} catch (error) {
next(error);
}
}
/**
* PUT /tenants/:id - Update tenant
*/
async update(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const validation = safeValidateUpdateTenant(req.body);
if (!validation.success) {
throw new ValidationError('Datos invalidos', validation.error.errors);
}
const tenantId = req.params.id;
const updatedBy = req.user!.userId;
const tenant = await tenantsService.update(tenantId, validation.data, updatedBy);
const response: ApiResponse = {
success: true,
data: tenant,
message: 'Tenant actualizado exitosamente',
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* POST /tenants/:id/suspend - Suspend tenant (super_admin only)
*/
async suspend(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const updatedBy = req.user!.userId;
const tenant = await tenantsService.suspend(tenantId, updatedBy);
const response: ApiResponse = {
success: true,
data: tenant,
message: 'Tenant suspendido exitosamente',
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* POST /tenants/:id/activate - Activate tenant (super_admin only)
*/
async activate(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const updatedBy = req.user!.userId;
const tenant = await tenantsService.activate(tenantId, updatedBy);
const response: ApiResponse = {
success: true,
data: tenant,
message: 'Tenant activado exitosamente',
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* DELETE /tenants/:id - Soft delete tenant (super_admin only)
* Only allowed if tenant has no active users
*/
async delete(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const deletedBy = req.user!.userId;
await tenantsService.delete(tenantId, deletedBy);
const response: ApiResponse = {
success: true,
message: 'Tenant eliminado exitosamente',
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* GET /tenants/:id/settings - Get tenant settings
*/
async getSettings(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const settings = await tenantsService.getSettings(tenantId);
const response: ApiResponse = {
success: true,
data: settings,
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* PUT /tenants/:id/settings - Update tenant settings
*/
async updateSettings(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const validation = safeValidateUpdateTenantSettings(req.body);
if (!validation.success) {
throw new ValidationError('Datos invalidos', validation.error.errors);
}
const tenantId = req.params.id;
const updatedBy = req.user!.userId;
const settings = await tenantsService.updateSettings(
tenantId,
validation.data,
updatedBy
);
const response: ApiResponse = {
success: true,
data: settings,
message: 'Configuracion actualizada exitosamente',
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* GET /tenants/:id/can-add-user - Check if tenant can add more users
*/
async canAddUser(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const result = await tenantsService.canAddUser(tenantId);
const response: ApiResponse = {
success: true,
data: result,
};
res.json(response);
} catch (error) {
next(error);
}
}
/**
* GET /tenants/:id/can-use-storage - Check if tenant has available storage
*/
async canUseStorage(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
try {
const tenantId = req.params.id;
const requiredMb = parseInt(req.query.requiredMb as string) || 0;
const result = await tenantsService.canUseStorage(tenantId, requiredMb);
const response: ApiResponse = {
success: true,
data: result,
};
res.json(response);
} catch (error) {
next(error);
}
}
}
export const tenantsController = new TenantsController();