This commit is contained in:
Aya
2026-05-07 15:21:10 +03:00
parent 9e5dd47a2f
commit e01e351713
9 changed files with 194 additions and 81 deletions

View File

@@ -313,54 +313,77 @@ class HRService {
// ========== LEAVES ==========
async createLeaveRequest(data: any, userId: string) {
const allowedLeaveTypes = ['ANNUAL', 'HOURLY'];
const allowedLeaveTypes = ['ANNUAL', 'HOURLY'];
const normalizedLeaveType = String(data.leaveType || '').toUpperCase();
if (!allowedLeaveTypes.includes(String(data.leaveType || '').toUpperCase())) {
throw new AppError(400, 'نوع الإجازة غير مدعوم - Only ANNUAL and HOURLY leave types are allowed');
}
const normalizedLeaveType = String(data.leaveType).toUpperCase();
const days = this.calculateLeaveDays(data.startDate, data.endDate);
const startDate = new Date(data.startDate);
const year = startDate.getFullYear();
const ent = await prisma.leaveEntitlement.findUnique({
where: {
employeeId_year_leaveType: {
employeeId: data.employeeId,
year,
leaveType: normalizedLeaveType,
},
},
});
if (ent) {
const available = ent.totalDays + ent.carriedOver - ent.usedDays;
if (days > available) {
throw new AppError(400, `رصيد الإجازة غير كافٍ - Insufficient leave balance. Available: ${available}, Requested: ${days}`);
if (!allowedLeaveTypes.includes(normalizedLeaveType)) {
throw new AppError(400, 'نوع الإجازة غير مدعوم - Only ANNUAL and HOURLY leave types are allowed');
}
const startDate = new Date(data.startDate);
const endDate = new Date(data.endDate);
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
throw new AppError(400, 'تاريخ أو وقت الإجازة غير صالح');
}
const isInvalidRange = normalizedLeaveType === 'HOURLY'
? endDate <= startDate
: endDate < startDate;
if (isInvalidRange) {
throw new AppError(400, 'وقت/تاريخ النهاية يجب أن يكون بعد البداية');
}
const days = normalizedLeaveType === 'HOURLY'
? 0
: this.calculateLeaveDays(startDate, endDate);
const year = startDate.getFullYear();
if (normalizedLeaveType !== 'HOURLY') {
const ent = await prisma.leaveEntitlement.findUnique({
where: {
employeeId_year_leaveType: {
employeeId: data.employeeId,
year,
leaveType: normalizedLeaveType,
},
},
});
if (ent) {
const available = ent.totalDays + ent.carriedOver - ent.usedDays;
if (days > available) {
throw new AppError(400, `رصيد الإجازة غير كافٍ - Insufficient leave balance. Available: ${available}, Requested: ${days}`);
}
}
}
const leave = await prisma.leave.create({
data: {
employeeId: data.employeeId,
leaveType: normalizedLeaveType,
startDate,
endDate,
days,
reason: data.reason || undefined,
},
include: {
employee: true,
},
});
await AuditLogger.log({
entityType: 'LEAVE',
entityId: leave.id,
action: 'CREATE',
userId,
});
return leave;
}
const leave = await prisma.leave.create({
data: {
...data,
leaveType: normalizedLeaveType,
days,
},
include: {
employee: true,
},
});
await AuditLogger.log({
entityType: 'LEAVE',
entityId: leave.id,
action: 'CREATE',
userId,
});
return leave;
}
async approveLeave(id: string, approvedBy: string, userId: string) {
const leave = await prisma.leave.update({
where: { id },

View File

@@ -144,19 +144,65 @@ export class PortalController {
}
}
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 submitLeaveRequest(req: AuthRequest, res: Response, next: NextFunction) {
try {
const body = { ...req.body };
const leaveType = String(body.leaveType || '').toUpperCase();
let startDate: Date;
let endDate: Date;
if (leaveType === 'HOURLY' && body.leaveDate && body.startTime && body.endTime) {
startDate = new Date(`${body.leaveDate}T${body.startTime}:00+03:00`);
endDate = new Date(`${body.leaveDate}T${body.endTime}:00+03:00`);
} else {
startDate = new Date(body.startDate);
endDate = new Date(body.endDate);
}
if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) {
return res.status(400).json({
success: false,
message: 'تاريخ أو وقت الإجازة غير صالح - Invalid leave date or time',
});
}
const isInvalidRange = leaveType === 'HOURLY'
? endDate <= startDate
: endDate < startDate;
if (isInvalidRange) {
return res.status(400).json({
success: false,
message: 'وقت/تاريخ النهاية يجب أن يكون بعد البداية - End date/time must be after start date/time',
});
}
const data = {
leaveType,
startDate,
endDate,
reason: body.reason || undefined,
};
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 {