SmartPRO Docs Β· Internal πŸ”’ ← Back to SmartPRO

Owner: Finance / Payroll Ops (per company) | Jurisdiction: Sultanate of Oman | Review: On any labour-law / rate change

SmartPRO computes Omani payroll and generates the WPS Salary Information File (SIF) required by Oman's Wage Protection System (Ministry of Labour). The run is transactional (all-or-nothing) and locked after payment so a period can't be silently re-run.

The statutory figures below are quoted from the code as of this writing β€” re-verify against current Oman regulations each review cycle. SmartPRO is the source of these numbers, not the law.

Statutory figures (from code)

Item Value Notes
PASI β€” employee 7% of basic Omani nationals only; deducted from net
PASI β€” employer 11.5% of basic tracked for reporting, not deducted from the employee
Overtime 1.25Γ— hourly; 208 monthly-hours basis OT is pre-computed in ot_records, not recalculated at pay time
VAT 5% on service/processing fees, not on wages
Gratuity (EOSB) RD 53/2023 cutoff 2023-07-31 15β†’30 days/yr before, 30 days/yr after; daily wage = basic Γ· 30
Min attendance to run 50% of expected workdays below this raises a reconciliation warning
Omanisation sector targets 5–60% (default 35%) reported, not blocked at the pay run

Monthly payroll cycle

Close & reconcile attendance

Confirm attendance sessions for the period are **closed**. The run needs β‰₯ 50% of expected workdays; below that, `executeMonthlyPayroll` raises a reconciliation warning that must be explicitly acknowledged.

Execute the run (draft)

`executeRun({ companyId, month, year })`. Per employee, inside one DB transaction: gross = basic + housing/transport/other allowances + overtime (`ot_records`) + KPI commission; deductions = **PASI 7% of basic (Omanis)** + loans + absence + other; net = gross βˆ’ deductions. Any error rolls the whole run back β€” no partial payroll.

Review the draft

Inspect line items for outliers β€” unexpected zero-PASI (nationality misclassified), missing overtime, loan balances. Remember the 11.5% employer PASI share is informational and not deducted.

Approve (separation of duties)

`approveRun(runId)` β€” **must be a different user than the preparer**; the procedure rejects self-approval. Stamps `approvedAt` / `approvedByUserId` and writes a governance audit record. Status β†’ `approved`.

Generate payslips

`generatePayslips(runId, employeeIds)` renders HTML payslips to storage (export is audited).

Generate the WPS SIF

`generateWpsBankFile(runId)` β†’ **Excel `.xlsx`** in the Bank Muscat WPS template: an employer header row (CR number, payer CR, bank short name, payer account, year/month, totals, record count) then one row per employee with 15 SIF fields β€” ID type/number, name, BIC + 16-digit account (from IBAN), salary frequency, working days, net, basic, extra hours, extra income, deductions (excl. PASI), **Social Security deductions (PASI, Omanis only)**, notes.

Upload to bank & mark paid

Upload the SIF to your WPS-enabled bank portal before the **MOL deadline (10th of the following month)**. Then `markPaid(runId)` (must be `approved` first) β†’ status `paid`, `paidAt` stamped, run **locked**.

Run state machine

draft ─executeRun─► processing ─► approved ─► paid (locked)
                                    β–²   β”‚
              different user β”€β”€β”€β”€β”€β”€β”€β”˜   └─► wps_generated / ready_for_upload

approved, paid, and locked all block re-execution (BLOCKED_RERUN_STATUSES).

WPS compliance reminder

The wpsComplianceReminder job (server/jobs/wpsComplianceReminder.ts) fires on the 25th of each month at 02:00 UTC, emailing each company's payroll officer (wps_configurations.contact_email) to validate and submit the SIF before the 10th-of-next-month MOL deadline, and writes an in-app notification for every HR-admin.

Control Effect
DISABLE_WPS_REMINDER_JOB=1 Halts the reminder job
RESEND_API_KEY Required for email delivery (notifications still post in-app)

Compliance notes

Failure modes

Symptom Cause Fix
SIF generation fails Employee missing IBAN / bank code Fill in HR β†’ Employees β†’ [name] β†’ Banking
Bank rejects the SIF Bank's BIC not in the short-name map Add the BIC→short-name mapping in server/lib/wpsSifService.ts
Employee shows 0 PASI unexpectedly Nationality not detected as Omani Correct nationality, re-run (only if not yet approved)
Can't re-run a period Status is approved/paid/locked Make a correction in the next period β€” locked is by design
Preparer can't approve own run Separation of duties A second Finance/Company admin must approve
Attendance < 50% blocks the run Reconciliation gate Fix attendance, or run with the acknowledge flag + documented justification

Key files

File Purpose
server/lib/payrollExecuteMonthly.ts executeMonthlyPayroll β€” the transactional run
server/lib/payrollExecution.ts PASI / overtime / rounding helpers
server/lib/wpsSifService.ts generateSIFFile β€” Excel WPS SIF
server/routers/payroll.ts executeRun / approveRun / markPaid / generatePayslips / generateWpsBankFile
server/lib/billingEngine.ts gratuity (Article 61) + VAT
shared/omanization.ts computeOmanizationRate + sector targets
server/jobs/wpsComplianceReminder.ts monthly WPS reminder job