writing/tutorial/2026/08
● TutorialAug 23, 2026·27 min read

Saudi Overtime Pay Engine in TypeScript (Art. 107)

Build an overtime pay engine for Saudi payroll in TypeScript, implementing Articles 98, 106 and 107 of the Labour Law — the actual-versus-basic wage split, the 240/180-hour divisors, rest-day and holiday hours, the Ramadan schedule, compensatory leave under Implementing Regulation Article 22 bis, and the 720-hour annual cap.

Overtime is the payroll line Saudi labour courts see most often, and the reason is always the same: the formula in Article 107 of the Labour Law is short, but almost every system implements a different one. The statute says an overtime hour is worth the hourly wage plus 50% of the basic wage — two different wage bases in a single sentence. Systems that apply the 50% premium to the full salary overpay every month; systems that compute the whole hour from the basic wage underpay every month, and the difference surfaces years later as a labour claim with interest in the form of bad faith.

This tutorial builds the engine properly: integer money, the actual-versus-basic wage split the statute actually requires, hour classification that knows what a rest day and a Ramadan schedule are, compensatory leave with the four limits the Implementing Regulation puts on it, and the 720-hour annual cap that most timesheet systems never check. It is the third engine in our Saudi payroll series, alongside the annual leave accrual engine and the end-of-service gratuity engine, and everything we build here is the logic behind our free overtime calculator, which you can use to cross-check your implementation at any point.

Correction, 28 September 2026. An earlier version of this tutorial treated compensatory leave as a consent flag and nothing more, and suggested routing it into the annual leave balance. Article 22 bis of the Implementing Regulation sets four terms that change the numbers: at least one and a half hours of leave for every overtime hour, leave scheduled within 60 days, no more than 30 days of it a year, and payment for any of it still owed when the employee leaves. Day-for-day compensatory leave, which many Arabic guides describe, is the civil-service rule; in the private sector it settles a third of the debt too little. Step 7 now implements all four terms, Step 8 adds the Article 106 ceiling of 10 actual hours a day and 60 a week, and the 720-hour cap is now attributed to Article 22 of the Implementing Regulation.

Prerequisites

Before starting, ensure you have:

  • Node.js 20+ installed
  • TypeScript 5+ (npm install -D typescript vitest)
  • Basic familiarity with TypeScript modules and unit testing
  • The Saudi Labour Law open in a tab, Articles 98 to 108, and the Implementing Regulation, Articles 22 and 22 bis — we will follow the text, not folklore

No framework is required. The engine is a plain TypeScript library you can drop into a Next.js API route, a payroll batch job, or a timesheet service.

What You'll Build

A small library, saudi-overtime-engine, exposing these functions:

  • hourlyRates(wage, ramadan, policy) — the actual and basic hourly rates, with the 240-hour and 180-hour divisors handled explicitly
  • overtimeHours(day, schedule) — how many of a day's hours are overtime, including rest days and official holidays where all hours are overtime
  • overtimePay(days, wage, schedule) — the Article 107 amount for a period, in halalas
  • settle(...) — how much of an overtime assignment compensatory leave may lawfully cover, how much still goes to payroll, and the date the leave must be taken by
  • unusedCompLeavePayout(...) — what leave still owed is worth when the employee leaves
  • capStatus(hoursThisYear) and the Article 106 ceiling checks — where the employee stands against the 720-hour annual cap and the 10-hour daily limit

Plus a test suite that pins every rule to a scenario you can show an auditor.

Step 1: Read the statute before writing the formula

Three articles carry everything, and each one contributes a rule your engine must encode:

Article 98 sets the standard: no more than 8 working hours a day or 48 a week. During Ramadan, for Muslim employees, the standard drops to 6 hours a day and 36 a week. Everything beyond the applicable standard is overtime.

Article 106 lists the situations in which an employer may exceed those limits — annual inventory, seasonal peaks, preventing an accident — and sets a ceiling that holds even then: no more than 10 actual working hours a day or 60 a week. Article 22 of the Implementing Regulation caps overtime at 720 hours per employee per year, and more is allowed only with the employee's consent. Most engines never track either number; yours will.

Article 107 prices it. An overtime hour is paid at the hourly wage plus 50% of the basic wage. Under the weekly standard, hours beyond the weekly total are overtime (107(2)); hours worked on holidays and feast days count as overtime in their entirety (107(3)). And the 2025 amendments made explicit what many contracts already did: with the employee's agreement, overtime can be settled as paid compensatory leave instead of pay, on terms that Article 22 bis of the Implementing Regulation spells out.

The trap sits in the two wage bases. The Saudi Labour Law distinguishes the basic wage (الأجر الأساسي) from the actual wage (الأجر الفعلي), which adds the fixed allowances — housing, transport, and anything else paid regularly. The first half of the overtime hour is priced from the actual wage; the 50% premium is priced from the basic wage alone. Any engine that carries a single "salary" field cannot implement Article 107 correctly.

Step 2: Hold money in halalas, never in floats

The same rule as every engine in this series: money is an integer number of halalas, fractions survive only inside a calculation, and rounding happens exactly once, at the end.

// money.ts
export type Halalas = number; // always an integer
 
export const fromSAR = (sar: number): Halalas => Math.round(sar * 100);
export const toSAR = (halalas: Halalas): number => halalas / 100;

Step 3: Model the wage the way the statute splits it

Two fields, not one. If your HR master data has a single gross figure, fixing that is a data task that comes before this engine, not after it.

// wage.ts
import type { Halalas } from './money';
 
export interface MonthlyWage {
  /** Basic wage — the contractual base, before any allowance. */
  basic: Halalas;
  /** Fixed, regularly paid allowances: housing, transport, and similar. */
  fixedAllowances: Halalas;
}
 
/** Actual wage: the base plus every fixed allowance (Labour Law, Art. 2). */
export const actualWage = (w: MonthlyWage): Halalas => w.basic + w.fixedAllowances;

One decision to record in writing: which allowances are "fixed". A transport allowance paid every month belongs in fixedAllowances; a one-off bonus does not. Auditors ask for this list — keep it in your policy document, not in someone's memory.

Step 4: The hourly rate — and the divisor question

The statute prices overtime per hour but states wages per month, so every implementation needs a divisor. The dominant convention — and the one our overtime calculator applies — divides the monthly wage by 240 (8 hours × 30 days). During Ramadan the working month shrinks to 6 hours a day, so the divisor becomes 180, which makes each Ramadan hour, and therefore each Ramadan overtime hour, worth more. Some payrolls instead derive the rate from the weekly standard (48 × 52 / 12 = 208 hours a month). Both produce defensible numbers; what is not defensible is mixing them. Make the divisor a policy value, set it once, and let the tests pin it.

// rates.ts
import type { MonthlyWage } from './wage';
import { actualWage } from './wage';
 
export interface RatePolicy {
  /** Hours dividing the monthly wage in a normal month. 240 = 8h x 30d. */
  monthlyDivisorHours: number;
  /** Hours dividing the monthly wage in Ramadan. 180 = 6h x 30d. */
  ramadanDivisorHours: number;
}
 
export const defaultRatePolicy: RatePolicy = {
  monthlyDivisorHours: 240,
  ramadanDivisorHours: 180,
};
 
export interface HourlyRates {
  /** Hourly rate from the actual wage, in halalas (may carry fractions). */
  actualHourly: number;
  /** Hourly rate from the basic wage, in halalas (may carry fractions). */
  basicHourly: number;
}
 
export function hourlyRates(
  wage: MonthlyWage,
  ramadan: boolean,
  policy: RatePolicy = defaultRatePolicy,
): HourlyRates {
  const divisor = ramadan ? policy.ramadanDivisorHours : policy.monthlyDivisorHours;
  return {
    actualHourly: actualWage(wage) / divisor,
    basicHourly: wage.basic / divisor,
  };
}

Note that the two rates stay as floating-point halalas here. That is deliberate: they are intermediate values. The single Math.round waits until Step 6.

Step 5: Classify the hours — the part timesheets get wrong

Article 107 does not only price hours beyond the daily standard. It says hours worked on the weekly rest day and on official holidays are overtime from the first minute. A timesheet that only measures "hours above 8" silently drops both cases.

// classify.ts
export type DayKind = 'workday' | 'rest-day' | 'official-holiday';
 
export interface DayRecord {
  /** ISO date, e.g. "2026-08-21". */
  date: string;
  kind: DayKind;
  hoursWorked: number;
  /** True when the employee is Muslim and the date falls in Ramadan. */
  ramadan: boolean;
}
 
export interface SchedulePolicy {
  /** Daily standard outside Ramadan (Art. 98): 8. */
  dailyStandardHours: number;
  /** Daily standard during Ramadan for Muslim employees (Art. 98): 6. */
  ramadanDailyStandardHours: number;
}
 
export const defaultSchedule: SchedulePolicy = {
  dailyStandardHours: 8,
  ramadanDailyStandardHours: 6,
};
 
/** Overtime hours in one day, per Art. 98 and Art. 107(3). */
export function overtimeHours(
  day: DayRecord,
  schedule: SchedulePolicy = defaultSchedule,
): number {
  if (day.kind !== 'workday') {
    // Rest day or official holiday: every hour is overtime.
    return day.hoursWorked;
  }
  const standard = day.ramadan
    ? schedule.ramadanDailyStandardHours
    : schedule.dailyStandardHours;
  return Math.max(0, day.hoursWorked - standard);
}

Two things worth stating in your policy document. First, whether your establishment applies the daily standard or the weekly one — Article 98 allows either, and the choice changes which hours are overtime for irregular schedules. This engine applies the daily standard, which is the common choice and the stricter one for the employer. Second, which days are official holidays — Eid al-Fitr, Eid al-Adha, National Day, Founding Day — because someone has to feed official-holiday into the day records, and "the timesheet didn't know it was Eid" is not a defence a labour court accepts.

Step 6: The Article 107 formula in one function

With rates and classification in place, the pricing function is small enough to read against the statute line by line.

// pay.ts
import type { Halalas } from './money';
import type { MonthlyWage } from './wage';
import { hourlyRates, type RatePolicy } from './rates';
import { overtimeHours, type DayRecord, type SchedulePolicy } from './classify';
 
/** Price of one overtime hour: hourly wage + 50% of basic hourly (Art. 107(1)). */
export function overtimeHourRate(actualHourly: number, basicHourly: number): number {
  return actualHourly + 0.5 * basicHourly;
}
 
/** Article 107 overtime pay for a period, in halalas. One rounding, at the end. */
export function overtimePay(
  days: DayRecord[],
  wage: MonthlyWage,
  schedule?: SchedulePolicy,
  ratePolicy?: RatePolicy,
): Halalas {
  let total = 0;
  for (const day of days) {
    const hours = overtimeHours(day, schedule);
    if (hours === 0) continue;
    const rates = hourlyRates(wage, day.ramadan, ratePolicy);
    total += hours * overtimeHourRate(rates.actualHourly, rates.basicHourly);
  }
  return Math.round(total);
}

Walk through the worked example every Saudi HR forum eventually reaches. Basic wage 4,000 SAR, fixed allowances 800 SAR, so the actual wage is 4,800 SAR. Outside Ramadan the actual hourly rate is 4,800 / 240 = 20 SAR and the basic hourly rate is 4,000 / 240 = 16.67 SAR. One overtime hour is worth 20 + 8.33 = 28.33 SAR. Ten overtime hours pay 283.33 SAR — the engine returns 28,333 halalas. The wrong implementations produce 300 SAR (premium on the actual wage) or 250 SAR (whole hour from the basic wage). Fifty riyals a month, times a workforce, times years: that is the size of the liability this one function decides.

Run the same ten hours in Ramadan and the divisor does the work: 4,800 / 180 = 26.67 SAR actual hourly, 4,000 / 180 = 22.22 SAR basic hourly, 37.78 SAR per overtime hour — 377.78 SAR in total, without a single special case in the pricing code.

Step 7: Compensatory leave is a settlement mode, not a discount

The 2025 amendments allow overtime to be settled as paid compensatory leave instead of pay, with the employee's agreement, and hand the details to the Implementing Regulation. Article 22 bis sets four terms, and each one is a line of code:

  1. The agreement names the assignment and the leave, and the leave is at least one and a half hours for every overtime hour. Ten overtime hours buy at least fifteen hours of leave, not ten. Day for day is the rule for civil servants, and many Arabic guides quote it for the private sector by mistake. An agreement below the floor cannot be relied on, so the engine pays those hours in cash.
  2. The employer may schedule the leave within 60 days of the date the overtime started, unless the two parties agreed otherwise. The engine computes that date so the leave does not drift into a balance nobody schedules. It counts the first overtime day as day one, which is the cautious reading.
  3. No more than 30 days of compensatory leave in a year. Overtime beyond what the cap can absorb still has to be compensated, and pay is the default in Article 107(1). The regulation does not say which year it means; Article 10 counts the Labour Law's periods in the Hijri calendar unless the contract says otherwise, so decide in writing which year you track.
  4. Leave still owed when the employee leaves is paid. It is paid leave, so it is paid at the wage, and Article 2 makes the wage the actual wage.

The consent is an event with a date and a reference, not a boolean on the employee record, because the burden of proving agreement sits with the employer. And the leave lives in its own balance. It has its own deadline, its own cap and its own exit rule, so folding it into annual leave loses all three.

// settlement.ts
import type { Halalas } from './money';
import type { MonthlyWage } from './wage';
import { hourlyRates, type RatePolicy } from './rates';
 
/** Implementing Regulation Art. 22 bis(1): at least 1.5 leave hours per overtime hour. */
export const MIN_LEAVE_HOURS_PER_OVERTIME_HOUR = 1.5;
/** Art. 22 bis(2): the employer schedules the leave within 60 days, unless agreed otherwise. */
export const SCHEDULING_WINDOW_DAYS = 60;
/** Art. 22 bis(3): no more than 30 days of compensatory leave in a year. */
export const ANNUAL_COMP_LEAVE_CAP_DAYS = 30;
 
export interface CompLeaveAgreement {
  /** ISO date the employee agreed in writing. */
  consentDate: string;
  /** Reference to the signed agreement. */
  consentRef: string;
  /** Leave hours granted per overtime hour, as written in the agreement. */
  leaveHoursPerOvertimeHour: number;
  /** A different scheduling deadline the parties agreed in writing, if any. */
  agreedScheduleBy?: string;
}
 
export type OvertimeSettlement =
  | { mode: 'pay' }
  | { mode: 'comp-leave'; agreement: CompLeaveAgreement };
 
export interface SettlementSplit {
  /** Overtime hours that go to payroll at the Art. 107 rate. */
  paidHours: number;
  /** Hours credited to the compensatory-leave balance, never to annual leave. */
  leaveHours: number;
  /** ISO date by which the leave must be taken, when any hours went to leave. */
  scheduleBy?: string;
}
 
const addDays = (iso: string, days: number): string => {
  const d = new Date(`${iso}T00:00:00Z`);
  d.setUTCDate(d.getUTCDate() + days);
  return d.toISOString().slice(0, 10);
};
 
/**
 * Split one overtime assignment between pay and compensatory leave (Reg. Art. 22 bis).
 * Whatever the agreement cannot lawfully cover falls back to pay, the Art. 107(1) default.
 */
export function settle(
  overtimeHours: number,
  overtimeStart: string,
  settlement: OvertimeSettlement,
  compLeaveDaysUsedThisYear: number,
  hoursPerLeaveDay: number,
): SettlementSplit {
  if (settlement.mode === 'pay') return { paidHours: overtimeHours, leaveHours: 0 };
  const a = settlement.agreement;
  if (!a.consentRef || a.leaveHoursPerOvertimeHour < MIN_LEAVE_HOURS_PER_OVERTIME_HOUR) {
    // No provable consent, or a ratio under the floor: the agreement cannot be relied on.
    return { paidHours: overtimeHours, leaveHours: 0 };
  }
  const capLeftDays = Math.max(0, ANNUAL_COMP_LEAVE_CAP_DAYS - compLeaveDaysUsedThisYear);
  const leaveHours = Math.min(
    overtimeHours * a.leaveHoursPerOvertimeHour,
    capLeftDays * hoursPerLeaveDay,
  );
  if (leaveHours === 0) return { paidHours: overtimeHours, leaveHours: 0 };
  return {
    paidHours: overtimeHours - leaveHours / a.leaveHoursPerOvertimeHour,
    leaveHours,
    // The first overtime day counts as day one: the cautious reading of "within 60 days".
    scheduleBy: a.agreedScheduleBy ?? addDays(overtimeStart, SCHEDULING_WINDOW_DAYS - 1),
  };
}
 
/** Reg. Art. 22 bis(4): compensatory leave still owed is paid when the employee leaves. */
export function unusedCompLeavePayout(
  unusedLeaveHours: number,
  wage: MonthlyWage,
  ratePolicy?: RatePolicy,
): Halalas {
  // Paid leave is paid at the wage, and Art. 2 makes the wage the actual wage.
  return Math.round(unusedLeaveHours * hourlyRates(wage, false, ratePolicy).actualHourly);
}

Two numbers are worth showing whoever proposes compensatory leave as a saving. Ten overtime hours on the Step 6 wage cost 283.33 SAR in cash. Settled as leave, they become fifteen hours off, and if the employee leaves before taking them the payout is 15 x 20 = 300 SAR. With any allowance in the wage, one and a half actual hours is worth more than an actual hour plus half a basic one, so compensatory leave never costs less than pay. It is a way to schedule overtime without cash, not a way to spend less on it.

hoursPerLeaveDay converts the regulation's days into the hours the ratio is written in. Use the contractual working day, and record that choice next to the divisor from Step 4.

Step 8: Track the 720-hour cap before the inspector does

The annual ceiling is the rule nobody codes because it lives in Article 22 of the Implementing Regulation rather than in the article everyone quotes. The engine's job is not to block the 721st hour — operations will always win that argument — but to see it coming and to demand the consent paperwork when it arrives.

// cap.ts
import type { DayRecord } from './classify';
 
/** Implementing Regulation Art. 22: 720 overtime hours a year, more only with consent. */
export const ANNUAL_OVERTIME_CAP_HOURS = 720;
/** Art. 106: even where overtime is allowed, 10 actual hours a day and 60 a week at most. */
export const MAX_ACTUAL_HOURS_PER_DAY = 10;
export const MAX_ACTUAL_HOURS_PER_WEEK = 60;
 
export interface CapStatus {
  used: number;
  remaining: number;
  exceeded: boolean;
}
 
export function capStatus(hoursThisYear: number): CapStatus {
  return {
    used: hoursThisYear,
    remaining: Math.max(0, ANNUAL_OVERTIME_CAP_HOURS - hoursThisYear),
    exceeded: hoursThisYear > ANNUAL_OVERTIME_CAP_HOURS,
  };
}
 
/** Dates on which actual hours broke the Art. 106 daily ceiling. */
export function dailyCeilingBreaches(days: DayRecord[]): string[] {
  return days.filter((d) => d.hoursWorked > MAX_ACTUAL_HOURS_PER_DAY).map((d) => d.date);
}
 
/** True when one week's actual hours broke the Art. 106 weekly ceiling. */
export function weekExceedsCeiling(week: DayRecord[]): boolean {
  return week.reduce((sum, d) => sum + d.hoursWorked, 0) > MAX_ACTUAL_HOURS_PER_WEEK;
}

Surface remaining on the HR dashboard at 600 hours, not at 719. The consent requirement above the cap is per employee and in writing — the same evidence discipline as Step 7.

The Article 106 ceiling is different in kind. The 720-hour cap can be exceeded with consent; the limit of 10 actual hours a day and 60 a week cannot be waived by anyone. That is why the checks read hoursWorked, the whole day, and not the overtime alone. A 12-hour shift is a breach whatever the timesheet calls it.

Testing Your Implementation

Every rule above becomes a scenario. These are the ones that catch real implementations:

// engine.test.ts
import { describe, expect, it } from 'vitest';
import { fromSAR } from './money';
import { overtimePay } from './pay';
import type { DayRecord } from './classify';
import { settle, unusedCompLeavePayout, type OvertimeSettlement } from './settlement';
import { dailyCeilingBreaches, weekExceedsCeiling } from './cap';
 
const wage = { basic: fromSAR(4000), fixedAllowances: fromSAR(800) };
 
const workday = (hoursWorked: number, ramadan = false): DayRecord => ({
  date: '2026-03-02',
  kind: 'workday',
  hoursWorked,
  ramadan,
});
 
describe('Article 107 pricing', () => {
  it('prices the premium from the basic wage, not the actual wage', () => {
    // 2h overtime: 2 x (4800/240 + 0.5 x 4000/240) = 2 x 28.333 SAR
    expect(overtimePay([workday(10)], wage)).toBe(5667);
  });
 
  it('pays nothing at or under the daily standard', () => {
    expect(overtimePay([workday(8)], wage)).toBe(0);
  });
 
  it('treats every rest-day hour as overtime', () => {
    const friday: DayRecord = {
      date: '2026-03-06',
      kind: 'rest-day',
      hoursWorked: 5,
      ramadan: false,
    };
    // 5 x 28.333 = 141.67 SAR
    expect(overtimePay([friday], wage)).toBe(14167);
  });
 
  it('applies the 180-hour divisor and 6-hour standard in Ramadan', () => {
    // 8h worked in Ramadan = 2h overtime at (4800/180 + 0.5 x 4000/180)
    expect(overtimePay([workday(8, true)], wage)).toBe(7556);
  });
 
  it('rounds once at the end, not per day', () => {
    const days = Array.from({ length: 3 }, () => workday(9));
    // 3 x 28.333... rounds to 8500, not 3 x 2833 = 8499
    expect(overtimePay(days, wage)).toBe(8500);
  });
});
 
const compLeave = (ratio: number, consentRef = 'OT-2026-014'): OvertimeSettlement => ({
  mode: 'comp-leave',
  agreement: { consentDate: '2026-02-20', consentRef, leaveHoursPerOvertimeHour: ratio },
});
 
describe('Compensatory leave (Implementing Regulation Art. 22 bis)', () => {
  it('grants at least an hour and a half of leave per overtime hour', () => {
    const split = settle(10, '2026-03-02', compLeave(1.5), 0, 8);
    expect(split).toEqual({ paidHours: 0, leaveHours: 15, scheduleBy: '2026-04-30' });
  });
 
  it('pays in cash when the agreed ratio is under 1.5', () => {
    // Day for day is the civil-service rule, not the private-sector one.
    expect(settle(10, '2026-03-02', compLeave(1), 0, 8)).toEqual({ paidHours: 10, leaveHours: 0 });
  });
 
  it('pays in cash when there is no consent reference', () => {
    expect(settle(10, '2026-03-02', compLeave(1.5, ''), 0, 8)).toEqual({ paidHours: 10, leaveHours: 0 });
  });
 
  it('sends overtime past the 30-day annual cap to payroll', () => {
    // 29 days already taken: one 8-hour day left covers 8 / 1.5 overtime hours.
    const split = settle(10, '2026-03-02', compLeave(1.5), 29, 8);
    expect(split.leaveHours).toBe(8);
    expect(split.paidHours).toBeCloseTo(4.667, 3);
  });
 
  it('pays unused leave at the actual wage when the employee leaves', () => {
    // 15 leave hours x 4800/240 = 300 SAR, more than the 283.33 SAR cash settlement.
    expect(unusedCompLeavePayout(15, wage)).toBe(30000);
    expect(overtimePay(Array.from({ length: 5 }, () => workday(10)), wage)).toBe(28333);
  });
});
 
describe('Article 106 ceilings', () => {
  it('flags any day over 10 actual hours', () => {
    expect(dailyCeilingBreaches([workday(10), { ...workday(11), date: '2026-03-03' }])).toEqual(['2026-03-03']);
  });
 
  it('flags a week over 60 actual hours', () => {
    expect(weekExceedsCeiling(Array.from({ length: 6 }, () => workday(10)))).toBe(false);
    expect(weekExceedsCeiling([...Array.from({ length: 6 }, () => workday(10)), workday(1)])).toBe(true);
  });
});

The last test is the one that matters most in production: rounding per day instead of per period drifts by a halala at a time until a reconciliation fails. Cross-check any scenario against our free overtime calculator — it runs this same logic.

Troubleshooting

Your numbers disagree with the employee's own calculation. Nine times out of ten they computed the whole overtime hour from the actual wage (giving 1.5 × actual hourly). Show the split: the statute prices the base hour from the actual wage and only the premium from the basic wage.

Your numbers disagree with the previous payroll system. Check the divisor first — 240 versus 208 versus "calendar days in that month" explains almost every legacy delta. Decide which policy you are adopting, record it, and migrate deliberately rather than matching the old system bug for bug.

Ramadan totals look too high. They are supposed to be higher per hour: the divisor drops to 180 and the daily standard to 6, so both the rate and the overtime hour count rise. The wrong result is Ramadan overtime priced at the normal rate.

Compensatory leave settles less than the overtime. Check the ratio in the agreement. Day for day is the civil-service rule; the private-sector floor is one and a half hours of leave per overtime hour, and settle pays the whole assignment in cash when the agreement is under it.

Friday work shows zero overtime. Your timesheet is classifying the day as a workday with hours under the standard. The kind field exists precisely so rest days and holidays never pass through the daily-standard branch.

Next Steps

Conclusion

Article 107 fits in a sentence, and that is exactly why it is implemented wrong so often: the sentence contains two wage bases, a divisor nobody states, three day types, a settlement option with four terms of its own, and two caps living outside the article. The engine that handles all of it is a few hundred lines: integer money, a two-field wage model, explicit divisors, day classification, one pricing function, and one rounding.

If your overtime is computed in a spreadsheet, or your timesheet system and your payroll disagree and the difference is being paid out of goodwill, tell us what your stack looks like — we will run a month of your real timesheet data through an engine like this one and show you exactly which of the three classic mistakes your current formula is making, before an inspector or a labour court does.