Build a complete payroll system with EPF, SOCSO, EIS, PCB tax calculations per Malaysian employment law
* Preview is for reference only. Actual results may vary depending on the AI model, variable values, and tools used.
You are a senior full-stack engineer with deep expertise in Malaysian employment law and statutory payroll compliance. Build a complete payroll management system for {{company_name}} that fully complies with the Employment Act 1955, EPF Act 1991, SOCSO Act 1969, EIS regulations, and LHDN PCB requirements.
TECH STACK
- Framework: Next.js 14 (App Router) with TypeScript strict mode
- Styling: Tailwind CSS + shadcn/ui component library (new-york style, slate base)
- Database: Supabase (PostgreSQL with Row Level Security enabled)
- Deployment: Vercel
- PDF generation: @react-pdf/renderer for payslip export
- Forms: zod schema validation + react-hook-form
- State management: Server Components for data fetching, client components only for interactivity
DATABASE SCHEMA
Create all tables with the following columns and indexes:
1. employees
- id UUID PRIMARY KEY DEFAULT gen_random_uuid()
- employee_code VARCHAR(20) UNIQUE NOT NULL (e.g. EMP-001)
- full_name VARCHAR(100) NOT NULL
- ic_number VARCHAR(20) UNIQUE NOT NULL
- nationality VARCHAR(50) DEFAULT 'Malaysian'
- date_of_birth DATE
- gender VARCHAR(10)
- marital_status VARCHAR(20) — single / married / divorced
- spouse_working BOOLEAN DEFAULT false
- num_dependents INTEGER DEFAULT 0
- department VARCHAR(100)
- position VARCHAR(100)
- employment_type VARCHAR(20) — permanent / contract / probation
- join_date DATE NOT NULL
- resign_date DATE (nullable)
- epf_number VARCHAR(20)
- socso_number VARCHAR(20)
- income_tax_number VARCHAR(30)
- bank_name VARCHAR(100)
- bank_account VARCHAR(30)
- email VARCHAR(100)
- phone VARCHAR(20)
- status VARCHAR(20) DEFAULT 'active'
- created_at TIMESTAMPTZ DEFAULT now()
- Indexes: department, status, employment_type
2. salary_structures
- id UUID PRIMARY KEY
- employee_id UUID REFERENCES employees(id)
- effective_date DATE NOT NULL
- basic_salary NUMERIC(12,2) NOT NULL
- housing_allowance NUMERIC(12,2) DEFAULT 0
- transport_allowance NUMERIC(12,2) DEFAULT 0
- meal_allowance NUMERIC(12,2) DEFAULT 0
- other_allowance NUMERIC(12,2) DEFAULT 0
- allowance_notes TEXT
- epf_employer_rate NUMERIC(5,2) DEFAULT {{epf_employer_rate}}.00
- epf_employee_rate NUMERIC(5,2) DEFAULT {{epf_employee_rate}}.00
- is_current BOOLEAN DEFAULT true
- created_at TIMESTAMPTZ DEFAULT now()
- Indexes: employee_id, effective_date, is_current
3. payroll_runs
- id UUID PRIMARY KEY
- pay_year INTEGER NOT NULL
- pay_month INTEGER NOT NULL (1–12)
- pay_cycle VARCHAR(20) DEFAULT '{{pay_cycle}}'
- status VARCHAR(20) DEFAULT 'draft' — draft / processing / approved / paid
- total_employees INTEGER
- total_gross NUMERIC(14,2)
- total_net NUMERIC(14,2)
- total_epf_employee NUMERIC(12,2)
- total_epf_employer NUMERIC(12,2)
- total_socso_employee NUMERIC(12,2)
- total_socso_employer NUMERIC(12,2)
- total_eis_employee NUMERIC(12,2)
- total_eis_employer NUMERIC(12,2)
- total_pcb NUMERIC(12,2)
- approved_by UUID (nullable)
- approved_at TIMESTAMPTZ (nullable)
- pay_date DATE (nullable)
- notes TEXT
- created_at TIMESTAMPTZ DEFAULT now()
- UNIQUE constraint: (pay_year, pay_month)
4. payslips
- id UUID PRIMARY KEY
- payroll_run_id UUID REFERENCES payroll_runs(id)
- employee_id UUID REFERENCES employees(id)
- basic_salary NUMERIC(12,2)
- housing_allowance NUMERIC(12,2) DEFAULT 0
- transport_allowance NUMERIC(12,2) DEFAULT 0
- meal_allowance NUMERIC(12,2) DEFAULT 0
- other_allowance NUMERIC(12,2) DEFAULT 0
- overtime_amount NUMERIC(12,2) DEFAULT 0
- bonus_amount NUMERIC(12,2) DEFAULT 0
- gross_salary NUMERIC(12,2)
- epf_employee NUMERIC(12,2)
- epf_employer NUMERIC(12,2)
- socso_employee NUMERIC(12,2)
- socso_employer NUMERIC(12,2)
- eis_employee NUMERIC(12,2)
- eis_employer NUMERIC(12,2)
- pcb_tax NUMERIC(12,2)
- other_deductions NUMERIC(12,2) DEFAULT 0
- total_deductions NUMERIC(12,2)
- net_pay NUMERIC(12,2)
- ytd_gross NUMERIC(14,2)
- ytd_pcb NUMERIC(12,2)
- ytd_epf NUMERIC(12,2)
- status VARCHAR(20) DEFAULT 'draft'
- created_at TIMESTAMPTZ DEFAULT now()
- Indexes: payroll_run_id, employee_id, UNIQUE(payroll_run_id, employee_id)
5. overtime_records
- id UUID PRIMARY KEY
- employee_id UUID REFERENCES employees(id)
- work_date DATE NOT NULL
- day_type VARCHAR(20) — normal / rest_day / public_holiday
- overtime_hours NUMERIC(5,2)
- hourly_rate NUMERIC(10,2)
- multiplier NUMERIC(4,2) — 1.5 / 2.0 / 3.0
- overtime_amount NUMERIC(12,2) (computed: hours × rate × multiplier)
- pay_month INTEGER
- pay_year INTEGER
- approved_by UUID
- created_at TIMESTAMPTZ DEFAULT now()
- Indexes: employee_id, (pay_year, pay_month)
6. bonus_records
- id UUID PRIMARY KEY
- employee_id UUID REFERENCES employees(id)
- pay_month INTEGER
- pay_year INTEGER
- bonus_type VARCHAR(50) — annual / performance / festival
- amount NUMERIC(12,2)
- remarks TEXT
- created_at TIMESTAMPTZ DEFAULT now()
7. deduction_adjustments
- id UUID PRIMARY KEY
- employee_id UUID REFERENCES employees(id)
- pay_month INTEGER
- pay_year INTEGER
- deduction_type VARCHAR(50) — loan / salary_advance / unpaid_leave
- amount NUMERIC(12,2)
- remarks TEXT
- created_at TIMESTAMPTZ DEFAULT now()
STATUTORY CALCULATION RULES
1. EPF (Employees Provident Fund) — EPF Act 1991
- Employee contribution: gross salary × {{epf_employee_rate}}% (employees earning ≤ RM 5,000 may opt for 9%)
- Employer contribution: gross salary × {{epf_employer_rate}}% (12% for salaries > RM 5,000, 13% for ≤ RM 5,000)
- Wage ceiling: RM 100,000/month — no ceiling for computation, contributions apply on full amount
- Round all amounts up to the nearest RM 0.10
- Implement the full EPF contribution rate table broken into RM 20 salary bands for accuracy
- Non-Malaysian employees: different rates apply — implement accordingly
2. SOCSO (Social Security Organisation) — SOCSO Act 1969
- Two schemes: First Category (Employment Injury + Invalidity) for employees < 60 years old; Second Category (Employment Injury only) for employees ≥ 60 or salary > RM 4,000 threshold
- Wage ceiling: RM 5,000/month (contributions capped at RM 5,000 wage band)
- Implement the full SOCSO contribution table (JADUAL CARUMAN PERKESO) with exact employee and employer amounts per wage band
- Foreign workers: employer pays only, employee contribution not required
3. EIS (Employment Insurance System) — Employment Insurance System Act 2017
- Employee contribution: 0.2% of insured salary
- Employer contribution: 0.2% of insured salary
- Wage ceiling: RM 5,000/month (use RM 5,000 for salaries above this)
- Round to nearest RM 0.10 using the EIS contribution table
- Employees aged 57 and above who have never contributed to EIS are exempt
4. PCB (Potongan Cukai Bulanan / Monthly Tax Deduction) — LHDN 2024 schedule
- Step 1: Calculate annual chargeable income = (monthly gross × 12) - (EPF employee annual) - personal reliefs
- Personal relief: RM 9,000 for all individuals
- Additional spouse relief: RM 4,000 if married and spouse has no income
- Child relief: RM 2,000 per qualifying child
- Step 2: Apply 2024 income tax rate table (progressive brackets from 0% to 30%)
- Step 3: Monthly PCB = annual tax ÷ 12, adjusted for any previous month over/under-deductions
- Implement the complete 2024 individual income tax rate schedule
5. Overtime calculation — Employment Act 1955, Section 60A
- Hourly rate = monthly salary ÷ (26 working days × 8 hours)
- Normal working day overtime: hourly rate × 1.5
- Rest day overtime (within normal hours): one day's wages (minimum half-day wages if ≤ 4 hours)
- Rest day overtime (exceeding normal hours): two days' wages
- Public holiday overtime: hourly rate × 3.0 (in addition to normal public holiday pay)
CORE FEATURE MODULES
Module 1 — Employee Salary Setup
- Employee list page with search bar, department filter, employment type filter
- Add/edit employee — 3-step wizard: (1) Personal info + employment details, (2) Salary structure + allowances, (3) Bank details + tax info
- Salary history timeline showing all past salary revisions with effective dates
- Employee salary card view displaying current compensation breakdown
- Bulk import employees via CSV upload with validation
Module 2 — Monthly Payroll Processing
- Create payroll run by selecting year and month (prevent duplicate runs)
- Auto-fetch all active employees with their current salary structures
- Import overtime records and bonus entries for the period
- Bulk calculate EPF, SOCSO, EIS, PCB for all employees simultaneously using Promise.all
- Support recalculation for individual employees after adjustments
- Payroll run status workflow: Draft → Under Review → Approved → Paid
- Pre-approval summary screen showing all totals for boss sign-off
- Lock payroll run after approval to prevent accidental edits
Module 3 — Payslip Generation
- Individual payslip preview with full earnings and deductions breakdown
- Payslip layout: company header, employee details, earnings table, deductions table, net pay highlight, employer contribution section, YTD cumulative figures
- PDF export for single payslip and bulk export as ZIP archive
- Email payslip to employee's registered email address
- Payslip access: employees can view their own payslips via a self-service link
Module 4 — Bank Transfer File Generation
- Generate bulk salary transfer files compatible with Maybank2U Business, CIMB Clicks, and Public Bank PBe formats
- Output CSV/TXT with employee name, bank, account number, transfer amount
- Validate all employee bank details before generating file
- Show summary of transfer amounts before download
Module 5 — Year-End EA Form Data
- Aggregate full-year taxable income, EPF contributions, and PCB deducted per employee
- Generate EA form data report in LHDN-compatible format (Form EA / Borang EA)
- Export per-employee and company-level summaries
- Flag employees with incomplete tax reference numbers
UI/UX DESIGN REQUIREMENTS
Navigation: dark navy (#0F172A) top bar, company name left, current month/year center, user profile right
Dashboard layout:
- Four summary stat cards at top: Total Employees, Total Gross Payroll, Total Net Payroll, Total Statutory Contributions
- Current month payroll run status banner with action button (Process / Approve / Download)
- Department cost breakdown bar chart
- Month-over-month payroll trend line chart (12-month rolling)
- Alert panel: employees missing bank details, unsubmitted overtime, payroll runs awaiting approval
Payslip design:
- Clean white card, company logo top-left, payslip period top-right
- Employee details row (name, ID, department, position, EPF no., tax no.)
- Earnings table with yellow (#FCD34D) header row
- Deductions table
- Net pay displayed prominently in yellow accent block
- Employer statutory contributions section (separate, clearly labeled)
- YTD summary footer row
- All amounts formatted as RM X,XXX.XX
All tables must be responsive. Payslips must be mobile-viewable. Protect all admin routes with Supabase Auth session check in middleware. Use RLS policies: public SELECT on published data, authenticated admin-only for all writes. Wrap all auth.uid() calls in (select auth.uid()) for performance.
After scaffolding, provide a setup checklist: Supabase project creation, environment variables needed (NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY), and the SQL migration script to run in Supabase SQL editor.