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:
106
backend/src/modules/hr/portal.controller.ts
Normal file
106
backend/src/modules/hr/portal.controller.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { AuthRequest } from '../../shared/middleware/auth';
|
||||
import { portalService } from './portal.service';
|
||||
import { ResponseFormatter } from '../../shared/utils/responseFormatter';
|
||||
|
||||
export class PortalController {
|
||||
async getMe(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const data = await portalService.getMe(req.user?.employeeId);
|
||||
res.json(ResponseFormatter.success(data));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMyLoans(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const loans = await portalService.getMyLoans(req.user?.employeeId);
|
||||
res.json(ResponseFormatter.success(loans));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async submitLoanRequest(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const loan = await portalService.submitLoanRequest(req.user?.employeeId, req.body, req.user!.id);
|
||||
res.status(201).json(ResponseFormatter.success(loan, 'تم إرسال طلب القرض - Loan request submitted'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMyLeaveBalance(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const year = req.query.year ? parseInt(req.query.year as string) : undefined;
|
||||
const balance = await portalService.getMyLeaveBalance(req.user?.employeeId, year);
|
||||
res.json(ResponseFormatter.success(balance));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMyLeaves(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const leaves = await portalService.getMyLeaves(req.user?.employeeId);
|
||||
res.json(ResponseFormatter.success(leaves));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async submitLeaveRequest(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const data = {
|
||||
...req.body,
|
||||
startDate: new Date(req.body.startDate),
|
||||
endDate: new Date(req.body.endDate),
|
||||
};
|
||||
const leave = await portalService.submitLeaveRequest(req.user?.employeeId, data, req.user!.id);
|
||||
res.status(201).json(ResponseFormatter.success(leave, 'تم إرسال طلب الإجازة - Leave request submitted'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMyPurchaseRequests(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const requests = await portalService.getMyPurchaseRequests(req.user?.employeeId);
|
||||
res.json(ResponseFormatter.success(requests));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async submitPurchaseRequest(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const pr = await portalService.submitPurchaseRequest(req.user?.employeeId, req.body, req.user!.id);
|
||||
res.status(201).json(ResponseFormatter.success(pr, 'تم إرسال طلب الشراء - Purchase request submitted'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMyAttendance(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const month = req.query.month ? parseInt(req.query.month as string) : undefined;
|
||||
const year = req.query.year ? parseInt(req.query.year as string) : undefined;
|
||||
const attendance = await portalService.getMyAttendance(req.user?.employeeId, month, year);
|
||||
res.json(ResponseFormatter.success(attendance));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getMySalaries(req: AuthRequest, res: Response, next: NextFunction) {
|
||||
try {
|
||||
const salaries = await portalService.getMySalaries(req.user?.employeeId);
|
||||
res.json(ResponseFormatter.success(salaries));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const portalController = new PortalController();
|
||||
Reference in New Issue
Block a user