/** * Puestos (Positions) Service * @module HR */ import { Repository, FindOptionsWhere, ILike } from 'typeorm'; import { Puesto } from '../entities/puesto.entity'; import { CreatePuestoDto, UpdatePuestoDto } from '../dto'; export interface PuestoSearchParams { tenantId: string; search?: string; activo?: boolean; nivelRiesgo?: string; requiereCapacitacionEspecial?: boolean; limit?: number; offset?: number; } export class PuestosService { constructor(private readonly puestoRepository: Repository) {} /** * Find all puestos with filters */ async findAll(params: PuestoSearchParams): Promise<{ data: Puesto[]; total: number }> { const { tenantId, search, activo, nivelRiesgo, requiereCapacitacionEspecial, limit = 50, offset = 0, } = params; const where: FindOptionsWhere[] = []; const baseWhere: FindOptionsWhere = { tenantId }; if (activo !== undefined) { baseWhere.activo = activo; } if (nivelRiesgo) { baseWhere.nivelRiesgo = nivelRiesgo; } if (requiereCapacitacionEspecial !== undefined) { baseWhere.requiereCapacitacionEspecial = requiereCapacitacionEspecial; } if (search) { where.push( { ...baseWhere, codigo: ILike(`%${search}%`) }, { ...baseWhere, nombre: ILike(`%${search}%`) } ); } else { where.push(baseWhere); } const [data, total] = await this.puestoRepository.findAndCount({ where, take: limit, skip: offset, order: { nombre: 'ASC' }, }); return { data, total }; } /** * Find one puesto by ID */ async findOne(id: string, tenantId: string): Promise { return this.puestoRepository.findOne({ where: { id, tenantId }, relations: ['empleados'], }); } /** * Find puesto by code */ async findByCode(codigo: string, tenantId: string): Promise { return this.puestoRepository.findOne({ where: { codigo, tenantId }, }); } /** * Create new puesto */ async create(tenantId: string, dto: CreatePuestoDto): Promise { // Check for existing code const existingCode = await this.findByCode(dto.codigo, tenantId); if (existingCode) { throw new Error('Ya existe un puesto con este codigo'); } const puesto = this.puestoRepository.create({ ...dto, tenantId, }); return this.puestoRepository.save(puesto); } /** * Update puesto */ async update(id: string, tenantId: string, dto: UpdatePuestoDto): Promise { const puesto = await this.findOne(id, tenantId); if (!puesto) return null; // If changing code, check for duplicates if (dto.codigo && dto.codigo !== puesto.codigo) { const existing = await this.findByCode(dto.codigo, tenantId); if (existing) { throw new Error('Ya existe un puesto con este codigo'); } } Object.assign(puesto, dto); return this.puestoRepository.save(puesto); } /** * Delete puesto */ async delete(id: string, tenantId: string): Promise { const puesto = await this.findOne(id, tenantId); if (!puesto) return false; // Check if has employees assigned if (puesto.empleados && puesto.empleados.length > 0) { throw new Error('No se puede eliminar un puesto con empleados asignados'); } const result = await this.puestoRepository.delete(id); return (result.affected ?? 0) > 0; } /** * Get active puestos */ async getActive(tenantId: string): Promise { return this.puestoRepository.find({ where: { tenantId, activo: true }, order: { nombre: 'ASC' }, }); } /** * Get puestos by risk level */ async getByRiskLevel(tenantId: string, nivelRiesgo: string): Promise { return this.puestoRepository.find({ where: { tenantId, nivelRiesgo, activo: true }, order: { nombre: 'ASC' }, }); } /** * Get puestos requiring special training */ async getRequiringSpecialTraining(tenantId: string): Promise { return this.puestoRepository.find({ where: { tenantId, requiereCapacitacionEspecial: true, activo: true }, order: { nombre: 'ASC' }, }); } /** * Activate puesto */ async activate(id: string, tenantId: string): Promise { const puesto = await this.findOne(id, tenantId); if (!puesto) return null; puesto.activo = true; return this.puestoRepository.save(puesto); } /** * Deactivate puesto */ async deactivate(id: string, tenantId: string): Promise { const puesto = await this.findOne(id, tenantId); if (!puesto) return null; puesto.activo = false; return this.puestoRepository.save(puesto); } /** * Get employee count by puesto */ async getEmployeeCount(tenantId: string): Promise> { const result = await this.puestoRepository .createQueryBuilder('puesto') .leftJoin('puesto.empleados', 'empleado', 'empleado.estado = :estado', { estado: 'activo' }) .select('puesto.id', 'puestoId') .addSelect('puesto.nombre', 'nombre') .addSelect('COUNT(empleado.id)', 'count') .where('puesto.tenant_id = :tenantId', { tenantId }) .andWhere('puesto.activo = true') .groupBy('puesto.id') .addGroupBy('puesto.nombre') .getRawMany(); return result.map(row => ({ puestoId: row.puestoId, nombre: row.nombre, count: parseInt(row.count, 10), })); } }