What it is
GroundIT covers everything that happens to an employee: pre-boarding, geofenced attendance, leave, payroll and tax, performance, ESOP, travel and expense, recruitment, exit. It runs in India and Saudi Arabia at the same time, so statutory work lives in country packs. EPFO, ESIC, TDS and professional tax for India. GOSI, WPS through Mudad and Saudization for KSA. Arabic RTL and Hijri calendars are in from the start, not bolted on.
I defined the product and led the team of six that built it. It is a pnpm monorepo with five deployable tiers: a NestJS API with 903 endpoints, a worker tier, a Next.js back-office with 188 screens, an offline-first PWA with 65 more, and a Flutter app. Underneath sits one Postgres database split into 23 schemas, one per domain module, with row-level security forced on every tenant-scoped table.
The part I am proudest of is the payroll engine. It lives in its own package, about 12k lines, and every payslip line it produces can explain itself: which formula ran, which statutory rule applied, what the inputs were. HR can run what-if simulations before committing a payroll cycle.
Architecture
Decisions that mattered
Schema per module, RLS forced at the database
Tenant isolation is not a WHERE clause someone remembers to write. Every module owns a Postgres schema, 23 in total, and row-level security is forced on tenant_id at the database layer. The test harness runs RLS-aware, so isolation is tested, not assumed. 161 migrations so far.
JavaScript budgets as tests, because our users carry cheap phones
Field staff use Android phones that cost around eight thousand rupees. So the test gate reads the build manifest and fails if any of the 64+ PWA routes ships more first-load JS than its budget. When the home route crept to 171.7 KB gzip, fixing sideEffects declarations brought it back to 149.7 KB. That fix is written up in the repo like everything else.
Offline first, with a real sync engine
Attendance punches can happen in a basement warehouse with no signal. The PWA queues writes in IndexedDB and drains them with idempotent replay, conflict handling and exponential backoff. Background sync picks up whatever the foreground missed.
Face-verified attendance without storing faces
Attendance pairs a geofence check with on-device face matching through TensorFlow.js. The server stores the verification result, never the raw biometrics. That is an explicit architecture decision record, one of 66 in the repo.
Payroll as a package, not a service method
The payroll engine is an independently testable package: formula evaluation, statutory rules, income tax, wage floors, final settlement. Country differences live in swappable compliance packs, so KSA payroll is configuration plus a pack, not a fork.