A complete guide to payroll processing in HRSanad — from first setup to final bank transfer.
Think of payroll as a factory. Every month, raw materials go in (employee details, attendance, leaves, loans) and a finished product comes out (a salary slip for every employee, and a file that tells the bank who gets paid how much).
The payroll pipeline: people & data go in → money goes out correctly.
A salary is not just one number. It is built from many smaller pieces called pay components. Each component is either something the employee earns or something that gets deducted.
Earning
Money added to what the employee receives.
Deduction
Money subtracted before the employee receives their pay.
Employer Contribution
Costs the company pays on top of the salary that do not go to the employee directly — but are still a cost to the business (e.g., employer's pension contribution in some countries).
| Method | How it works | Example |
|---|---|---|
fixed |
A flat amount every month, always the same. | Transport Allowance = AED 800 |
pct_basic |
A percentage of the employee's basic salary. | Housing = 25% of Basic → 25% × 10,000 = AED 2,500 |
pct_gross |
A percentage of total gross earnings (all earnings combined). | Bonus = 5% of Gross |
pct_component |
A percentage of one specific other component. | Extra Allowance = 10% of Housing Allowance |
formula |
A custom mathematical expression evaluated by the system. | (basic * 0.3) + 500 |
HRSanad uses a smart three-layer system to decide each employee's pay components. You don't have to set up every allowance for every employee individually — defaults flow down automatically.
Every employee grade (e.g., "Manager", "Engineer") has a standard set of pay components with default values. All employees in that grade start with these.
Example: All Managers get Housing = 30% of Basic by default.
A specific value or percentage override can be set at the grade level — affecting all employees in that grade without touching each one individually.
Example: Temporarily fix Housing to AED 3,000 flat for all Managers this year.
The highest priority. If an individual employee has a specific override, it completely replaces the grade default for that component, for that person only.
Example: Ahmed's Housing is fixed at AED 4,500 regardless of his grade's default.
Layer 3 always wins. If no override exists, the value falls through from Layer 2, then Layer 1.
Every month you create a Payroll Run. Think of it as a container that holds every employee's salary slip for that month. A run always follows a strict sequence of statuses — you cannot skip steps or go backwards.
Go to Payroll → Runs → New Run. Choose the year, month, and which company's employees to include. The run is created in draft status — it's just an empty shell at this point. No numbers yet.
API call: POST /v1/payroll/runs with { year, month, company_id }
Click Calculate. The system now works through every active employee one by one and produces a salary slip for each. This is the most important step — see the next section for exactly how the math works.
API call: POST /v1/payroll/runs/:id/calculate
After this, you can view all the salary slips and check for any errors.
Open the run and browse each department's slips. Look for anything unusual — an employee with zero salary, an unexpectedly high overtime figure, a missing loan deduction. Fix the root data (attendance, pay components) if needed, then recalculate.
API call: GET /v1/payroll/runs/:id/slips (supports department filter & pagination)
Once you're satisfied, click Approve. This represents the HR Manager's or Finance Manager's sign-off that the numbers are correct. After this, the slips are locked for editing.
API call: POST /v1/payroll/runs/:id/approve
Finalization confirms that the payroll is ready for payment. It's a second confirmation layer (e.g., CFO sign-off) before the bank files are generated.
API call: POST /v1/payroll/runs/:id/finalize
Click Generate WPS. The system creates a SIF file (Standard Input Format) which is the official UAE Wage Protection System format accepted by all UAE banks. Once generated, the run is locked — no more changes of any kind.
API call: POST /v1/payroll/runs/:id/generate-wps
You upload this SIF file to your bank's portal and the bank processes the transfers.
If your company uses accounting software (Oracle GL, SAP, Temenos T24, or any system accepting CSV), click Generate JV. This creates a journal entry CSV with payroll costs broken down by cost centre, department, and pay component — ready to import into your accounts.
API call: POST /v1/payroll/runs/:id/generate-jv
When you hit Calculate, the engine processes every eligible employee in this order:
Using the three-layer system described above, the engine builds a final list of all applicable pay components and their values for this employee.
From the Time & Attendance module, the engine reads how many working days were in the month, how many the employee was present, any absent days, and total overtime hours.
For each earning component, compute its value using the method (fixed, pct_basic, formula, etc.). Then pro-rate it if the employee was absent:
pro_rated_amount = (component_value / working_days) × paid_days
If the employee worked all days, paid_days = working_days, so they get the full amount.
Overtime is calculated from TNA data. UAE Labour Law rates:
hourly_rate = basic_salary / (working_days_in_month × 8)
ot_pay = ot_hours × hourly_rate × 1.5
total_earnings = sum of all earning components + ot_pay
Each deduction component is calculated (same methods as earnings). Loan instalments are automatically fetched from the Loans module for active loans and added as deductions.
total_deductions = sum of all deduction components + loan_instalments
net_salary = total_earnings − total_deductions
basic_salary / 12 per monthEach employee gets a salary slip (also called a payslip) that shows the complete breakdown. Here is what a typical slip looks like:
April 2026 | Processed: 30 Apr 2026
Ahmed Al-Mansouri
EMP-0042 | Engineering Manager
Under UAE Labour Law (Federal Decree 33/2021), every employee who completes at least one year of service is entitled to an end-of-service gratuity payment when they leave.
daily_rate = basic_salary / 30
gratuity = days_per_year × daily_rate × years_in_this_band
The number of days_per_year depends on total service length, split into bands:
| Band | Years | Days/Year | Daily Rate | Amount |
|---|---|---|---|---|
| 1–5 years | 5 | 21 days | AED 333.33 (10,000÷30) | AED 35,000 |
| Beyond 5 years | 2 | 30 days | AED 333.33 | AED 20,000 |
| Total Gratuity | AED 55,000 | |||
finance.gratuity_slabs table. If the law changes, the slabs are updated in the database and all future calculations automatically reflect the new rules — no code changes needed.
Every month, payroll computes how much gratuity liability grew that month. This appears on the payslip as an informational figure (not deducted from the employee):
monthly_accrual = annual_gratuity_for_full_service / years_of_service / 12
In the UAE, all private-sector companies are legally required to pay salaries through the Wage Protection System (WPS), regulated by the Central Bank of UAE (CBUAE) and the Ministry of Human Resources.
WPS works like this:
is_wps_included = true are included in the WPS payment amount. Typically this is basic salary + fixed allowances. Some employer contributions or one-time bonuses may be excluded depending on company policy.
finalized status.
After payroll is finalized, Finance needs to post the payroll costs to the company's accounting system. HRSanad generates a Journal Voucher (JV) CSV that can be imported directly into:
The JV breaks down payroll costs by:
| Action | Method | Endpoint | Required Status |
|---|---|---|---|
| List runs | GET | /v1/payroll/runs | Any |
| Create run | POST | /v1/payroll/runs | — |
| Get run details | GET | /v1/payroll/runs/:id | Any |
| Calculate slips | POST | /v1/payroll/runs/:id/calculate | draft |
| View slips | GET | /v1/payroll/runs/:id/slips | calculated+ |
| View single slip | GET | /v1/payroll/runs/:id/slips/:slipId | calculated+ |
| Approve run | POST | /v1/payroll/runs/:id/approve | calculated |
| Finalize run | POST | /v1/payroll/runs/:id/finalize | approved |
| Generate WPS (SIF) | POST | /v1/payroll/runs/:id/generate-wps | finalized |
| Generate JV CSV | POST | /v1/payroll/runs/:id/generate-jv | finalized |
| List employee pay comps | GET | /v1/payroll/employees/:id/pay-components | — |
| Add pay component override | POST | /v1/payroll/employees/:id/pay-components | — |
| Remove pay component override | DELETE | /v1/payroll/employees/:id/pay-components/:pcId | — |
hr_admin, payroll_admin, or sysadmin. Employees can only view their own pay components and payslips via the ESS portal. Salary data is never returned to users without role verification.
| Rule | Description |
|---|---|
| BR-PAY-001 | Monthly payroll supports mid-month processing via a separate run_type (for new joiners or separations mid-month). |
| BR-PAY-004 | Payroll finalization requires workflow approval — the status machine enforces the sequence and cannot be bypassed. |
| BR-PAY-005 | WPS file can only be generated when the run is in approved or finalized status. |
| Gratuity daily rate | Always basic_salary / 30. Never use 26. This is UAE Labour Law. |
| Gratuity slabs | Always read from finance.gratuity_slabs table. Never hardcoded. |
| Overtime rates | 1.5× on weekdays, 2× on public holidays (UAE Labour Law Article 67). |
| Minimum service for gratuity | Employee must have completed at least 1 full year of service. Less than 1 year = AED 0 gratuity. |