قواعد الحضور والورديات الكاملة — Moon HR

كل الإعدادات والسيناريوهات والاستثناءات — للباك إند والفرونت إند

1

إعدادات الحضور (انتقلت للوردية)

⚠️ تم التغيير: كل الإعدادات دي بقت على مستوى الوردية مش الشركة. كل وردية ليها إعداداتها الخاصة. الإعدادات العامة أدناه هي القيم الافتراضية اللي بتتحط لما تعمل وردية جديدة.
hrm.late_threshold_minutes
سماح التأخير
60 دقيقة — المدة اللي الموظف يقدر يتأخرها بدون ما يتحسب عليه تأخير
hrm.late_counts_full
احتساب التأخير الكامل
true — لو تجاوز السماح يحسب كل الوقت من بداية الوردية (مش الزيادة فقط)
hrm.early_leave_threshold_minutes
سماح الانصراف المبكر
30 دقيقة — المدة اللي الموظف يقدر ينصرف قبلها بدون مشكلة
hrm.early_leave_counts_full
احتساب الانصراف المبكر الكامل
true — نفس منطق التأخير
hrm.working_hours_per_day
ساعات العمل اليومية
8 ساعات (موجود حالياً) — يُستخدم لحساب الساعات الإضافية
hrm.presence_verification_enabled
بصمة إثبات التواجد
true/false — هل مطلوب بصمة إضافية أثناء الدوام لإثبات التواجد
hrm.presence_verification_interval
فترة إثبات التواجد
120 دقيقة — كل كام دقيقة لازم يبصم إثبات تواجد
hrm.presence_verification_tolerance
سماح إثبات التواجد
15 دقيقة — لو تأخر عن البصمة بأقل من كده = عادي
2

إعدادات الرضاعة (Nursing Settings)

استثناء للسيدات المرضعات — سماح إضافي في الحضور.

hrm.nursing_enabled
تفعيل سماح الرضاعة
true/false
hrm.nursing_extra_minutes
دقائق سماح الرضاعة
60 دقيقة — تضاف على السماح العادي (60+60=120 دقيقة إجمالي)
hrm.nursing_max_child_age_months
حد عمر الطفل
24 شهر — بعد سنتين ينتهي السماح تلقائي (أو 0 = يدوي بدون حد)

على الموظف: حقل is_nursing: boolean + nursing_start_date: date

مثال:

موظفة مرضعة، وردية 8:00، سماح عادي 60 + رضاعة 60 = 120 دقيقة:

  • حضرت 9:50 → تأخرت 110 دقيقة → أقل من 120 → حاضرة
  • حضرت 10:10 → تأخرت 130 دقيقة → أكتر من 120 → متأخرة 130 دقيقة كاملة
3

إعدادات ذوي الهمم (Special Needs Settings)

hrm.special_needs_enabled
تفعيل استثناء ذوي الهمم
true/false
hrm.special_needs_extra_minutes
دقائق سماح إضافية
60 دقيقة (تضاف على السماح العادي)
hrm.special_needs_early_leave_extra
سماح انصراف مبكر إضافي
30 دقيقة (تضاف على الـ 30 العادية = 60 دقيقة إجمالي)

على الموظف: حقل is_special_needs: boolean + special_needs_type: varchar

4

بصمة إثبات التواجد (Presence Verification)

بصمات إضافية أثناء الدوام للتأكد إن الموظف موجود في مكان العمل.

السيناريو:

وردية 8:00 - 16:00، إثبات تواجد كل ساعتين:

  • 8:00 — بصمة حضور ✅
  • 10:00 — بصمة إثبات تواجد (status: 6) ✅
  • 12:00 — بصمة إثبات تواجد ✅
  • 14:00 — بصمة إثبات تواجد ✅
  • 16:00 — بصمة انصراف ✅

لو فوّت بصمة إثبات → يتسجل تنبيه (مش تأخير — بس notification للمدير)

💡 بصمة إثبات التواجد = status: 6 (punch type: presence_verification)
5

إعدادات الوردية (Shift Settings)

كل وردية ليها إعداداتها الخاصة — تطغى على الإعدادات العامة.

حقول الوردية الحالية + الجديدة:

كل الإعدادات بقت على مستوى الوردية — مش الشركة. كل وردية ليها قواعدها الخاصة.

الحقلالحالةالشرح
الحقول الموجودة
name_ar / name_enموجوداسم الوردية
start_time / end_timeموجودوقت البداية والنهاية
working_hoursموجودساعات العمل (محسوبة)
break_duration_minutesموجودمدة الاستراحة
grace_period_minutesموجودفترة السماح (حالياً 15 — المفروض 60)
is_night_shiftموجودوردية ليلية
حقول جديدة — إعدادات الحضور (على مستوى الوردية)
late_threshold_minutesجديدسماح التأخير بالدقائق (مثال: 60)
late_counts_fullجديدbool — لو true يحسب كل الوقت تأخير
max_late_before_absentجديدالحد الأقصى للتأخير قبل الغياب (مثال: 180 دقيقة)
حقول جديدة — إعدادات الانصراف
early_leave_threshold_minutesجديدسماح الانصراف المبكر بالدقائق (مثال: 30)
early_leave_counts_fullجديدbool — لو true يحسب كل الوقت انصراف مبكر
حقول جديدة — المرونة والورديات المتعددة
flexibility_minutesجديدمرونة الوقت (0 = بدون مرونة، 120 = ساعتين)
hours_per_dayجديدكام ساعة تحسب يوم واحد (افتراضي 6، ممكن 8 أو أي رقم)
equivalent_daysجديد — محسوب تلقائي= working_hours ÷ hours_per_day (مثال: 24h ÷ 6h = 4 أيام)
deduction_base_hoursجديد — = hours_per_dayالساعات اللي يُخصم منها التأخير (= يوم واحد)
حقول جديدة — استثناءات
nursing_extra_minutesجديدسماح إضافي للرضاعة (0 = بدون، 60 = ساعة)
special_needs_extra_minutesجديدسماح إضافي لذوي الهمم (0 = بدون)
حقول جديدة — إثبات التواجد
presence_check_enabledجديدbool — هل مطلوب بصمة تواجد أثناء الدوام
presence_check_intervalجديدكل كام دقيقة (مثال: 120)
الفائدة: كل وردية مستقلة — مثلاً وردية الصباح سماح 60 دقيقة، وردية الليل سماح 30 دقيقة، وردية 24 ساعة سماح 120 دقيقة. بدون ما تأثر على بعض.

المرونة (Flexibility):

السيناريو:

وردية 8:00 - 16:00، مرونة 120 دقيقة (ساعتين):

  • الموظف يقدر يحضر من 8:00 لـ 10:00 بدون تأخير
  • لكن لازم يعوّض — لو حضر 10:00 ينصرف 18:00
  • المهم: إجمالي ساعات العمل يتحقق (8 ساعات)
  • لو حضر 10:30 (بعد المرونة بنص ساعة) → متأخر 150 دقيقة
القاعدة:
لو الوردية عندها flexibility_minutes > 0:
→ الحد الأقصى للحضور = start_time + flexibility_minutes
→ لو حضر قبل الحد = حاضر (مش متأخر)
→ لو حضر بعد الحد = متأخر (يحسب من بداية الوردية الأصلية)
→ وقت الانصراف المتوقع = check_in + working_hours (مش end_time الثابت)

الورديات متعددة الأيام:

المعادلة:
equivalent_days = working_hours ÷ hours_per_day (يُقرّب لأعلى)
deduction_base_hours = hours_per_day (يوم واحد)

مثال 1: hours_per_day = 6 ساعات

مدة الورديةhours_per_dayequivalent_daysالخصم من
6 ساعات66÷6 = 1 يومأول 6h
12 ساعة612÷6 = 2 يومأول 6h
18 ساعة618÷6 = 3 أيامأول 6h
24 ساعة624÷6 = 4 أيامأول 6h
36 ساعة636÷6 = 6 أيامأول 6h

مثال 2: hours_per_day = 8 ساعات

مدة الورديةhours_per_dayequivalent_daysالخصم من
8 ساعات88÷8 = 1 يومأول 8h
12 ساعة812÷8 = 1.5 يومأول 8h
16 ساعة816÷8 = 2 يومأول 8h
24 ساعة824÷8 = 3 أيامأول 8h
36 ساعة836÷8 = 4.5 أيامأول 8h
💡 المرونة: hours_per_day يتحدد في كل وردية — مش ثابت. ممكن وردية صباحية اليوم فيها 6 ساعات ووردية ليلية اليوم فيها 8 ساعات.

مثال وردية 24 ساعة:

وردية من 8:00 صباح السبت لـ 8:00 صباح الأحد (24 ساعة = 4 أيام):

  • حضر 8:00 ← عادي ← يحسب 4 أيام حضور
  • حضر 9:30 ← تأخر 90 دقيقة (بعد السماح 60) ← متأخر 90 دقيقة
  • الخصم من أول 6 ساعات فقط (اليوم الأول من 4)
  • الـ 3 أيام الباقية = حضور كامل بدون خصم
5.5

حد التأخير الأقصى — التحول لغياب (Max Late → Absent)

لو الموظف تأخر أكتر من حد معين، يتحول من "متأخر" لـ "غائب".

hrm.max_late_before_absent_minutes
حد التأخير الأقصى قبل الغياب
180 دقيقة (3 ساعات) — بعدها يتحول لغائب
hrm.absent_deducts_full_day
الغياب يخصم يوم كامل
true — لو غائب يخصم اليوم الأول كامل (مش الدقائق)

القاعدة:

تأخير ≤ 60 دقيقة (السماح)حاضر بدون تأخير

تأخير > 60 و ≤ 180 دقيقةمتأخر (يحسب كل الوقت)

تأخير > 180 دقيقة (3 ساعات)غائب (يخصم يوم كامل)

للورديات متعددة الأيام:

الورديةالتأخيرالنتيجة
6 ساعات (يوم واحد) > 180 دقيقة غائب يوم كامل
12 ساعة (يومين) 61 - 180 دقيقة متأخر — يخصم من أول 6h فقط. اليوم الثاني عادي.
181 - 360 دقيقة (3-6 ساعات) اليوم الأول غائب كامل — اليوم الثاني يحسب عادي
> 360 دقيقة (> 6 ساعات) اليوم الأول غائب + اليوم الثاني متأخر
24 ساعة (4 أيام) 181 - 360 دقيقة اليوم الأول غائب — باقي 3 أيام عادي
> 360 دقيقة اليوم الأول غائب + التأخير يتوزع على الأيام التالية

أمثلة عملية:

مثال 1 — وردية 6 ساعات (عادية):

  • وردية 8:00 - 14:00
  • حضر 10:30 → تأخر 150 دقيقة → أقل من 180 → متأخر 150 دقيقة
  • حضر 11:30 → تأخر 210 دقيقة → أكتر من 180 → غائب — يخصم يوم كامل

مثال 2 — وردية 12 ساعة (يومين):

  • وردية 8:00 - 20:00 (12h = يومين)
  • حضر 10:00 → تأخر 120 دقيقة → متأخر 120 دقيقة — يخصم من اليوم الأول (6h). اليوم الثاني عادي.
  • حضر 12:00 → تأخر 240 دقيقة → أكتر من 180 → اليوم الأول غائب كامل. اليوم الثاني يحسب عادي (حاضر).

مثال 3 — وردية 24 ساعة (4 أيام):

  • وردية من 8:00 السبت لـ 8:00 الأحد
  • حضر 11:30 → تأخر 210 دقيقة → أكتر من 180 → اليوم الأول غائب. الأيام 2+3+4 = حاضر عادي.
6

إعدادات الانصراف (Check-out Settings)

hrm.early_leave_threshold_minutes
سماح الانصراف المبكر
30 دقيقة
hrm.early_leave_counts_full
احتساب الانصراف المبكر الكامل
true — لو تجاوز يحسب كل الوقت

مثال:

وردية تنتهي 4:00 مساءً، سماح 30 دقيقة:

  • انصرف 3:40 → قبل 20 دقيقة → عادي
  • انصرف 3:10 → قبل 50 دقيقة → انصراف مبكر 50 دقيقة كاملة

مع المرونة: لو الوردية عندها flexibility — وقت الانصراف = check_in + working_hours

  • حضر 10:00 (مرونة) + 8 ساعات = الانصراف المتوقع 18:00
  • انصرف 17:40 → قبل 20 دقيقة → عادي

7

منطق الحساب الكامل (Full Algorithm)

عند تسجيل الحضور (Check-in):

function calculateCheckIn(employee, shift, checkInTime):

  // 1. حساب السماح الإجمالي (كل الإعدادات من الوردية)
  grace = shift.late_threshold_minutes  // 60 (من الوردية مباشرة)

  if (shift.flexibility_minutes > 0):
    grace = shift.flexibility_minutes  // المرونة تحل محل السماح

  if (employee.is_nursing):
    grace += shift.nursing_extra_minutes  // +60 (من الوردية)

  if (employee.is_special_needs):
    grace += shift.special_needs_extra_minutes  // +60 (من الوردية)

  // 2. حساب التأخير
  diff = checkInTime - shift.start_time  // بالدقائق
  max_late = shift.max_late_before_absent  // 180 (من الوردية)

  if (diff <= grace):
    // ✅ حاضر — في حدود السماح
    status = PRESENT
    late_minutes = 0
    absent_days = 0

  else if (diff <= max_late):
    // ⚠️ متأخر — تجاوز السماح بس لسه في حدود التأخير
    status = LATE
    if (shift.late_counts_full):
      late_minutes = diff  // كل الوقت
    else:
      late_minutes = diff - grace
    absent_days = 0

  else:
    // ❌ غائب — تجاوز حد التأخير الأقصى
    status = ABSENT
    late_minutes = 0  // مش متأخر — غائب

    // للورديات متعددة الأيام:
    if (shift.equivalent_days > 1):
      absent_days = 1  // اليوم الأول بس غائب
      remaining_days = shift.equivalent_days - 1  // باقي الأيام حاضر
      // لو التأخير > 6 ساعات (deduction_base_hours):
      if (diff > shift.deduction_base_hours * 60):
        // التأخير يتوزع على اليوم التالي
        extra_late = diff - (shift.deduction_base_hours * 60)
        // اليوم الثاني = متأخر بالزيادة
    else:
      absent_days = 1  // وردية عادية — يوم واحد غائب

  // 3. حساب الانصراف المتوقع (لو مرونة)
  if (shift.flexibility_minutes > 0 && diff > 0 && diff <= shift.flexibility_minutes):
    expected_checkout = checkInTime + shift.working_hours
  else:
    expected_checkout = shift.end_time

  return { status, late_minutes, absent_days, expected_checkout }
    

عند تسجيل الانصراف (Check-out):

function calculateCheckOut(attendance, shift, checkOutTime):

  // 1. حساب سماح الانصراف (من الوردية)
  early_grace = shift.early_leave_threshold_minutes  // 30

  if (employee.is_special_needs):
    early_grace += shift.special_needs_extra_minutes  // +60

  // 2. حساب الانصراف المتوقع
  expected_end = attendance.expected_checkout ?? shift.end_time

  // 3. حساب الانصراف المبكر
  early_diff = expected_end - checkOutTime  // بالدقائق

  if (early_diff <= early_grace):
    early_leave_minutes = 0
  else:
    if (shift.early_leave_counts_full):
      early_leave_minutes = early_diff
    else:
      early_leave_minutes = early_diff - early_grace

  // 4. حساب ساعات العمل
  worked_hours = checkOutTime - attendance.check_in
  overtime = max(0, worked_hours - shift.working_hours)

  // 5. حساب أيام الحضور
  hours_per_day = shift.hours_per_day ?? 6
  equivalent_days = ceil(shift.working_hours / hours_per_day)  // محسوب تلقائي

  return { worked_hours, overtime, early_leave_minutes, equivalent_days }
    

8

كل التغييرات المطلوبة — Backend (أحمد)

A. إعدادات HR جديدة (Settings Seeder):

المفتاحالنوعالقيمة
hrm.late_threshold_minutesint60
hrm.late_counts_fullbooltrue
hrm.early_leave_threshold_minutesint30
hrm.early_leave_counts_fullbooltrue
hrm.nursing_enabledbooltrue
hrm.nursing_extra_minutesint60
hrm.nursing_max_child_age_monthsint24
hrm.special_needs_enabledbooltrue
hrm.special_needs_extra_minutesint60
hrm.special_needs_early_leave_extraint30
hrm.presence_verification_enabledboolfalse
hrm.presence_verification_intervalint120
hrm.presence_verification_toleranceint15
hrm.max_late_before_absent_minutesint180
hrm.absent_deducts_full_daybooltrue

B. حقول جديدة على employees:

الحقلالنوع
is_nursingboolean default false
nursing_start_datedate nullable
is_special_needsboolean default false
special_needs_typevarchar(100) nullable

C. حقول جديدة على shifts (كل الإعدادات على مستوى الوردية):

الحقلالنوعالشرح
إعدادات الحضور
late_threshold_minutesint default 60سماح التأخير
late_counts_fullbool default trueاحتساب كامل
max_late_before_absentint default 180حد الغياب
إعدادات الانصراف
early_leave_threshold_minutesint default 30سماح الانصراف المبكر
early_leave_counts_fullbool default trueاحتساب كامل
المرونة والأيام
flexibility_minutesint default 0مرونة الوقت
hours_per_daydecimal(4,1) default 6كام ساعة = يوم واحد
equivalent_daysdecimal(4,1) — محسوب= working_hours ÷ hours_per_day
deduction_base_hoursdecimal(4,1) — = hours_per_dayساعات الخصم = يوم واحد
استثناءات
nursing_extra_minutesint default 60سماح الرضاعة
special_needs_extra_minutesint default 60سماح ذوي الهمم
إثبات التواجد
presence_check_enabledbool default falseتفعيل بصمة التواجد
presence_check_intervalint default 120كل كام دقيقة

D. حقل جديد على attendances:

الحقلالنوع
expected_checkouttime nullable (لو مرونة: check_in + working_hours)
equivalent_daysint default 1 (من الوردية)

E. تعديل AttendanceResource:

إضافة في الـ response: device_name, punch_type, expected_checkout, equivalent_days, early_leave_minutes

F. تعديل منطق AttendanceService::checkIn() و checkOut()

حسب الـ Algorithm في القسم 7 فوق.

9

التغييرات المطلوبة — Frontend (حازم)

  1. شاشة الحضور: عرض المصدر (بصمة/ويب/يدوي) + اسم الجهاز + انصراف مبكر + أيام مكافئة
  2. فورم الموظف: toggle مرضعة + تاريخ بداية + toggle ذوي همم + نوع الإعاقة
  3. فورم الوردية: مرونة (دقائق) + أيام مكافئة + ساعات الخصم + سماح تأخير/انصراف خاص
  4. شاشة الإعدادات: كل الإعدادات الجديدة الـ 13