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 name | EASetPermissionHourFromAttendanceShift |
| Script | EASetPermissionHourFromAttendanceShift.groovy |
| Runs on | Leave Permission, on the Pre Update Calculated Fields event |
| Uses | No parameters. Leave Parameter 1 empty. |
| Shipped as | Source 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
It looks at the permission type. Only Forgot Check In and Forgot Check Out are handled. Every other type is left alone.
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.
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.
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.
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
- Open Administration → Display Customization → Entity Flow and create a new flow.
- Set Target Type to Leave Permission and Target Action to Pre Update Calculated Fields.
- In the Details grid, paste the whole script into Groovy Script. Leave Class Name empty: it is filled in when you save.
- Save the flow.
| Parameter | Title shown after saving | What to enter |
|---|---|---|
| Parameter 1 | Not used - leave empty | Nothing. |
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 Type | Date | Hours typed | Hours after saving |
|---|---|---|---|
| Forgot Check In | 08-10-2026 | From 10:30 to 16:00 | From 08:00 to 16:00 |
| Forgot Check Out | 07-10-2026 | From 08:00 to 13:00 | From 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.
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")
}
}Related
- EAGroovyAction: how Groovy scripts are attached to entity flows and task schedules.
- Leave Permissions & Missions
- Attendance Plans & Shifts
- Show What the System Thinks Each Day Is for an Employee: to check which shift an employee has on a given day.