Context
Rafiq Jamil Textile Industries runs spinning, towel, stitching and finishing divisions. Several hundred staff across shifts. Certified GOTS, OEKO-TEX, BSCI and Sedex — meaning the business is regularly audited on labour practice, and has to be able to evidence how it treats its workforce.
The company could manufacture to international standards. It could not reconstruct, from records, who worked which shift last Tuesday.
The problem
HR ran on paper and WhatsApp.
Attendance was recorded on printed sheets. Leave was requested verbally or in chat. Overtime was agreed between a supervisor and a worker and remembered rather than logged. Shift changes were a message. Approvals happened in conversation, then travelled by hand to whoever needed to know.
The visible symptom was payroll disputes — an employee saying they worked overtime that payroll had no record of, with no way for either party to prove it. The underlying problem was that the company had no auditable record of its own decisions. Not because anyone was careless, but because the process produced no artifact that could be checked afterwards.
Three consequences followed:
- Nothing could be reconstructed. A disputed month meant asking people to remember. Whoever remembered more confidently won.
- Nothing could be delegated safely.Because approval authority lived in people's heads rather than in a rule, work concentrated on whoever was trusted to remember correctly.
- Nothing could be audited. For a business whose certifications depend on demonstrable labour practice, the absence of a record is itself the risk.
Why the previous approach failed
The company had tried spreadsheets before. They failed for a reason worth stating plainly: a spreadsheet records state, not events. It can tell you that an employee has six leave days left. It cannot tell you who approved the four they took, when, on what basis, or what the balance was before the decision was made.
Every question that mattered was a question about history. A spreadsheet stores the present and overwrites the past.
What I did
I led requirements discovery, designed the data model and workflow logic, specified the system, and built it with AI-assisted development. This was not a purchased product configured to fit — it was designed around how the factory actually operates.
Discovery
I mapped the real process before designing anything: sat with the current paper flow, traced what actually happened to a leave request from the moment a worker asked to the moment it affected their pay, and documented where decisions were made and where they evaporated.
The important finding was that the process was not undocumented because it was informal. It was undocumented because nobody had ever been asked to write down who is allowed to approve what. The rules existed — they lived in seniority and habit.
Most of the work was writing those rules down, checking them with the people who held them, and encoding them.
The system
Nineteen tables across six domains — people, attendance and leave, payroll, hiring, discipline, and infrastructure — built as a React and TypeScript frontend against a PostgreSQL database, with Python serverless functions handling backend operations.
The ledger decision
The central architectural decision was to model leave as a transaction ledger rather than a balance field.
A balance field is the obvious design and the wrong one. If an employee's record simply says casual_leave_remaining: 6, then approving a request decrements a number and the reason is gone. Every dispute becomes irreconcilable.
Instead, leave balance is derived: a baseline snapshot plus earn rows minus use rows, each linked to the request that caused it. The balance is always a computation over history, never a stored figure. Any number the system displays can be traced to the decisions that produced it.
This is the difference between a system that reports and a system that can be audited.
Encoded rules
The workflow logic that previously lived in people's judgment is now explicit:
- Five leave categories, each with its own accrual model — some annual, some scaling with length of service, some accruing from compensatory earn rows within a rolling window
- Accrual cycles anchored to each employee's own join-date anniversary rather than a shared calendar year
- A service eligibility gate before any request is accepted
- Half-day handling, applicable to some categories and not others
- Overtime claims requiring supporting documentation where the category demands it, with approved claims writing to the ledger for payroll reconciliation
- Ten roles with granular permissions, including division-scoped access so a divisional manager sees only their own division
Requests that would exceed an employee's balance are flagged and surfaced to the approver, not blocked. This was a deliberate decision. Real operations include justified exceptions; a system that makes them impossible gets worked around, and the workaround is invisible. Better to permit the exception and record who authorised it.
Safeguards
Three engineering decisions worth naming, because each came from a real failure mode:
Idempotency on approvals.A double-click on an approval button could insert two ledger rows for one request, silently corrupting the balance. Fixed structurally with a uniqueness constraint on the request reference, not with a disabled button. UI guards fail; database constraints don't.
Audit writes never block operations. Audit logging is wrapped so that a failed log write cannot prevent the underlying action from completing. The alternative — an approval failing because the audit table was momentarily unavailable — trades a complete log for an unusable system. The log is important; the operation is more important.
No cascading deletes on audited records. Deleting a user nulls the reference rather than removing the history attached to it. Audit integrity survives personnel changes.
The audit trail
Every consequential action writes an entry capturing the action, the module, the affected record, who performed it, their role at the time, and full before-and-after snapshots of the changed data.
The “role at the time” detail matters more than it appears. Roles change. An audit log that resolves the performer's current role misrepresents history — it shows what someone is now, not the authority they held when they acted.
What is live
Authentication and role-based access with per-user permission overrides. Employee records with document management. Daily and bulk attendance entry with biometric punch integration. The full leave lifecycle across all categories. Overtime claims, attendance amendments, shift changes. Monthly payroll runs with payslip generation and mid-month advance recovery. Loans through the complete lifecycle from request to instalment tracking. The hiring pipeline from requisition through offer to onboarding, including automatic employee code generation by division. Disciplinary case management. Task assignment with two deadline models and extension requests. Reporting across all modules. A complete, filterable audit trail.
What is not
Stated plainly, because a case study that claims completeness invites the question it can't survive:
- Leave deduction in payroll is partially implemented. The adjustment structure exists and is wired, but payroll runs do not yet auto-calculate leave deductions — these are entered manually. The automation is specified, not built.
- Inspections and performance reviews are scaffolded. Routes and schema exist; the workflows do not. They are placeholders for a later phase, not working modules.
Outcome
The honest framing: this system is live and in daily use, and the outcome is structural rather than numerical.
What changed is that the company now has a record. Attendance, approvals, overtime, and disciplinary decisions produce artifacts instead of memories. A disputed month is now a query rather than an argument. Approval authority is encoded rather than assumed, which means it can be delegated without being lost.
I have not published time-saved or error-reduction figures because the before-state was paper and chat — there is no baseline to measure against honestly. Producing one would mean inventing it.
What I would do differently
The leave ledger should have been the design from the first version rather than something I arrived at. I started with balance fields because they were simpler, and rebuilt when the first disputed request proved they couldn't answer the question being asked. The lesson generalises: when the questions a business asks are historical, the data model has to store events, not state.
Details of internal policy — specific entitlement figures, division codes, and role structures — are omitted. This case study describes system design and engineering decisions, not the client's internal policy.