feat(hr): Complete HR module with Employee Portal, Loans, Leave, Purchase Requests, Contracts
- Database: Add Loan, LoanInstallment, PurchaseRequest, LeaveEntitlement, EmployeeContract models - Database: Extend Attendance with ZK Tico fields (sourceDeviceId, externalId, rawData) - Database: Add Employee.attendancePin for device mapping - Backend: HR admin - Loans, Purchase Requests, Leave entitlements, Employee contracts CRUD - Backend: Leave reject, bulk attendance sync (ZK Tico ready) - Backend: Employee Portal API - scoped by employeeId (loans, leaves, purchase-requests, attendance, salaries) - Frontend: Employee Portal - dashboard, loans, leave, purchase-requests, attendance, salaries - Frontend: HR Admin - new tabs for Leaves, Loans, Purchase Requests, Contracts (approve/reject) - Dashboard: Add My Portal link - No destructive schema changes; additive migrations only Made-with: Cursor
This commit is contained in:
@@ -178,6 +178,9 @@ model Employee {
|
||||
// Documents
|
||||
documents Json? // Array of document references
|
||||
|
||||
// ZK Tico / Attendance device - maps to employee pin on device
|
||||
attendancePin String? @unique
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -192,6 +195,10 @@ model Employee {
|
||||
disciplinaryActions DisciplinaryAction[]
|
||||
allowances Allowance[]
|
||||
commissions Commission[]
|
||||
loans Loan[]
|
||||
purchaseRequests PurchaseRequest[]
|
||||
leaveEntitlements LeaveEntitlement[]
|
||||
employeeContracts EmployeeContract[]
|
||||
|
||||
@@index([departmentId])
|
||||
@@index([positionId])
|
||||
@@ -270,12 +277,18 @@ model Attendance {
|
||||
status String // PRESENT, ABSENT, LATE, HALF_DAY, etc.
|
||||
notes String?
|
||||
|
||||
// ZK Tico / External device sync
|
||||
sourceDeviceId String?
|
||||
externalId String?
|
||||
rawData Json?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([employeeId, date])
|
||||
@@index([employeeId])
|
||||
@@index([date])
|
||||
@@index([sourceDeviceId])
|
||||
@@map("attendances")
|
||||
}
|
||||
|
||||
@@ -418,6 +431,115 @@ model DisciplinaryAction {
|
||||
@@map("disciplinary_actions")
|
||||
}
|
||||
|
||||
model Loan {
|
||||
id String @id @default(uuid())
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id])
|
||||
loanNumber String @unique
|
||||
type String // SALARY_ADVANCE, EQUIPMENT, PERSONAL, etc.
|
||||
amount Decimal @db.Decimal(12, 2)
|
||||
currency String @default("SAR")
|
||||
installments Int @default(1)
|
||||
monthlyAmount Decimal? @db.Decimal(12, 2)
|
||||
reason String?
|
||||
status String @default("PENDING") // PENDING, APPROVED, REJECTED, ACTIVE, PAID_OFF
|
||||
approvedBy String?
|
||||
approvedAt DateTime?
|
||||
rejectedReason String?
|
||||
startDate DateTime? @db.Date
|
||||
endDate DateTime? @db.Date
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
installmentsList LoanInstallment[]
|
||||
|
||||
@@index([employeeId])
|
||||
@@index([status])
|
||||
@@map("loans")
|
||||
}
|
||||
|
||||
model LoanInstallment {
|
||||
id String @id @default(uuid())
|
||||
loanId String
|
||||
loan Loan @relation(fields: [loanId], references: [id], onDelete: Cascade)
|
||||
installmentNumber Int
|
||||
dueDate DateTime @db.Date
|
||||
amount Decimal @db.Decimal(12, 2)
|
||||
paidDate DateTime? @db.Date
|
||||
status String @default("PENDING") // PENDING, PAID, OVERDUE
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([loanId, installmentNumber])
|
||||
@@index([loanId])
|
||||
@@map("loan_installments")
|
||||
}
|
||||
|
||||
model PurchaseRequest {
|
||||
id String @id @default(uuid())
|
||||
requestNumber String @unique
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id])
|
||||
items Json // Array of { description, quantity, estimatedPrice, etc. }
|
||||
totalAmount Decimal? @db.Decimal(12, 2)
|
||||
reason String?
|
||||
priority String @default("NORMAL") // LOW, NORMAL, HIGH, URGENT
|
||||
status String @default("PENDING") // PENDING, APPROVED, REJECTED, ORDERED
|
||||
approvedBy String?
|
||||
approvedAt DateTime?
|
||||
rejectedReason String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([employeeId])
|
||||
@@index([status])
|
||||
@@map("purchase_requests")
|
||||
}
|
||||
|
||||
model LeaveEntitlement {
|
||||
id String @id @default(uuid())
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id])
|
||||
year Int
|
||||
leaveType String // ANNUAL, SICK, EMERGENCY, etc.
|
||||
totalDays Int @default(0)
|
||||
usedDays Int @default(0)
|
||||
carriedOver Int @default(0)
|
||||
notes String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([employeeId, year, leaveType])
|
||||
@@index([employeeId])
|
||||
@@map("leave_entitlements")
|
||||
}
|
||||
|
||||
model EmployeeContract {
|
||||
id String @id @default(uuid())
|
||||
employeeId String
|
||||
employee Employee @relation(fields: [employeeId], references: [id])
|
||||
contractNumber String @unique
|
||||
type String // FIXED, UNLIMITED, PROBATION, etc.
|
||||
startDate DateTime @db.Date
|
||||
endDate DateTime? @db.Date
|
||||
salary Decimal @db.Decimal(12, 2)
|
||||
currency String @default("SAR")
|
||||
documentUrl String?
|
||||
status String @default("ACTIVE") // DRAFT, ACTIVE, EXPIRED, TERMINATED
|
||||
notes String?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([employeeId])
|
||||
@@index([status])
|
||||
@@map("employee_contracts")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MODULE 1: CONTACT MANAGEMENT
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user