[TASK-006] feat: Add HR entities and DTOs
Add TypeORM entities for the HR module: - Department: Organizational departments with hierarchy - JobPosition: Job titles/positions - Employee: Employee records with manager self-reference - Contract: Employment contracts - LeaveType: Configurable leave types - LeaveAllocation: Employee leave allocations - Leave: Leave requests Add DTOs with class-validator: - CreateEmployeeDto, UpdateEmployeeDto - CreateContractDto, UpdateContractDto, ActivateContractDto, TerminateContractDto - CreateLeaveDto, UpdateLeaveDto, ApproveLeaveDto, RejectLeaveDto - Filter DTOs for all entities - HrPaginationDto Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8565056de3
commit
56f5663583
218
src/modules/hr/dto/create-contract.dto.ts
Normal file
218
src/modules/hr/dto/create-contract.dto.ts
Normal file
@ -0,0 +1,218 @@
|
||||
import {
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsOptional,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsPositive,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
Min,
|
||||
Max,
|
||||
} from 'class-validator';
|
||||
import { ContractType, ContractStatus, WageType } from '../entities/contract.entity';
|
||||
|
||||
/**
|
||||
* DTO for creating a new employment contract
|
||||
*/
|
||||
export class CreateContractDto {
|
||||
@IsUUID()
|
||||
companyId: string;
|
||||
|
||||
@IsUUID()
|
||||
employeeId: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
reference?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['permanent', 'temporary', 'contractor', 'internship', 'part_time'])
|
||||
contractType?: ContractType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['draft', 'active', 'expired', 'terminated', 'cancelled'])
|
||||
status?: ContractStatus;
|
||||
|
||||
@IsDateString()
|
||||
dateStart: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateEnd?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
jobPositionId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
departmentId?: string;
|
||||
|
||||
// Compensation
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
wage: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['hourly', 'daily', 'weekly', 'biweekly', 'monthly', 'annual'])
|
||||
wageType?: WageType;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(3)
|
||||
currency?: string;
|
||||
|
||||
// Schedule
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(168)
|
||||
hoursPerWeek?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
scheduleType?: string;
|
||||
|
||||
// Trial period
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(12)
|
||||
trialPeriodMonths?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
trialDateEnd?: string;
|
||||
|
||||
// Metadata
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
documentUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for updating an existing contract
|
||||
*/
|
||||
export class UpdateContractDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
reference?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['permanent', 'temporary', 'contractor', 'internship', 'part_time'])
|
||||
contractType?: ContractType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['draft', 'active', 'expired', 'terminated', 'cancelled'])
|
||||
status?: ContractStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateStart?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateEnd?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
jobPositionId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
departmentId?: string | null;
|
||||
|
||||
// Compensation
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
wage?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['hourly', 'daily', 'weekly', 'biweekly', 'monthly', 'annual'])
|
||||
wageType?: WageType;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(3)
|
||||
currency?: string;
|
||||
|
||||
// Schedule
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(168)
|
||||
hoursPerWeek?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
scheduleType?: string | null;
|
||||
|
||||
// Trial period
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(12)
|
||||
trialPeriodMonths?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
trialDateEnd?: string | null;
|
||||
|
||||
// Metadata
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
documentUrl?: string | null;
|
||||
|
||||
// Termination (only for termination flow)
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
terminationReason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for activating a contract
|
||||
*/
|
||||
export class ActivateContractDto {
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
activatedAt?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for terminating a contract
|
||||
*/
|
||||
export class TerminateContractDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
terminationReason: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
terminatedAt?: string;
|
||||
}
|
||||
180
src/modules/hr/dto/create-employee.dto.ts
Normal file
180
src/modules/hr/dto/create-employee.dto.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import {
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsOptional,
|
||||
IsDateString,
|
||||
IsEmail,
|
||||
IsEnum,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { EmployeeStatus } from '../entities/employee.entity';
|
||||
|
||||
/**
|
||||
* DTO for creating a new employee
|
||||
*/
|
||||
export class CreateEmployeeDto {
|
||||
@IsUUID()
|
||||
companyId: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(50)
|
||||
employeeNumber: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
firstName: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
lastName: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
userId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
partnerId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
departmentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
jobPositionId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
managerId?: string;
|
||||
|
||||
// Work contact
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
@MaxLength(255)
|
||||
workEmail?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
workPhone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
workMobile?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
workLocation?: string;
|
||||
|
||||
// Personal data
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
@MaxLength(255)
|
||||
personalEmail?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
personalPhone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
birthDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
gender?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
maritalStatus?: string;
|
||||
|
||||
// Identification
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
identificationType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
identificationNumber?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
taxId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
socialSecurityNumber?: string;
|
||||
|
||||
// Address
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
street?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
city?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
state?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
postalCode?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
country?: string;
|
||||
|
||||
// Employment
|
||||
@IsDateString()
|
||||
hireDate: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['active', 'inactive', 'on_leave', 'terminated'])
|
||||
status?: EmployeeStatus;
|
||||
|
||||
// Emergency contact
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
emergencyContactName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
emergencyContactPhone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
emergencyContactRelationship?: string;
|
||||
|
||||
// Metadata
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
avatarUrl?: string;
|
||||
}
|
||||
267
src/modules/hr/dto/create-leave.dto.ts
Normal file
267
src/modules/hr/dto/create-leave.dto.ts
Normal file
@ -0,0 +1,267 @@
|
||||
import {
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsOptional,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsBoolean,
|
||||
IsPositive,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { LeaveStatus, HalfDayType } from '../entities/leave.entity';
|
||||
|
||||
/**
|
||||
* DTO for creating a new leave request
|
||||
*/
|
||||
export class CreateLeaveDto {
|
||||
@IsUUID()
|
||||
companyId: string;
|
||||
|
||||
@IsUUID()
|
||||
employeeId: string;
|
||||
|
||||
@IsUUID()
|
||||
leaveTypeId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
allocationId?: string;
|
||||
|
||||
@IsDateString()
|
||||
dateFrom: string;
|
||||
|
||||
@IsDateString()
|
||||
dateTo: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
daysRequested: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isHalfDay?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['morning', 'afternoon'])
|
||||
halfDayType?: HalfDayType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['draft', 'submitted', 'approved', 'rejected', 'cancelled'])
|
||||
status?: LeaveStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
requestReason?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
documentUrl?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for updating an existing leave request
|
||||
*/
|
||||
export class UpdateLeaveDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
leaveTypeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
allocationId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateFrom?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateTo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
daysRequested?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isHalfDay?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['morning', 'afternoon'])
|
||||
halfDayType?: HalfDayType | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['draft', 'submitted', 'approved', 'rejected', 'cancelled'])
|
||||
status?: LeaveStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
requestReason?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
documentUrl?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for submitting a leave request for approval
|
||||
*/
|
||||
export class SubmitLeaveDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
requestReason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for approving a leave request
|
||||
*/
|
||||
export class ApproveLeaveDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for rejecting a leave request
|
||||
*/
|
||||
export class RejectLeaveDto {
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(1000)
|
||||
rejectionReason: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for cancelling a leave request
|
||||
*/
|
||||
export class CancelLeaveDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(1000)
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for creating a leave type
|
||||
*/
|
||||
export class CreateLeaveTypeDto {
|
||||
@IsUUID()
|
||||
companyId: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(255)
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(50)
|
||||
code: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(7)
|
||||
color?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['vacation', 'sick', 'personal', 'maternity', 'paternity', 'bereavement', 'unpaid', 'other'])
|
||||
leaveCategory?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['fixed', 'accrual', 'unlimited'])
|
||||
allocationType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
requiresApproval?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
requiresDocument?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
maxDaysPerRequest?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
maxDaysPerYear?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
minDaysNotice?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isPaid?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
payPercentage?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for creating a leave allocation
|
||||
*/
|
||||
export class CreateLeaveAllocationDto {
|
||||
@IsUUID()
|
||||
employeeId: string;
|
||||
|
||||
@IsUUID()
|
||||
leaveTypeId: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
daysAllocated: number;
|
||||
|
||||
@IsDateString()
|
||||
dateFrom: string;
|
||||
|
||||
@IsDateString()
|
||||
dateTo: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* DTO for updating a leave allocation
|
||||
*/
|
||||
export class UpdateLeaveAllocationDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@IsPositive()
|
||||
daysAllocated?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateFrom?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateTo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string | null;
|
||||
}
|
||||
260
src/modules/hr/dto/hr-filters.dto.ts
Normal file
260
src/modules/hr/dto/hr-filters.dto.ts
Normal file
@ -0,0 +1,260 @@
|
||||
import {
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsOptional,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsBoolean,
|
||||
IsNumber,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { EmployeeStatus } from '../entities/employee.entity';
|
||||
import { ContractStatus, ContractType } from '../entities/contract.entity';
|
||||
import { LeaveStatus } from '../entities/leave.entity';
|
||||
import { LeaveTypeCategory } from '../entities/leave-type.entity';
|
||||
|
||||
/**
|
||||
* Filters for querying employees
|
||||
*/
|
||||
export class EmployeeFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
departmentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
jobPositionId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
managerId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['active', 'inactive', 'on_leave', 'terminated'])
|
||||
status?: EmployeeStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
hasUser?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
hireDateFrom?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
hireDateTo?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for querying departments
|
||||
*/
|
||||
export class DepartmentFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
parentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isRoot?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for querying job positions
|
||||
*/
|
||||
export class JobPositionFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
departmentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for querying contracts
|
||||
*/
|
||||
export class ContractFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
employeeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['permanent', 'temporary', 'contractor', 'internship', 'part_time'])
|
||||
contractType?: ContractType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['draft', 'active', 'expired', 'terminated', 'cancelled'])
|
||||
status?: ContractStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateStartFrom?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateStartTo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateEndFrom?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateEndTo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
expiringWithinDays?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for querying leave types
|
||||
*/
|
||||
export class LeaveTypeFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['vacation', 'sick', 'personal', 'maternity', 'paternity', 'bereavement', 'unpaid', 'other'])
|
||||
leaveCategory?: LeaveTypeCategory;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isPaid?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
requiresApproval?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for querying leaves
|
||||
*/
|
||||
export class LeaveFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
companyId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
employeeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
leaveTypeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
approverId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['draft', 'submitted', 'approved', 'rejected', 'cancelled'])
|
||||
status?: LeaveStatus;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateFrom?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
dateTo?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
pendingApproval?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters for querying leave allocations
|
||||
*/
|
||||
export class LeaveAllocationFiltersDto {
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
employeeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
leaveTypeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
asOfDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
hasRemainingDays?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination parameters for HR queries
|
||||
*/
|
||||
export class HrPaginationDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
limit?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sortBy?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['asc', 'desc', 'ASC', 'DESC'])
|
||||
sortOrder?: 'asc' | 'desc' | 'ASC' | 'DESC';
|
||||
}
|
||||
29
src/modules/hr/dto/index.ts
Normal file
29
src/modules/hr/dto/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export { CreateEmployeeDto } from './create-employee.dto';
|
||||
export { UpdateEmployeeDto } from './update-employee.dto';
|
||||
export {
|
||||
CreateContractDto,
|
||||
UpdateContractDto,
|
||||
ActivateContractDto,
|
||||
TerminateContractDto,
|
||||
} from './create-contract.dto';
|
||||
export {
|
||||
CreateLeaveDto,
|
||||
UpdateLeaveDto,
|
||||
SubmitLeaveDto,
|
||||
ApproveLeaveDto,
|
||||
RejectLeaveDto,
|
||||
CancelLeaveDto,
|
||||
CreateLeaveTypeDto,
|
||||
CreateLeaveAllocationDto,
|
||||
UpdateLeaveAllocationDto,
|
||||
} from './create-leave.dto';
|
||||
export {
|
||||
EmployeeFiltersDto,
|
||||
DepartmentFiltersDto,
|
||||
JobPositionFiltersDto,
|
||||
ContractFiltersDto,
|
||||
LeaveTypeFiltersDto,
|
||||
LeaveFiltersDto,
|
||||
LeaveAllocationFiltersDto,
|
||||
HrPaginationDto,
|
||||
} from './hr-filters.dto';
|
||||
180
src/modules/hr/dto/update-employee.dto.ts
Normal file
180
src/modules/hr/dto/update-employee.dto.ts
Normal file
@ -0,0 +1,180 @@
|
||||
import {
|
||||
IsString,
|
||||
IsUUID,
|
||||
IsOptional,
|
||||
IsDateString,
|
||||
IsEmail,
|
||||
IsEnum,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { EmployeeStatus } from '../entities/employee.entity';
|
||||
|
||||
/**
|
||||
* DTO for updating an existing employee
|
||||
* All fields are optional since it's a partial update
|
||||
*/
|
||||
export class UpdateEmployeeDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
firstName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
lastName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
userId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
partnerId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
departmentId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
jobPositionId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
managerId?: string | null;
|
||||
|
||||
// Work contact
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
@MaxLength(255)
|
||||
workEmail?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
workPhone?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
workMobile?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
workLocation?: string | null;
|
||||
|
||||
// Personal data
|
||||
@IsOptional()
|
||||
@IsEmail()
|
||||
@MaxLength(255)
|
||||
personalEmail?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
personalPhone?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
birthDate?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
gender?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
maritalStatus?: string | null;
|
||||
|
||||
// Identification
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
identificationType?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
identificationNumber?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
taxId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
socialSecurityNumber?: string | null;
|
||||
|
||||
// Address
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
street?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
city?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
state?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
postalCode?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
country?: string | null;
|
||||
|
||||
// Employment
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
hireDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
terminationDate?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['active', 'inactive', 'on_leave', 'terminated'])
|
||||
status?: EmployeeStatus;
|
||||
|
||||
// Emergency contact
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
emergencyContactName?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
emergencyContactPhone?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
emergencyContactRelationship?: string | null;
|
||||
|
||||
// Metadata
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
avatarUrl?: string | null;
|
||||
}
|
||||
168
src/modules/hr/entities/contract.entity.ts
Normal file
168
src/modules/hr/entities/contract.entity.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Employee } from './employee.entity';
|
||||
import { Department } from './department.entity';
|
||||
import { JobPosition } from './job-position.entity';
|
||||
|
||||
/**
|
||||
* Contract Type Enum
|
||||
*/
|
||||
export type ContractType = 'permanent' | 'temporary' | 'contractor' | 'internship' | 'part_time';
|
||||
|
||||
/**
|
||||
* Contract Status Enum
|
||||
*/
|
||||
export type ContractStatus = 'draft' | 'active' | 'expired' | 'terminated' | 'cancelled';
|
||||
|
||||
/**
|
||||
* Wage Type for payment frequency
|
||||
*/
|
||||
export type WageType = 'hourly' | 'daily' | 'weekly' | 'biweekly' | 'monthly' | 'annual';
|
||||
|
||||
/**
|
||||
* Contract Entity (schema: hr.contracts)
|
||||
*
|
||||
* Employment contracts with details about compensation, duration,
|
||||
* and terms of employment. Tracks contract lifecycle from draft to termination.
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'contracts', schema: 'hr' })
|
||||
export class Contract {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'employee_id', type: 'uuid' })
|
||||
employeeId: string;
|
||||
|
||||
@ManyToOne(() => Employee, (employee) => employee.contracts, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'employee_id' })
|
||||
employee: Employee;
|
||||
|
||||
@Column({ name: 'name', type: 'varchar', length: 255 })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'reference', type: 'varchar', length: 50, nullable: true })
|
||||
reference: string | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
name: 'contract_type',
|
||||
type: 'enum',
|
||||
enum: ['permanent', 'temporary', 'contractor', 'internship', 'part_time'],
|
||||
enumName: 'hr_contract_type',
|
||||
default: 'permanent',
|
||||
})
|
||||
contractType: ContractType;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'enum',
|
||||
enum: ['draft', 'active', 'expired', 'terminated', 'cancelled'],
|
||||
enumName: 'hr_contract_status',
|
||||
default: 'draft',
|
||||
})
|
||||
status: ContractStatus;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'date_start', type: 'date' })
|
||||
dateStart: Date;
|
||||
|
||||
@Column({ name: 'date_end', type: 'date', nullable: true })
|
||||
dateEnd: Date | null;
|
||||
|
||||
@Column({ name: 'job_position_id', type: 'uuid', nullable: true })
|
||||
jobPositionId: string | null;
|
||||
|
||||
@ManyToOne(() => JobPosition, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'job_position_id' })
|
||||
jobPosition: JobPosition | null;
|
||||
|
||||
@Column({ name: 'department_id', type: 'uuid', nullable: true })
|
||||
departmentId: string | null;
|
||||
|
||||
@ManyToOne(() => Department, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'department_id' })
|
||||
department: Department | null;
|
||||
|
||||
// Compensation
|
||||
@Column({ name: 'wage', type: 'decimal', precision: 15, scale: 2 })
|
||||
wage: number;
|
||||
|
||||
@Column({
|
||||
name: 'wage_type',
|
||||
type: 'varchar',
|
||||
length: 20,
|
||||
default: 'monthly',
|
||||
})
|
||||
wageType: WageType;
|
||||
|
||||
@Column({ name: 'currency', type: 'varchar', length: 3, default: 'MXN' })
|
||||
currency: string;
|
||||
|
||||
// Schedule
|
||||
@Column({
|
||||
name: 'hours_per_week',
|
||||
type: 'decimal',
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
nullable: true,
|
||||
default: 48,
|
||||
})
|
||||
hoursPerWeek: number | null;
|
||||
|
||||
@Column({ name: 'schedule_type', type: 'varchar', length: 50, nullable: true })
|
||||
scheduleType: string | null;
|
||||
|
||||
// Trial period
|
||||
@Column({ name: 'trial_period_months', type: 'integer', nullable: true, default: 0 })
|
||||
trialPeriodMonths: number | null;
|
||||
|
||||
@Column({ name: 'trial_date_end', type: 'date', nullable: true })
|
||||
trialDateEnd: Date | null;
|
||||
|
||||
// Metadata
|
||||
@Column({ name: 'notes', type: 'text', nullable: true })
|
||||
notes: string | null;
|
||||
|
||||
@Column({ name: 'document_url', type: 'text', nullable: true })
|
||||
documentUrl: string | null;
|
||||
|
||||
// Audit
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ name: 'activated_at', type: 'timestamptz', nullable: true })
|
||||
activatedAt: Date | null;
|
||||
|
||||
@Column({ name: 'terminated_at', type: 'timestamptz', nullable: true })
|
||||
terminatedAt: Date | null;
|
||||
|
||||
@Column({ name: 'terminated_by', type: 'uuid', nullable: true })
|
||||
terminatedBy: string | null;
|
||||
|
||||
@Column({ name: 'termination_reason', type: 'text', nullable: true })
|
||||
terminationReason: string | null;
|
||||
}
|
||||
80
src/modules/hr/entities/department.entity.ts
Normal file
80
src/modules/hr/entities/department.entity.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Employee } from './employee.entity';
|
||||
|
||||
/**
|
||||
* Department Entity (schema: hr.departments)
|
||||
*
|
||||
* Organizational departments with self-referential hierarchy.
|
||||
* Supports parent/child relationships for department structure.
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'departments', schema: 'hr' })
|
||||
@Index(['tenantId', 'companyId', 'code'], { unique: true })
|
||||
export class Department {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId: string;
|
||||
|
||||
@Column({ name: 'name', type: 'varchar', length: 255 })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'code', type: 'varchar', length: 50, nullable: true })
|
||||
code: string | null;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'parent_id', type: 'uuid', nullable: true })
|
||||
parentId: string | null;
|
||||
|
||||
@ManyToOne(() => Department, (department) => department.children, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn({ name: 'parent_id' })
|
||||
parent: Department | null;
|
||||
|
||||
@OneToMany(() => Department, (department) => department.parent)
|
||||
children: Department[];
|
||||
|
||||
@Column({ name: 'manager_id', type: 'uuid', nullable: true })
|
||||
managerId: string | null;
|
||||
|
||||
@ManyToOne(() => Employee, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'manager_id' })
|
||||
manager: Employee | null;
|
||||
|
||||
@OneToMany(() => Employee, (employee) => employee.department)
|
||||
employees: Employee[];
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@Column({ name: 'description', type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ name: 'color', type: 'varchar', length: 7, nullable: true })
|
||||
color: string | null;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
223
src/modules/hr/entities/employee.entity.ts
Normal file
223
src/modules/hr/entities/employee.entity.ts
Normal file
@ -0,0 +1,223 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Department } from './department.entity';
|
||||
import { JobPosition } from './job-position.entity';
|
||||
import { Contract } from './contract.entity';
|
||||
import { Leave } from './leave.entity';
|
||||
import { LeaveAllocation } from './leave-allocation.entity';
|
||||
|
||||
/**
|
||||
* Employee Status Enum
|
||||
*/
|
||||
export type EmployeeStatus = 'active' | 'inactive' | 'on_leave' | 'terminated';
|
||||
|
||||
/**
|
||||
* Employee Entity (schema: hr.employees)
|
||||
*
|
||||
* Core employee information including personal data, organization assignment,
|
||||
* and employment details. Supports self-referential manager relationship.
|
||||
*
|
||||
* Relations:
|
||||
* - user_id: References auth.users (optional - employee may not have system access)
|
||||
* - partner_id: References partners.partners (optional - employee as business contact)
|
||||
* - department_id: Current department assignment
|
||||
* - job_position_id: Current job position
|
||||
* - manager_id: Direct manager (self-reference)
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'employees', schema: 'hr' })
|
||||
@Index(['tenantId', 'employeeNumber'], { unique: true })
|
||||
export class Employee {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId: string;
|
||||
|
||||
@Column({ name: 'employee_number', type: 'varchar', length: 50 })
|
||||
employeeNumber: string;
|
||||
|
||||
@Column({ name: 'first_name', type: 'varchar', length: 100 })
|
||||
firstName: string;
|
||||
|
||||
@Column({ name: 'last_name', type: 'varchar', length: 100 })
|
||||
lastName: string;
|
||||
|
||||
/**
|
||||
* Generated column in PostgreSQL: first_name || ' ' || last_name
|
||||
* Mark as insert: false, update: false since it's computed by DB
|
||||
*/
|
||||
@Index()
|
||||
@Column({
|
||||
name: 'full_name',
|
||||
type: 'varchar',
|
||||
length: 255,
|
||||
insert: false,
|
||||
update: false,
|
||||
nullable: true,
|
||||
})
|
||||
fullName: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'user_id', type: 'uuid', nullable: true })
|
||||
userId: string | null;
|
||||
|
||||
@Column({ name: 'partner_id', type: 'uuid', nullable: true })
|
||||
partnerId: string | null;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'department_id', type: 'uuid', nullable: true })
|
||||
departmentId: string | null;
|
||||
|
||||
@ManyToOne(() => Department, (department) => department.employees, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn({ name: 'department_id' })
|
||||
department: Department | null;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'job_position_id', type: 'uuid', nullable: true })
|
||||
jobPositionId: string | null;
|
||||
|
||||
@ManyToOne(() => JobPosition, (position) => position.employees, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn({ name: 'job_position_id' })
|
||||
jobPosition: JobPosition | null;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'manager_id', type: 'uuid', nullable: true })
|
||||
managerId: string | null;
|
||||
|
||||
@ManyToOne(() => Employee, (employee) => employee.subordinates, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn({ name: 'manager_id' })
|
||||
manager: Employee | null;
|
||||
|
||||
@OneToMany(() => Employee, (employee) => employee.manager)
|
||||
subordinates: Employee[];
|
||||
|
||||
// Work contact
|
||||
@Column({ name: 'work_email', type: 'varchar', length: 255, nullable: true })
|
||||
workEmail: string | null;
|
||||
|
||||
@Column({ name: 'work_phone', type: 'varchar', length: 50, nullable: true })
|
||||
workPhone: string | null;
|
||||
|
||||
@Column({ name: 'work_mobile', type: 'varchar', length: 50, nullable: true })
|
||||
workMobile: string | null;
|
||||
|
||||
@Column({ name: 'work_location', type: 'varchar', length: 255, nullable: true })
|
||||
workLocation: string | null;
|
||||
|
||||
// Personal data
|
||||
@Column({ name: 'personal_email', type: 'varchar', length: 255, nullable: true })
|
||||
personalEmail: string | null;
|
||||
|
||||
@Column({ name: 'personal_phone', type: 'varchar', length: 50, nullable: true })
|
||||
personalPhone: string | null;
|
||||
|
||||
@Column({ name: 'birth_date', type: 'date', nullable: true })
|
||||
birthDate: Date | null;
|
||||
|
||||
@Column({ name: 'gender', type: 'varchar', length: 20, nullable: true })
|
||||
gender: string | null;
|
||||
|
||||
@Column({ name: 'marital_status', type: 'varchar', length: 20, nullable: true })
|
||||
maritalStatus: string | null;
|
||||
|
||||
// Official identification
|
||||
@Column({ name: 'identification_type', type: 'varchar', length: 50, nullable: true })
|
||||
identificationType: string | null;
|
||||
|
||||
@Column({ name: 'identification_number', type: 'varchar', length: 100, nullable: true })
|
||||
identificationNumber: string | null;
|
||||
|
||||
@Column({ name: 'tax_id', type: 'varchar', length: 50, nullable: true })
|
||||
taxId: string | null;
|
||||
|
||||
@Column({ name: 'social_security_number', type: 'varchar', length: 50, nullable: true })
|
||||
socialSecurityNumber: string | null;
|
||||
|
||||
// Address
|
||||
@Column({ name: 'street', type: 'varchar', length: 255, nullable: true })
|
||||
street: string | null;
|
||||
|
||||
@Column({ name: 'city', type: 'varchar', length: 100, nullable: true })
|
||||
city: string | null;
|
||||
|
||||
@Column({ name: 'state', type: 'varchar', length: 100, nullable: true })
|
||||
state: string | null;
|
||||
|
||||
@Column({ name: 'postal_code', type: 'varchar', length: 20, nullable: true })
|
||||
postalCode: string | null;
|
||||
|
||||
@Column({ name: 'country', type: 'varchar', length: 100, nullable: true })
|
||||
country: string | null;
|
||||
|
||||
// Employment
|
||||
@Column({ name: 'hire_date', type: 'date' })
|
||||
hireDate: Date;
|
||||
|
||||
@Column({ name: 'termination_date', type: 'date', nullable: true })
|
||||
terminationDate: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'enum',
|
||||
enum: ['active', 'inactive', 'on_leave', 'terminated'],
|
||||
enumName: 'hr_employee_status',
|
||||
default: 'active',
|
||||
})
|
||||
status: EmployeeStatus;
|
||||
|
||||
// Emergency contact
|
||||
@Column({ name: 'emergency_contact_name', type: 'varchar', length: 255, nullable: true })
|
||||
emergencyContactName: string | null;
|
||||
|
||||
@Column({ name: 'emergency_contact_phone', type: 'varchar', length: 50, nullable: true })
|
||||
emergencyContactPhone: string | null;
|
||||
|
||||
@Column({ name: 'emergency_contact_relationship', type: 'varchar', length: 50, nullable: true })
|
||||
emergencyContactRelationship: string | null;
|
||||
|
||||
// Metadata
|
||||
@Column({ name: 'notes', type: 'text', nullable: true })
|
||||
notes: string | null;
|
||||
|
||||
@Column({ name: 'avatar_url', type: 'text', nullable: true })
|
||||
avatarUrl: string | null;
|
||||
|
||||
// Relations
|
||||
@OneToMany(() => Contract, (contract) => contract.employee)
|
||||
contracts: Contract[];
|
||||
|
||||
@OneToMany(() => Leave, (leave) => leave.employee)
|
||||
leaves: Leave[];
|
||||
|
||||
@OneToMany(() => LeaveAllocation, (allocation) => allocation.employee)
|
||||
leaveAllocations: LeaveAllocation[];
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
7
src/modules/hr/entities/index.ts
Normal file
7
src/modules/hr/entities/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export { Department } from './department.entity';
|
||||
export { JobPosition } from './job-position.entity';
|
||||
export { Employee, EmployeeStatus } from './employee.entity';
|
||||
export { Contract, ContractType, ContractStatus, WageType } from './contract.entity';
|
||||
export { LeaveType, LeaveTypeCategory, AllocationType } from './leave-type.entity';
|
||||
export { LeaveAllocation } from './leave-allocation.entity';
|
||||
export { Leave, LeaveStatus, HalfDayType } from './leave.entity';
|
||||
65
src/modules/hr/entities/job-position.entity.ts
Normal file
65
src/modules/hr/entities/job-position.entity.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Department } from './department.entity';
|
||||
import { Employee } from './employee.entity';
|
||||
|
||||
/**
|
||||
* Job Position Entity (schema: hr.job_positions)
|
||||
*
|
||||
* Defines job titles/positions within the organization.
|
||||
* Can be associated with a specific department.
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'job_positions', schema: 'hr' })
|
||||
@Index(['tenantId', 'companyId', 'code'], { unique: true })
|
||||
export class JobPosition {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId: string;
|
||||
|
||||
@Column({ name: 'name', type: 'varchar', length: 255 })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'code', type: 'varchar', length: 50, nullable: true })
|
||||
code: string | null;
|
||||
|
||||
@Column({ name: 'description', type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'department_id', type: 'uuid', nullable: true })
|
||||
departmentId: string | null;
|
||||
|
||||
@ManyToOne(() => Department, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'department_id' })
|
||||
department: Department | null;
|
||||
|
||||
@OneToMany(() => Employee, (employee) => employee.jobPosition)
|
||||
employees: Employee[];
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
89
src/modules/hr/entities/leave-allocation.entity.ts
Normal file
89
src/modules/hr/entities/leave-allocation.entity.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Employee } from './employee.entity';
|
||||
import { LeaveType } from './leave-type.entity';
|
||||
|
||||
/**
|
||||
* Leave Allocation Entity (schema: hr.leave_allocations)
|
||||
*
|
||||
* Tracks allocated leave days per employee and leave type.
|
||||
* Supports period-based allocations with used/remaining tracking.
|
||||
*
|
||||
* Note: days_remaining is a computed column in PostgreSQL
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'leave_allocations', schema: 'hr' })
|
||||
export class LeaveAllocation {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'employee_id', type: 'uuid' })
|
||||
employeeId: string;
|
||||
|
||||
@ManyToOne(() => Employee, (employee) => employee.leaveAllocations, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'employee_id' })
|
||||
employee: Employee;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'leave_type_id', type: 'uuid' })
|
||||
leaveTypeId: string;
|
||||
|
||||
@ManyToOne(() => LeaveType, (leaveType) => leaveType.allocations, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'leave_type_id' })
|
||||
leaveType: LeaveType;
|
||||
|
||||
@Column({ name: 'days_allocated', type: 'decimal', precision: 5, scale: 2 })
|
||||
daysAllocated: number;
|
||||
|
||||
@Column({ name: 'days_used', type: 'decimal', precision: 5, scale: 2, default: 0 })
|
||||
daysUsed: number;
|
||||
|
||||
/**
|
||||
* Generated column in PostgreSQL: days_allocated - days_used
|
||||
* Mark as insert: false, update: false since it's computed by DB
|
||||
*/
|
||||
@Column({
|
||||
name: 'days_remaining',
|
||||
type: 'decimal',
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
insert: false,
|
||||
update: false,
|
||||
nullable: true,
|
||||
})
|
||||
daysRemaining: number;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'date_from', type: 'date' })
|
||||
dateFrom: Date;
|
||||
|
||||
@Column({ name: 'date_to', type: 'date' })
|
||||
dateTo: Date;
|
||||
|
||||
@Column({ name: 'notes', type: 'text', nullable: true })
|
||||
notes: string | null;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
131
src/modules/hr/entities/leave-type.entity.ts
Normal file
131
src/modules/hr/entities/leave-type.entity.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
OneToMany,
|
||||
} from 'typeorm';
|
||||
import { Leave } from './leave.entity';
|
||||
import { LeaveAllocation } from './leave-allocation.entity';
|
||||
|
||||
/**
|
||||
* Leave Type Category Enum
|
||||
*/
|
||||
export type LeaveTypeCategory =
|
||||
| 'vacation'
|
||||
| 'sick'
|
||||
| 'personal'
|
||||
| 'maternity'
|
||||
| 'paternity'
|
||||
| 'bereavement'
|
||||
| 'unpaid'
|
||||
| 'other';
|
||||
|
||||
/**
|
||||
* Allocation Type Enum
|
||||
*/
|
||||
export type AllocationType = 'fixed' | 'accrual' | 'unlimited';
|
||||
|
||||
/**
|
||||
* Leave Type Entity (schema: hr.leave_types)
|
||||
*
|
||||
* Configurable leave/absence types for the organization.
|
||||
* Defines rules for approval, allocation, and payment.
|
||||
*
|
||||
* Examples: Vacation, Sick Leave, Maternity, etc.
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'leave_types', schema: 'hr' })
|
||||
@Index(['tenantId', 'companyId', 'code'], { unique: true })
|
||||
export class LeaveType {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId: string;
|
||||
|
||||
@Column({ name: 'name', type: 'varchar', length: 255 })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'code', type: 'varchar', length: 50 })
|
||||
code: string;
|
||||
|
||||
@Column({ name: 'description', type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ name: 'color', type: 'varchar', length: 7, default: '#3B82F6' })
|
||||
color: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
name: 'leave_category',
|
||||
type: 'enum',
|
||||
enum: ['vacation', 'sick', 'personal', 'maternity', 'paternity', 'bereavement', 'unpaid', 'other'],
|
||||
enumName: 'hr_leave_type_category',
|
||||
default: 'other',
|
||||
})
|
||||
leaveCategory: LeaveTypeCategory;
|
||||
|
||||
@Column({
|
||||
name: 'allocation_type',
|
||||
type: 'enum',
|
||||
enum: ['fixed', 'accrual', 'unlimited'],
|
||||
enumName: 'hr_allocation_type',
|
||||
default: 'fixed',
|
||||
})
|
||||
allocationType: AllocationType;
|
||||
|
||||
@Column({ name: 'requires_approval', type: 'boolean', default: true })
|
||||
requiresApproval: boolean;
|
||||
|
||||
@Column({ name: 'requires_document', type: 'boolean', default: false })
|
||||
requiresDocument: boolean;
|
||||
|
||||
// Limits
|
||||
@Column({ name: 'max_days_per_request', type: 'integer', nullable: true })
|
||||
maxDaysPerRequest: number | null;
|
||||
|
||||
@Column({ name: 'max_days_per_year', type: 'integer', nullable: true })
|
||||
maxDaysPerYear: number | null;
|
||||
|
||||
@Column({ name: 'min_days_notice', type: 'integer', default: 0 })
|
||||
minDaysNotice: number;
|
||||
|
||||
// Payment
|
||||
@Column({ name: 'is_paid', type: 'boolean', default: true })
|
||||
isPaid: boolean;
|
||||
|
||||
@Column({
|
||||
name: 'pay_percentage',
|
||||
type: 'decimal',
|
||||
precision: 5,
|
||||
scale: 2,
|
||||
default: 100,
|
||||
})
|
||||
payPercentage: number;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
|
||||
// Relations
|
||||
@OneToMany(() => Leave, (leave) => leave.leaveType)
|
||||
leaves: Leave[];
|
||||
|
||||
@OneToMany(() => LeaveAllocation, (allocation) => allocation.leaveType)
|
||||
allocations: LeaveAllocation[];
|
||||
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
142
src/modules/hr/entities/leave.entity.ts
Normal file
142
src/modules/hr/entities/leave.entity.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { Employee } from './employee.entity';
|
||||
import { LeaveType } from './leave-type.entity';
|
||||
import { LeaveAllocation } from './leave-allocation.entity';
|
||||
|
||||
/**
|
||||
* Leave Status Enum
|
||||
*/
|
||||
export type LeaveStatus = 'draft' | 'submitted' | 'approved' | 'rejected' | 'cancelled';
|
||||
|
||||
/**
|
||||
* Half Day Type
|
||||
*/
|
||||
export type HalfDayType = 'morning' | 'afternoon';
|
||||
|
||||
/**
|
||||
* Leave Entity (schema: hr.leaves)
|
||||
*
|
||||
* Leave/absence requests from employees.
|
||||
* Tracks the full lifecycle from draft to approval/rejection.
|
||||
*
|
||||
* RLS Policy: tenant_id = current_setting('app.current_tenant_id')
|
||||
*/
|
||||
@Entity({ name: 'leaves', schema: 'hr' })
|
||||
export class Leave {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||
tenantId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'company_id', type: 'uuid' })
|
||||
companyId: string;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'employee_id', type: 'uuid' })
|
||||
employeeId: string;
|
||||
|
||||
@ManyToOne(() => Employee, (employee) => employee.leaves, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'employee_id' })
|
||||
employee: Employee;
|
||||
|
||||
@Index()
|
||||
@Column({ name: 'leave_type_id', type: 'uuid' })
|
||||
leaveTypeId: string;
|
||||
|
||||
@ManyToOne(() => LeaveType, (leaveType) => leaveType.leaves, {
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
@JoinColumn({ name: 'leave_type_id' })
|
||||
leaveType: LeaveType;
|
||||
|
||||
@Column({ name: 'allocation_id', type: 'uuid', nullable: true })
|
||||
allocationId: string | null;
|
||||
|
||||
@ManyToOne(() => LeaveAllocation, { onDelete: 'SET NULL' })
|
||||
@JoinColumn({ name: 'allocation_id' })
|
||||
allocation: LeaveAllocation | null;
|
||||
|
||||
// Period
|
||||
@Index()
|
||||
@Column({ name: 'date_from', type: 'date' })
|
||||
dateFrom: Date;
|
||||
|
||||
@Column({ name: 'date_to', type: 'date' })
|
||||
dateTo: Date;
|
||||
|
||||
@Column({ name: 'days_requested', type: 'decimal', precision: 5, scale: 2 })
|
||||
daysRequested: number;
|
||||
|
||||
// Half day support
|
||||
@Column({ name: 'is_half_day', type: 'boolean', default: false })
|
||||
isHalfDay: boolean;
|
||||
|
||||
@Column({
|
||||
name: 'half_day_type',
|
||||
type: 'varchar',
|
||||
length: 20,
|
||||
nullable: true,
|
||||
})
|
||||
halfDayType: HalfDayType | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
name: 'status',
|
||||
type: 'enum',
|
||||
enum: ['draft', 'submitted', 'approved', 'rejected', 'cancelled'],
|
||||
enumName: 'hr_leave_status',
|
||||
default: 'draft',
|
||||
})
|
||||
status: LeaveStatus;
|
||||
|
||||
// Approval
|
||||
@Index()
|
||||
@Column({ name: 'approver_id', type: 'uuid', nullable: true })
|
||||
approverId: string | null;
|
||||
|
||||
@Column({ name: 'approved_at', type: 'timestamptz', nullable: true })
|
||||
approvedAt: Date | null;
|
||||
|
||||
@Column({ name: 'rejection_reason', type: 'text', nullable: true })
|
||||
rejectionReason: string | null;
|
||||
|
||||
// Metadata
|
||||
@Column({ name: 'request_reason', type: 'text', nullable: true })
|
||||
requestReason: string | null;
|
||||
|
||||
@Column({ name: 'document_url', type: 'text', nullable: true })
|
||||
documentUrl: string | null;
|
||||
|
||||
@Column({ name: 'notes', type: 'text', nullable: true })
|
||||
notes: string | null;
|
||||
|
||||
// Audit
|
||||
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ name: 'submitted_at', type: 'timestamptz', nullable: true })
|
||||
submittedAt: Date | null;
|
||||
|
||||
@Column({ name: 'cancelled_at', type: 'timestamptz', nullable: true })
|
||||
cancelledAt: Date | null;
|
||||
|
||||
@Column({ name: 'cancelled_by', type: 'uuid', nullable: true })
|
||||
cancelledBy: string | null;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user