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:
76
frontend/src/lib/api/hrAdmin.ts
Normal file
76
frontend/src/lib/api/hrAdmin.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { api } from '../api'
|
||||
|
||||
export const hrAdminAPI = {
|
||||
// Leaves
|
||||
getLeaves: async (params?: { employeeId?: string; status?: string; page?: number; pageSize?: number }) => {
|
||||
const q = new URLSearchParams()
|
||||
if (params?.employeeId) q.append('employeeId', params.employeeId)
|
||||
if (params?.status) q.append('status', params.status)
|
||||
if (params?.page) q.append('page', String(params.page))
|
||||
if (params?.pageSize) q.append('pageSize', String(params.pageSize))
|
||||
const res = await api.get(`/hr/leaves?${q}`)
|
||||
return { leaves: res.data.data || [], pagination: res.data.pagination }
|
||||
},
|
||||
approveLeave: (id: string) => api.post(`/hr/leaves/${id}/approve`),
|
||||
rejectLeave: (id: string, rejectedReason: string) => api.post(`/hr/leaves/${id}/reject`, { rejectedReason }),
|
||||
|
||||
// Loans
|
||||
getLoans: async (params?: { employeeId?: string; status?: string; page?: number; pageSize?: number }) => {
|
||||
const q = new URLSearchParams()
|
||||
if (params?.employeeId) q.append('employeeId', params.employeeId)
|
||||
if (params?.status) q.append('status', params.status)
|
||||
if (params?.page) q.append('page', String(params.page))
|
||||
if (params?.pageSize) q.append('pageSize', String(params.pageSize))
|
||||
const res = await api.get(`/hr/loans?${q}`)
|
||||
return { loans: res.data.data || [], pagination: res.data.pagination }
|
||||
},
|
||||
getLoanById: (id: string) => api.get(`/hr/loans/${id}`),
|
||||
createLoan: (data: { employeeId: string; type: string; amount: number; installments?: number; reason?: string }) =>
|
||||
api.post('/hr/loans', data),
|
||||
approveLoan: (id: string, startDate?: string) => api.post(`/hr/loans/${id}/approve`, { startDate: startDate || new Date().toISOString().split('T')[0] }),
|
||||
rejectLoan: (id: string, rejectedReason: string) => api.post(`/hr/loans/${id}/reject`, { rejectedReason }),
|
||||
payInstallment: (loanId: string, installmentId: string, paidDate?: string) =>
|
||||
api.post(`/hr/loans/${loanId}/pay-installment`, { installmentId, paidDate: paidDate || new Date().toISOString().split('T')[0] }),
|
||||
|
||||
// Purchase Requests
|
||||
getPurchaseRequests: async (params?: { employeeId?: string; status?: string; page?: number; pageSize?: number }) => {
|
||||
const q = new URLSearchParams()
|
||||
if (params?.employeeId) q.append('employeeId', params.employeeId)
|
||||
if (params?.status) q.append('status', params.status)
|
||||
if (params?.page) q.append('page', String(params.page))
|
||||
if (params?.pageSize) q.append('pageSize', String(params.pageSize))
|
||||
const res = await api.get(`/hr/purchase-requests?${q}`)
|
||||
return { purchaseRequests: res.data.data || [], pagination: res.data.pagination }
|
||||
},
|
||||
approvePurchaseRequest: (id: string) => api.post(`/hr/purchase-requests/${id}/approve`),
|
||||
rejectPurchaseRequest: (id: string, rejectedReason: string) => api.post(`/hr/purchase-requests/${id}/reject`, { rejectedReason }),
|
||||
|
||||
// Leave Entitlements
|
||||
getLeaveBalance: (employeeId: string, year?: number) => {
|
||||
const q = year ? `?year=${year}` : ''
|
||||
return api.get(`/hr/leave-balance/${employeeId}${q}`).then((r) => r.data.data)
|
||||
},
|
||||
getLeaveEntitlements: (params?: { employeeId?: string; year?: number }) => {
|
||||
const q = new URLSearchParams()
|
||||
if (params?.employeeId) q.append('employeeId', params.employeeId)
|
||||
if (params?.year) q.append('year', String(params.year))
|
||||
return api.get(`/hr/leave-entitlements?${q}`).then((r) => r.data.data)
|
||||
},
|
||||
upsertLeaveEntitlement: (data: { employeeId: string; year: number; leaveType: string; totalDays: number; carriedOver?: number; notes?: string }) =>
|
||||
api.post('/hr/leave-entitlements', data),
|
||||
|
||||
// Employee Contracts
|
||||
getContracts: async (params?: { employeeId?: string; status?: string; page?: number; pageSize?: number }) => {
|
||||
const q = new URLSearchParams()
|
||||
if (params?.employeeId) q.append('employeeId', params.employeeId)
|
||||
if (params?.status) q.append('status', params.status)
|
||||
if (params?.page) q.append('page', String(params.page))
|
||||
if (params?.pageSize) q.append('pageSize', String(params.pageSize))
|
||||
const res = await api.get(`/hr/contracts?${q}`)
|
||||
return { contracts: res.data.data || [], pagination: res.data.pagination }
|
||||
},
|
||||
createContract: (data: { employeeId: string; type: string; startDate: string; endDate?: string; salary: number; documentUrl?: string; notes?: string }) =>
|
||||
api.post('/hr/contracts', data),
|
||||
updateContract: (id: string, data: Partial<{ type: string; endDate: string; salary: number; status: string; notes: string }>) =>
|
||||
api.put(`/hr/contracts/${id}`, data),
|
||||
}
|
||||
Reference in New Issue
Block a user