An ERP with AI invoice capture and an audit trail that holds
Eighteen modules replacing a legacy desktop ledger, with supplier invoices captured by photograph, approvals that gate revenue, and a field-level audit log nobody can rewrite.

- operational modules
- 18
- of history recovered
- 3 yrs
- AI providers, auto-failover
- 2
The problem
The books lived in a proprietary desktop accounting application: one machine, one copy, no remote access, and an export path that produced nothing usable. Three years of purchase and sales history were effectively trapped inside it.
Day to day, supplier invoices arrived on paper and were re-keyed by hand into both the ledger and a stock spreadsheet. The two disagreed constantly. When a figure was wrong there was no way to establish who had changed it, or when, or what it had been before.
Anything that replaced it had to be usable on a phone in a warehouse, work for staff who should not see the accounting modules, and, critically, be trustworthy enough to run the actual books on.
What we built
Recovering the history before building anything
The desktop files turned out to be SQLite databases whose records were protobuf blobs. We wrote a decoder for them and recovered inventory, customers, suppliers and the full purchase and sales invoice history with line items, quantities and rates, rebuilding customer names from the audit trail where the live records had been blanked, and merging entities across books that used different identifiers. The date epoch was calibrated against a known paper invoice and verified: one document decoded to the correct date with all seven line items matching the printed amounts exactly. Loads were made idempotent, so re-running corrects rather than duplicates.
Capture by camera, confirmation by human
Photograph a supplier invoice and a vision model reads the line items. Nothing is written on that basis alone: the extraction is presented for review, and only the person's confirmation creates the purchase invoice and moves stock quantities, item cost prices and the supplier balance. The scanner runs across multiple API keys per provider, rotating on a schedule and advancing automatically on rate limit, quota or auth failure before falling through to a second provider. With no keys configured it says so plainly instead of failing silently.
Approvals that actually gate the numbers
Invoices move draft → pending review → approved → unpaid → partial → paid, and the approval step is not decorative: drafts and invoices still under review are deliberately excluded from revenue, purchases and outstanding totals on both the lists and the dashboard, because nothing is owed until an invoice is approved.
An audit trail that survives the application
Every write goes through a single data-access layer that stamps the acting user and appends a field-level diff to an append-only table. That table grants staff select and insert only (there is no update or delete policy), so the trail cannot be rewritten through the API even by the application itself. Deleting sets a timestamp rather than destroying the row; only an administrator can purge, and the audit entry outlives the record.
Permissions enforced three times over
Roles are a grid of eighteen modules against five actions, editable per role and overridable per person. Navigation hides what you cannot view, routes redirect, the write layer checks before touching the database, and a trigger discards any change a non-administrator makes to their own role or permissions. The layers above the database are the application being tidy; the trigger is what actually stops privilege escalation.

The outcome
Three years of trapped history recovered and reconciled against paper records, with the trading and accounting books finally in one place.
Invoice entry became a review task rather than a data-entry task. The model proposes, a person confirms, and the confirmation is what moves stock and balances.
Every change across the system is attributable, filterable and exportable, and the delete button stopped being destructive.
Installed as a progressive web app, it runs full-screen from the home screen and still opens cached pages when the warehouse loses signal.
Built with
- React
- Supabase
- PostgreSQL
- Row-level security
- Claude
- OpenAI
- Edge Functions
- Vercel
- Python
Where this is applied
Page copy drafted with AI assistance. Skofi is accountable for it. Editorial policy
Have something shaped like this?
If any part of the above resembles a problem you are living with, a short call is the fastest way to find out whether it is the same problem underneath.
What happens on the call
- 15 minutes, no deck
- We map the process on the call
- A written scope, or an honest no
