← Resources/GuideHuman Resources

HR Platform Architecture: Payroll Engine, Leave Accrual, and Compliance Pipeline Design

3 April 2026·16 min read

Overview

An HR platform for Indian SMBs must solve a uniquely complex problem: compute accurate payroll for a diverse workforce (full-time, part-time, contractual) while staying compliant with PF, ESI, TDS, PT, LWF, and Gratuity regulations - all of which have different computation rules, thresholds, and filing deadlines. This guide covers the architectural decisions behind IdeaSprout's HR module.


1. Employee Data Model

The Core Challenge

Employee data is not static. Salary changes, promotions, department transfers, and tax declarations all affect payroll computation. The system must know the exact state of an employee's record on any given date - not just the current state.

Temporal Data Pattern

We use a bi-temporal data model for all payroll-affecting employee attributes:

sql
CREATE TABLE employee_salary_history (
  id              UUID PRIMARY KEY,
  employee_id     UUID NOT NULL,
  basic_salary    NUMERIC(12,2),
  hra             NUMERIC(12,2),
  special_allowance NUMERIC(12,2),
  effective_from  DATE NOT NULL,
  effective_to    DATE,  -- NULL means currently active
  created_at      TIMESTAMPTZ DEFAULT NOW()
);

To compute payroll for March 2026, the engine queries salary records where effective_from <= 2026-03-31 AND (effective_to IS NULL OR effective_to >= 2026-03-01). Mid-month salary changes are prorated automatically.


2. Payroll Computation Engine

Computation Order (Critical)

Payroll components have dependencies - computing them out of order produces wrong results:

1. Gross Salary = Basic + HRA + Allowances
2. Attendance Deduction = (Gross / Working Days) × Absent Days
3. Adjusted Gross = Gross - Attendance Deduction
4. PF Employee = min(Adjusted Basic, 15000) × 12%
5. ESI Employee = Adjusted Gross × 0.75%  [if Gross ≤ 21,000]
6. Professional Tax = lookup(state, Adjusted Gross)
7. TDS = compute_tds(annual_projected_income, declarations)
8. Net Pay = Adjusted Gross - PF Employee - ESI Employee - PT - TDS - Other Deductions

TDS Computation Complexity

TDS is the most complex component because it requires annual income projection:

Annual Projected Income = 
  (YTD Gross) + (Remaining Months × Current Month Gross)
  - Standard Deduction (₹50,000)
  - HRA Exemption (min of: actual HRA, 50%/40% of basic, rent paid - 10% of basic)
  - Section 80C declarations (max ₹1,50,000)
  - Section 80D declarations (max ₹25,000)
  - NPS (Section 80CCD) declarations

TDS Per Month = (Tax on Annual Projected Income - TDS Already Deducted) / Remaining Months

This means TDS changes every month as the projection updates. The engine recomputes the full annual projection on every payroll run.

Idempotent Payroll Runs

Payroll runs are idempotent - running the same month twice produces the same result and does not create duplicate records. Each run is identified by (employee_id, payroll_period). A re-run overwrites the draft; once a run is "locked" (payslips distributed), re-runs create a revision record instead.


3. Leave Accrual Engine

Accrual Policies

Different leave types accrue differently:

Leave TypeAccrual RuleMax BalanceCarry Forward
Earned Leave (EL)1 day per 20 days worked45 daysYes (max 30)
Casual Leave (CL)1 day per month (credited upfront)12 daysNo
Sick Leave (SL)0.5 days per month12 daysNo
Maternity Leave26 weeks (one-time)N/AN/A

Accrual Computation

The accrual engine runs on the last day of each month:

python
def accrue_earned_leave(employee, month):
    days_worked = attendance_service.get_days_worked(employee, month)
    # Days worked includes: present days + paid leaves
    # Excludes: unpaid leaves, LOP days
    accrual = floor(days_worked / 20)
    current_balance = leave_balance_service.get(employee, 'EL')
    new_balance = min(current_balance + accrual, MAX_EL_BALANCE)
    leave_balance_service.update(employee, 'EL', new_balance)

Leave Balance Consistency

Leave balances are maintained as a running ledger (not a single balance field):

sql
CREATE TABLE leave_transactions (
  id            UUID PRIMARY KEY,
  employee_id   UUID,
  leave_type    TEXT,
  txn_type      ENUM('accrual', 'deduction', 'encashment', 'lapse', 'opening'),
  days          NUMERIC(4,1),
  reference_id  UUID,  -- leave_request_id or payroll_run_id
  txn_date      DATE,
  balance_after NUMERIC(5,1)
);

Current balance = SUM(days) WHERE employee_id = X AND leave_type = Y. The balance_after column is a denormalised snapshot for fast reads.


4. Statutory Compliance Pipeline

Filing Calendar

ComplianceFrequencyDeadlinePenalty for Late Filing
PF ECR (EPFO)Monthly15th of next month₹5,000 + 12% p.a. interest
ESI ReturnMonthly15th of next month₹5,000
TDS Return (Form 24Q)Quarterly31st of month after quarter₹200/day
Professional TaxMonthly/QuarterlyVaries by stateVaries
Form 16 (TDS Certificate)Annual15th June₹100/day

Automated Challan Generation

The compliance pipeline generates statutory challans automatically after payroll lock:

  1. 1PF ECR: Aggregate pf_employee + pf_employer by UAN, generate ECR file in EPFO-specified format
  2. 2ESI: Aggregate ESI contributions by IP number, generate return in ESIC portal format
  3. 3TDS: Aggregate TDS by PAN, generate Form 24Q in NSDL-specified format

All generated files are stored with a hash for integrity verification and linked to the payroll run.


5. Audit Trail Design

What Must Be Auditable

Indian labour law requires employers to maintain payroll records for 8 years. The audit trail must capture:

  • •Every payroll computation with all inputs (salary, attendance, declarations)
  • •Every leave transaction with approver and timestamp
  • •Every statutory filing with submission proof
  • •Every salary revision with approver

Implementation

All payroll-affecting changes go through an event sourcing pattern. The current state is a projection of all events. Events are immutable - corrections create new events, not overwrites.

sql
CREATE TABLE hr_events (
  id          UUID PRIMARY KEY,
  event_type  TEXT,  -- 'salary_revised', 'leave_approved', 'payroll_locked'
  entity_id   UUID,
  payload     JSONB,
  actor_id    UUID,
  occurred_at TIMESTAMPTZ DEFAULT NOW()
);

6. Common Architectural Pitfalls

PitfallConsequenceFix
Mutable salary recordsCannot recompute historical payroll correctlyBi-temporal salary history
TDS computed on current month onlyWrong TDS, employee gets surprise tax bill in MarchAnnual projection recomputed monthly
Leave balance as single fieldRace conditions on concurrent approvalsLedger-based leave transactions
Payroll not idempotentDuplicate payslips on re-runUpsert on (employee_id, period)
Compliance files generated manuallyFiling delays, human errorAutomated challan generation post-lock

Summary

A production HR platform for Indian SMBs requires bi-temporal employee data, a dependency-ordered payroll engine with annual TDS projection, a ledger-based leave accrual system, and an automated compliance pipeline that generates statutory challans without manual intervention. The audit trail must be append-only and retained for 8 years.

HR architecturepayroll enginePF ESI TDSleave accrualcompliance