AAuditPro Suite· HR manual
HR manual Wave 2e · Leave
Oman Labour Law Art. 61 Art. 62 sick Art. 67 hajj Art. 83 maternity

The 9 seeded leave types

TypeDaysPayEligibilityArticle
Annual30/yrFullAll staff after > 6 monthsArt. 61
Sick70/yr split1-14 days 100% · 15-42 days 75% · 43-70 days 50%AllArt. 62
Casual6/yrFullAllFirm policy
Hajj15 once-per-serviceFullMuslim, > 1 year serviceArt. 67
Maternity50FullFemaleArt. 83
Paternity7FullMaleFirm policy
Bereavement3FullAll · close familyFirm policy
Marriage3 onceFullAllFirm policy
Unpaidper approvalNoneAll · approval required

Per-leave-type configuration

1default_days_per_year
int

Annual entitlement. Drives the auto-materialised balance row each year.

2max_carry_forward
int

How many days roll over at year-end. Annual leave typically max 30; sick / casual typically 0.

3max_per_request
int

Single-request cap. Prevents one 60-day annual-leave block.

4applies_to
all / male / female / muslim

Filter at apply-time. Hajj only Muslim, maternity only female, etc.

5requires_attachment
TINYINT(1)

True for sick (medical certificate) and hajj (visa proof). Apply form enforces.

6counts_weekends / counts_holidays
TINYINT(1)

Drives day calculation. Annual: don't count Fri-Sat or holidays. Sick: counts everything.

7minimum_service_days
int

Annual: 180; Hajj: 365; others usually 0.

8pay_percent
For sick split-payment

Sick: 100/75/50 by band. Annual: 100. Unpaid: 0.

The balance auto-materialisation

For each (employee × leave_type × calendar_year), employee_leave_balances has one row with:

The balance is recomputed on every status transition via LeaveService::recalculateBalance().

The application workflow

applied employee pending manager mgr-approved HR step approved balance − done

Validation chain (LeaveService::apply)

  1. Dates: from ≤ to · not in past · not too far future
  2. Eligibility: gender (maternity female, paternity male) · religion (hajj Muslim) · service days >= minimum
  3. Hajj: once per total service — checked across all prior approved hajj requests
  4. Balance: requested days ≤ current balance (computed live)
  5. Per-request cap: requested days ≤ leave_type.max_per_request
  6. Required attachment: if leave_type.requires_attachment, file present
  7. Overlap: no other approved/pending leave covering same dates

Failures return inline form errors; nothing persists until all checks pass.

Year-end carry-forward cron

bin/cron-m13-leave-carry-forward.php runs Jan 1 at 02:00 (Asia/Muscat). For each (employee × leave_type):

  1. Compute prior-year remaining balance
  2. Cap by leave_type.max_carry_forward
  3. Materialise new-year row with that capped balance as opening + default accrued
  4. Idempotent on re-run (UPDATE existing rows)

Step-by-step — apply for leave

  1. Self-service: My Leave portal

    Logged-in employee → My Leave. Balance hero per leave type. Click + Apply.

  2. Form

    Type · from · to · half-day flag (morning/afternoon) · reason · handover_to (employee picker) · attachment (if required).

  3. Submit

    Validation chain runs. On success: auto-numbered LV/{YYYY}/{NNNN} · 2-step approval chain built · M17 email queued to manager · status pending.

  4. Manager decides

    HR Leave Dashboard at /hr/leave shows pending decisions. Manager clicks Approve / Reject (with comment). Status: pending → mgr-approved (or rejected).

  5. HR approves (step 2)

    If the firm has a HR step, HR approves to flip to approved. Balance auto-deducted. M17 email to applicant.

  6. Cancel — refund

    If applicant cancels approved leave, LeaveService::cancel() refunds the balance + adds an audit-log entry.

Try this

Open HR → Leave Dashboard. 4 stat tiles (pending · approved · upcoming · stuck). Pending decisions table with inline approve/reject. Calendar overlay band shows upcoming leave. This is the partner's "who's where" view.

Watch out

Hajj leave is once-per-total-service, not once-per-year. The eligibility check walks all prior approved hajj requests across years. Don't try to override at the apply form — the service rejects.

Tip — auto-approve admins

m13.leave.auto_approve_admins=1 short-circuits the approval chain for super_admin / partner roles' own leave (no manager above them). Keeps decision flow sensible at the top of the org.