erp-construccion-backend/dist/modules/hr/services/employee.service.js

209 lines
7.6 KiB
JavaScript

"use strict";
/**
* EmployeeService - Servicio para gestión de empleados
*
* CRUD de empleados con gestión de estado y asignaciones a obras.
*
* @module HR
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmployeeService = void 0;
class EmployeeService {
employeeRepository;
asignacionRepository;
constructor(employeeRepository, asignacionRepository) {
this.employeeRepository = employeeRepository;
this.asignacionRepository = asignacionRepository;
}
async findWithFilters(ctx, filters = {}, page = 1, limit = 20) {
const skip = (page - 1) * limit;
const queryBuilder = this.employeeRepository
.createQueryBuilder('employee')
.leftJoinAndSelect('employee.puesto', 'puesto')
.where('employee.tenant_id = :tenantId', { tenantId: ctx.tenantId });
if (filters.estado) {
queryBuilder.andWhere('employee.estado = :estado', { estado: filters.estado });
}
if (filters.puestoId) {
queryBuilder.andWhere('employee.puesto_id = :puestoId', { puestoId: filters.puestoId });
}
if (filters.departamento) {
queryBuilder.andWhere('employee.departamento = :departamento', { departamento: filters.departamento });
}
if (filters.fraccionamientoId) {
queryBuilder
.innerJoin('employee.asignaciones', 'asig')
.andWhere('asig.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId: filters.fraccionamientoId })
.andWhere('asig.activo = true');
}
if (filters.search) {
queryBuilder.andWhere('(employee.codigo ILIKE :search OR employee.nombre ILIKE :search OR employee.apellido_paterno ILIKE :search OR employee.curp ILIKE :search)', { search: `%${filters.search}%` });
}
queryBuilder
.orderBy('employee.apellido_paterno', 'ASC')
.addOrderBy('employee.nombre', 'ASC')
.skip(skip)
.take(limit);
const [data, total] = await queryBuilder.getManyAndCount();
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
async findById(ctx, id) {
return this.employeeRepository.findOne({
where: {
id,
tenantId: ctx.tenantId,
},
relations: ['puesto', 'asignaciones', 'asignaciones.fraccionamiento'],
});
}
async findByCodigo(ctx, codigo) {
return this.employeeRepository.findOne({
where: {
codigo,
tenantId: ctx.tenantId,
},
});
}
async findByCurp(ctx, curp) {
return this.employeeRepository.findOne({
where: {
curp,
tenantId: ctx.tenantId,
},
});
}
async create(ctx, dto) {
// Validate unique codigo
const existingCodigo = await this.findByCodigo(ctx, dto.codigo);
if (existingCodigo) {
throw new Error(`Employee with codigo ${dto.codigo} already exists`);
}
// Validate unique CURP if provided
if (dto.curp) {
const existingCurp = await this.findByCurp(ctx, dto.curp);
if (existingCurp) {
throw new Error(`Employee with CURP ${dto.curp} already exists`);
}
}
const employee = this.employeeRepository.create({
tenantId: ctx.tenantId,
createdById: ctx.userId,
estado: 'activo',
...dto,
});
return this.employeeRepository.save(employee);
}
async update(ctx, id, dto) {
const existing = await this.findById(ctx, id);
if (!existing) {
return null;
}
// Validate unique CURP if changed
if (dto.curp && dto.curp !== existing.curp) {
const existingCurp = await this.findByCurp(ctx, dto.curp);
if (existingCurp) {
throw new Error(`Employee with CURP ${dto.curp} already exists`);
}
}
const updated = this.employeeRepository.merge(existing, dto);
return this.employeeRepository.save(updated);
}
async changeStatus(ctx, id, estado, fechaBaja) {
const existing = await this.findById(ctx, id);
if (!existing) {
return null;
}
existing.estado = estado;
if (estado === 'baja' && fechaBaja) {
existing.fechaBaja = fechaBaja;
}
return this.employeeRepository.save(existing);
}
async assignToFraccionamiento(ctx, employeeId, dto) {
const employee = await this.findById(ctx, employeeId);
if (!employee) {
throw new Error('Employee not found');
}
// Deactivate previous active assignment to same fraccionamiento
await this.asignacionRepository.update({
tenantId: ctx.tenantId,
employeeId,
fraccionamientoId: dto.fraccionamientoId,
activo: true,
}, { activo: false, fechaFin: new Date() });
const asignacion = this.asignacionRepository.create({
tenantId: ctx.tenantId,
employeeId,
fraccionamientoId: dto.fraccionamientoId,
fechaInicio: dto.fechaInicio,
fechaFin: dto.fechaFin,
rol: dto.rol,
activo: true,
});
return this.asignacionRepository.save(asignacion);
}
async removeFromFraccionamiento(ctx, employeeId, fraccionamientoId) {
const result = await this.asignacionRepository.update({
tenantId: ctx.tenantId,
employeeId,
fraccionamientoId,
activo: true,
}, { activo: false, fechaFin: new Date() });
return (result.affected ?? 0) > 0;
}
async getEmployeesByFraccionamiento(ctx, fraccionamientoId) {
const asignaciones = await this.asignacionRepository.find({
where: {
tenantId: ctx.tenantId,
fraccionamientoId,
activo: true,
},
relations: ['employee', 'employee.puesto'],
});
return asignaciones.map(a => a.employee);
}
async getStats(ctx) {
const [total, activos, inactivos, bajas] = await Promise.all([
this.employeeRepository.count({
where: { tenantId: ctx.tenantId },
}),
this.employeeRepository.count({
where: { tenantId: ctx.tenantId, estado: 'activo' },
}),
this.employeeRepository.count({
where: { tenantId: ctx.tenantId, estado: 'inactivo' },
}),
this.employeeRepository.count({
where: { tenantId: ctx.tenantId, estado: 'baja' },
}),
]);
const porDepartamento = await this.employeeRepository
.createQueryBuilder('employee')
.select('employee.departamento', 'departamento')
.addSelect('COUNT(*)', 'count')
.where('employee.tenant_id = :tenantId', { tenantId: ctx.tenantId })
.andWhere('employee.estado = :estado', { estado: 'activo' })
.groupBy('employee.departamento')
.getRawMany();
return {
total,
activos,
inactivos,
bajas,
porDepartamento: porDepartamento.map(p => ({
departamento: p.departamento || 'Sin departamento',
count: parseInt(p.count, 10),
})),
};
}
}
exports.EmployeeService = EmployeeService;
//# sourceMappingURL=employee.service.js.map