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

محرّك حساب المقابل المالي لرخص العمل بـ TypeScript

ابنِ وحدة حساب المقابل المالي لرخص العمل في نظام موارد بشرية سعودي بلغة TypeScript: الشريحتان ومتوسط الـ26 أسبوعاً الذي يقرّر بينهما، والشهر المحسوب بثلاثين يوماً، والقيمة اليومية المنشورة التي لا تتطابق مع القيمة السنوية في الجدول نفسه، وحاجز 21 يناير 2027 الذي يجعل تجديداً واحداً يحمل سعرين.

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

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

يبني هذا الدرس المحرّك الذي يصيب الثلاثة، بلغة TypeScript، مع الاختبارات التي تثبت ذلك. والقواعد مأخوذة من وثيقة قواعد العمل الصادرة عن وزارة الموارد البشرية والتنمية الاجتماعية تنفيذاً للقرار الوزاري رقم 197 وتاريخ 1438-03-23هـ، ومن قرار مجلس الوزراء رقم 325 وتاريخ 1442-06-13هـ في التجزئة، ومن إعلان منصة قوى عن انتهاء إعفاء المنشآت متناهية الصغر.

المتطلبات المسبقة

  • Node.js 22 فأحدث (الأمثلة تستخدم مُشغّل الاختبارات المدمج وتجريد الأنواع الأصلي)
  • TypeScript 5.6 فأحدث
  • إلمام بحساب التواريخ والتعامل مع المبالغ كأعداد صحيحة
  • لا حاجة إلى بيانات دخول واجهة قوى — كل ما هنا هو القاعدة المنشورة تُحسب محلياً

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

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

وفي أثناء ذلك سترفض الوحدة الإجابة عن سؤالين، عن قصد، والرفض فيهما لا يقلّ أهمية عن الحساب.

الخطوة 1: شريحتان، والرقم الذي يقرّر بينهما عمره ستة أشهر

للمقابل المالي عمودان. عن كل عامل وافد، شهرياً، منذ 1 يناير 2020:

الحالةشهرياًسنوياً
العمالة الوافدة لا تزيد عن عدد العمالة السعودية700 ريال8,400 ريال
العمالة الوافدة تزيد عن عدد العمالة السعودية800 ريال9,600 ريال

وبمعزل عن الشريحة، تكلّف رخصة العمل نفسها 100 ريال عن كل عامل وافد سنوياً.

وهنا الجزء الذي يخطئ فيه أغلب التطبيقات. «عدد العمالة السعودية» ليس عدد السعوديين على كشف رواتبك اليوم. تعرّفه قواعد العمل هكذا:

«يتم احتساب عدد الوحدات المستحقة بناء على بيانات عدد العمالة الوافدة ومتوسط عدد السعوديين المدفوع عنهم التأمينات خلال 26 أسبوع على مستوى الرقم الموحد»

فالمقارنة تجري مع متوسط عدد السعوديين المدفوع عنهم التأمينات خلال 26 أسبوعاً، على مستوى الرقم الموحد للمنشأة — لا على مستوى الفرع ولا السجل التجاري — بتغذية أسبوعية من التأمينات.

ويترتّب على ذلك أمران مباشرةً، وكلاهما تجاريّ لا تقنيّ:

  1. توظيف سعودي اليوم لا يخفض سعرك اليوم. فهو يدخل في متوسط ستة وعشرين أسبوعاً، أي يحرّك الأساس بنحو جزء من ستة وعشرين في الأسبوع. وأثر التوظيف على الشريحة يظهر بعد نحو ستة أشهر. ومن يبيعك تخفيضاً فورياً يبيعك شيئاً آخر.
  2. والخروج بطيء بالقدر نفسه، وهذه الحسنة الوحيدة هنا. فاستقالةٌ واحدة لا ترفع سعرك في الأسبوع التالي أيضاً.

وإن بدت نافذة المتوسط هذه مألوفة، فهي النافذة نفسها التي يعمل عليها نطاق التوطين — وقد عالجنا آثارها في لماذا صار نطاقك أحمر.

وتُحتسب فئات محدودة بسعوديٍّ واحد لكلٍّ منها في هذه المقارنة، والقائمة قصيرة ومحدّدة:

export type SpecialCategory =
  | 'disabledFullTime'      // عامل معاق بدوام كامل
  | 'prisonerFullTime'      // مسجون بدوام كامل
  | 'remoteFullTime'        // عامل عن بعد بدوام كامل
  | 'student'               // الطالب
  | 'saudiOwnerFullTime';   // المالك السعودي المتفرغ
 
export const AVERAGE_WEEKS = 26;
 
export function saudiEquivalents(
  weeklyGosiSaudis: number[],
  special: SpecialCategory[] = [],
) {
  const window = weeklyGosiSaudis.slice(-AVERAGE_WEEKS);
  if (window.length === 0) throw new Error('no GOSI weeks supplied');
 
  const average = window.reduce((a, b) => a + b, 0) / window.length;
  return {
    weeksUsed: window.length,
    // أظهر النافذة الناقصة بدل حساب متوسط صامت على تسعة أسابيع.
    complete: window.length === AVERAGE_WEEKS,
    average,
    basis: average + special.length,
  };
}
 
export type Tier = 'notExceeding' | 'exceeding';
 
export function tierFor(expats: number, saudiBasis: number): Tier {
  return expats > saudiBasis ? 'exceeding' : 'notExceeding';
}

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

ولاحظ كذلك أن complete تُعاد ولا يُرمى بها استثناء. فالمنشأة الحديثة لديها فعلاً أقل من ستة وعشرين أسبوعاً من التاريخ؛ وعلى المحرّك أن يقول ذلك ويترك القرار للمستدعي، لا أن يخترع أساساً.

الخطوة 2: الشهر ثلاثون يوماً، والقيمة اليومية المنشورة لا تستقيم

قواعد العمل صريحة في أساس الاحتساب:

«سيتم حساب المقابل المالي على أساس يومي على أن يكون عدد أيام الشهر 30 يوم»

فالمقابل المالي يُحسب يومياً على شهر من ثلاثين يوماً. أي إن السنة المحاسَب عليها ثلاثمائة وستون يوماً. وفبراير ليس قصيراً. واليوم الحادي والثلاثون لا وجود له. وإن استعملت differenceInDays من date-fns هنا، فكل فاتورة تُصدرها ستخطئ بأيام في السنة، في اتجاه يتراكم.

هذا هو الجزء المعروف. أما غير المعروف فهو أن جدول القرار ينشر ثلاث قيم لكل شريحة — سنوية وشهرية ويومية — وهي لا تتفق فيما بينها. القيمة اليومية المنشورة للشريحة الدنيا 23.3. اضربها في سنة الوزارة نفسها ذات الثلاثمائة والستين يوماً:

23.3 × 360 = 8,388     بينما العمود السنوي يقول 8,400
26.6 × 360 = 9,576     بينما العمود السنوي يقول 9,600

فالقيم اليومية المنشورة مبتورة عند منزلة عشرية واحدة لا مقرَّبة — إذ 800 ÷ 30 تساوي 26.67 والجدول يطبع 26.6. وهي قيم عرضٍ لا غير. والسعر الذي يستقيم فعلاً هو monthly / 30، وعلى المحرّك أن يصرّح بأيّهما يستعمل:

export const DAYS_PER_MONTH = 30;
 
/**
 * أساس 30/360. السنة ثلاثمائة وستون يوماً وفبراير ليس قصيراً — القرار يثبّت
 * الشهر عند ثلاثين يوماً، فلا يُستشار التقويم أصلاً.
 */
export function days360(from: string, to: string): number {
  const a = parse(from);
  const b = parse(to);
  const d1 = Math.min(a.d, 30);
  const d2 = a.d >= 30 && b.d === 31 ? 30 : Math.min(b.d, 31);
  return Math.max(0, (b.y - a.y) * 360 + (b.m - a.m) * 30 + (Math.min(d2, 30) - d1));
}
 
/**
 * السعر الذي تستقيم عليه الفاتورة. القيمة اليومية المنشورة قيمة عرض مبتورة:
 * 23.3 × 360 تساوي 8,388 لا 8,400 المطبوعة في العمود المجاور. احتسب على
 * الشهري ÷ 30.
 */
export function dailyRateHalalas(rates: Rates): number {
  return (rates.monthly * 100) / DAYS_PER_MONTH;
}

واعمل بـالهللات — أجزاء المئة الصحيحة — وقرِّب مرةً واحدة في النهاية. فـ700 / 30 غير قابلة للتمثيل في الفاصلة العائمة الثنائية؛ وتراكمها على ثلاثمائة وستين يوماً يجعل الانحراف لكل عامل ظاهراً في منشأةٍ لديها مئتا رخصة.

ويستحق الجدول الكامل أن يُرمَّز بدل تثبيت سعر اليوم، لأن المتأخرات واقعة ولأن مدة الرخصة قد تعبر درجةً من درجات الجدول:

export type Rates = { annual: number; monthly: number; publishedDaily: number };
export type ScheduleStep = { from: string; notExceeding: Rates; exceeding: Rates };
 
export const LEVY_SCHEDULE: ScheduleStep[] = [
  { from: '2018-01-01',
    notExceeding: { annual: 3600, monthly: 300, publishedDaily: 10 },
    exceeding:    { annual: 4800, monthly: 400, publishedDaily: 13.3 } },
  { from: '2019-01-01',
    notExceeding: { annual: 6000, monthly: 500, publishedDaily: 16.6 },
    exceeding:    { annual: 7200, monthly: 600, publishedDaily: 20 } },
  { from: '2020-01-01',
    notExceeding: { annual: 8400, monthly: 700, publishedDaily: 23.3 },
    exceeding:    { annual: 9600, monthly: 800, publishedDaily: 26.6 } },
];
 
export function ratesOn(date: string, tier: Tier): Rates {
  const step = [...LEVY_SCHEDULE].reverse().find((s) => s.from <= date);
  if (!step) throw new Error(`no levy step in force on ${date}`);
  return step[tier];
}

وإبقاء publishedDaily في النوع، دون استعمالها في الاحتساب، مقصود. فهي ما سيجده القارئ في ملف الوزارة، واختبارٌ يؤكّد أنها لا تستقيم هو أرخص وسيلة تمنع صيانةً لاحقة من «إصلاح» المحرّك ليستعملها.

الخطوة 3: الإعفاء تاريخ لا صفة

ظلّت المنشآت متناهية الصغر تدفع 100 ريال عن الرخصة ولا تدفع مقابلاً مالياً سنوات. وهذا الترتيب، الذي مُدِّد مراراً منذ أبريل 2020، له نهاية: 21 يناير 2027، الموافق 14 شعبان 1448هـ. وقد نشرت قوى التنبيه بذلك. ولا تمديد رابع معلَن حتى كتابة هذا الدرس.

وشروط الدخول فيه ضيّقة، وكلٌّ منها قيمة منطقية لا يخزّنها نظام الموارد البشرية عندك على الأرجح:

  • تسعة عاملين أو أقل إجمالاً تحت الرقم الموحد — سعوديين وغير سعوديين، والمالك منهم
  • مالك واحد — شخص طبيعي واحد
  • أن يكون المالك متفرغاً للعمل في المنشأة ومسجّلاً في التأمينات بصفته المالك

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

export type ExemptionInput = {
  totalWorkers: number;          // الجميع تحت الرقم الموحد، والمالك منهم
  singleOwner: boolean;
  ownerFullTimeInsured: boolean; // متفرغ ومسجّل في التأمينات بصفته المالك
  saudiFullTimeEmployees: number;
};
 
export function exemptSlots(input: ExemptionInput): number {
  if (input.totalWorkers > 9) return 0;
  if (!input.singleOwner || !input.ownerFullTimeInsured) return 0;
  return input.saudiFullTimeEmployees >= 1 ? 4 : 2;
}

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

الخطوة 4: تسعير رخصة تعبر الحاجز

هنا يظهر أثر التاريخ. فرخصةٌ جُدِّدت في نوفمبر 2026 لسنة لا تقع كاملةً داخل الإعفاء ولا كاملةً خارجه. وتُطبَّق الرسوم على الأيام الواقعة بعد انتهاء الإعفاء فقط — فتُسعَّر الرخصة في مقطعين.

لذلك لا يمكن لدالة التسعير أن تأخذ سعراً. عليها أن تأخذ مدةً، وتقطعها عند كل حدٍّ يغيّر السعر، وتسعّر كل قطعة:

export const EXEMPTION_LAST_DAY = '2027-01-21';
export const WORK_PERMIT_FEE = 100;
 
export function priceWorkPermit(input: PriceInput) {
  const cliff = input.exemptionLastDay ?? EXEMPTION_LAST_DAY;
  const firstChargeable = dayAfter(cliff);
 
  // كل تاريخ يمكن أن يتغير عنده السعر، ومنه طرفا المدة نفسها.
  const boundaries = new Set<string>([input.start, input.end]);
  for (const step of LEVY_SCHEDULE) {
    if (step.from > input.start && step.from < input.end) boundaries.add(step.from);
  }
  if (input.exempt && firstChargeable > input.start && firstChargeable < input.end) {
    boundaries.add(firstChargeable);
  }
  const cuts = [...boundaries].sort();
 
  const segments: PricedSegment[] = [];
  for (let i = 0; i < cuts.length - 1; i++) {
    const from = cuts[i];
    const to = cuts[i + 1];
    const days = days360(from, to);
    if (days === 0) continue;
 
    const exemptHere = input.exempt && to <= firstChargeable;
    const rates = ratesOn(from, input.tier);
    segments.push({
      from, to, days, tier: input.tier, monthly: rates.monthly, exempt: exemptHere,
      levyHalalas: exemptHere ? 0 : Math.round(dailyRateHalalas(rates) * days),
    });
  }
 
  const levyHalalas = segments.reduce((a, s) => a + s.levyHalalas, 0);
  return {
    segments,
    chargeableDays: segments.filter((s) => !s.exempt).reduce((a, s) => a + s.days, 0),
    levy: levyHalalas / 100,
    permitFee: WORK_PERMIT_FEE,
    total: (levyHalalas + WORK_PERMIT_FEE * 100) / 100,
  };
}

وملحوظتان في التصميم تستحقّان الدفاع عنهما في المراجعة.

exemptionLastDay مُدخَل له قيمة افتراضية. فهذا التاريخ تحرّك مرات منذ 2020. وإن تحرّك مجدداً فذلك تغييرُ إعداد لا نشرُ إصدار. ودفن تاريخٍ سياسيّ في ثابتٍ هو الطريق الذي يتعفّن به كود الامتثال.

والحاجز هو آخر يومٍ مغطّى، فأول يوم محاسَب هو الذي يليه. فالمصادر تقول إن الرسوم تُطبَّق على المدة الواقعة بعد 21 يناير 2027. وجعل firstChargeable قيمةً مسمّاة صريحة بدل مقارنةٍ مضمّنة يجعل الافتراض ظاهراً وقابلاً للاختبار وسهل القلب إن قالت فاتورة قوى غير ذلك يوم صدورها.

شغّلها على رخصة جُدِّدت في 1 نوفمبر 2026 لسنة، على الشريحة العليا:

المقطع 1  2026-11-01 → 2027-01-22   81 يوماً   معفى         0 ريال
المقطع 2  2027-01-22 → 2027-11-01  279 يوماً   800/شهر      7,440 ريال
                                               رسم الرخصة     100 ريال
                                               المجموع      7,540 ريال

فالمنشأة التي رصدت 100 ريال لذلك التجديد ينقصها 7,440 ريالاً، عن عاملٍ واحد.

الخطوة 5: التجزئة والمتأخرات والسؤالان اللذان على المحرّك أن يرفضهما

التجزئة. أجاز قرار مجلس الوزراء رقم 325 وتاريخ 1442-06-13هـ تجزئة المقابل المالي إلى دفعات لا تقلّ عن ثلاثة أشهر. وتُستثنى العمالة المنزلية ومن في حكمها فتدفع كاملاً:

export function fractionQuarterly(totalSar: number, months: number, domesticLabour = false) {
  if (domesticLabour || months < 3) return [{ months, amount: totalSar }];
 
  const tranches = Math.floor(months / 3);
  const perHalalas = Math.floor((totalSar * 100) / tranches);
  const out = Array.from({ length: tranches }, () => perHalalas);
  // الباقي يقع على الدفعة الأخيرة؛ فلا يضيع شيء في التقريب.
  out[out.length - 1] += totalSar * 100 - perHalalas * tranches;
  return out.map((h) => ({ months: months / tranches, amount: h / 100 }));
}

والمتأخرات لا تُسعَّر تاريخياً. تنصّ قواعد العمل على أن رسوم الأعوام السابقة المتأخرة تُحسب على سعر المقابل المالي المستحق حالياً، لا على السعر الذي كان سارياً في العام الذي نشأ فيه الالتزام. والمحرّك الذي يعيد بناء سعر 2019 لمتأخّرٍ من 2019 سيُنقص الفاتورة، وسيظهر النقص في أسوأ وقت. سعِّر المتأخرات بسعر اليوم؛ ومصفوفة الجدول للمُدد التي تعبر درجةً بحق، لا للتأريخ الرجعي.

أما الرفضان:

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

وعلى المحرّك ألّا يوصي بتوظيف سعودي لتخفيض المقابل المالي. أجرِ الحساب بأمانة: عشرون وافداً مقابل متوسط سعودي على ستة وعشرين أسبوعاً قدره اثنا عشر يقعون في الشريحة العليا، أي 20 × 9,600 = 192,000 ريال سنوياً. ورفع المتوسط إلى عشرين ينزل بها إلى 20 × 8,400 = 168,000 ريال. فالوفر 24,000 ريال — أي 1,200 ريال عن كل وافد سنوياً — ويتطلّب ثمانية توظيفات سعودية مستدامة لن تؤثّر في المتوسط قبل نحو ستة أشهر. ورواتب ثمانية موظفين تكلّف أضعاف الوفر.

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

الخطوة 6: اختبار التنفيذ

الاختبارات هنا هي المُنتَج أكثر من الكود. وهذه كلها تمرّ:

import test from 'node:test';
import assert from 'node:assert/strict';
 
test('a 30/360 year is 360 days', () => {
  assert.equal(days360('2026-01-01', '2027-01-01'), 360);
  assert.equal(days360('2026-02-01', '2026-03-01'), 30);
});
 
test('the published daily rate does not reconcile to the published annual', () => {
  const r = ratesOn('2026-06-01', 'notExceeding');
  assert.equal(r.annual, 8400);
  assert.equal(r.publishedDaily * 360, 8388);          // قيمة العرض
  assert.equal((dailyRateHalalas(r) * 360) / 100, 8400); // قيمة الاحتساب
});
 
test('both tiers close on monthly / 30, in every step', () => {
  for (const step of LEVY_SCHEDULE) {
    for (const tier of ['notExceeding', 'exceeding'] as const) {
      const r = step[tier];
      assert.equal((dailyRateHalalas(r) * 360) / 100, r.annual);
    }
  }
});
 
test('the tier is decided by a 26-week average, not by today', () => {
  const weeks = [...Array(25).fill(4), 12];  // وظّف ثمانية هذا الأسبوع
  const basis = saudiEquivalents(weeks);
  assert.equal(Number(basis.average.toFixed(4)), 4.3077);
  assert.equal(tierFor(6, basis.basis), 'exceeding');   // ما زال في الشريحة العليا
  assert.equal(tierFor(6, 12), 'notExceeding');         // ما كان عدد اليوم سيقوله
});
 
test('exemption slots follow the owner and the Saudi employee', () => {
  const base = { totalWorkers: 6, singleOwner: true,
                 ownerFullTimeInsured: true, saudiFullTimeEmployees: 0 };
  assert.equal(exemptSlots(base), 2);
  assert.equal(exemptSlots({ ...base, saudiFullTimeEmployees: 1 }), 4);
  assert.equal(exemptSlots({ ...base, totalWorkers: 10 }), 0);
  assert.equal(exemptSlots({ ...base, singleOwner: false }), 0);
});
 
test('a non-exempt full year is the published annual plus the permit fee', () => {
  const r = priceWorkPermit({ start: '2026-03-01', end: '2027-03-01',
                              tier: 'exceeding', exempt: false });
  assert.equal(r.chargeableDays, 360);
  assert.equal(r.levy, 9600);
  assert.equal(r.total, 9700);
});
 
test('an exempt permit renewed across the cliff pays only the days after it', () => {
  const r = priceWorkPermit({ start: '2026-11-01', end: '2027-11-01',
                              tier: 'exceeding', exempt: true });
  assert.equal(r.segments.length, 2);
  assert.equal(r.segments.filter((s) => !s.exempt)[0].from, '2027-01-22');
  assert.equal(r.chargeableDays, 279);
  assert.equal(r.total, 7540);
});
 
test('a period spanning a schedule step is priced on both rates', () => {
  const r = priceWorkPermit({ start: '2019-07-01', end: '2020-07-01',
                              tier: 'notExceeding', exempt: false });
  assert.deepEqual(r.segments.map((s) => s.monthly), [500, 700]);
  assert.equal(r.levy, 3000 + 4200);
});

شغّلها بـ node --experimental-strip-types --test levy.test.ts.

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

حلّ المشكلات

المجموع يختلف عن فاتورة قوى بريالات قليلة. افحص عدّ الأيام أولاً. فإن استعملت تقويماً حقيقياً في أي موضع، فسنةٌ من 365 يوماً على سعرٍ مبنيّ على 360 تعطي زيادةً متوقّعة. وإن كان الفرق 12 أو 24 ريالاً بالضبط لكل عامل سنوياً، فأنت تحتسب على القيمة اليومية المنشورة بدل monthly / 30.

الشريحة تنقلب بعد أسبوع من التوظيف. أنت تقارن بعدد لحظيّ لا بمتوسط التأمينات على ستة وعشرين أسبوعاً. المتوسط هو الحكم؛ والعدد اللحظي معاينة في أحسن الأحوال.

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

الفروع تخالف المركز الرئيسي. المقارنة تجري على مستوى الرقم الموحد لا لكل سجل تجاري. وتجميع الأعداد لكل فرع ينتج شريحةً غير التي ستطبّقها الوزارة. ومن أين تأتي أعداد القوى العاملة سؤالُ تكاملٍ قبل أن يكون سؤال حساب — انظر تكامل قوى مع أنظمة الموارد البشرية.

هللات عائمة في الفاتورة. راكِم الهللات أعداداً صحيحة، وقرِّب مرة واحدة في النهاية.

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

  • تحقّق من موقع منشأتك فعلاً بـحاسبة نطاقات — فنسبة السعوديين إلى الوافدين هي التي تضعك على شريحة 700 ريال أو شريحة 800، وهي المتوسط نفسه على ستة وعشرين أسبوعاً الذي يقرأه هذا المحرّك.
  • ابنِ النطاق نفسه بـمحرّك نطاقات بـ TypeScript.
  • افهم آثار نافذة المتوسط في لماذا صار نطاقك أحمر.
  • وسّع المحرّك بتوقّع: بالنظر إلى الرخص الحالية وتواريخ انتهائها، كيف يبدو بند ميزانية 2027 بعد سقوط الإعفاء؟ هذا هو التقرير الذي تطلبه الإدارة المالية فعلاً.

الخلاصة

يبدو المقابل المالي ثابتاً ويسلك سلوك سلسلة زمنية. فالسعر يعتمد على متوسط لا يمكنك تغييره بسرعة، والتقويم المحاسَب عليه ثلاثمائة وستون يوماً، وجدول الوزارة نفسه يخالف نفسه باثني عشر ريالاً في السنة، وفي 21 يناير 2027 يبدأ تجديدٌ واحد بحمل سعرين.

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

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