الكتابات/tutorial/2026/08
Tutorial28 أغسطس 2026·26 دقيقة

محرّك الإجازات الخاصة في السعودية بـ TypeScript (المواد 113 و114 و115 و160)

ابنِ محرّك الإجازات الخاصة الذي تخطئ فيه أنظمة الرواتب السعودية: إجازة الزواج والمولود والوفاة والحج والامتحان والعِدّة وفق المواد 113 و114 و115 و160. يغطّي نوافذ المطالبة التي تسقط بمضيّها، والفرق بين خمسة أيام وثلاثة حسب درجة القرابة، ولماذا لا تساوي عِدّة الأرملة 130 يومًا ثابتة، ولماذا يجب ألّا تُدفع أيام عيد الأضحى مرّتين.

معظم أنظمة الرواتب في السعودية تتعامل مع الإجازة كرقم واحد: للموظف رصيد، يأخذ منه أيامًا، فينقص الرصيد. هذا النموذج صحيح للإجازة السنوية، وخاطئ لكلّ ما عداها.

المواد 113 و114 و115 و160 من نظام العمل السعودي تُنشئ فئة ثانية من الإجازات لا تتصرّف كرصيد البتّة. لا تتراكم، ولا تُرحَّل، ولا يجوز رفضها بحجّة أن لدى العامل رصيدًا سنويًا متاحًا. إنّها مرتبطة بواقعة: زواج، أو ولادة، أو وفاة، أو موسم حج، أو امتحان. وفي عدّة حالات تسقط إن لم يطالب بها العامل خلال نافذة تُقاس بالأيام.

والخطأ هنا صامت. لا أحد يلاحظ نوع إجازة لم يُمنح أصلًا. الموظّف وحده يلاحظه بعد سنة تقريبًا، أمام لجنة عمالية، في صورة دعوى.

هذا الدرس يبني المحرّك الذي يضبط ذلك. كلّ قاعدة أدناه مسنَدة إلى رقم مادّة، وكلّ فرع مغطّى باختبار، ومجموعة الاختبارات تعمل بنجاح في نهاية الدرس.

المتطلبات الأساسية

  • Node.js 22 أو أحدث (الأمثلة تستخدم node --experimental-strip-types بلا خطوة بناء)
  • إلمام بـ TypeScript: الاتحادات المميّزة (discriminated unions) وswitch الشامل
  • فهم عملي لمفاهيم الرواتب السعودية (وعاء الأجر، بنود المسيّر)
  • لا حاجة لقاعدة بيانات؛ المحرّك دوالّ خالصة تعمل على التواريخ

ما الذي ستبنيه

دالّة واحدة awardSpecialLeave تأخذ سجلّ التوظيف وواقعة الإجازة، وتُرجع استحقاقًا: كم يومًا مدفوعًا، وكم غير مدفوع، ومتى تبدأ الإجازة وتنتهي، وهل تمسّ الرصيد السنوي، والأهمّ: سبب الرفض حين لا ينشأ الاستحقاق أصلًا.

الوقائع الستّ التي يعالجها:

الواقعةالمادةالاستحقاق
الزواج1135 أيام بأجر كامل
ولادة مولود1133 أيام بأجر كامل، تُطلب خلال 7 أيام من الولادة
الوفاة1135 أيام للزوج أو الأصول أو الفروع، و3 أيام للأخ أو الأخت
الحج114من 10 إلى 15 يومًا، مرّة واحدة طوال الخدمة، بعد سنتين متّصلتين
الامتحان115أيام الامتحان الفعلية، بأجر لسنة غير معادة، وبدون أجر للسنة المعادة
العِدّة1604 أشهر وعشرة أيام للمسلمة، و15 يومًا لغيرها

الخطوة 1: نمذج الواقعة لا الرصيد

القرار التصميمي الأول هو ما ينقذك لاحقًا. لا تُضِف هذه الإجازات إلى جدول الإجازة السنوية بعمود type. شكلها مختلف: لا رصيد افتتاحي، ولا معدّل تراكم، ولا ترحيل. نمذجها كوقائع تُنتج استحقاقات.

// src/special-leave/types.ts
export type Religion = 'muslim' | 'non-muslim';
export type Relation = 'spouse' | 'ascendant' | 'descendant' | 'sibling';
 
export type Employment = {
  hiredOn: string;
  religion: Religion;
  performedHajjBefore: boolean;
  hajjLeaveUsedInService: boolean;
  enrolmentApproved: boolean;
};
 
export type Event =
  | { kind: 'marriage'; on: string; requestedStart?: string }
  | { kind: 'newborn'; on: string; requestedStart?: string }
  | { kind: 'bereavement'; on: string; relation: Relation }
  | { kind: 'iddah'; on: string; pregnant?: boolean; deliveryOn?: string }
  | { kind: 'hajj'; on: string; requestedDays?: number }
  | { kind: 'exam'; on: string; examDays: number; repeatYear: boolean };
 
export type Award = {
  article: string;
  paidDays: number;
  unpaidDays: number;
  startsOn: string | null;
  endsOn: string | null;
  deductsFromAnnual: boolean;
  notes: string[];
  refused?: string;
};

لاحظ وجود deductsFromAnnual داخل الاستحقاق لا كثابت عام. قيمته false دائمًا في هذه المواد، والتصريح به في كلّ استحقاق هو ما يمنع إعادة هيكلة لاحقة من خصم هذه الأيام بهدوء من الرصيد السنوي — وهو أكثر الأخطاء شيوعًا في هذا الباب.

القاعدة التي يُخطئ فيها الناس: لا يجوز لصاحب العمل رفض إجازة المادة 113 بحجّة أن لدى العامل رصيدًا سنويًا. الاستحقاق مستقلّ تمامًا عن الرصيد السنوي. تتكرّر هذه المسألة كثيرًا في نقاشات الموارد البشرية السعودية، وهي محسومة: المادة تمنح الإجازة ابتداءً.

الخطوة 2: حساب تواريخ يصمد أمام نهايات الأشهر

كلّ قاعدة في هذا المحرّك قاعدة تواريخ، فدوالّ التاريخ هي المحرّك نفسه. استخدم UTC في كلّ مكان؛ فكائن Date بالتوقيت المحلّي سيزيح حدّ الإجازة يومًا كاملًا لدى من يشغّل الرواتب من جهاز مضبوط على فرق زمني آخر.

// src/special-leave/dates.ts
const MS_DAY = 86_400_000;
 
export const parseDate = (iso: string) => {
  const [y, m, d] = iso.split('-').map(Number);
  return new Date(Date.UTC(y, m - 1, d));
};
 
export const toISO = (d: Date) => d.toISOString().slice(0, 10);
 
export const addDays = (iso: string, n: number) =>
  toISO(new Date(parseDate(iso).getTime() + n * MS_DAY));
 
export const daysBetween = (a: string, b: string) =>
  Math.round((parseDate(b).getTime() - parseDate(a).getTime()) / MS_DAY);
 
export function addMonths(iso: string, n: number): string {
  const d = parseDate(iso);
  const day = d.getUTCDate();
  const t = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + n, 1));
  const last = new Date(Date.UTC(t.getUTCFullYear(), t.getUTCMonth() + 1, 0)).getUTCDate();
  t.setUTCDate(Math.min(day, last));
  return toISO(t);
}
 
export const overlapDays = (aS: string, aE: string, bS: string, bE: string) => {
  const s = Math.max(parseDate(aS).getTime(), parseDate(bS).getTime());
  const e = Math.min(parseDate(aE).getTime(), parseDate(bE).getTime());
  return e < s ? 0 : Math.round((e - s) / MS_DAY) + 1;
};

دالّة addMonths تقصّ الفائض: إضافة شهر إلى 31 يناير تعطي 28 فبراير، لا 3 مارس. هذا القصّ ليس زخرفة، بل حامل أساسي في الخطوة 5.

الخطوة 3: الثوابت، كلٌّ مسنَد إلى مادّته

// src/special-leave/constants.ts
import type { Relation } from './types';
 
export const BEREAVEMENT_DAYS: Record<Relation, number> = {
  spouse: 5,      // Art. 113
  ascendant: 5,   // parents, grandparents
  descendant: 5,  // children, grandchildren
  sibling: 3,     // brother or sister
};
 
export const MARRIAGE_DAYS = 5;                 // Art. 113
export const NEWBORN_DAYS = 3;                  // Art. 113
export const NEWBORN_CLAIM_WINDOW_DAYS = 7;     // Art. 113 — from the date of birth
 
export const IDDAH_MONTHS = 4;                  // Art. 160
export const IDDAH_EXTRA_DAYS = 10;             // Art. 160
export const IDDAH_NON_MUSLIM_DAYS = 15;        // Art. 160
 
export const HAJJ_MIN_DAYS = 10;                // Art. 114
export const HAJJ_MAX_DAYS = 15;                // Art. 114
export const HAJJ_MIN_TENURE_YEARS = 2;         // Art. 114

يستحقّ الفرق بين الخمسة والثلاثة وقفة، لأنّ نتائج البحث نفسها تتناقض فيه. المادة 113 تمنح خمسة أيام عند وفاة الزوج أو أحد الأصول أو الفروع، بينما الأخ أو الأخت ثلاثة أيام. عدد من صفحات الموارد البشرية السعودية واسعة الانتشار تذكر رقم الخمسة وحده وتترك القارئ يفترض أنّه يشمل كلّ قريب — فيدفع زيادة في حالة الأخ، والأسوأ أنّه يُرسّخ لدى الموارد البشرية قاعدة تنهار أول ما يراجعها أحد.

الخطوة 4: المادة 113 — ثلاث إجازات واقعة ونوافذها

// src/special-leave/engine.ts
const refuse = (article: string, why: string): Award => ({
  article, paidDays: 0, unpaidDays: 0, startsOn: null, endsOn: null,
  deductsFromAnnual: false, notes: [], refused: why,
});
 
const span = (start: string, days: number) => ({ start, end: addDays(start, days - 1) });
 
// --- marriage ---
case 'marriage': {
  const { start, end } = span(ev.requestedStart ?? ev.on, MARRIAGE_DAYS);
  return {
    article: '113', paidDays: MARRIAGE_DAYS, unpaidDays: 0,
    startsOn: start, endsOn: end, deductsFromAnnual: false,
    notes: ['Runs from the marriage date, not the date of the contract (aqd qiran).'],
  };
}
 
// --- newborn ---
case 'newborn': {
  const start = ev.requestedStart ?? ev.on;
  const elapsed = daysBetween(ev.on, start);
  if (elapsed < 0) return refuse('113', 'Leave cannot start before the birth.');
  if (elapsed >= NEWBORN_CLAIM_WINDOW_DAYS)
    return refuse('113', `Claim window of ${NEWBORN_CLAIM_WINDOW_DAYS} days from birth has expired.`);
  const { end } = span(start, NEWBORN_DAYS);
  return {
    article: '113', paidDays: NEWBORN_DAYS, unpaidDays: 0,
    startsOn: start, endsOn: end, deductsFromAnnual: false,
    notes: [`Started on day ${elapsed} of the 7-day window.`],
  };
}
 
// --- bereavement ---
case 'bereavement': {
  const days = BEREAVEMENT_DAYS[ev.relation];
  const { start, end } = span(ev.on, days);
  return {
    article: '113', paidDays: days, unpaidDays: 0,
    startsOn: start, endsOn: end, deductsFromAnnual: false,
    notes: [`Relation '${ev.relation}' carries ${days} days.`],
  };
}

في هذه الكتلة فخّان.

نافذة المولود تُغلَق. الأيام الثلاثة مرتبطة بالولادة ويجب أخذها خلال سبعة أيام منها. الأب الذي يطلبها بعد أسبوعين لم يعد لديه استحقاق يطالب به. والنظام الذي يمنحها رغم ذلك ليس كريمًا، بل يُنتج مسيّرًا لا يطابق النصّ. والحالة المعاكسة — نظام يُسقِط الطلب بصمت دون بيان السبب — هي الطريقة التي يضيع بها الاستحقاق أصلًا. لذلك تحمل refuse نصّ سبب بدل أن تُرجع صفر أيام فحسب.

إجازة الزواج تبدأ من الزواج لا من عقد القران. التفريق بين تاريخ العقد وتاريخ الزواج نقاش قائم في الممارسة السعودية، والأولى أن يسجّل المحرّك أيّ تاريخ استعمل بدل تركه ضمنيًا.

الخطوة 5: المادة 160 — العِدّة ليست 130 يومًا

هذه هي القاعدة التي تُثبّتها أغلب التطبيقات كرقم صلب، وتثبيتها خطأ.

تمنح المادة 160 العاملة المسلمة التي يتوفّى زوجها إجازة بأجر كامل مدّة لا تقلّ عن أربعة أشهر وعشرة أيام. والعاملة غير المسلمة في الحالة ذاتها تستحقّ خمسة عشر يومًا. وإن كانت حاملًا، فلها تمديد الإجازة دون أجر حتى تضع.

و«أربعة أشهر وعشرة أيام» حساب تقويمي، لا عدد ثابت، لأنّ الأشهر الميلادية ليست متساوية الطول.

case 'iddah': {
  if (emp.religion === 'non-muslim') {
    const { start, end } = span(ev.on, IDDAH_NON_MUSLIM_DAYS);
    return {
      article: '160', paidDays: IDDAH_NON_MUSLIM_DAYS, unpaidDays: 0,
      startsOn: start, endsOn: end, deductsFromAnnual: false,
      notes: ['Non-Muslim widow: 15 days at full pay.'],
    };
  }
 
  const end = addDays(addMonths(ev.on, IDDAH_MONTHS), IDDAH_EXTRA_DAYS);
  const paid = daysBetween(ev.on, end);
  const notes = [`Four calendar months plus ten days resolved to ${paid} days.`];
 
  let unpaid = 0;
  if (ev.pregnant && ev.deliveryOn && daysBetween(end, ev.deliveryOn) > 0) {
    unpaid = daysBetween(end, ev.deliveryOn);
    notes.push(`Pregnant: unpaid extension of ${unpaid} days to delivery.`);
  }
 
  return {
    article: '160', paidDays: paid, unpaidDays: unpaid,
    startsOn: ev.on, endsOn: unpaid ? ev.deliveryOn! : end,
    deductsFromAnnual: false, notes,
  };
}

شغّلها من تاريخين مختلفين في السنة نفسها فيُرجع المحرّك إجابتين مختلفتين:

iddah from 2026-02-01 = 130 days
iddah from 2026-10-01 = 133 days

الوفاة في فبراير تُعطي 130 يومًا. والوفاة نفسها في أكتوبر تُعطي 133 يومًا، لأنّ الأشهر الأربعة التي تمتدّ عليها أطول. وكلّ نظام يخزّن 130 كثابت يُنقص أرملة أكتوبر ثلاثة أيام بأجر كامل — بهدوء، وإلى الأبد، وفي حقّ الموظّفة الأقلّ احتمالًا لمراجعة مسيّرها بنفسها.

انتبه إلى صيغة الحدّ الأدنى. النصّ يقول «لا تقلّ عن» أربعة أشهر وعشرة أيام. فالقيمة المحسوبة حدّ أدنى لا سقف. وسياسة صاحب عمل تمنح أكثر منها نظامية، أمّا التي تمنح 130 يومًا ثابتة فلا.

الخطوة 6: المادة 114 — إجازة الحج يجب ألّا تدفع العيد مرّتين

إجازة الحج تحمل أكثر الشروط المسبقة بين إجازات النظام، وفخًّا حسابيًا واحدًا يكلّف مالًا حقيقيًا.

الاستحقاق من عشرة إلى خمسة عشر يومًا بأجر، بما فيها إجازة عيد الأضحى، مرّة واحدة طوال مدّة الخدمة، وبشرط ألّا يكون العامل قد أدّى الفريضة من قبل. ويشترط أن يكون قد أمضى لدى صاحب العمل سنتين متّصلتين على الأقلّ، ولصاحب العمل أن يحدّد عدد العمّال الذين يُمنحون هذه الإجازة سنويًا وفق مقتضيات العمل.

case 'hajj': {
  const tenureDays = daysBetween(emp.hiredOn, ev.on);
  if (tenureDays < HAJJ_MIN_TENURE_YEARS * 365)
    return refuse('114', 'Requires two continuous years with the same employer.');
  if (emp.performedHajjBefore)
    return refuse('114', 'Worker has already performed Hajj.');
  if (emp.hajjLeaveUsedInService)
    return refuse('114', 'Hajj leave is once for the whole service.');
 
  const granted = Math.min(
    HAJJ_MAX_DAYS,
    Math.max(HAJJ_MIN_DAYS, ev.requestedDays ?? HAJJ_MIN_DAYS),
  );
  const { start, end } = span(ev.on, granted);
 
  const eid = holidays
    .filter((h) => h.name === 'eid-al-adha')
    .reduce((n, h) => n + overlapDays(start, end, h.start, addDays(h.start, h.days - 1)), 0);
 
  const notes = [`Granted ${granted} days, of which ${eid} fall inside the Eid al-Adha holiday.`];
  if (eid) notes.push('Eid days are already paid as a public holiday and are not paid twice.');
 
  return {
    article: '114', paidDays: granted - eid, unpaidDays: 0,
    startsOn: start, endsOn: end, deductsFromAnnual: false, notes,
  };
}

عبارة «بما فيها إجازة عيد الأضحى» تعني أنّ أيام العيد تقع داخل المنحة. وهي مدفوعة أصلًا لكلّ موظّف بوصفها عطلة رسمية. فمنحة من خمسة عشر يومًا تتقاطع مع عيد من أربعة أيام تُنتج أحد عشر يومًا من الكلفة الإضافية، لا خمسة عشر. والأنظمة التي تعاملهما كبندين منفصلين تضخّم مخصّص الإجازات بما يصل إلى أربعة أيام عن كلّ حاجّ.

تواريخ العيد بيانات لا معادلة. عيد الأضحى يُحدَّد برؤية الهلال ويُعلَن كلّ سنة. لا تحسبه، بل احقنه:

export type Holiday = { name: string; start: string; days: number };
 
// Replace with the officially announced dates each year — these are astronomical
// estimates and the announced dates routinely differ by a day.
export const HOLIDAYS_2026: Holiday[] = [
  { name: 'eid-al-adha', start: '2026-05-27', days: 4 },
];

وهذا الدرس نفسه الذي تعلّمه محرّك فترة التجربة: أيّ تاريخ تُعلنه الدولة ولا تشتقّه ينتمي إلى جدول يحدّثه إنسان مرّة في السنة، مع وسم التقدير بوصفه تقديرًا. فمعادلة تصيب في أربع سنوات من خمس أسوأ من جدول، لأنّ أحدًا لا يراجع المعادلات.

الخطوة 7: المادة 115 — إجازة الامتحان التي تنقلب بلا أجر

المادة 115 مشروطة بشكل لا تشترطه بقيّة المواد. إن وافق صاحب العمل على انتساب العامل إلى مؤسسة تعليمية، استحقّ أجرًا كاملًا عن أيام الامتحان الفعلية لسنة غير معادة. أمّا السنة المعادة فالإجازة مستحقّة كذلك، لكن دون أجر.

case 'exam': {
  if (!emp.enrolmentApproved)
    return refuse('115', 'Employer never approved the enrolment, so no entitlement arises.');
 
  const { start, end } = span(ev.on, ev.examDays);
 
  return ev.repeatYear
    ? {
        article: '115', paidDays: 0, unpaidDays: ev.examDays,
        startsOn: start, endsOn: end, deductsFromAnnual: false,
        notes: ['Repeated year: leave is owed, but unpaid.'],
      }
    : {
        article: '115', paidDays: ev.examDays, unpaidDays: 0,
        startsOn: start, endsOn: end, deductsFromAnnual: false,
        notes: ['Non-repeated year: actual exam days at full pay.'],
      };
}

و«أيام الامتحان الفعلية» تُؤخذ حرفيًا. ليست طول فترة الامتحانات، بل الأيام التي يجلس فيها العامل للاختبار. فدورة امتحانات مدّتها ثلاثة أسابيع بأربع مواد هي أربعة أيام، لا واحد وعشرون.

الخطوة 8: التجميع وإظهار سبب الرفض

اربط الفروع في دالّة واحدة شاملة:

export function awardSpecialLeave(
  emp: Employment,
  ev: Event,
  holidays: Holiday[] = [],
): Award {
  switch (ev.kind) {
    // ... the six cases from Steps 4 to 7
  }
}

ولأنّ Event اتّحاد مميّز وكلّ فرع فيه يُرجع قيمة، سيُفشِل TypeScript البناء إذا أُضيف نوع إجازة سابع وتُرك دون معالجة. وهذه الخاصية هي المطلوبة: المُصرِّف لا المراجع البشري هو من يلتقط تغيّر النظام.

وحقل refused لا يقلّ أهمّية عن الأيام المدفوعة. شاشة موارد بشرية تعرض «0 يوم» لا تعلّم أحدًا شيئًا. أمّا التي تعرض «انتهت نافذة المطالبة البالغة 7 أيام من تاريخ الولادة» فتخبر المدير بما حدث، وتخبر الموظّف بالسبب، وتمنحك أثرًا تدقيقيًا حين تصل الدعوى.

اختبار التنفيذ

ثلاثة عشر اختبارًا تغطّي كلّ فرع وكلّ فخّ. هذه المجموعة تعمل بنجاح:

import assert from 'node:assert/strict';
import { awardSpecialLeave, addMonths, type Employment } from './engine.ts';
 
const emp: Employment = {
  hiredOn: '2022-01-10',
  religion: 'muslim',
  performedHajjBefore: false,
  hajjLeaveUsedInService: false,
  enrolmentApproved: true,
};
 
// Art. 113 — marriage never touches the annual balance
const marriage = awardSpecialLeave(emp, { kind: 'marriage', on: '2026-03-01' });
assert.equal(marriage.paidDays, 5);
assert.equal(marriage.endsOn, '2026-03-05');
assert.equal(marriage.deductsFromAnnual, false);
 
// Art. 113 — the newborn window closes on day 7
const late = awardSpecialLeave(emp, {
  kind: 'newborn', on: '2026-03-01', requestedStart: '2026-03-08',
});
assert.equal(late.paidDays, 0);
assert.match(late.refused!, /window/);
 
// Art. 113 — five days for an ascendant, three for a sibling
assert.equal(awardSpecialLeave(emp,
  { kind: 'bereavement', on: '2026-03-01', relation: 'ascendant' }).paidDays, 5);
assert.equal(awardSpecialLeave(emp,
  { kind: 'bereavement', on: '2026-03-01', relation: 'sibling' }).paidDays, 3);
 
// Art. 160 — the iddah period is calendar arithmetic, not a constant
const feb = awardSpecialLeave(emp, { kind: 'iddah', on: '2026-02-01' });
const oct = awardSpecialLeave(emp, { kind: 'iddah', on: '2026-10-01' });
assert.equal(feb.paidDays, 130);
assert.equal(oct.paidDays, 133);
 
// Art. 114 — fifteen granted days over a four-day Eid cost eleven
const hajj = awardSpecialLeave(emp,
  { kind: 'hajj', on: '2026-05-25', requestedDays: 15 },
  [{ name: 'eid-al-adha', start: '2026-05-27', days: 4 }]);
assert.equal(hajj.paidDays, 11);
 
// Art. 115 — a repeated year is owed, but unpaid
const repeat = awardSpecialLeave(emp,
  { kind: 'exam', on: '2026-06-01', examDays: 4, repeatYear: true });
assert.equal(repeat.paidDays, 0);
assert.equal(repeat.unpaidDays, 4);
 
// month-end clamp
assert.equal(addMonths('2026-01-31', 1), '2026-02-28');

شغّلها بـ node --experimental-strip-types test.ts. والتأكيدان الجديران بالبقاء إلى الأبد هما زوج العِدّة وتقاطع الحج مع العيد — فهما أول ما تكسره إعادة هيكلة حسنة النيّة.

حلّ المشكلات

مدّة العِدّة ترجع أقصر بيوم. بنيت تاريخ النهاية بالتوقيت المحلّي بدل UTC، فأرجع جهاز غربيّ عن الرياض الحدّ يومًا للوراء. كلّ تاريخ في هذا المحرّك يجب أن يمرّ عبر parseDate.

إجازة الحج تُرفض لموظّف طويل الخدمة. فحص المدّة يستعمل 2 * 365 يومًا ويتجاهل السنوات الكبيسة. وهذا تحفّظ مقصود: يرفض حالة حدّية بدل أن يمنح حالة تسقط في التدقيق. وإن كانت سياستك عدّ السنوات التقويمية، فاستبدل الفحص بـ daysBetween(emp.hiredOn, addMonths(ev.on, -24)) >= 0.

الإجازة الخاصة تظهر خصمًا من الرصيد السنوي. ثمّة جهة لاحقة تقرأ paidDays وتخصمها. حقل deductsFromAnnual موجود تحديدًا ليؤكّد عليه المستهلك — افحصه في خطوة ترحيل الرواتب، لا في المحرّك وحده.

نوع إجازة يُرجع undefined. أضفت متغيّرًا إلى Event ولم تضف له case. فعّل noImplicitReturns وسيلتقطه المصرّف قبل أن تلتقطه دورة الرواتب.

الخطوات التالية

الخلاصة

الإجازات الخاصة جزء صغير من نظام الرواتب يولّد نصيبًا غير متناسب من الدعاوى العمالية، لأنّ كلّ قاعدة فيه قاعدة تواريخ، وكلّ خلل فيه غير مرئي على المسيّر. المحرّك أعلاه نحو مئتي سطر، يُرمِّز ستّ موادّ، وأربعة شروط مسبقة، ونافذتَي مطالبة تسقطان بمضيّهما، وقطعة حساب تقويمي تستبدلها أغلب التطبيقات بثابت فتُخطئ ثلاثة أيام في كلّ مرّة.

والنمط الذي يجعله قابلًا للصيانة ليس ذكيًا: اتّحاد مميّز يفحصه المصرّف، وثوابت موسومة بالمادة التي جاءت منها، وسبب رفض مع كلّ رفض، وجدول للتواريخ التي تُعلنها الدولة ولا تشتقّها.

إن كان نظام مواردك البشرية يحسب الإجازة السنوية بصورة صحيحة، ويظلّ يعالج الزواج والوفاة والحج موافقاتٍ يدوية في جدول إكسل، فتلك الفجوة هي مصدر الدعاوى. نحن نربط أنظمة الموارد البشرية بالرواتب حتى تُحتسب الاستحقاقات من النصّ النظامي بدل أن يتذكّرها مدير — أخبرنا بما لديك ونخبرك بما ينقصه.