Payroll Admin
Configure Malaysian payroll for a company, put employees into pay groups with salaries and bank details, and run each month from draft through calculate, approve, finalize, bank file and Post GL, with the statutory files generated from the run. Two things the status path does not show: Finalize does not lock the payslips, and Post GL writes three lines into the payroll module’s own journal and nothing anywhere else — bring the month into your accounts with a manual journal, and check the journal rather than the status. It is not separately installable and nothing installs it today: HR & Payroll is a planned module, not yet offered, so read this page to judge what payroll will and will not do, not to run this month’s payroll.
Overview
The Payroll Admin applet is where you configure Malaysian payroll for a company, assign employees to pay groups with salaries and bank details, then run each month’s pay run from draft through calculate, approve, finalize, bank-file generation, and Post GL. It also generates official statutory TXT files (CP39 / e-Data PCB, EPF Form A, SOCSO+EIS) and payroll summary reports for a selected pay run.
Where it fits
| Direction | Applet | Why |
|---|---|---|
| Upstream | Organisation | Companies you pay under |
| Upstream | Employee Maintenance | Employee identity, employment status, bank/payment config |
| Upstream | Chart of Account | GL codes used by GL Mapping and Post GL |
| Adjacent | Claim / Claim Cycle | Salary-payment claims can feed into payroll review |
| Downstream | Bank portal / KWSP / PERKESO / LHDN | Bank files and statutory TXT you download and upload outside BigLedger |
Screens and menus
The applet has four menu items and one gear. Everything you do here is in one of them, and the pay run is the only screen with a state machine behind it.
Main menu
| Menu | What it is |
|---|---|
| Pay Runs | Create and open pay runs; workspace for Calculate → Approve → Finalize → Bank file → Post GL |
| Employees | Payroll roster — link an employee to a company, pay group, salary, bank, statutory flags |
| Pay Groups | Groups of employees who share frequency / calendar / structure |
| Reports | Statutory TXT + payroll summary / register / bank payment summary for a pay run |
Settings (gear)
Payroll Configuration
| Setting | Purpose |
|---|---|
| Company Setup | Per-company employer numbers: EPF, SOCSO, EIS, PCB (E), optional HRDF, PCB category defaults |
| Pay Calendar | Cutoff and payment-day rules (e.g. salary cutoff day 25, payment day 28, frequency MONTHLY) |
| Pay Period | Concrete date ranges for a calendar. A pay run attaches to a period, so a month with no period cannot be run. The period code is locked once saved — it is the one field you cannot correct later, so decide the naming before the first period |
| Salary Structure | Template of earnings and deduction components. It is attached to a pay group or to an individual employee; changing it changes what the next Calculate produces, not what a finalised run already produced. Defaults to MONTHLY, MYR, country MY |
| Allowance Types | Definitions of what you add on top of salary. Every applicability flag — taxable, EPF, SOCSO, EIS, PCB, HRDF — starts ticked, and the pay-run calculation does not use it: every earning is added to gross and every statutory amount is computed on gross regardless. Record them for later; they do not change this month’s pay (see On the screen and doing nothing) |
| Deduction Types | Definitions of what you take off. Recurring starts ticked and Pre-tax starts unticked, and neither is read by the calculation either. What decides a deduction is the component type on the employee’s salary line — a type whose code contains DED — and nothing else |
| Statutory Rates | Country MY rate headers (EPF / SOCSO / EIS / PCB) with default tier % and PCB annual threshold |
| GL Mapping | Map payroll components to chart-of-account codes (expense, net-pay liability, statutory liability). One Default mapping per company |
System Configuration
| Setting | Purpose |
|---|---|
| Application Settings (Field Configuration) | Show/hide listing columns via HIDE_* applet settings |
| Default Selection | Tenant defaults: default company, pay group, currency |
Screens in detail
Company Setup
Settings → Company Setup
Create one setup row per paying company. Required employer identifiers for Malaysian filings:
- EPF Employer No
- SOCSO Employer Code
- EIS Employer Code
- PCB Employer No (E)
- Optional HRDF Applicable + HRDF Employer No
- PCB category / EA form defaults used when generating statutory files
These values are stamped onto statutory report payloads so the TXT matches what KWSP / PERKESO / LHDN expect for that company.
Pay Calendar and Pay Period
Settings → Pay Calendar then Pay Period
- Create a calendar for the company (
MONTHLYfrequency is the Phase-1 path). - Create pay periods under that calendar with Period Start, Period End, and Payment Date.
- Keep period status
OPENuntil you are ready to close the month operationally.
Pay runs are linked to a pay period so payment date and period boundaries stay consistent.
Salary Structure, Allowances, Deductions
Define the earning and deduction catalogue the company uses, then point the Pay Group (and/or employee salary) at the structure you need. Allowance and deduction types carry flags such as taxable, EPF/SOCSO/EIS/PCB applicable, pre-tax and recurring; they are stored for the record and Calculate does not read any of them — what it reads is the component type on each of the employee’s salary lines. See Fields.
Statutory Rates
Settings → Statutory Rates
Create one header per statutory type you need (EPF, SOCSO, EIS, PCB) for country MY. The create form stores:
- Contribution basis (default
GROSS) - Employer / employee contributes flags
- Optional salary ceiling
- Effective date
- Default tier employee % and employer % (virtual fields saved as rate lines)
- For PCB: annual threshold (default 34000 on the form)
GL Mapping
Settings → GL Mapping
Each mapping belongs to one company and maps three Chart of Account picks:
| Component | Typical use |
|---|---|
| Payroll expense | Debit side of the payroll journal |
| Net pay liability | Credit for net salary payable |
| Statutory liability | Credit for statutory payables |
Tick Default Mapping (or use the listing Default checkbox) so Post GL knows which mapping to use. Only one default is allowed per company — checking a new default clears the previous one.
Listing columns show the company name, the default tick, and the GL code/name for each component.
Employees (payroll roster)
Employees → Add / Edit
Assign payroll facts that the employee master alone does not carry for the run:
- Employee (from Employee Maintenance)
- Company
- Pay group
- Employment type (
FULL_TIME/PART_TIME/FREELANCER) - Salary basis and basic amount
- Frequency / currency
- Payment method and bank account link
- Statutory membership flags (EPF / SOCSO / EIS / PCB as applicable)
- Optional On Hold to exclude from payroll until cleared
- Effective date
Employees on hold can still appear in a calculated run as excluded, depending on how you manage the workspace Exclude checkbox.
Pay Groups
Pay Groups (main menu) or settings pay-group master
Group employees who share company, frequency, calendar, and optionally a salary structure. Create the pay run against a pay group so Calculate pulls the right roster.
Pay Runs workspace
Pay Runs → open a run
Workflow statuses the UI advances through:
| Status | Primary action |
|---|---|
DRAFT | Calculate — build employee lines and amounts |
CALCULATED | Review, Exclude employees if needed, edit leave/claim/deduction inputs, then Approve |
APPROVED | Finalize — lock the run |
FINALIZED | Pick bank format → Generate Bank File |
| Bank file ready | Download file → Post GL |
Supported bank format codes in the workspace (do not invent others):
MAYBANK_M2E_TXT— Maybank M2U Biz / M2E fixed-width TXT- Additional formats exposed by the same option list in the applet (searchable dropdown)
After Calculate, you can open a Payslip for an employee line when available. Recalculate is only allowed while the run is still in draft/calculated territory — the UI blocks recalculate after approve/finalize.
Reports
Reports
- Select a pay run.
- Run a statutory or payroll report.
- Result opens in a dialog with row preview and TXT / base64 download when the backend returns a file.
| Report key | Label | Notes |
|---|---|---|
cp39 | CP39 / e-Data PCB | LHDN MTD TXT for e-PCB Plus |
epf | EPF Form A | KWSP Form A TXT for i-Akaun |
socso | SOCSO + EIS | PERKESO Assist combined TXT |
eis | EIS / SIP | EIS-focused file |
ea / e | Form EA / Form E | Draft / Phase-2 annual packs — monthly summary only |
summary | Payroll Summary | Totals for the run |
register | Payroll Register | Line-level register |
bank | Bank Payment Summary | Bank payment view |
variance | Variance | Draft |
Configuration
Payroll has two quite different configuration surfaces, and only one of them matters. The Settings (gear) entries above — company setup, calendars, periods, structures, allowance and deduction types, statutory rates and GL mapping — are the payroll masters, and they are what a first-time setup consists of. The generic Application Settings and Default Selection screens that every Wavelet applet carries are almost entirely inert here.
Applet settings
Settings → Application Settings opens the shared field-configuration screen. The applet declares a
settings model of about 120 keys — the standard HIDE_*, EXPAND_*, ENABLE_CUSTOM_STATUS_*,
NAME_CUSTOM_STATUS_* and dimension families inherited by every applet built from the same template
— and none of them changes what payroll does. They hide columns on the listings. See
how settings are stored and who they apply to.
Settings → Default Selection holds the three that are read: default company, default pay group and default currency, used to pre-fill the pay-run and roster forms. They are a convenience, not a constraint — every form lets you change them.
On the screen and doing nothing
Named rather than quietly left out, because each one looks like it decides money and does not.
- Allowance Type → Taxable · EPF Applicable · SOCSO Applicable · EIS Applicable · PCB Applicable · HRDF Applicable, and the same six on a Salary Structure line. They are captured, stored and shown as a column, and the pay-run calculation never consults them. Every earning on a salary structure is added to gross, and EPF, SOCSO, EIS and PCB are all computed on gross — see Fields.
- Deduction Type → Pre-tax and Recurring. The same: Pre-tax changes nothing because there is no pre-tax step; Recurring changes nothing because the calculation reads the employee’s salary lines each time, not a recurrence rule.
- Application Settings → the
HIDE_*/EXPAND_*/ custom-status families. Template keys. Setting one hides a listing column at most.
Both of these are configuration that has shipped ahead of the calculation meant to use it, rather than settings that were removed. Fill them in if you want the master data to be right for later; do not expect this month’s pay to change.
Which means deciding what each component attracts is a person’s job, done on paper, and it is a real decision rather than a formality — the four Malaysian schemes do not agree with one another. Overtime is not EPF wages but is SOCSO wages. An annual bonus is EPF wages but is not SOCSO or EIS wages. Payment in lieu of notice is not EPF wages; payment for unutilised annual leave is. The HRD Corp levy is on basic pay and fixed allowances only, with bonus, commission and overtime all excluded. Whoever owns payroll writes the component-by-component table down once, has it approved, and re-reads it whenever a new allowance is introduced. The check is that a sample payslip’s EPF base equals the components that table calls EPF wages — which, today, it will not, because everything is on gross.
Who can change what
The applet defines no client-side permissions. There is therefore no per-field or per-button gating inside the applet, and no way to let one person run a pay run while another may only read it.
Access is all-or-nothing inside the applet: whoever has it installed can open every screen,
including Approve, Finalize and Post GL. The boundary that does exist is at the API, and it is
better than it looks — the payroll endpoints carry six separate permissions on the pay run
(CALCULATE, APPROVE, FINALIZE, BANK_FILE_GENERATE, GL_POST, FILE), not one write
permission, and the run header records who calculated, who approved and who finalised it in three
separate columns.
What it does not do is check that those are three different people, and that is the whole of
segregation of duties in payroll. So it is an assignment decision, made by the tenant
administrator at implementation, not by the software at run time: give Calculate to the person who
prepares the run, Approve to somebody who did not, and the bank file to a third if you have one.
Where the company is too small for three, the compensating control is that a second person compares
the bank file’s total and record count against the approved run before it is uploaded, and signs the
comparison. You know the separation is real when a payroll login gets a 403 on approve — check it
once, on the day the permissions are assigned, rather than assuming it.
Configuration checklist (first company)
Use this order once per company. Full narrative with the GadgetSphere example: Configure and run monthly payroll.
- Company exists in Organisation; chart of accounts + payroll expense / liability codes exist.
- Employees exist in Employee Maintenance with bank payment config.
- Company Setup — employer numbers.
- Statutory Rates — EPF / SOCSO / EIS / PCB headers + tiers.
- Pay Calendar → Pay Period for the month.
- Salary Structure / allowance / deduction types as needed.
- Pay Group linked to calendar (and structure).
- Employees roster — salary + pay group + bank + statutory flags.
- GL Mapping — three COA codes, tick Default.
- Default Selection — optional default company / pay group / currency.
- Create Pay Run → Calculate → Approve → Finalize → Bank file → Post GL.
- Reports — generate statutory TXT and keep the download pack.
Permissions and install
There is no install path today (see Availability at the top). With no registry row there are also no client-side permission definitions for this applet; the payroll backend endpoints enforce the standard tenant API permissions.
Fields
The forms themselves are ordinary — the screens above name every tab and every box. What is not on any of those screens is which stored field ends up in which number on a payslip, and that is the question you will actually be asked when somebody’s pay looks wrong. This is the map, read from the calculation itself.
| What you entered, and where | What Calculate does with it |
|---|---|
| Basic amount on the payroll roster | Becomes Basic salary on the payslip. If it is zero or the employee has no salary set up, the line’s existing gross amount is used as the basic instead — so a roster row saved with no salary does not fail; it quietly pays whatever was last on the line |
| Salary structure lines attached to the employee | Classified by whether the component type’s code contains DED: if it does, it is a deduction, and everything else is an earning. A component typed EMPLOYER_CONTRIBUTION, STATUTORY or a misspelt DEDUCTON is added to gross pay |
| Leave / claim / deduction adjustments on the pay-run workspace | CLAIM adds to earnings; LEAVE and DEDUCTION subtract. Any other adjustment type is silently ignored — no error, no line, no trace. A row with Include in calculation unticked, or with no employee, is skipped |
| Exclude on a workspace line | Zeroes every amount on the line, sets it EXCLUDED and detaches its payslip. The employee still appears in the run |
Country on the pay run (defaults MY) | Decides which Statutory Rates headers are looked up. A pay run for a country with no rate headers falls back to the built-in figures below |
| Allowance / deduction applicability flags | Nothing. See On the screen and doing nothing |
The rates the calculation uses when your Statutory Rates are missing
This is the single most surprising thing about a first pay run. Calculate looks up the Statutory Rates master for the country and the employee’s salary — and if that lookup fails for any reason it falls back, without a message, to constants compiled into the backend:
| Contribution | Fallback used when the master lookup fails |
|---|---|
| EPF | 11% employee, 13% employer, on full gross, no ceiling |
| SOCSO | 0.5% employee, 1.75% employer, on gross capped at 4,000.00 |
| EIS | 0.2% employee and 0.2% employer, on gross capped at 4,000.00 |
| PCB / MTD | zero if gross × 12 is at or below 34,000.00; otherwise a flat 1% of gross |
| SKBBK (Lindung 24 Jam) | zero — the scheme is applied only when a SKBBK rate header exists |
The SKBBK zero needs reading carefully. The code comments that the scheme “may not apply until
gazette / company opt-in”. PERKESO’s LINDUNG 24 Jam commenced on 1 June 2026: it is an
employee-only contribution taken off the Act 4 Third Schedule column, capped at the RM6,000 ceiling,
mandatory for foreign workers, and the opt-out window for local employees ran from 13 July to
31 August 2026. So a zero here is not evidence that the scheme does not apply to you. Whoever owns
payroll checks each employee’s SKBBK position against PERKESO’s ASSIST portal and adds a
SKBBK Statutory Rates header before the first live run; the check that it worked is that a foreign
worker’s payslip carries a non-zero SKBBK line.
These are not the gazetted tables. They are placeholders that keep the calculation running, and a pay run built on them will produce plausible, wrong statutory amounts and a CP39 / EPF Form A that agrees with them. Create the Statutory Rates headers before the first live run, and then check the payslip’s calculation trace to confirm which set was used.
The calculation trace — where every number on a payslip came from
Every payslip Calculate builds also writes seven trace rows, in
order: GROSS · EPF · SOCSO · EIS · PCB · SKBBK · NET. Each records the inputs it used
and the result, and the EPF, SOCSO and EIS rows carry a label that says which source the rate came
from — EPF master 11% EE / 13% ER when your Statutory Rates were read, or EPF fallback when
they were not. That label is the fastest answer to “why is this employee’s EPF wrong”, and it is not
shown on any screen: read it through the payroll API for the payslip.
The traces, the payslip and its lines are destroyed and rebuilt on every Calculate — see Lifecycle and effects.
Lifecycle and effects
A pay run is not a document in the usual sense. It has no document type, no amounts that post through the ordinary document posting, and it lives in the payroll module’s own records. So none of the usual document machinery applies to it: nothing moves stock, nothing raises an e-Invoice, and no background job runs for payroll at all.
The status path, and the two transitions that are looser than they look
DRAFT → CALCULATED → APPROVED → FINALIZED → BANK_GENERATED → POSTED → FILED| Action | Allowed from | What it writes |
|---|---|---|
| Calculate | DRAFT, CALCULATING, CALCULATED | Payslips, payslip lines, the Malaysian payslip extension and the seven calculation traces; then the run’s totals and employee count |
| Approve | CALCULATED | Approved date and approver on the header. Nothing else |
| Reject | CALCULATED, APPROVED, PENDING_APPROVAL | Clears the approval and sends the run back to DRAFT |
| Finalize | APPROVED | Finalised date and finaliser. It does not lock the payslips — it is a status, not a seal |
| Generate Bank File | FINALIZED or APPROVED | The bank file header and lines in the chosen format |
| Post GL | BANK_GENERATED or FINALIZED | The payroll journal — see below |
| File statutory | POSTED or BANK_GENERATED | Builds the EPF, SOCSO+EIS and CP39 packs and stores them on the run |
Two of those are worth reading twice. Generate Bank File accepts an APPROVED run, so a run can
reach the bank without ever being finalised; and Post GL accepts a FINALIZED run, so a run can
be posted without a bank file having been generated. Neither is blocked and neither warns. Anything
else is refused with Cannot move pay run from <current> to <target>. Allowed from: […].
Calculate is destructive and re-runnable. Every Calculate first clears payslip_guid from the
run’s lines and its bank-file lines, then permanently deletes every payslip in the run together
with its lines, extension and traces, and rebuilds them from scratch. It is a hard delete, not a soft
one, because the payslip number is unique per company and a soft-deleted row would block the
rebuild. So a payslip number is reused, the previous figures are gone, and there is no history of
what the run said before. If you need evidence of an earlier calculation, export it before you
recalculate.
Calculate is refused once the run is APPROVED or beyond — the only way back is Reject, which
returns the run to DRAFT and clears the approval.
What Post GL actually posts
Post GL writes three lines into the payroll module’s own journal — and nothing anywhere else.
| Line | Debit | Credit | Account used |
|---|---|---|---|
| Payroll expense | gross + employer statutory | — | the GL code on the PAYROLL_EXPENSE mapping row |
| Net pay liability | — | net pay | the NET_PAY_LIABILITY mapping row |
| Statutory liability | — | employee + employer statutory | the STATUTORY_LIABILITY mapping row |
The journal is stamped POSTED immediately, numbered PRJ-<pay run code>, and dated the run’s
payment date.
This is not a general-ledger posting. Nothing reaches the chart of accounts balances, the trial balance or any financial report. The payroll journal has room for a link to a financial journal, and nothing ever fills it — the general-ledger posting is a later phase. Until that phase lands, payroll has to be brought into your accounts by a manual journal.
Two more things follow from the same three lines. The journal does not balance whenever the run
carries a non-statutory deduction — a staff loan, unpaid leave, a manual deduction — because
nothing credits it: debits exceed credits by exactly the total of those deductions. And a missing
GL mapping does not stop Post GL: the mapping is looked up per component, and a component with no
mapping row is written anyway, with the component code (PAYROLL_EXPENSE, NET_PAY_LIABILITY,
STATUTORY_LIABILITY) standing in as the account code and no GL code attached. The run
reaches POSTED and looks finished. Check the journal, not the status.
What a finalised run does not do
- It does not pay anybody. The bank file is a file you download and upload to your bank’s portal.
- It does not file anything with KWSP, PERKESO or LHDN. File statutory builds the TXT packs and
attaches them to the run; submitting them is a separate, manual step outside BigLedger. And
FILEDmeans three files were attempted, not three files were produced: each generator runs in its own try/catch, a failure is recorded as anerrorkey inside the run’s properties, nothing is surfaced, and the run reachesFILEDregardless. Open all three files before you believe the status. - It does not check the file against the regulator’s current specification, and three of them are
known not to match. The EPF Form A generator writes a fixed-width file where KWSP’s i-Akaun
(Majikan) channel takes a CSV; the PERKESO generator writes a pipe-delimited file where a
combined fixed-width contribution file becomes compulsory on 1 October 2026; and the CP39
generator hard-codes country
MYwith a blank passport, which is wrong for every foreign employee. So whoever files checks each file in the regulator’s own portal before the 15th — upload it, read the validation response, and keep the acknowledgement — rather than treating a downloaded file as a filed return. - It does not lock the employee’s salary or the masters behind the run. Changing a salary structure changes what the next Calculate produces; a run already calculated keeps its payslips until somebody recalculates it.
- It does not notify anyone. There is no approval e-mail, no reminder and no scheduled job. The calendar is a person’s: every Malaysian monthly obligation — EPF, SOCSO, EIS, SKBBK, PCB/MTD and the HRD Corp levy — falls on the 15th of the month after the wage month, moving to the next working day if the 15th is a holiday. Whoever files puts all six in a diary at the start of the year; the check is six acknowledgements on file by the 16th.
Related applets
Payroll Admin holds almost no master data of its own. Nearly every value a pay run needs is owned by another applet, which is why a first-time setup is mostly a tour of the five below.
| Applet | Why you end up there |
|---|---|
| Employee Maintenance | The employee master, and the applet this one is most often confused with. Employee Maintenance holds who the person is; it holds no salary figure at all and no EPF or SOCSO number. The pay is on the payroll roster here. If a name, e-mail or bank identity is wrong on a payslip, it is wrong in Employee Maintenance and correcting it here will not help. |
| Entity Maintenance | An employee is an entity record, and the Entity applet is where one is deleted — there is no delete action in Employee Maintenance and none here. A leaver removed from the roster still exists as an entity. |
| Chart of Account | Every account GL Mapping points at lives here. Post GL has nothing to post until a default mapping exists with accounts that resolve, which is the last step of the run and the one that fails silently if the chart is incomplete. |
| Organisation | The companies you pay under, and their branches. Company Setup on this page fills in the statutory employer numbers for a company that must already exist there. |
| Claim · Claim Cycle | The adjacent staff-money workflow, sharing the HR & Payroll module and the same employee records. Reimbursements are claims, not payroll lines; approvers are configured in Claim Cycle, never here. |
Payroll Admin is not in the applet registry, so none of those five pages links back to this one today and you will not meet it by browsing from them. Reach it from the HR & Payroll module page or from Configure and run monthly payroll.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Create GL mapping fails on the effective date or debit/credit | An older version of the payroll service | Ask support to update it |
| Company column shows an internal ID | An older version of the listing | Ask support to update it; refresh |
| Multiple defaults for one company | Older data | Use listing Default checkbox — keeps one default |
| Calculate race / GUID errors | Concurrent recalculate or stale line | Follow workspace status; recalculate only from allowed statuses |
| Company Settings listing 417 | Wrong orderBy column | Applet uses modified_date on company queries |
| Statutory TXT missing employer no | Company Setup incomplete | Fill EPF/SOCSO/EIS/PCB numbers, re-run report |
| Post GL “succeeded” but the accounts did not move | Correct — Post GL writes only the payroll module’s own journal; nothing reaches the general ledger | Bring payroll into the accounts with a manual journal; see Lifecycle and effects |
A posted payroll journal has PAYROLL_EXPENSE where an account code should be | No default GL mapping for that component — the line is written anyway with the component code standing in | Set a Default mapping with valid GL codes, then Reject, recalculate and post again |
| A posted payroll journal does not balance | The run carries non-statutory deductions and no line credits them | Expected today; correct it in the manual journal you raise |
Related documentation
- Configure and run monthly payroll — user guide
- HR & Payroll Module — module context
- Employee Maintenance — employee master
- Chart of Accounts Setup — COA before Post GL