import { getPublicHolidaysForNYears } from "@/app/utils/holidayUtils"; import { HolidaysResult } from "../holidays"; import { LeaveEntry, RecordTimeLeaveInput, TimeEntry } from "./actions"; import { convertDateArrayToString } from "@/app/utils/formatUtil"; import compact from "lodash/compact"; import dayjs from "dayjs"; export type TimeEntryError = { [field in keyof TimeEntry]?: string; }; /** * @param entry - the time entry * @returns an object where the keys are the error fields and the values the error message, and undefined if there are no errors */ export const validateTimeEntry = ( entry: Partial, isHoliday: boolean, ): TimeEntryError | undefined => { // Test for errors const error: TimeEntryError = {}; // Either normal or other hours need to be inputted if (!entry.inputHours && !entry.otHours) { error[isHoliday ? "otHours" : "inputHours"] = "Required"; } else if (entry.inputHours && isHoliday) { error.inputHours = "Cannot input normal hours on holidays"; } else if (entry.inputHours && entry.inputHours <= 0) { error.inputHours = "Input hours should be between 0 and {{DAILY_NORMAL_MAX_HOURS}}"; } else if (entry.inputHours && entry.inputHours > DAILY_NORMAL_MAX_HOURS) { error.inputHours = "Input hours should be between 0 and {{DAILY_NORMAL_MAX_HOURS}}"; } else if (entry.otHours && entry.otHours <= 0) { error.otHours = "Hours should be bigger than 0"; } // If there is a project id, there should also be taskGroupId, taskId, inputHours if (entry.projectId) { if (!entry.taskGroupId) { error.taskGroupId = "Required"; } else if (!entry.taskId) { error.taskId = "Required"; } } else { if (entry.taskGroupId && !entry.taskId) { error.taskId = "Required"; } else if (!entry.remark) { error.remark = "Required for non-billable tasks"; } } return Object.keys(error).length > 0 ? error : undefined; }; export type LeaveEntryError = { [field in keyof LeaveEntry]?: string; }; export const validateLeaveEntry = ( entry: Partial, isHoliday: boolean, ): LeaveEntryError | undefined => { // Test for errrors const error: LeaveEntryError = {}; if (!entry.leaveTypeId) { error.leaveTypeId = "Required"; } else if (entry.inputHours && isHoliday) { error.inputHours = "Cannot input normal hours on holidays"; } else if (!entry.inputHours) { error.inputHours = "Required"; } else if ( entry.inputHours && (entry.inputHours <= 0 || entry.inputHours > DAILY_NORMAL_MAX_HOURS) ) { error.inputHours = "Input hours should be between 0 and {{DAILY_NORMAL_MAX_HOURS}}"; } return Object.keys(error).length > 0 ? error : undefined; }; export const validateTimeLeaveRecord = ( records: RecordTimeLeaveInput, companyHolidays: HolidaysResult[], isFullTime?: boolean, ): { [date: string]: string } | undefined => { const errors: { [date: string]: string } = {}; const holidays = new Set( compact([ ...getPublicHolidaysForNYears(2).map((h) => h.date), ...companyHolidays.map((h) => convertDateArrayToString(h.date)), ]), ); Object.keys(records).forEach((date) => { const dayJsObj = dayjs(date); const isHoliday = holidays.has(date) || dayJsObj.day() === 0 || dayJsObj.day() === 6; const entries = records[date]; // Check each entry for (const entry of entries) { let entryError; if (entry.type === "leaveEntry") { entryError = validateLeaveEntry(entry, isHoliday); } else { entryError = validateTimeEntry(entry, isHoliday); } if (entryError) { errors[date] = "There are errors in the entries"; return; } } // Check total hours const totalHourError = checkTotalHours( entries.filter((e) => e.type === "timeEntry") as TimeEntry[], entries.filter((e) => e.type === "leaveEntry") as LeaveEntry[], isHoliday, isFullTime, ); if (totalHourError) { errors[date] = totalHourError; } }); return Object.keys(errors).length > 0 ? errors : undefined; }; export const checkTotalHours = ( timeEntries: TimeEntry[], leaves: LeaveEntry[], isHoliday?: boolean, isFullTime?: boolean, ): string | undefined => { const leaveHours = leaves.reduce((acc, entry) => acc + entry.inputHours, 0); const totalInputHours = timeEntries.reduce((acc, entry) => { return acc + (entry.inputHours || 0); }, 0); const totalOtHours = timeEntries.reduce((acc, entry) => { return acc + (entry.otHours || 0); }, 0); if (totalInputHours + leaveHours > DAILY_NORMAL_MAX_HOURS) { return "The daily normal hours (timesheet hours + leave hours) cannot be more than {{DAILY_NORMAL_MAX_HOURS}}. Please use other hours for exceeding hours or decrease the leave hours."; } else if ( isFullTime && !isHoliday && totalInputHours + leaveHours !== DAILY_NORMAL_MAX_HOURS ) { return "The daily normal hours (timesheet hours + leave hours) for full-time staffs should be {{DAILY_NORMAL_MAX_HOURS}}."; } else if ( totalInputHours + totalOtHours + leaveHours > TIMESHEET_DAILY_MAX_HOURS ) { return "The daily total hours cannot be more than {{TIMESHEET_DAILY_MAX_HOURS}}"; } }; export const DAILY_NORMAL_MAX_HOURS = 8; export const TIMESHEET_DAILY_MAX_HOURS = 20;