import { Repository, FindOptionsWhere, ILike, In } from 'typeorm'; import { Role, Permission, User } from '../entities/index.js'; export interface RoleSearchParams { tenantId: string; search?: string; isSystem?: boolean; limit?: number; offset?: number; } export interface CreateRoleDto { name: string; code: string; description?: string; isSystem?: boolean; color?: string; permissionIds?: string[]; } export interface UpdateRoleDto extends Partial {} export interface AssignPermissionsDto { permissionIds: string[]; } export class RolesService { constructor( private readonly roleRepository: Repository, private readonly permissionRepository: Repository, ) {} async findAll(params: RoleSearchParams): Promise<{ data: Role[]; total: number }> { const { tenantId, search, isSystem, limit = 50, offset = 0, } = params; const where: FindOptionsWhere[] = []; const baseWhere: FindOptionsWhere = { tenantId }; if (isSystem !== undefined) { baseWhere.isSystem = isSystem; } if (search) { where.push( { ...baseWhere, name: ILike(`%${search}%`) }, { ...baseWhere, code: ILike(`%${search}%`) }, { ...baseWhere, description: ILike(`%${search}%`) } ); } else { where.push(baseWhere); } const [data, total] = await this.roleRepository.findAndCount({ where, relations: ['permissions'], take: limit, skip: offset, order: { isSystem: 'DESC', name: 'ASC' }, }); return { data, total }; } async findOne(id: string, tenantId: string): Promise { return this.roleRepository.findOne({ where: { id, tenantId }, relations: ['permissions', 'users'], }); } async findByCode(code: string, tenantId: string): Promise { return this.roleRepository.findOne({ where: { code, tenantId }, relations: ['permissions'], }); } async create(tenantId: string, dto: CreateRoleDto, createdBy: string): Promise { // Check if code already exists const existingCode = await this.findByCode(dto.code, tenantId); if (existingCode) { throw new Error(`Ya existe un rol con el codigo "${dto.code}"`); } const role = this.roleRepository.create({ tenantId, name: dto.name, code: dto.code, description: dto.description ?? null, isSystem: dto.isSystem ?? false, color: dto.color ?? null, createdBy, }); // If permissions were provided, assign them if (dto.permissionIds && dto.permissionIds.length > 0) { const permissions = await this.permissionRepository.findBy({ id: In(dto.permissionIds), }); role.permissions = permissions; } return this.roleRepository.save(role); } async update( id: string, tenantId: string, dto: UpdateRoleDto, updatedBy: string ): Promise { const role = await this.findOne(id, tenantId); if (!role) return null; // Cannot modify system roles if (role.isSystem && !dto.description && !dto.color) { throw new Error('Los roles del sistema solo permiten modificar descripcion y color'); } // Check for code uniqueness if changing code if (dto.code && dto.code !== role.code) { const existingCode = await this.findByCode(dto.code, tenantId); if (existingCode && existingCode.id !== id) { throw new Error(`Ya existe un rol con el codigo "${dto.code}"`); } } // Update basic fields if (dto.name !== undefined && !role.isSystem) role.name = dto.name; if (dto.code !== undefined && !role.isSystem) role.code = dto.code; if (dto.description !== undefined) role.description = dto.description ?? null; if (dto.color !== undefined) role.color = dto.color ?? null; role.updatedBy = updatedBy; // Update permissions if provided if (dto.permissionIds !== undefined) { const permissions = await this.permissionRepository.findBy({ id: In(dto.permissionIds), }); role.permissions = permissions; } return this.roleRepository.save(role); } async assignPermissions( id: string, tenantId: string, dto: AssignPermissionsDto, updatedBy: string ): Promise { const role = await this.findOne(id, tenantId); if (!role) return null; const permissions = await this.permissionRepository.findBy({ id: In(dto.permissionIds), }); role.permissions = permissions; role.updatedBy = updatedBy; return this.roleRepository.save(role); } async addPermission( id: string, tenantId: string, permissionId: string, updatedBy: string ): Promise { const role = await this.findOne(id, tenantId); if (!role) return null; const permission = await this.permissionRepository.findOne({ where: { id: permissionId }, }); if (!permission) { throw new Error('Permiso no encontrado'); } // Check if permission already assigned const hasPermission = role.permissions.some(p => p.id === permissionId); if (!hasPermission) { role.permissions.push(permission); role.updatedBy = updatedBy; return this.roleRepository.save(role); } return role; } async removePermission( id: string, tenantId: string, permissionId: string, updatedBy: string ): Promise { const role = await this.findOne(id, tenantId); if (!role) return null; role.permissions = role.permissions.filter(p => p.id !== permissionId); role.updatedBy = updatedBy; return this.roleRepository.save(role); } async getPermissions(id: string, tenantId: string): Promise { const role = await this.findOne(id, tenantId); if (!role) return []; return role.permissions; } async getUsers(id: string, tenantId: string): Promise { const role = await this.findOne(id, tenantId); if (!role) return []; return role.users; } async getUserCount(id: string, tenantId: string): Promise { const role = await this.roleRepository.findOne({ where: { id, tenantId }, relations: ['users'], }); return role?.users?.length ?? 0; } // Get system roles (admin, user, etc.) async getSystemRoles(tenantId: string): Promise { return this.roleRepository.find({ where: { tenantId, isSystem: true }, relations: ['permissions'], order: { name: 'ASC' }, }); } // Get custom roles (non-system) async getCustomRoles(tenantId: string): Promise { return this.roleRepository.find({ where: { tenantId, isSystem: false }, relations: ['permissions'], order: { name: 'ASC' }, }); } // Clone a role async cloneRole( id: string, tenantId: string, newCode: string, newName: string, createdBy: string ): Promise { const sourceRole = await this.findOne(id, tenantId); if (!sourceRole) return null; // Check if new code exists const existingCode = await this.findByCode(newCode, tenantId); if (existingCode) { throw new Error(`Ya existe un rol con el codigo "${newCode}"`); } const newRole = this.roleRepository.create({ tenantId, name: newName, code: newCode, description: sourceRole.description, isSystem: false, // Cloned roles are never system roles color: sourceRole.color, permissions: sourceRole.permissions, createdBy, }); return this.roleRepository.save(newRole); } async delete(id: string, tenantId: string): Promise { const role = await this.findOne(id, tenantId); if (!role) return false; // Cannot delete system roles if (role.isSystem) { throw new Error('No se pueden eliminar roles del sistema'); } // Check if role has users if (role.users && role.users.length > 0) { throw new Error('No se puede eliminar un rol que tiene usuarios asignados'); } const result = await this.roleRepository.softDelete(id); return (result.affected ?? 0) > 0; } }