- CartaPorteService: CRUD completo para Carta Porte 3.1 SAT - RolesService: Gestión de roles con aislamiento por tenant - PermissionsService: Gestión de permisos con bulk creation - TarifasService: Motor de cotización con múltiples tipos de tarifa - LanesService: Gestión de rutas origen-destino Total: ~1,800 líneas de código de servicios Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
303 lines
8.0 KiB
TypeScript
303 lines
8.0 KiB
TypeScript
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<CreateRoleDto> {}
|
|
|
|
export interface AssignPermissionsDto {
|
|
permissionIds: string[];
|
|
}
|
|
|
|
export class RolesService {
|
|
constructor(
|
|
private readonly roleRepository: Repository<Role>,
|
|
private readonly permissionRepository: Repository<Permission>,
|
|
) {}
|
|
|
|
async findAll(params: RoleSearchParams): Promise<{ data: Role[]; total: number }> {
|
|
const {
|
|
tenantId,
|
|
search,
|
|
isSystem,
|
|
limit = 50,
|
|
offset = 0,
|
|
} = params;
|
|
|
|
const where: FindOptionsWhere<Role>[] = [];
|
|
const baseWhere: FindOptionsWhere<Role> = { 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<Role | null> {
|
|
return this.roleRepository.findOne({
|
|
where: { id, tenantId },
|
|
relations: ['permissions', 'users'],
|
|
});
|
|
}
|
|
|
|
async findByCode(code: string, tenantId: string): Promise<Role | null> {
|
|
return this.roleRepository.findOne({
|
|
where: { code, tenantId },
|
|
relations: ['permissions'],
|
|
});
|
|
}
|
|
|
|
async create(tenantId: string, dto: CreateRoleDto, createdBy: string): Promise<Role> {
|
|
// 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<Role | null> {
|
|
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<Role | null> {
|
|
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<Role | null> {
|
|
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<Role | null> {
|
|
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<Permission[]> {
|
|
const role = await this.findOne(id, tenantId);
|
|
if (!role) return [];
|
|
|
|
return role.permissions;
|
|
}
|
|
|
|
async getUsers(id: string, tenantId: string): Promise<User[]> {
|
|
const role = await this.findOne(id, tenantId);
|
|
if (!role) return [];
|
|
|
|
return role.users;
|
|
}
|
|
|
|
async getUserCount(id: string, tenantId: string): Promise<number> {
|
|
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<Role[]> {
|
|
return this.roleRepository.find({
|
|
where: { tenantId, isSystem: true },
|
|
relations: ['permissions'],
|
|
order: { name: 'ASC' },
|
|
});
|
|
}
|
|
|
|
// Get custom roles (non-system)
|
|
async getCustomRoles(tenantId: string): Promise<Role[]> {
|
|
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<Role | null> {
|
|
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<boolean> {
|
|
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;
|
|
}
|
|
}
|