Manufacturing ERP
A system of record I scoped and built for a manufacturing client — operational data in, immutable financial reporting out.
Stack

A custom internal system of record I scoped and built for a manufacturing client. The discovery surfaced the real requirement — figures had to be trustworthy and history had to be immutable — and it turns day-to-day operational data into per-day and per-month financial reporting on that basis. The load-bearing decision is time-effective pricing — every rate (prices, costs, wages) is an effective-dated history, so figures are computed from the values in effect on each date and editing a rate today never rewrites past months. Built on Next.js + Neon Postgres behind Google Sign-In, with capability-based access control enforced server-side and an append-only audit log written in the same transaction as every change.
The problem
The naïve design stores a reference to each priced item and joins to current rates when a report runs. The moment a rate changes, every past report silently rewrites itself — last month's figures move because someone edited a price today. For a financial record, that's the bug, not the design. The real requirement was numbers that stay put: history has to be immutable, while rates still have to be free to change going forward.
What I built
- A correctness-first engine. Every amount is integer cents — never floats. The in-app (JavaScript) and in-database (SQL) computations are written to agree bit-for-bit through exact integer arithmetic, so a figure never depends on which path produced it.
- Time-effective rates. Every rate — prices, costs, wages — is an effective-dated history keyed by the date it takes effect. Any figure is derived from the values that were in effect on its own date, so editing a rate today touches only future dates, while a deliberate historical correction re-derives the affected period cleanly. That is the immutability guarantee, in one mechanism.
- Access control that holds. Sign-in is gated to an allow-list; authorization is capability-based and resolved server-side on every action and every sensitive read (3 roles, 8 capabilities, with per-user overrides). Sensitive fields are withheld entirely from roles that lack the capability — not hidden in the UI, never sent.
- An audit log you can't forget to write. Every change lands in an append-only log written inside the same database transaction as the change itself — a record exists if and only if the change committed.
Architecture
One Next.js 16 app on Vercel, one Neon Postgres database — single-vendor on purpose to keep an internal tool cheap to run and simple to hand off. Same engineering discipline as the SaaS products (ADRs, hooks, CI), deliberately scoped down to what a one-app internal tool warrants.
The import "server-only"boundary is a compile-time stand-in for row-level security: any browser bundle that tries to import the DB client fails the build. RLS was dropped on purpose — there is no anonymous browser surface to defend, and the threat model didn't justify the complexity. Every such divergence from the reference repo is written down in an ADR.
What I learned
A production system I built for a real client — pinning down the requirement, then shipping it. A few things it taught me:
- Immutability has a shape, and you have to match it. I started with snapshot-on-write — copy the price onto each row at entry — and reworked it mid-build to fully time-effective pricing once it was clear historical back-fill corrections needed to re-derive a past period cleanly. The reversal is written down in the effective-pricing migration and the ADRs; the right temporal model was worth changing course for.
- Choosing where AI shouldn't live. There is no LLM anywhere in this product — a financial system of record demands determinism, and an integer-cents profit engine is exactly where a language model does not belong. The AI was in how it was built, not in what it runs.
- Build for the maintainer, not the builder. Single vendor, plain-SQL migrations, configuration editable through the UI — each a deliberate trade against the "correct" data-warehouse pattern, because long-term maintainability and a clean handoff were the actual requirements, not architectural purity.