[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