209 lines
7.6 KiB
JavaScript
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
|