Payroll Module — User Manual
Table of Contents
1. Introduction
The Payroll module (Mod-Payroll-*) runs South African payroll for the firm's own staff: employee records and salary structures, monthly payrun processing (PAYE, UIF, SDL, ETI), payslips, and the SARS-facing outputs (EMP201, IRP5/IT3(a), EMP501 reconciliation). It is a separate concern from Trust Accounting — payroll never touches a client's trust ledger; it posts to the firm's own General Ledger via the same GL posting pipeline every other financial transaction uses.
This manual documents the module as it exists today, including recently added capability (employer retirement fringe benefit, bonuses, reimbursive travel, termination lump-sum tax directives) and — per the module's own compliance audit — what is not yet implemented. Section 9 below lists known limitations plainly so nobody mistakes a documented gap for a bug report.
For the underlying compliance research this module is built against, see docs/plans/2026-08-25-sa-payroll-compliance-audit.md and the standing SARS source-code matrix.
2. Getting Started
Menu Locations
| Screen | Menu Path | Purpose |
|---|---|---|
| Employees | Accounting → Payroll → Employee Records → Employees | Employee list, create/edit employee, salary structure |
| Payroll Cockpit | Accounting → Payroll → Payroll Processing → Payroll Cockpit | Tax-year dashboard: payrun status at a glance, outstanding validation issues |
| Payrun Workspace | Accounting → Payroll → Payroll Processing → Payrun Workspace | Create, calculate, approve, post, cancel, and reverse a payrun |
| Submissions Hub | Accounting → Payroll → Payroll Processing → Submissions Hub | Generate EMP201, IRP5/IT3(a), and EMP501 reconciliation outputs |
| Payroll Options | Accounting → Payroll → Payroll Processing → Payroll Options | GL account mapping, SARS reference numbers, SDL/ETI toggles, periods-per-year |
Prerequisites Before Running Payroll
- Tax table imported and activated for the tax year you intend to pay (Payroll Options → Tax Tables). A payrun cannot be created without an active table for its tax year.
- Employees created with a tax number (an employee with no tax number is silently excluded from calculation — see §4).
- A current salary structure effective on or before the intended pay date for every employee expected to be paid.
- Payroll settings configured — at minimum the GL account mapping (
GL_SALARY_EXPENSE,GL_PAYE_PAYABLE,GL_UIF_PAYABLE, etc.) in Payroll Options, otherwise posting a payrun will fail.
3. Employee Records
Creating / Editing an Employee
The Employees screen (Employee Explorer) lists all employees; selecting one opens the employee editor across four tabs — Personal, Tax, Pay & Allowances, Compliance, Banking.
Key fields:
- Personal — name, ID number, employment status, remuneration type, equity category, cost centre, start/end date, and Termination Reason (RESIGNATION / RETRENCHMENT / RETIREMENT / DEATH / DISMISSAL / CONTRACT_END / OTHER — enabled once an end date is set; left blank for an active employee).
- Tax — SARS tax number. An employee without a tax number is excluded from every payrun calculation (a valid IRP5 cannot be produced for one), so this is the single most common reason a new hire doesn't appear on a payslip.
- Pay & Allowances — basic salary, housing allowance, fixed travel allowance, Reimbursive Rate (R/km) (see §5.2), other allowances, medical aid (employee/employer/dependants), retirement fund (employee % / employer %), other deductions.
- Compliance — has-medical-scheme, UIF-exempt, ETI-eligible flags. These are manual, auditable judgement calls the payroll administrator makes — the software does not attempt to infer UIF exemption or ETI eligibility from ID/hours data it doesn't have (see §9).
- Banking — bank name, branch code, account number, account type.
Salary Structure Versioning
Saving a salary change does not overwrite the existing structure — it creates a new effective-dated version and flags it current. A payrun always calculates using the salary structure version that was actually in effect on the payrun's pay date, not whichever version is flagged current at calculation time. This means a mid-month raise or a back-dated/delayed payrun correctly reproduces the salary that applied at the time, and re-running an old payrun's numbers later (e.g. for a reversal) stays reproducible even after later raises.
4. Running a Payrun
The payrun lifecycle is a strict state machine: DRAFT → CALCULATED → APPROVED → POSTED, with CANCELLED available from DRAFT/CALCULATED/APPROVED and REVERSED available only from POSTED. Each transition is guarded — you cannot, for example, post a payrun that hasn't been approved.
- Create — pick a period start/end and pay date. The system rejects a period that overlaps an existing non-cancelled/non-reversed payrun.
- Calculate — runs every active employee through the calculation engine (PAYE, UIF, SDL, ETI, and — where applicable — this period's bonus/reimbursive travel/lump-sum directive, see §5) and produces one payslip per included employee. Employees are excluded, not silently dropped: any employee with no tax number, or no salary structure effective on the pay date, is listed with a reason in the calculation result so you can fix the underlying data and recalculate before proceeding.
- Recalculate — from DRAFT, re-runs calculation (e.g. after fixing a skipped employee's data or changing a bonus/travel-km entry). Not available once APPROVED.
- Approve — transitions CALCULATED → APPROVED. Requires the "Approve Payroll" permission, distinct from "Run Payroll".
- Post — writes the payslips as immutable, posts the balanced GL journal entry, and updates every included employee's year-to-date accumulators. Requires the "Post Payroll" permission. A posted payrun cannot be edited or recalculated.
- Cancel — available before posting; deletes the (unposted) payslips and marks the payrun CANCELLED. Use this to discard a payrun created or calculated in error.
- Reverse — available only after posting; posts a mirror-image GL batch (debits and credits swapped) and rolls back every affected employee's YTD accumulators. Warning: if later payruns in the same tax year have already been posted, their cumulative PAYE figures depended on this payrun's YTD contribution — those payruns must be recalculated and reposted after a reversal, or their PAYE will be wrong going forward.
5. This Period's Extras — Bonus, Reimbursive Travel, Termination Lump Sums
Three variable, per-employee, per-payrun inputs are supported by the calculation engine so that once-off events don't get mistaxed as if they recurred every month:
5.1 Bonuses
A bonus entered for an employee in a given payrun is taxed under SARS's annual-payment method — the increment in tax that results from adding the bonus, un-annualised, on top of the employee's already-computed annual-equivalent income — rather than being folded into ordinary recurring pay and taxed as if it happened every month. It is reported separately on the payslip and IRP5 (SARS code 3605).
5.2 Reimbursive Travel (Per-Kilometre)
Distinct from the fixed monthly travel allowance already on the salary structure (which is a recurring amount, 80% taxable by default per SARS's rules for a fixed allowance), reimbursive travel is business kilometres reimbursed at the employee's configured Reimbursive Rate (R/km):
- The portion of the reimbursement at or below the SARS-prescribed rate per kilometre is not taxable (IRP5 code 3703).
- Any portion of the employee's rate above the SARS-prescribed rate is taxable and folds into gross remuneration (IRP5 code 3722).
Set the employee's own rate on the Pay & Allowances tab; the kilometres driven are entered per payrun (see §5.4).
5.3 Termination Lump Sums (Tax Directives)
The software does not calculate lump-sum/retirement/retrenchment/severance tax. SARS's Second Schedule directive process determines that figure, and Principle 10 of this module's design (see the compliance audit) is explicit that a legally uncertain determination like this must never be silently computed by application code. Instead:
- Once SARS has issued a tax directive for an employee's termination lump sum, record it (directive number, type — RETIREMENT/RETRENCHMENT/WITHDRAWAL/DEATH/SEVERANCE — gross lump sum, and the directive's own tax amount).
- Apply the recorded directive to the specific payrun that will pay it out. The engine applies the gross and tax figures exactly as directed — it never recalculates them — and reports them under IRP5 codes 3901 (lump sum) and 4102 (directive tax).
- A directive can only be applied to one payrun. Applying an already-applied directive to a second payrun is rejected.
5.4 Entering These Per Payrun
While a payrun is DRAFT, click Extras… in the Payrun Workspace action bar (next to Calculate/Recalculate). This opens "This Period's Extras," listing every active employee with editable Bonus (R) and Reimb. KM columns, plus a Choose Directive…/Clear Directive pair of buttons that apply to the selected row — the picker offers that employee's own unapplied directives (those already recorded and not yet applied to another payrun). Saving writes each row to payrun_employee_adjustments; a row with everything blank is simply not saved (or removed, if one existed before). Extras… is disabled once the payrun leaves DRAFT — the same guard PayrunServiceContract.setEmployeeAdjustment enforces server-side.
To record a new directive, select the employee's row and click New Directive… — a small form collects the directive number, type, gross lump sum, directive tax, tax year (pre-filled from the payrun), issued date, and notes. The directive is recorded but not automatically applied; use Choose Directive… afterwards to apply it to this payrun (or a later one — a directive is not tied to the payrun it was recorded from until it's actually applied).
If the payrun has already been calculated once, saving extras does not retroactively change the existing payslips — click Recalculate afterwards so the engine picks up the new bonus/km/directive values (the dialog reminds you of this when it applies).
6. Employer Retirement Fund Contribution
The employer's percentage contribution to an employee's retirement fund (retirement_employer_pct on the salary structure) is now:
- Added to gross remuneration as a taxable fringe benefit (IRP5 code 3817).
- Simultaneously treated as a deemed employee contribution for the s11F deduction — normally netting to tax-neutral, since the same amount is added to gross and to the deduction. It only changes taxable income once the combined employee + employer contribution exceeds the periodic deduction cap, at which point the excess is correctly taxed.
- Posted to its own GL expense line.
No action is required beyond setting the employer percentage on the salary structure — the effect is automatic from the next calculated payrun onward.
7. SARS Submission Outputs — Submissions Hub
| Output | What it is | Notes |
|---|---|---|
| EMP201 | Monthly employer declaration (PAYE/UIF/SDL/ETI totals) | Reconciled from actual posted payslips, never manually re-entered |
| IRP5 / IT3(a) | Per-employee annual employees' tax certificate | Generated per employee or as a combined booklet for the whole tax year. Reports employer retirement contribution (3817), bonus (3605), reimbursive travel (3703/3722), and termination lump sum/directive tax (3901/4102) alongside the original 3601/3701/4001/4474/4005/4101/4115/4116 codes — each only appears on a certificate when the corresponding amount is non-zero for that employee/year |
| EMP501 reconciliation | Compares cumulative EMP201 PAYE against summed IRP5 figures and flags discrepancies | Produces a CSV suitable for manual review/capture — not a SARS e@syFile-importable file (see §9) |
8. Payroll Options
Configure once per firm, changed rarely:
- GL account mapping — which chart-of-accounts code each payroll posting line debits/credits (salary expense, PAYE payable, UIF payable, SDL payable, retirement payable, employer retirement expense, lump-sum expense, etc.). Posting a payrun fails cleanly if a required mapping is missing, rather than posting an unbalanced entry.
- SARS reference numbers — PAYE, UIF, SDL registration numbers used on generated declarations.
- SDL_ENABLED / ETI_ENABLED — firm-wide toggles.
- PAYROLL_PERIODS_PER_YEAR — normally 12 (monthly); affects annualisation.
9. Known Limitations / Outstanding Issues
This section is maintained deliberately honestly. Every item below is a confirmed gap traced against the actual code, not a hedge — see the linked compliance audit for the file-and-method-level evidence behind each row.
Confirmed gaps, not yet started (see the compliance audit for full detail):
- ETI reduced-hours gross-up/gross-down — an employee who worked fewer than 160 hours in a month has their ETI credit computed as if they worked a full month. No hours-worked field exists anywhere in the data model yet.
- Employer/payrun-level ETI eligibility gate — ETI is calculated per employee in isolation; there is no check across all employees in a payrun for an employer-level disqualification.
- National Minimum Wage — no NMW rate, hours-worked data, or validation exists anywhere in the module.
- Leave and termination as an operational subsystem — no leave accrual/balance table, no leave-payout-on-termination, no notice-pay or severance calculation.
employees.end_dateandtermination_reasonare captured, but there is no dedicated final-payrun workflow. - Fringe benefits beyond travel and retirement — company vehicle, accommodation, low/no-interest loans, bursaries, and other Seventh Schedule benefits have no field, calculation, or IRP5 code.
- IRP5 correction/reissue tracking — certificates are regenerated live from current YTD data every time; there is no "certificate issued" record, numbering, or superseded-by linkage.
- EMP501 SARS submission artifact — the EMP501 CSV is accurate and useful for manual review, but is not SARS e@syFile's import format. A firm filing EMP501 through this system still needs to capture it manually into e@syFile.
calc_hashscope — the per-payslip hash proves the stored result values haven't been tampered with after the fact, but does not cover the calculation inputs (salary structure version, full statutory configuration). It is tamper-evidence for the output, not proof the output was derived from a specific input state.- No in-app diff view when activating a new tax table —
TaxTableManagerTopComponentsupports import-and-activate but does not show "here's what changed" against the table it would replace.
By design, not a gap:
- UIF-exempt and ETI-eligible are manual, auditable flags the payroll administrator sets — the software deliberately does not attempt to infer these from data it cannot verify (SA ID details, connected-person status, wage-regulating measures). See the audit's Principle 10.
- This application is architected single-firm (one
firmrow, one set of PAYE/UIF/SDL/SETA reference numbers), consistent with the rest of the platform. It is not a multi-employer payroll bureau product.
10. Troubleshooting
"No active tax table for [tax year]"
Import and activate a tax table for that tax year in Payroll Options before creating a payrun.
An employee doesn't appear on a calculated payrun
Check the calculation result's skipped-employee list first — the two most common reasons are a missing tax number and no salary structure effective on the pay date. Fix the underlying employee/salary record and recalculate.
"A payrun already exists that overlaps with [dates]"
Cancel or reverse the conflicting payrun before creating a new one for an overlapping period — the system will not allow two active payruns to cover the same dates.
Posting fails with a missing GL account error
A required account code in Payroll Options → GL account mapping is blank. Set it and retry — the posting service refuses to submit an unbalanced or incompletely-mapped entry rather than posting silently wrong figures.
A reversed payrun's later payruns look wrong
This is expected, not a bug — see the Reverse warning in §4. Recalculate and repost every payrun for the same tax year that was posted after the one you reversed.