722 lines
27 KiB
TypeScript
722 lines
27 KiB
TypeScript
import { Response, NextFunction } from 'express';
|
|
import { z } from 'zod';
|
|
import { employeesService, CreateEmployeeDto, UpdateEmployeeDto, EmployeeFilters } from './employees.service.js';
|
|
import { departmentsService, CreateDepartmentDto, UpdateDepartmentDto, DepartmentFilters, CreateJobPositionDto, UpdateJobPositionDto } from './departments.service.js';
|
|
import { contractsService, CreateContractDto, UpdateContractDto, ContractFilters } from './contracts.service.js';
|
|
import { leavesService, CreateLeaveDto, UpdateLeaveDto, LeaveFilters, CreateLeaveTypeDto, UpdateLeaveTypeDto } from './leaves.service.js';
|
|
import { AuthenticatedRequest } from '../../shared/middleware/auth.middleware.js';
|
|
import { ValidationError } from '../../shared/errors/index.js';
|
|
|
|
// Employee schemas
|
|
const createEmployeeSchema = z.object({
|
|
company_id: z.string().uuid(),
|
|
employee_number: z.string().min(1).max(50),
|
|
first_name: z.string().min(1).max(100),
|
|
last_name: z.string().min(1).max(100),
|
|
middle_name: z.string().max(100).optional(),
|
|
user_id: z.string().uuid().optional(),
|
|
birth_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
gender: z.string().max(20).optional(),
|
|
marital_status: z.string().max(20).optional(),
|
|
nationality: z.string().max(100).optional(),
|
|
identification_id: z.string().max(50).optional(),
|
|
identification_type: z.string().max(50).optional(),
|
|
social_security_number: z.string().max(50).optional(),
|
|
tax_id: z.string().max(50).optional(),
|
|
email: z.string().email().max(255).optional(),
|
|
work_email: z.string().email().max(255).optional(),
|
|
phone: z.string().max(50).optional(),
|
|
work_phone: z.string().max(50).optional(),
|
|
mobile: z.string().max(50).optional(),
|
|
emergency_contact: z.string().max(255).optional(),
|
|
emergency_phone: z.string().max(50).optional(),
|
|
street: z.string().max(255).optional(),
|
|
city: z.string().max(100).optional(),
|
|
state: z.string().max(100).optional(),
|
|
zip: z.string().max(20).optional(),
|
|
country: z.string().max(100).optional(),
|
|
department_id: z.string().uuid().optional(),
|
|
job_position_id: z.string().uuid().optional(),
|
|
manager_id: z.string().uuid().optional(),
|
|
hire_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
bank_name: z.string().max(100).optional(),
|
|
bank_account: z.string().max(50).optional(),
|
|
bank_clabe: z.string().max(20).optional(),
|
|
photo_url: z.string().url().max(500).optional(),
|
|
notes: z.string().optional(),
|
|
});
|
|
|
|
const updateEmployeeSchema = createEmployeeSchema.partial().omit({ company_id: true, employee_number: true, hire_date: true });
|
|
|
|
const employeeQuerySchema = z.object({
|
|
company_id: z.string().uuid().optional(),
|
|
department_id: z.string().uuid().optional(),
|
|
status: z.enum(['active', 'inactive', 'on_leave', 'terminated']).optional(),
|
|
manager_id: z.string().uuid().optional(),
|
|
search: z.string().optional(),
|
|
page: z.coerce.number().int().positive().default(1),
|
|
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
});
|
|
|
|
// Department schemas
|
|
const createDepartmentSchema = z.object({
|
|
company_id: z.string().uuid(),
|
|
name: z.string().min(1).max(100),
|
|
code: z.string().max(20).optional(),
|
|
parent_id: z.string().uuid().optional(),
|
|
manager_id: z.string().uuid().optional(),
|
|
description: z.string().optional(),
|
|
color: z.string().max(20).optional(),
|
|
});
|
|
|
|
const updateDepartmentSchema = z.object({
|
|
name: z.string().min(1).max(100).optional(),
|
|
code: z.string().max(20).optional().nullable(),
|
|
parent_id: z.string().uuid().optional().nullable(),
|
|
manager_id: z.string().uuid().optional().nullable(),
|
|
description: z.string().optional().nullable(),
|
|
color: z.string().max(20).optional().nullable(),
|
|
active: z.boolean().optional(),
|
|
});
|
|
|
|
const departmentQuerySchema = z.object({
|
|
company_id: z.string().uuid().optional(),
|
|
active: z.coerce.boolean().optional(),
|
|
search: z.string().optional(),
|
|
page: z.coerce.number().int().positive().default(1),
|
|
limit: z.coerce.number().int().positive().max(100).default(50),
|
|
});
|
|
|
|
// Job Position schemas
|
|
const createJobPositionSchema = z.object({
|
|
name: z.string().min(1).max(100),
|
|
department_id: z.string().uuid().optional(),
|
|
description: z.string().optional(),
|
|
requirements: z.string().optional(),
|
|
responsibilities: z.string().optional(),
|
|
min_salary: z.number().min(0).optional(),
|
|
max_salary: z.number().min(0).optional(),
|
|
});
|
|
|
|
const updateJobPositionSchema = z.object({
|
|
name: z.string().min(1).max(100).optional(),
|
|
department_id: z.string().uuid().optional().nullable(),
|
|
description: z.string().optional().nullable(),
|
|
requirements: z.string().optional().nullable(),
|
|
responsibilities: z.string().optional().nullable(),
|
|
min_salary: z.number().min(0).optional().nullable(),
|
|
max_salary: z.number().min(0).optional().nullable(),
|
|
active: z.boolean().optional(),
|
|
});
|
|
|
|
// Contract schemas
|
|
const createContractSchema = z.object({
|
|
company_id: z.string().uuid(),
|
|
employee_id: z.string().uuid(),
|
|
name: z.string().min(1).max(100),
|
|
reference: z.string().max(100).optional(),
|
|
contract_type: z.enum(['permanent', 'temporary', 'contractor', 'internship', 'part_time']),
|
|
job_position_id: z.string().uuid().optional(),
|
|
department_id: z.string().uuid().optional(),
|
|
date_start: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
date_end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
trial_date_end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
wage: z.number().min(0),
|
|
wage_type: z.string().max(20).optional(),
|
|
currency_id: z.string().uuid().optional(),
|
|
hours_per_week: z.number().min(0).max(168).optional(),
|
|
vacation_days: z.number().int().min(0).optional(),
|
|
christmas_bonus_days: z.number().int().min(0).optional(),
|
|
document_url: z.string().url().max(500).optional(),
|
|
notes: z.string().optional(),
|
|
});
|
|
|
|
const updateContractSchema = z.object({
|
|
reference: z.string().max(100).optional().nullable(),
|
|
job_position_id: z.string().uuid().optional().nullable(),
|
|
department_id: z.string().uuid().optional().nullable(),
|
|
date_end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
|
|
trial_date_end: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
|
|
wage: z.number().min(0).optional(),
|
|
wage_type: z.string().max(20).optional(),
|
|
currency_id: z.string().uuid().optional().nullable(),
|
|
hours_per_week: z.number().min(0).max(168).optional(),
|
|
vacation_days: z.number().int().min(0).optional(),
|
|
christmas_bonus_days: z.number().int().min(0).optional(),
|
|
document_url: z.string().url().max(500).optional().nullable(),
|
|
notes: z.string().optional().nullable(),
|
|
});
|
|
|
|
const contractQuerySchema = z.object({
|
|
company_id: z.string().uuid().optional(),
|
|
employee_id: z.string().uuid().optional(),
|
|
status: z.enum(['draft', 'active', 'expired', 'terminated', 'cancelled']).optional(),
|
|
contract_type: z.enum(['permanent', 'temporary', 'contractor', 'internship', 'part_time']).optional(),
|
|
search: z.string().optional(),
|
|
page: z.coerce.number().int().positive().default(1),
|
|
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
});
|
|
|
|
// Leave Type schemas
|
|
const createLeaveTypeSchema = z.object({
|
|
name: z.string().min(1).max(100),
|
|
code: z.string().max(20).optional(),
|
|
leave_type: z.enum(['vacation', 'sick', 'personal', 'maternity', 'paternity', 'bereavement', 'unpaid', 'other']),
|
|
requires_approval: z.boolean().optional(),
|
|
max_days: z.number().int().min(1).optional(),
|
|
is_paid: z.boolean().optional(),
|
|
color: z.string().max(20).optional(),
|
|
});
|
|
|
|
const updateLeaveTypeSchema = z.object({
|
|
name: z.string().min(1).max(100).optional(),
|
|
code: z.string().max(20).optional().nullable(),
|
|
requires_approval: z.boolean().optional(),
|
|
max_days: z.number().int().min(1).optional().nullable(),
|
|
is_paid: z.boolean().optional(),
|
|
color: z.string().max(20).optional().nullable(),
|
|
active: z.boolean().optional(),
|
|
});
|
|
|
|
// Leave schemas
|
|
const createLeaveSchema = z.object({
|
|
company_id: z.string().uuid(),
|
|
employee_id: z.string().uuid(),
|
|
leave_type_id: z.string().uuid(),
|
|
name: z.string().max(255).optional(),
|
|
date_from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
date_to: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
description: z.string().optional(),
|
|
});
|
|
|
|
const updateLeaveSchema = z.object({
|
|
leave_type_id: z.string().uuid().optional(),
|
|
name: z.string().max(255).optional().nullable(),
|
|
date_from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
date_to: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
description: z.string().optional().nullable(),
|
|
});
|
|
|
|
const leaveQuerySchema = z.object({
|
|
company_id: z.string().uuid().optional(),
|
|
employee_id: z.string().uuid().optional(),
|
|
leave_type_id: z.string().uuid().optional(),
|
|
status: z.enum(['draft', 'submitted', 'approved', 'rejected', 'cancelled']).optional(),
|
|
date_from: z.string().optional(),
|
|
date_to: z.string().optional(),
|
|
search: z.string().optional(),
|
|
page: z.coerce.number().int().positive().default(1),
|
|
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
});
|
|
|
|
const terminateSchema = z.object({
|
|
termination_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
});
|
|
|
|
const rejectSchema = z.object({
|
|
reason: z.string().min(1),
|
|
});
|
|
|
|
class HrController {
|
|
// ========== EMPLOYEES ==========
|
|
|
|
async getEmployees(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const queryResult = employeeQuerySchema.safeParse(req.query);
|
|
if (!queryResult.success) {
|
|
throw new ValidationError('Parametros de consulta invalidos', queryResult.error.errors);
|
|
}
|
|
|
|
const filters: EmployeeFilters = queryResult.data;
|
|
const result = await employeesService.findAll(req.tenantId!, filters);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.data,
|
|
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async getEmployee(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const employee = await employeesService.findById(req.params.id, req.tenantId!);
|
|
res.json({ success: true, data: employee });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async createEmployee(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = createEmployeeSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de empleado invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: CreateEmployeeDto = parseResult.data;
|
|
const employee = await employeesService.create(dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.status(201).json({ success: true, data: employee, message: 'Empleado creado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateEmployee(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = updateEmployeeSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de empleado invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: UpdateEmployeeDto = parseResult.data;
|
|
const employee = await employeesService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.json({ success: true, data: employee, message: 'Empleado actualizado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async terminateEmployee(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = terminateSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const employee = await employeesService.terminate(req.params.id, parseResult.data.termination_date, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: employee, message: 'Empleado dado de baja exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async reactivateEmployee(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const employee = await employeesService.reactivate(req.params.id, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: employee, message: 'Empleado reactivado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteEmployee(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
await employeesService.delete(req.params.id, req.tenantId!);
|
|
res.json({ success: true, message: 'Empleado eliminado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async getSubordinates(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const subordinates = await employeesService.getSubordinates(req.params.id, req.tenantId!);
|
|
res.json({ success: true, data: subordinates });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ========== DEPARTMENTS ==========
|
|
|
|
async getDepartments(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const queryResult = departmentQuerySchema.safeParse(req.query);
|
|
if (!queryResult.success) {
|
|
throw new ValidationError('Parametros de consulta invalidos', queryResult.error.errors);
|
|
}
|
|
|
|
const filters: DepartmentFilters = queryResult.data;
|
|
const result = await departmentsService.findAll(req.tenantId!, filters);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.data,
|
|
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 50)) },
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async getDepartment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const department = await departmentsService.findById(req.params.id, req.tenantId!);
|
|
res.json({ success: true, data: department });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async createDepartment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = createDepartmentSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de departamento invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: CreateDepartmentDto = parseResult.data;
|
|
const department = await departmentsService.create(dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.status(201).json({ success: true, data: department, message: 'Departamento creado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateDepartment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = updateDepartmentSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de departamento invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: UpdateDepartmentDto = parseResult.data;
|
|
const department = await departmentsService.update(req.params.id, dto, req.tenantId!);
|
|
|
|
res.json({ success: true, data: department, message: 'Departamento actualizado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteDepartment(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
await departmentsService.delete(req.params.id, req.tenantId!);
|
|
res.json({ success: true, message: 'Departamento eliminado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ========== JOB POSITIONS ==========
|
|
|
|
async getJobPositions(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const includeInactive = req.query.include_inactive === 'true';
|
|
const positions = await departmentsService.getJobPositions(req.tenantId!, includeInactive);
|
|
res.json({ success: true, data: positions });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async createJobPosition(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = createJobPositionSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de puesto invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: CreateJobPositionDto = parseResult.data;
|
|
const position = await departmentsService.createJobPosition(dto, req.tenantId!);
|
|
|
|
res.status(201).json({ success: true, data: position, message: 'Puesto creado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateJobPosition(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = updateJobPositionSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de puesto invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: UpdateJobPositionDto = parseResult.data;
|
|
const position = await departmentsService.updateJobPosition(req.params.id, dto, req.tenantId!);
|
|
|
|
res.json({ success: true, data: position, message: 'Puesto actualizado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteJobPosition(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
await departmentsService.deleteJobPosition(req.params.id, req.tenantId!);
|
|
res.json({ success: true, message: 'Puesto eliminado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ========== CONTRACTS ==========
|
|
|
|
async getContracts(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const queryResult = contractQuerySchema.safeParse(req.query);
|
|
if (!queryResult.success) {
|
|
throw new ValidationError('Parametros de consulta invalidos', queryResult.error.errors);
|
|
}
|
|
|
|
const filters: ContractFilters = queryResult.data;
|
|
const result = await contractsService.findAll(req.tenantId!, filters);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.data,
|
|
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async getContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const contract = await contractsService.findById(req.params.id, req.tenantId!);
|
|
res.json({ success: true, data: contract });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async createContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = createContractSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de contrato invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: CreateContractDto = parseResult.data;
|
|
const contract = await contractsService.create(dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.status(201).json({ success: true, data: contract, message: 'Contrato creado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = updateContractSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de contrato invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: UpdateContractDto = parseResult.data;
|
|
const contract = await contractsService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.json({ success: true, data: contract, message: 'Contrato actualizado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async activateContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const contract = await contractsService.activate(req.params.id, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: contract, message: 'Contrato activado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async terminateContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = terminateSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const contract = await contractsService.terminate(req.params.id, parseResult.data.termination_date, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: contract, message: 'Contrato terminado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async cancelContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const contract = await contractsService.cancel(req.params.id, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: contract, message: 'Contrato cancelado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteContract(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
await contractsService.delete(req.params.id, req.tenantId!);
|
|
res.json({ success: true, message: 'Contrato eliminado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ========== LEAVE TYPES ==========
|
|
|
|
async getLeaveTypes(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const includeInactive = req.query.include_inactive === 'true';
|
|
const leaveTypes = await leavesService.getLeaveTypes(req.tenantId!, includeInactive);
|
|
res.json({ success: true, data: leaveTypes });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async createLeaveType(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = createLeaveTypeSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de tipo de ausencia invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: CreateLeaveTypeDto = parseResult.data;
|
|
const leaveType = await leavesService.createLeaveType(dto, req.tenantId!);
|
|
|
|
res.status(201).json({ success: true, data: leaveType, message: 'Tipo de ausencia creado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateLeaveType(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = updateLeaveTypeSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de tipo de ausencia invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: UpdateLeaveTypeDto = parseResult.data;
|
|
const leaveType = await leavesService.updateLeaveType(req.params.id, dto, req.tenantId!);
|
|
|
|
res.json({ success: true, data: leaveType, message: 'Tipo de ausencia actualizado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteLeaveType(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
await leavesService.deleteLeaveType(req.params.id, req.tenantId!);
|
|
res.json({ success: true, message: 'Tipo de ausencia eliminado exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
// ========== LEAVES ==========
|
|
|
|
async getLeaves(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const queryResult = leaveQuerySchema.safeParse(req.query);
|
|
if (!queryResult.success) {
|
|
throw new ValidationError('Parametros de consulta invalidos', queryResult.error.errors);
|
|
}
|
|
|
|
const filters: LeaveFilters = queryResult.data;
|
|
const result = await leavesService.findAll(req.tenantId!, filters);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.data,
|
|
meta: { total: result.total, page: filters.page, limit: filters.limit, totalPages: Math.ceil(result.total / (filters.limit || 20)) },
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async getLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const leave = await leavesService.findById(req.params.id, req.tenantId!);
|
|
res.json({ success: true, data: leave });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async createLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = createLeaveSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de ausencia invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: CreateLeaveDto = parseResult.data;
|
|
const leave = await leavesService.create(dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.status(201).json({ success: true, data: leave, message: 'Solicitud de ausencia creada exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async updateLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = updateLeaveSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos de ausencia invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const dto: UpdateLeaveDto = parseResult.data;
|
|
const leave = await leavesService.update(req.params.id, dto, req.tenantId!, req.user!.userId);
|
|
|
|
res.json({ success: true, data: leave, message: 'Solicitud de ausencia actualizada exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async submitLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const leave = await leavesService.submit(req.params.id, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: leave, message: 'Solicitud enviada exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async approveLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const leave = await leavesService.approve(req.params.id, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: leave, message: 'Solicitud aprobada exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async rejectLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const parseResult = rejectSchema.safeParse(req.body);
|
|
if (!parseResult.success) {
|
|
throw new ValidationError('Datos invalidos', parseResult.error.errors);
|
|
}
|
|
|
|
const leave = await leavesService.reject(req.params.id, parseResult.data.reason, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: leave, message: 'Solicitud rechazada' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async cancelLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
const leave = await leavesService.cancel(req.params.id, req.tenantId!, req.user!.userId);
|
|
res.json({ success: true, data: leave, message: 'Solicitud cancelada exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
async deleteLeave(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise<void> {
|
|
try {
|
|
await leavesService.delete(req.params.id, req.tenantId!);
|
|
res.json({ success: true, message: 'Solicitud eliminada exitosamente' });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
export const hrController = new HrController();
|