import { Router } from 'express'; import { hrController } from './hr.controller'; import { portalController } from './portal.controller'; import { authenticate, authorize } from '../../shared/middleware/auth'; import multer from 'multer'; import path from 'path'; import fs from 'fs'; import crypto from 'crypto'; import { config } from '../../config'; const router = Router(); const expenseClaimsUploadDir = path.join(config.upload.path, 'expense-claims'); if (!fs.existsSync(expenseClaimsUploadDir)) { fs.mkdirSync(expenseClaimsUploadDir, { recursive: true }); } const expenseClaimStorage = multer.diskStorage({ destination: (_req, _file, cb) => cb(null, expenseClaimsUploadDir), filename: (_req, file, cb) => { const safeName = (file.originalname || 'file').replace(/[^a-zA-Z0-9.-]/g, '_'); cb(null, `${crypto.randomUUID()}-${safeName}`); }, }); const expenseClaimUpload = multer({ storage: expenseClaimStorage, limits: { fileSize: config.upload.maxFileSize }, fileFilter: (_req, file, cb) => { const allowedTypes = [ 'image/jpeg', 'image/png', 'image/webp', 'image/gif', 'application/pdf', ]; if (!allowedTypes.includes(file.mimetype)) { return cb( new Error('نوع الملف غير مدعوم. يرجى رفع صورة أو ملف PDF.') ); } cb(null, true); }, }); router.use(authenticate); // ========== EMPLOYEE PORTAL (authenticate only, scoped by employeeId) ========== router.get('/portal/me', portalController.getMe); router.get('/portal/loans', portalController.getMyLoans); router.post('/portal/loans', portalController.submitLoanRequest); router.get('/portal/leave-balance', portalController.getMyLeaveBalance); router.get('/portal/leaves', portalController.getMyLeaves); router.post('/portal/leaves', portalController.submitLeaveRequest); router.get( '/portal/managed-leaves', authorize('department_leave_requests', '*', 'read'), portalController.getManagedLeaves ); router.post( '/portal/managed-leaves/:id/approve', authorize('department_leave_requests', '*', 'approve'), portalController.approveManagedLeave ); router.post( '/portal/managed-leaves/:id/reject', authorize('department_leave_requests', '*', 'approve'), portalController.rejectManagedLeave ); router.get('/portal/overtime-requests', portalController.getMyOvertimeRequests); router.post('/portal/overtime-requests', portalController.submitOvertimeRequest); router.get( '/portal/managed-overtime-requests', authorize('department_overtime_requests', '*', 'view'), portalController.getManagedOvertimeRequests ); router.post( '/portal/managed-overtime-requests/:attendanceId/approve', authorize('department_overtime_requests', '*', 'approve'), portalController.approveManagedOvertimeRequest ); router.post( '/portal/managed-overtime-requests/:attendanceId/reject', authorize('department_overtime_requests', '*', 'approve'), portalController.rejectManagedOvertimeRequest ); router.get('/portal/purchase-requests', portalController.getMyPurchaseRequests); router.post('/portal/purchase-requests', portalController.submitPurchaseRequest); router.get('/portal/attendance', portalController.getMyAttendance); router.get('/portal/salaries', portalController.getMySalaries); router.get('/portal/expense-claims', portalController.getMyExpenseClaims); router.post( '/portal/expense-claims', (req, res, next) => { expenseClaimUpload.single('attachment')(req, res, (error: any) => { if (error) { return res.status(400).json({ success: false, message: error.message || 'تعذر رفع المرفق', }); } next(); }); }, portalController.submitExpenseClaim ); router.get( '/portal/expense-claims/attachments/:attachmentId/view', portalController.viewExpenseClaimAttachment ); router.get('/portal/managed-expense-claims', authorize('department_expense_claims', '*', 'read'), portalController.getManagedExpenseClaims); router.post( '/portal/managed-expense-claims/:id/approve', authorize('department_expense_claims', '*', 'approve'), portalController.approveManagedExpenseClaim ); router.post( '/portal/managed-expense-claims/:id/reject', authorize('department_expense_claims', '*', 'approve'), portalController.rejectManagedExpenseClaim ); // ========== EMPLOYEES ========== router.get('/employees', authorize('hr', 'employees', 'read'), hrController.findAllEmployees); router.get('/employees/:id', authorize('hr', 'employees', 'read'), hrController.findEmployeeById); router.post('/employees', authorize('hr', 'employees', 'create'), hrController.createEmployee); router.put('/employees/:id', authorize('hr', 'employees', 'update'), hrController.updateEmployee); router.post('/employees/:id/terminate', authorize('hr', 'employees', 'terminate'), hrController.terminateEmployee); // ========== ATTENDANCE ========== router.post('/attendance', authorize('hr', 'attendance', 'create'), hrController.recordAttendance); router.get('/attendance/:employeeId', authorize('hr', 'attendance', 'read'), hrController.getAttendance); router.post('/attendance/sync', authorize('hr', 'attendance', 'create'), hrController.bulkSyncAttendance); // ========== LEAVES ========== router.get('/leaves', authorize('hr', 'leaves', 'read'), hrController.findAllLeaves); router.post('/leaves', authorize('hr', 'leaves', 'create'), hrController.createLeaveRequest); router.post('/leaves/:id/approve', authorize('hr', 'leaves', 'approve'), hrController.approveLeave); router.post('/leaves/:id/reject', authorize('hr', 'leaves', 'approve'), hrController.rejectLeave); // ========== SALARIES ========== router.post('/salaries/process', authorize('hr', 'salaries', 'process'), hrController.processSalary); // ========== DEPARTMENTS ========== router.get('/departments', authorize('hr', 'all', 'read'), hrController.findAllDepartments); router.get('/departments/hierarchy', authorize('hr', 'all', 'read'), hrController.getDepartmentsHierarchy); router.post('/departments', authorize('hr', 'all', 'create'), hrController.createDepartment); router.put('/departments/:id', authorize('hr', 'all', 'update'), hrController.updateDepartment); router.delete('/departments/:id', authorize('hr', 'all', 'delete'), hrController.deleteDepartment); // ========== POSITIONS ========== router.get('/positions', authorize('hr', 'all', 'read'), hrController.findAllPositions); // ========== LOANS ========== router.get('/loans', authorize('hr', 'all', 'read'), hrController.findAllLoans); router.get('/loans/:id', authorize('hr', 'all', 'read'), hrController.findLoanById); router.post('/loans', authorize('hr', 'all', 'create'), hrController.createLoan); router.post('/loans/:id/approve', authorize('hr', 'all', 'approve'), hrController.approveLoan); router.post('/loans/:id/reject', authorize('hr', 'all', 'approve'), hrController.rejectLoan); router.post('/loans/:id/pay-installment', authorize('hr', 'all', 'update'), hrController.recordLoanInstallmentPayment); // ========== PURCHASE REQUESTS ========== router.get('/purchase-requests', authorize('hr', 'all', 'read'), hrController.findAllPurchaseRequests); router.get('/purchase-requests/:id', authorize('hr', 'all', 'read'), hrController.findPurchaseRequestById); router.post('/purchase-requests', authorize('hr', 'all', 'create'), hrController.createPurchaseRequest); router.post('/purchase-requests/:id/approve', authorize('hr', 'all', 'approve'), hrController.approvePurchaseRequest); router.post('/purchase-requests/:id/reject', authorize('hr', 'all', 'approve'), hrController.rejectPurchaseRequest); // ========== LEAVE ENTITLEMENTS ========== router.get('/leave-balance/:employeeId', authorize('hr', 'all', 'read'), hrController.getLeaveBalance); router.get('/leave-entitlements', authorize('hr', 'all', 'read'), hrController.findAllLeaveEntitlements); router.post('/leave-entitlements', authorize('hr', 'all', 'create'), hrController.upsertLeaveEntitlement); // ========== EMPLOYEE CONTRACTS ========== router.get('/contracts', authorize('hr', 'all', 'read'), hrController.findAllEmployeeContracts); router.get('/contracts/:id', authorize('hr', 'all', 'read'), hrController.findEmployeeContractById); router.post('/contracts', authorize('hr', 'all', 'create'), hrController.createEmployeeContract); router.put('/contracts/:id', authorize('hr', 'all', 'update'), hrController.updateEmployeeContract); export default router;