Case study
GreenEarth Agro Industries
Eight modules replacing a folder of spreadsheets, so executives overseas can see an agro-processing operation in Ajumako as it actually runs.
Record
- Client
- GreenEarth Agro Industries Limited
- Work
- Design and build, front to back
- Period
- August 2025 to present
- Stack
- Next.js App Router, NestJS, GraphQL (code-first), PostgreSQL, TypeORM, Tailwind v4
- Availability
- Runs on site. No public URL.
The problem
The operation ran on Excel. Stock sat in one workbook, procurement in another, farm activity in a third, and they disagreed with each other. Reconciling them was somebody's job every week, and the reconciliation was itself a spreadsheet.
The people who needed the numbers most were not in Ghana. What reached them had been summarised twice and was several days old, which meant every decision about buying, pricing, or production was made against a picture of the factory that no longer existed.
The failure was not that the spreadsheets were wrong. It was that nobody could say which one was right.
Plate 01
What was built
Eight modules, one record
Inventory: live stock, batch and lot tracking, minimum-stock alerts, storage locations.
Production control: run management for oil and pallet lines, input to output mapping, cost per run.
Procurement and sales: supplier and buyer records, purchase orders, sales orders, payment tracking.
Farm operations: crop monitoring, activity logs, worker attendance, input costs.
Workforce: attendance, payroll, salary disbursement, shift scheduling.
Finance: cost of production, profit and loss, two currencies (GHS and USD).
Executive reporting: daily, weekly and monthly KPI dashboards, exportable.
Communication: automated SMS and email notifications, task management.
What shaped the build
The network is not a given. Field and farm work happens where connectivity drops without warning, so capture writes to IndexedDB first and synchronises in the background. A lost signal costs nobody their morning's entries.
The decision-makers are elsewhere. Role-based access and per-role dashboards exist because a CEO abroad, a factory manager and a field worker need different slices of the same records. Every state change is written to an audit log, which is what makes a remote approval defensible later.
A batch has to stay identifiable. Goods received carry batch identity from the receiving register through production into finished output. In agro-processing that is not bookkeeping, it is the ability to answer where a bad lot came from.
It runs on site, not on the internet. The system is deployed locally at Ajumako with a hosted database. For a single site on unreliable public bandwidth that is the correct arrangement: the factory keeps working when the connection does not, and the executives abroad still read from the same database. It is also why there is no link on this page to click.
Plate 02
Plate 03
Outcome
| Measure | Before | After |
|---|---|---|
| Operational data discrepancies | Baseline | Down 80% |
| Manual reporting effort | ~20 hrs / week | Recovered |
| Source of truth for stock | 3+ workbooks | 1 system |
| Executive view of the factory | Days old | Live |
| Staff working in the system | 0 | 50+ |
In hindsight
The entities carry both their database mapping and their GraphQL type on the same class. It ships, and it was fast to write, but it means the shape of a table and the shape of the public API are the same decision. Renaming a column is an API change whether you meant it to be or not.
On the next system I separated them: persistence models on one side, API types on the other, with a deliberate mapping between. It costs a file per module and buys the freedom to change either side alone.
Have an operation that runs on spreadsheets?
The first conversation is free and usually short.
Start a project