CASE STUDY 02 · SaaS
EMS
Multi-tenant HR for Indian companies, payroll compliance included.

Handles the full employee lifecycle — onboarding, shift-based attendance, leave, payroll with Indian statutory compliance (PF, ESI, PT, TDS), and OKR performance reviews.
4
statutory regimes implemented (PF, ESI, PT, TDS)
full lifecycle
onboarding → attendance → payroll → reviews
multi-tenant
one deployment, row-isolated companies
THE PROBLEM
Indian SMEs live in a gap: global HR SaaS doesn't understand PF, ESI, professional tax, or TDS, and the local options are desktop-era software. Most companies end up running payroll in spreadsheets that one person understands.
Payroll is unforgiving territory — a rounding error or a mis-slabbed tax isn't a bug, it's someone's salary.
WHAT I BUILT
A multi-tenant HR platform covering the lifecycle end-to-end: onboarding, shift-based attendance with configurable policies, leave accrual and approval chains, payroll runs that produce compliant payslips, and OKR-based performance reviews.
The payroll engine implements Indian statutory logic — PF and ESI contributions, state-wise professional tax slabs, TDS projection across the financial year — as versioned, testable rules rather than spreadsheet formulas.
ARCHITECTURE
Lit 3 web components (SPA)
│ REST
▼
Fastify 5 API ── multi-tenant guard (tenant_id scoping)
│
├─ payroll engine (versioned statutory rules)
├─ attendance/shift scheduler
└─ Drizzle ORM ──► PostgreSQL (per-tenant row isolation)- Tenancy is row-level: every query path goes through a tenant guard, so one database serves all companies without cross-tenant leakage.
- Statutory rules (PF caps, ESI thresholds, PT slabs per state, TDS regime) are data with effective dates, not hardcoded constants — when the rules change in a budget year, old payslips stay reproducible.
- Payroll runs are idempotent and auditable: a run snapshots its inputs, so re-running never silently changes issued payslips.
STACK — AND WHY
Lit 3
Web components keep the front end framework-agnostic and small — the same bet as Pulse, shared muscle memory.
Fastify 5
Fast, schema-validated API layer; JSON schema validation catches malformed payroll inputs at the boundary.
PostgreSQL + Drizzle
Relational integrity for money and attendance data; typed queries so payroll math is checked end-to-end.
Docker
One container per service on the VPS, deployed behind Caddy.
THE HARD PARTS
Statutory correctness with a straight face
PF has wage ceilings, ESI has eligibility thresholds that flip mid-year, professional tax differs by state, and TDS depends on a projected annual income that changes every time salary changes. Encoding these as effective-dated rule tables — and testing them against worked examples — was the bulk of the engineering.
Shift-based attendance without edge-case hell
Night shifts cross date boundaries, half-days interact with leave accrual, and late-mark policies vary per company. The attendance model had to be policy-driven configuration, not code branches per customer.
Multi-tenancy you can trust with salaries
Row-level tenancy is easy to claim and easy to get wrong. Every data access path is forced through a tenant-scoped query layer — there is no way to write an unscoped query without deliberately bypassing the ORM wrapper.
WHAT IT TAUGHT ME
- Compliance logic is a data-modeling problem. The moment tax rules became effective-dated rows instead of constants, yearly rule changes stopped being rewrites.
- For money, idempotency beats cleverness — snapshot inputs, make runs reproducible, never mutate an issued payslip.
- Building for Indian SMEs specifically (rather than 'HR for everyone') is what made the product coherent.