ONEERP: Building an ERP for the Garments Industry
A unified ERP for 12 departments across 8 group companies. React, TypeScript, RTK Query, and a component library that became the real leverage point.
The Problem
Garment manufacturing runs on paperwork in a way that surprises engineers who've only worked in consumer software. Production orders travel between cutting, sewing, and finishing on printed sheets that get signed, photocopied, and physically walked across the factory. Inventory counts are reconciled by hand at the end of every shift. Commercial documentation — letters of credit, packing lists, shipment schedules — lives in Excel files on shared drives that nobody fully trusts. Every department had built its own workaround over the years, and none of those workarounds talked to each other. Reconciling data across departments meant someone manually cross-referencing three or four spreadsheets, which took hours on a good day and days when a shipment deadline was close.
The factory floor ran on low-end devices — aging Windows machines with 4GB of RAM, integrated graphics, browsers two major versions behind, connected over a factory LAN that dropped packets during peak production hours. Any frontend we built had to render and stay responsive on that hardware, on that network, while being used by people who didn't have time to wait for a spinner. That single constraint — perform on bad hardware over a bad network — shaped almost every architectural decision, from the choice of charting library (Chart.js over ECharts for smaller bundle and canvas rendering) to the granularity of API response payloads.
What We Built
A React and TypeScript frontend on top of a reusable in-house component library, with RTK Query handling data fetching and caching. RTK Query was chosen over a hand-rolled data layer because in a system with hundreds of screens sharing overlapping slices of the same data, request deduplication and automatic background refetching are baseline requirements, not nice-to-haves. Role-based dashboards used Chart.js for visualizations — line charts for trends, bar charts for department comparisons, gauge widgets for KPI targets. Each department saw only the data it needed: inventory got stock levels and reorder alerts, production got line efficiency and downtime, leadership got a consolidated KPI view. Role-based scoping was enforced at three layers (route, component, API) so no path could surface unauthorized data.
The component library was the real multiplier. It grew to 40+ accessible, theme-able components — form inputs, data tables with sorting and pagination, modals, toasts, permission-aware buttons — all built once and documented with usage examples. Once that foundation existed, the marginal cost of shipping a new module dropped from weeks to days, because the work was no longer "build UI primitives and compose them" but just "compose existing primitives around new business logic." The library also enforced consistency as a side effect: because every team used the same components, the visual and interaction language stayed uniform without anyone writing a design-system enforcement policy.
Outcome and Lessons
The platform was adopted across 8 group companies, each with its own configuration but sharing the same codebase. Multi-tenancy was achieved through runtime configuration rather than forking — each tenant had its own theme, permission sets, and enabled modules, loaded from a tenant configuration at startup. Report generation that used to take a finance clerk hours of copying numbers between spreadsheets now completed in seconds, generated from the same canonical data the operational teams were using. Month-end reconciliation time dropped by roughly 60% in the first quarter — not because the system was faster at calculation, but because the data was consistent across departments for the first time.
The biggest lesson was about premature abstraction. The first attempt at a generic module template tried to anticipate every future use case and ended up fighting every new requirement. I now write the third version, not the first: build something concrete and specific, let a second implementation reveal the real patterns, and only then extract the abstraction. The cost of the first version being slightly duplicative is almost always lower than the cost of an abstraction that doesn't fit the real problem, because duplication can be cleaned up later but a wrong abstraction resists cleanup and bends every new requirement out of shape.
