Garment factories often run payroll from spreadsheets and paper attendance registers. Punch data lives on the biometric machine, and someone re-types it and works out late deductions, overtime and loan repayments by hand every month. That is slow and easy to get wrong, and it is hard to audit. Employees dispute their pay because the hours behind a payslip can't be traced.
I built one system that follows the whole chain. The device pushes punches straight to the server. The system processes them into daily attendance against each person's shift, then feeds approved overtime, leave and loans into the salary run, and produces payslips and reports.
- 01Biometric attendance: a ZKTeco face-recognition terminal pushes punches to the server, with no polling.
- 02Day Process engine that turns raw punches into daily records (present, late, absent, overtime) against each employee's shift.
- 03Shift and roster engine: base shifts, rotations, night shifts and special duty, with a monthly roster view.
- 04Leave management: applications, short leave and holidays, with an approval workflow.
- 05Loan ledger with repayments, and an automatic monthly deduction schedule.
- 06Salary structures with effective dates and a full history of raises.
- 07Monthly salary run with late deductions, approved overtime and loan repayments, plus printable payslips.
- 08Reports (salary sheet, attendance summary, muster roll, loan ledger) with one-click PDF export.
- 09Live dashboard: KPIs, ranked alerts and 14-day trends, trimmed to each user's role.
- 10Role-based access for four roles (HR Admin, Attendance Operator, Payroll Officer, Factory Management).
- 11Employee photos stored on Cloudinary, and a check of who is enrolled on the device.
- Device time zones. The terminal reports wall-clock time with no time zone, but the server stores real instants. I kept device time as labelled wall-clock values and converted only at the boundaries. Late and overtime figures now match what people saw at the gate.
- Batch attendance processing. Day Process is a re-runnable snapshot. Re-processing a day has to recompute cleanly without wiping manual edits or approved overtime. I made it idempotent and covered it with tests.
- A silent PATCH bug. Sending a partial
PATCHreset omitted fields to their create-time defaults. Renaming a device flipped it to PULL mode, and a shift lost its "ends next day" flag. It traced back to how the validation library keeps defaults in partial schemas. I wrote apartialUpdate()helper and added regression tests. - Role-trimmed dashboard. A single endpoint returns only the sections each role may see, with alerts ranked by severity, so nothing leaks to the wrong role.
- Going to the cloud with hardware. The device connects outbound, so a hosted backend works. Because an unknown device could push fake punches, I made unregistered devices get a 403 in production and added rate limiting and security headers.
A React 19 single-page app (Vite, Ant Design, TanStack Query for server state, Zustand for auth) talks over REST to a NestJS API. The API validates every request with Zod and enforces JWT authentication with rotating refresh tokens and role-based guards. Prisma sits in front of PostgreSQL on Neon.
A ZKTeco terminal pushes punches to an ADMS endpoint on the same API. The Day Process engine turns those punches into daily records. The payroll engine combines them with approved overtime, leave and loans to produce salary runs and payslips. Photos go to Cloudinary. The frontend runs on Vercel and the backend on Render, with the database on Neon. The build ran in 10 backend and 8 frontend phases, with 275 automated tests across unit, integration, end-to-end and UI layers.