Setup & Configuration — Detailed Requirements
Everything an operator configures before the first pay run: the payroll item catalogue (what an earning or deduction is — its tax, statutory, EA-form and GL behaviour), contribution groups (which statutory schemes apply to whom), per-year tax brackets (PCB bands + reliefs), the company's statutory registration numbers, and the GL accounts and HRDF settings in company configuration.
Item catalogue
Allowance / deduction / overtime / tax-relief definitions with taxability, statutory bases, EA category and GL account.
Contribution groups
Reusable statutory policies — which schemes apply, who pays, and the GL payable account per scheme.
Tax brackets
Per-year PCB bands, personal reliefs and statutory relief caps; auto-seeded from LHDN defaults.
Statutory identity
The employer's EPF / SOCSO / EIS / LHDN registration numbers, one per scheme.
Setup · Detailed
Data model
Four configuration entities plus the company-config singleton. All are company/branch-scoped, soft-deleted, and audited.
item catalogue| Field | Type | Notes |
|---|---|---|
name | string | Required. Display name, e.g. "Travel Allowance". |
type key | enum | ALLOWANCE / DEDUCTION / OVERTIME / TAX_RELIEF. |
statutoryTypes key | reference list | Which statutory bases (EPF/SOCSO/EIS) this item contributes to. Empty → the default base (basic + allowances) applies. |
accountId | reference → GL account | Journal account for deduction items; unmapped deductions block journal posting. |
isBasicSalary key | boolean | Marks the basic-salary item — only one catalogue item may carry it. |
isTaxable | boolean | Taxable for PCB; non-taxable amounts land in EA Section F (exempt). |
isAdditionalRemuneration key | boolean | Bonus / commission / arrears → LHDN differential PCB, never ÷12-smoothed, never prorated. |
taxReliefType, taxRelief, maxTaxRelief | enum + numbers | AMOUNT / PERCENTAGE relief with cap, for tax-relief items. |
frequency | enum | MONTHLY / ANNUALLY (÷12-smoothed) / ONE_OFF (descriptive only). |
eaCategory | enum | EA/CP8A line mapping: B1_GROSS, B2_BIK, B3_VOLA, B4_REFUND, B5_COMPENSATION, C_PENSION, D_ZAKAT, D_CP38, EXEMPT. |
statutory policy| Field | Type | Notes |
|---|---|---|
name | string | e.g. "EPF+SOCSO+EIS". Assigned to employees on their master record. |
types[].statutoryTypeId key | reference | The scheme (seeded masters: epf, socso, eis, pcb). |
types[].accountId | reference → GL account | Payable account credited for this scheme in the salary journal. |
types[].paidBy | enum | EMPLOYEE / EMPLOYER / BOTH. |
types[].frequency | enum | MONTHLY (default) / ANNUALLY. |
types[].employeePayment / employerPayment | {mandatory, voluntary, valueType} | PERCENTAGE / FIXED / TABLE. ignored for EPF/SOCSO/EIS — the legal schedule always applies. |
per-year PCB configuration| Field | Type | Notes |
|---|---|---|
taxYear key | number | Matched on the run's pay-date year. |
name | string | Seeded as Malaysia PCB {year}. |
bands[] | {minAmount, maxAmount?, baseTax, ratePercentage} | Progressive cumulative-base-tax bands. |
statutoryReliefs[] | {statutoryTypeId, annualLimit} | Configurable relief caps (defaults EPF RM4,000; SOCSO+EIS RM350). |
individualRelief, spouseRelief, perChildRelief | numbers | Personal reliefs (defaults RM9,000 / RM4,000 / RM2,000). |
employer registration numbers| Field | Type | Notes |
|---|---|---|
statutoryTypeId key | reference | One record per scheme per company (unique). |
number | string | The employer's registration number (free text, no format validation). The pcb entry feeds the Borang E header (LHDN E-number). |
Setup · Detailed
Item catalogue CFG-I
Administrators define what every pay component is. The catalogue carries all behavioural flags; per-employee assignments only add an amount and a date range.
- Type is one of
ALLOWANCE/DEDUCTION/OVERTIME/TAX_RELIEF. - No default items are seeded — the catalogue is entirely tenant-defined (create or import).
- Statutory bases are expressed by tagging seeded statutory types, not by per-scheme booleans.
- Creating or updating an item with the basic-salary flag automatically unsets it elsewhere.
- The basic-salary item's assigned amount is what the run treats as the employee's monthly base.
ONE_OFFfrequency is descriptive only — bonus tax treatment requires the additional-remuneration flag.- A deduction item without a GL account blocks journal posting (reported by name).
- A
D_ZAKAT-categorised deduction offsets PCB ringgit-for-ringgit.
Setup · Detailed
Contribution groups CFG-G
A named, reusable statutory policy. Employees point at a group; the group's types decide which schemes are computed when the employee joins a run.
- An employee is linked to exactly one group via their master record; unassigned employees get no statutory contributions.
- The per-scheme GL account is credited with the employee share in the salary journal.
- For EPF/SOCSO/EIS the configured rates are ignored — the statutory schedule always applies (see statutory / STA-R).
Setup · Detailed
Tax brackets CFG-T
The per-year PCB configuration: progressive bands, personal reliefs, and statutory relief caps. Values change with every national Budget, so they are operator data — not code.
- Bands are cumulative:
tax = baseTax + (P − minAmount) × rate%. - Statutory relief caps default to EPF RM4,000 and SOCSO+EIS RM350 when not configured.
- Seeded name:
Malaysia PCB {year}. - A year with no bracket computes zero PCB (see PCB known gaps) — the seed exists to prevent this.
Setup · Detailed
Statutory identity CFG-S
The employer's registration numbers with each agency — EPF, SOCSO, EIS, LHDN — held one per scheme and surfaced on the statutory forms.
- Upserting a blank number deletes the record.
- The LHDN (
pcb) number feeds the Borang E header as the employer's E-number. - Numbers are free text — no agency format validation.
Setup · Detailed
GL accounts & HRDF configuration CFG-A
Company configuration carries the payroll ledger accounts and the HRDF toggle/rate.
| Config key | Purpose |
|---|---|
payrollExpenseAccountId | Salary Expense — the journal's single debit line. |
payrollLiabilityAccountId | Net-salary payable — credited per employee (overridable at post time). |
payrollTaxAccountId | PCB payable — credited per employee's tax. |
payrollEmployerExpenseAccountId | unused Read but never posted — employer costs are not journalled. |
hrdfEnabled, hrdfRate | HRDF levy toggle and rate (1% mandatory category / 0.5% optional category). |
- The liability account chosen at post time is remembered on the run.
- HRDF settings are read at calculate time — toggling them after a run requires recalculation to take effect.
Setup · Detailed
Bulk import CFG-M
Item catalogue, contribution groups, and employee item assignments all support spreadsheet import with a preview-then-confirm flow: parse and validate every row first, show per-row errors, then persist only on confirmation.
- The preview returns each row with a resolved payload or a specific error message (row numbers account for the header row).
- Confirmation re-validates and reports a success count plus per-row errors.
- Boolean columns accept yes/no, true/false, y/n, 1/0, checked/unchecked.
Setup · Detailed
Business rules
- Catalogue owns behaviour; assignments own amounts. All tax/statutory/GL semantics live on the item definition — an employee assignment can only set the amount and effective window.
- Statutory types are seeded master data (
epf,socso,eis,pcb) created at boot; configuration references them, never re-defines them. - One basic-salary item per company, enforced automatically.
- Tax brackets are effective-dated by year; historic years stay untouched so late runs and re-runs reproduce their original tax.
- All setup entities are company/branch-scoped, soft-deleted and audited — mutations snapshot who changed what.
Setup · Detailed
Known gaps
payrollEmployerExpenseAccountId is read but never used — employer statutory costs and HRDF never reach the GL through the payroll journal.