erp-core-backend/src/modules/hr/hr.controller.ts

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();