diff --git a/server/src/module/attendance/attendance.constants.ts b/server/src/module/attendance/attendance.constants.ts new file mode 100644 index 000000000..00a793a6f --- /dev/null +++ b/server/src/module/attendance/attendance.constants.ts @@ -0,0 +1 @@ +export const MAX_WORK_HOURS = 24; diff --git a/server/src/module/attendance/attendance.controller.ts b/server/src/module/attendance/attendance.controller.ts index b7039f16e..74dec4972 100644 --- a/server/src/module/attendance/attendance.controller.ts +++ b/server/src/module/attendance/attendance.controller.ts @@ -90,6 +90,11 @@ export class AttendanceController { const record = await this.attendanceService.regularize(result.data); return res.json({ message: "Attendance regularized", record }); } catch (error) { + if (error instanceof Error) { + const msg = error.message; + if (msg.includes("must be after") || msg.includes("must not exceed") || msg.includes("Cannot regularize") || msg.includes("cannot be in the future")) + return res.status(400).json({ message: msg }); + } console.error(error); return res.status(500).json({ message: "Internal Server Error" }); } diff --git a/server/src/module/attendance/attendance.service.ts b/server/src/module/attendance/attendance.service.ts index a51c5986a..c2f069596 100644 --- a/server/src/module/attendance/attendance.service.ts +++ b/server/src/module/attendance/attendance.service.ts @@ -1,5 +1,6 @@ import { prisma } from "../../database/db.js"; import type { AttendanceStatus, Prisma } from "@prisma/client"; +import { MAX_WORK_HOURS } from "./attendance.constants.js"; interface AttendanceQuery { page: number; @@ -133,6 +134,9 @@ export class AttendanceService { const checkOut = new Date(data.checkOut); const workHours = (checkOut.getTime() - checkIn.getTime()) / 3600000; + if (workHours <= 0) throw new Error("checkOut must be after checkIn"); + if (workHours > MAX_WORK_HOURS) throw new Error("Work hours must not exceed 24"); + return prisma.attendanceRecord.upsert({ where: { employeeId_date: { employeeId: data.employeeId, date } }, create: { diff --git a/server/src/module/attendance/attendance.validation.ts b/server/src/module/attendance/attendance.validation.ts index 4443fdd29..c6035880e 100644 --- a/server/src/module/attendance/attendance.validation.ts +++ b/server/src/module/attendance/attendance.validation.ts @@ -1,4 +1,5 @@ import { z } from "zod"; +import { MAX_WORK_HOURS } from "./attendance.constants.js"; export const checkInSchema = z.object({ employeeId: z.number().int().positive(), @@ -16,6 +17,16 @@ export const regularizeSchema = z.object({ checkIn: z.string().datetime(), checkOut: z.string().datetime(), notes: z.string().min(1, "Reason for regularization is required").max(500), +}).superRefine((data, ctx) => { + const workHours = (new Date(data.checkOut).getTime() - new Date(data.checkIn).getTime()) / 3600000; + + if (workHours <= 0) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: "checkOut must be after checkIn", path: ["checkOut"] }); + } + + if (workHours > MAX_WORK_HOURS) { + ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Work hours must not exceed ${MAX_WORK_HOURS}`, path: ["checkOut"] }); + } }); export const attendanceQuerySchema = z.object({