Skip to content

Fill a Forgotten Punch's Hour From the Employee's Shift ​

This Groovy script fills in the hour of a Leave Permission for a forgotten punch. For Forgot Check In it sets From Hour to the start of the employee's shift that day. For Forgot Check Out it sets To Hour to the end of the shift.

Recommended pattern

This script is not tied to any customer's setup. You can paste it as it is; it takes no parameters.

Class nameEASetPermissionHourFromAttendanceShift
ScriptEASetPermissionHourFromAttendanceShift.groovy
Runs onLeave Permission, on the Pre Update Calculated Fields event
UsesNo parameters. Leave Parameter 1 empty.
Shipped asSource to paste. It is not built into the system.

When to use it ​

When an employee forgets to punch in, HR covers the missing punch with a Leave Permission whose Permission Type is Forgot Check In. Somebody still has to type the hour the employee should have punched, and that hour depends on the employee's shift: 08:00 for one person, 07:00 for another, 16:00 for someone on the evening shift. Typing it by hand is slow, and a wrong hour gives the permission a wrong period.

With this script, the hour is taken from the shift the attendance plan gives that employee on that day. HR only picks the employee, the type and the date.

How it works ​

  1. It looks at the permission type. Only Forgot Check In and Forgot Check Out are handled. Every other type is left alone.

  2. It picks the day. For Forgot Check In it uses From Date; for Forgot Check Out it uses To Date. If that date is empty, it uses the document's date.

  3. It finds the employee's shift on that day. It reads the attendance plan in force on that day for the employee, taking added shifts and plan priorities into account, and takes that day's work periods from the shift.

  4. It sets the hour.

    • Forgot Check In: From Hour becomes the start of the first work period of the day.
    • Forgot Check Out: To Hour becomes the end of the last work period of the day.

    On a split shift, that is the start of the morning period and the end of the evening period.

  5. On a weekend or weekly rest day, the shift's own hours for that day are used if the shift defines any. If no plan covers the employee, or the shift has no hours for that day, the permission is left as the user typed it.

The script runs on Pre Update Calculated Fields, so the permission's period is calculated from the new hour when the document is saved.

Setting it up ​

  1. Open Administration → Display Customization → Entity Flow and create a new flow.
  2. Set Target Type to Leave Permission and Target Action to Pre Update Calculated Fields.
  3. In the Details grid, paste the whole script into Groovy Script. Leave Class Name empty: it is filled in when you save.
  4. Save the flow.
ParameterTitle shown after savingWhat to enter
Parameter 1Not used - leave emptyNothing.

Example ​

An employee's attendance plan gives him a shift from 08:00 to 16:00 on working days. HR records two permissions for him:

Permission TypeDateHours typedHours after saving
Forgot Check In08-10-2026From 10:30 to 16:00From 08:00 to 16:00
Forgot Check Out07-10-2026From 08:00 to 13:00From 08:00 to 16:00

Messages you may see ​

The script raises no messages of its own. When it cannot find a shift for the day, it simply leaves the hours as they were typed, so check From Hour and To Hour after saving if the employee has no attendance plan.

The script ​

The comments in the script are in Arabic; they repeat what this page explains.

groovy
package com.namasoft.modules.humanresource.utils.actions

import com.namasoft.common.utilities.ObjectChecker
import com.namasoft.infra.domainbase.datafields.DateDF
import com.namasoft.infra.domainbase.datafields.LongTextDF
import com.namasoft.infra.domainbase.datafields.TimeDF
import com.namasoft.infra.domainbase.entity.base.EntityAction
import com.namasoft.infra.domainbase.modules.hr.Employee
import com.namasoft.infra.domainbase.util.Result
import com.namasoft.modules.humanresource.domain.entities.AbstractLeavePermission
import com.namasoft.modules.humanresource.domain.primitives.LeavePermissionType
import com.namasoft.modules.humanresource.domain.valueobjects.TimeRange
import com.namasoft.modules.humanresource.domain.valueobjects.WorkDay
import com.namasoft.modules.humanresource.utils.AttendanceCalcContext
import com.namasoft.modules.humanresource.utils.AttendanceDayShift

/**
 * إذن الانصراف - ضبط وقت الإذن من ملف الدوام
 *
 * - نوع الإذن "نسيان بصمة دخول"  : يتم تعديل (من وقت) إلى وقت بداية الدوام في ذلك اليوم
 * - نوع الإذن "نسيان بصمة خروج" : يتم تعديل (إلى وقت) إلى وقت نهاية الدوام في ذلك اليوم
 *
 * وقت الدوام يُحسب من وردية الموظف حسب خطة الدوام السارية في تاريخ الإذن،
 * مع مراعاة الورديات المضافة والأولويات. وإذا كان اليوم إجازة أسبوعية أو راحة
 * ولكن الوردية تحمل أوقات دوام لذلك اليوم، تُستخدم تلك الأوقات.
 *
 * يجب ربط هذا المسار على الحدث: PreUpdateCalculatedFields
 * حتى تُعاد المدة (leavePeriod) وتُحسب تلقائيا بعد تعديل الوقت.
 */
class EASetPermissionHourFromAttendanceShift implements EntityAction<AbstractLeavePermission> {

    @Override
    Result doAction(AbstractLeavePermission object, LongTextDF... parameters) {
        Result result = Result.createAccumulatingResult()

        LeavePermissionType type = object.getPermissionType()
        if (ObjectChecker.isEmptyOrNull(type))
            return result

        boolean forgotCheckIn = ObjectChecker.areEqual(type, LeavePermissionType.ForgotCheckIn())
        boolean forgotCheckOut = ObjectChecker.areEqual(type, LeavePermissionType.ForgotCheckOut())
        // أي نوع إذن آخر لا يتم التدخل فيه
        if (!forgotCheckIn && !forgotCheckOut)
            return result

        Employee employee = object.getEmployee()
        if (ObjectChecker.isEmptyOrNull(employee))
            return result

        // تاريخ اليوم الذي سيؤخذ منه وقت الدوام
        DateDF day = forgotCheckIn ? object.getFromDate() : object.getToDate()
        if (ObjectChecker.isEmptyOrNull(day))
            day = object.getValueDate()
        if (ObjectChecker.isEmptyOrNull(day))
            return result

        WorkDay workDay = findWorkDay(employee, day)
        if (workDay == null)
            return result

        TimeDF shiftTime = forgotCheckIn ? startOfDay(workDay) : endOfDay(workDay)
        if (ObjectChecker.isEmptyOrNull(shiftTime))
            return result

        if (forgotCheckIn)
            object.setFromHour(shiftTime)
        else
            object.setToHour(shiftTime)

        return result
    }

    /**
     * يرجع بيانات يوم الدوام للموظف في التاريخ المحدد حسب خطة الدوام السارية.
     *
     * الأولوية ليوم دوام عادي. فإذا كان اليوم إجازة أسبوعية أو راحة أسبوعية،
     * نقبل أوقات ذلك اليوم من الوردية طالما كانت معرّفة فعلا (دوام في يوم إجازة).
     * ويرجع null فقط إذا لم توجد خطة دوام أو لم تكن هناك أي أوقات دوام لذلك اليوم.
     */
    private static WorkDay findWorkDay(Employee employee, DateDF day) {
        AttendanceCalcContext.EmployeeAttendancePlanData planData =
                AttendanceCalcContext.getRelatedPlans(day, day.nextDay(), employee)
        List<AttendanceDayShift> dayShifts =
                AttendanceCalcContext.calcAttendancePlanFor(day, employee, planData, new ArrayList<>())
        WorkDay weekEndFallback = null
        for (AttendanceDayShift dayShift : dayShifts) {
            if (ObjectChecker.isEmptyOrNull(dayShift.getShift()))
                continue
            WorkDay workDay = dayShift.getShift().getWorkDayAndReturnNullIfWeekEnd(day, dayShift.getWeekendLines())
            if (workDay != null)
                return workDay
            if (weekEndFallback == null) {
                WorkDay weekEndDay = dayShift.getShift().getWorkDay(day, dayShift.getWeekendLines())
                if (hasWorkTimes(weekEndDay))
                    weekEndFallback = weekEndDay
            }
        }
        return weekEndFallback
    }

    /** هل يحمل يوم الدوام أي فترة دوام حقيقية (بداية تختلف عن النهاية) */
    private static boolean hasWorkTimes(WorkDay workDay) {
        if (workDay == null)
            return false
        return ObjectChecker.isNotEmptyOrNull(WorkDay.createWorkHoursList(workDay))
    }

    /** بداية أول فترة دوام في اليوم (تراعي الورديات المقسمة: فترة 1 و 2 و 3) */
    private static TimeDF startOfDay(WorkDay workDay) {
        TimeRange range = workDay.findFirstTimeRange()
        return range == null ? null : range.getStart()
    }

    /** نهاية آخر فترة دوام في اليوم */
    private static TimeDF endOfDay(WorkDay workDay) {
        TimeRange range = workDay.findLastTimeRange()
        return range == null ? null : range.getEnd()
    }

    @Override
    boolean shouldNotDisplayEntityFlowNameWhenFailure() {
        return true
    }

    @Override
    String describe() {
        return "Leave Permission: when the permission type is Forgot Check In, sets From Hour to the start of the employee's first work period " +
                "on that day; when it is Forgot Check Out, sets To Hour to the end of the last work period. The work periods come from " +
                "the employee's shift in the attendance plan in force on that day, including added shifts and priorities; on a weekend " +
                "or weekly rest day the shift's own times for that day are used if it has any. Other permission types are not changed. " +
                "Attach it to the Pre Update Calculated Fields event so that the permission period is recalculated. Parameter 1 is not used."
    }

    @Override
    List<String> columnNames() {
        return Arrays.asList("Not used - leave empty")
    }
}