Case 02 · Under NDA
The system a company spends its day inside
An installation company outgrew its spreadsheets, then discovered that off-the-shelf software did not fit the way it actually works. This is what replaced it: one platform covering sales, planning, field execution, logistics, finance and a customer portal — and the decisions underneath it.
- Role
- Sole developer — domain model to deploys
- Running since
- Early 2026, in daily production use
- Users
- Dozens of people daily, plus a customer portal
- Stack
- React · TypeScript · Postgres · Supabase
Context
What it replaced
Every company that reaches a certain size has the same three tools: a spreadsheet nobody dares touch, a shared mailbox, and one person who remembers how it all fits together. It works until it does not — usually on the day that person is on holiday.
The obvious answer is to buy something. That fails for a specific reason: packaged CRM assumes a sales process, packaged ERP assumes a factory, and a company that installs things at a customer’s address is neither. You end up bending the business to fit the software, and the spreadsheet quietly comes back to hold whatever did not fit.
So the brief was not “digitise this”. It was: model what this company actually does, and make every role’s day shorter rather than better documented.
Scope
Five layers, one database
every layer owns its colour
-
Leads
- qualification
- source attribution
- follow-up
-
Quotes
- line pricing
- VAT regimes
- digital signing
-
Site
- planning
- field execution
- stock
-
Invoices
- reconciliation
- VAT returns
- collections
-
Portal
- customer & partner
- roles & rights
- row-level security
A job moves left to right through these, and every handover is a place where a spreadsheet used to live. Putting them on one database is what removes the re-typing — and what makes access control the hardest problem on the page.
Engineering
Decisions that mattered
and what the alternative costs
A quote is a contract, so its lines are immutable
A quote that has been signed cannot change — not its prices, not its VAT regime, not its line order. Editing one creates a new revision and leaves the signed version exactly as the customer saw it. The invoice is then generated from the signed lines rather than re-derived from current prices.
Why not the other way
The alternative looks simpler for about a year, until a price list update silently rewrites what a customer agreed to. That is not a bug you find in testing; you find it in a dispute.
The portal sits on the same database, so RLS is the floor
Customers and partners read their own projects out of the same tables the finance team works in. There is no mirror database and no sync job. Every table the portal touches carries a row-level policy, and the policy — not the query, not the component — decides what comes back.
Why not the other way
A separate read model is a second source of truth, and a second source of truth drifts. Keeping one database means a policy is the only thing standing between a customer and the rest of the company, which is exactly the pressure it should be under. I test them by signing in as the wrong user and watching the request fail.
Money invariants are checked by the database, on a schedule
Invoice totals must equal the sum of their lines. VAT must match the regime on the signed quote. A payment must never reconcile against an invoice from another customer. These are scheduled checks against the live data, and they alert only when the state changes — first time wrong, and again when it goes right.
Why not the other way
Financial bugs are silent. Nothing throws; a number is simply wrong, and stays wrong until someone recounts by hand. An alarm that fires every fifteen minutes gets muted within a day and is worthless after that, so it fires on transitions instead.
Each role gets its own screen, not a filtered version of one screen
The planner, the field team, the warehouse and the bookkeeper look at the same project through four different interfaces. The field app shows today, this address, these materials, this photo. The bookkeeper never sees a planning grid.
Why not the other way
One screen with everything hidden behind permissions is how enterprise software becomes something people avoid. The cost of building four focused screens is real; the cost of one screen nobody wants to open is higher, and it shows up as a spreadsheet reappearing on someone’s desktop.
Process
How the work goes
- 01
Sit with the work before modelling it
The domain model comes out of watching how a job actually moves, not out of a requirements document. The words people already use become the table names.
- 02
Ship one layer end to end
Quoting, all the way from lead to signed PDF, before anything else is started. A layer that is live teaches you more in a week than a quarter of planning.
- 03
Put the rules in the database
Constraints, policies and invariant checks, because application code is where rules go to be forgotten. If it matters, it should refuse the write.
- 04
Own the deploy and the Saturday phone call
Architecture, migrations, deploys and the incident are the same job. Knowing you will be the one woken up changes what you are willing to ship.