Part 1 — The Business of Fashion Wholesale
V Case Study: Butter, and Thinking in Systems
Every principle in this book is being tested against one real codebase: Butter, a multi-tenant apparel-wholesale ERP being built by two people, in Go and vanilla JavaScript, in the open of its own commit history. This chapter does three things. It walks a real day of real people using Butter as it exists right now, screen by screen. It then lists — honestly, without salesmanship — what Butter does not yet do, because knowing what a system lacks is half of knowing the system. And it closes with the mental discipline underneath both halves: how to think about an ERP from first principles, as a system of stocks, flows and invariants, so that you can judge any ERP — Butter, NetSuite, or the one you are about to build — with the same cold eye.
In this chapter
What you need to know first
Butter is a working system, not a finished one. As of this writing it runs one Go server that serves both an API and two buildless JavaScript clients, on Postgres, deployable as a single "cell" — one box running one whole company's instance, the architecture chapter C called the honest starting point. Its domain model covers the product matrix (styles → colorways → SKUs), the vocabularies (brands, seasons, collections, colors, size scales), a four-verb inventory ledger, customers with credit control, suppliers with per-style sourcing terms, a full wholesale order book with allocation and shipping, staged production runs, per-style cost sheets, product images, and an audit trail — with permissions granular to the field level.
Why study a half-built system rather than a finished giant? Because a finished giant hides its decisions under twenty years of sediment. Butter's decisions are young enough that you can still see why each one was made — most are written down next to the code — and its gaps are honest enough to teach the other half of the lesson: what a system chooses not to do yet, and how it chooses.
One disclosure for fairness: this book and Butter share an author's orbit. Treat the case study the way you would treat any vendor's account of their own product — as testimony, to be checked against the principles the rest of the book gave you tools to check it with.
A Tuesday on Butter
The demo tenant Butter ships with is a fictional brand called Harrow & Vale — mid-season: one season shipping, the order book being worked, the next season taking preorders. Here is a compressed Tuesday across four of chapter T's personas, exactly as the running software behaves.
8:40 — Dana, who runs the company. Signs in, lands on the workspace home. Presses ⌘K — Butter's command palette, one box that answers three kinds of question: type a style number and jump to its record, type a page name and go there, type an action ("new style") and do it. A small chip on the palette shows the working season and location — two filters that belong to the person, not to any page, and ride in the URL so a link she pastes to Sam carries the same view of the world she was looking at. She types "west", lands on Westfield Trading's account page, and reads it top to bottom the way a rep would brief her: header and standing, credit, recent orders, ship-to doors, contacts, billing terms. The credit section shows the limit and the dated review ledger behind it — who set it, when, why — because in Butter a credit limit is the cache of a decision ledger, not an editable number. That is chapter K's doctrine, running.
9:15 — Maya, sales. Opens the customers screen: accounts on the left, one account's whole record beside it, and — the working surface — an order panel that opens as a third column against the account. A line in that panel is one style: colorways down, sizes across, every square one SKU, quantities filled wave by wave from the account's prepacks. The style picker searches only the order's season. When she confirms a different order for a held account, Butter refuses: the account is on credit hold, and confirm is the gate — she can keep typing quantities (a hold stops the promise, not the typing), but the promise itself waits for credit. Note what she never saw all morning: a cost column. Field policy strips cost from her rows, her totals, and her sort options — the chapter T masking rule, enforced at the server where it cannot be worked around.
10:30 — Priya, operations. The order book screen: every confirmed order, worked in bulk. She flips between saved cuts of the book — by fill, by window, by buyer's PO — selects fourteen orders entering their window, and allocates them in one action; the system reserves the lesser of need and available per delivery, and the ATS every rep sees drops in the same breath. Then the production side: the cut sheet screen states the arithmetic of chapter U as a plain table — confirmed demand minus open coverage, per SKU, margin riding along from the cost sheet — and what survives her judgment becomes a draft production run, staged dye → cut → sew, each stage's dates back-scheduled from the cancel date. A truck arrived this morning, so she receives 380 units against run stage three: on-hand rises and on-order falls in one transaction, the release capped at what was still outstanding.
2:00 — Sam, product. In the styles ledger — the dense grid — he stages a price change and a lifecycle change on three styles as chips in an edit cart, then posts them as one batch; the server re-reads each header before writing so a stale cached row cannot silently resurrect a value someone else changed. On one style's detail he uploads a product photo: the browser gets a one-shot signed URL and puts the bytes straight to object storage — they never pass through the app server — and the record flips to "uploaded" only after the server verifies the object exists. Later, a question: "who changed the wholesale on HV-104?" The style's activity trail answers — verb, actor, timestamp, old and new values — and had Sam lacked the permission to see prices, the trail would have masked those values too, because history obeys the same field policy as the present.
Nothing above is a mock-up; it is the seeded demo you can click through. But the deeper point is where the behavior lives. The hold refusing a confirm, the masking of cost in totals, the capped release on receiving, the one-transaction receipt — every one is a server-side rule, not a screen convention. The screens could all be rewritten tomorrow (in fact Butter is rewriting its entire frontend beside itself right now) and every one of those behaviors would survive, because they live where invariants belong.
What the walkthrough just showed you
Map the Tuesday against the book, because the case study is the book in miniature:
| What you saw | The principle it embodies | Where taught |
|---|---|---|
| Four inventory verbs; a receipt moving two numbers in one transaction | Ledger-first; stock as ledger + cache, never an editable cell | Ch. 1 |
| Allocation reserving the lesser of need and ATS, per delivery | ATS = on-hand + on-order − allocated, every term with one writer | Ch. 1, U |
| Credit limit as a review ledger; hold gating confirm, not typing | Decisions as dated records; gates at the promise, not the keyboard | Ch. K |
| Cost invisible in rows, totals and sorts for Maya | Field policy enforced at view construction, including aggregates | Ch. T, 5 |
| The cut sheet as a query over two books | The order book and inbound book joined by arithmetic, not meetings | Ch. U |
| Activity trail with actors, masked by the reader's policy | Audit at the grain of intent, written by verbs, in their transaction | Ch. 1, 9 |
| Season and location riding the URL as personal filters | Three seasons at once; unscoped numbers are noise | Ch. U |
What Butter lacks, plainly
Now the other half. Graded against chapter B's full map of what an apparel ERP must eventually be, here is what Butter does not do today. This list is drawn from the project's own tracking, which is a point in its favor — the gaps are recorded, statused and reasoned inside the repo — but a gap recorded is still a gap.
| Missing | What that means in practice | Book chapter |
|---|---|---|
| Invoicing & receivables | The custody chain stops at the shipment. No invoice is born when goods ship, so no AR aging, no statements, no collections — and credit "exposure" can only count open orders, not unpaid bills. Butter's own schema comments admit it: half of exposure is invoices, which do not exist. | K |
| Accounting | No GL, and no decision yet on the classic fork: build a ledger or sync to QuickBooks/Xero. Month-end lives entirely in the accountant's world. | K |
| EDI & retail compliance | No 850/856/810, no routing-guide engine, no GS1-128 label generation. A major retailer cannot be served. The schema is deliberately EDI-literate (buyer PO numbers live at the grain the 850 wants) but literacy is not fluency. | I, 4 |
| Warehouse execution | Ledger yes, WMS no: no pick lists, no carton packing, no scanner workflows, no ASN. The ship verb records the truth but nothing helps the picker produce it. | J |
| Returns & chargebacks | No RA numbers, no deduction ledger, no dispute tracking. The evidence (shipment records) is kept; the fight it arms is not yet built. | I |
| Linesheets & the buyer portal | No generated linesheet, and the buyer's own door is half-hung: a portal invitation exists end to end, but the portal it would open does not. Buyers interact by email and phone, through staff. | H |
| Reporting | Strong operational worklists (the book by fill/window, the cut sheet, the pivot) but no report surface: no sell-through, no season scorecard, no margin-by-account. The reads exist as queries waiting for screens. | 8, L |
| Imports | The landing page offers a data-migration drop that honestly uploads nothing; tenants arrive by hand-carried migration. Chapter 7's tooling is still ahead. | 7 |
| Offline order capture | The trade-show scenario — chapter R's bad-wifi booth — is a stated requirement the new frontend is being designed around, not a shipped capability. | 6, R |
| Smaller but real | Barcode/GTIN assignment (a table waits, nothing writes it), landed-cost actuals flowing back to cost sheets, commissions, price lists per buyer or currency, dye lots, prepack-aware order lines, a partners roster screen (the supplier records exist; the page is a stub). | — |
Read the pattern in the list, not just the items. Butter built inward-facing operational truth first — product, stock, orders, production, credit, audit — and has not yet built most of the edges: the surfaces where outsiders (buyers, majors, factors, accountants) meet the system. That is a defensible order for a two-person team; it is also exactly the moat the incumbents in chapter B keep: nobody churns off a system that holds their EDI relationships. The gap list is therefore also Butter's risk list, in priority order: invoicing first (it completes the money loop), then the pick/pack/ship floor, then the buyer-facing edges.
How to read a gap list
You will evaluate systems for the rest of your career — vendors' demos, your own roadmap, an acquisition target's stack. Three habits, practiced on the table above:
Ask what the gap breaks, not what it is. "No invoicing" sounds like a missing feature; it is actually a broken loop — goods flow out but the money loop never closes inside the system, so every downstream number (exposure, margin, commissions) is partial. Gaps that break loops outrank gaps that lower convenience, always.
Ask whether the schema already knows. A missing screen over a present, well-grained schema (Butter's reporting) is weeks of work. A missing concept — a grain the schema never recorded — is a migration, a backfill, and a re-education (chapter U: no report can recover detail that was never written). Butter's gaps are mostly of the first kind, which is what "the schema is EDI-literate" is worth.
Ask whether the gap is a decision or an accident. A recorded "not yet, because X" — Butter's tracking is full of them — tells you the system knows its own shape. An accidental gap, discovered only when a customer hits it, tells you nobody is holding the map. When a vendor cannot tell you why they lack a feature, the feature is the smaller problem.
First principles: what an ERP is made of
Strip away every screen, every acronym, every module name this book has taught you, and ask what an ERP is, from nothing. You arrive at four irreducible ideas, and everything else in the field is these four wearing costumes:
1. Facts. Things that happened: a buyer promised, goods landed, a box shipped, money arrived. Facts are append-only — the past does not change — so the natural shape of a fact is a ledger entry, and "editing" a fact is really appending a correction. Every chapter that said ledger (stock, credit reviews, audit) was applying this one idea to a different noun.
2. Derived state. Numbers computed from facts: on-hand is the sum of movements; a credit limit is the latest review's conclusion; ATS is arithmetic over three ledgers. Derived state may be cached for speed, but the cache must never become the authority — when cache and ledger disagree, the ledger wins and the cache is rebuilt. Half of all ERP corruption in the wild is a cache that was allowed to become the truth.
3. Invariants. Sentences about the business that must never be false: allocation cannot exceed what a delivery needs; a shipment cannot draw stock that is not allocated; one owner per company; a spent grant spends once. Invariants live in exactly one place — the transaction that could violate them — because an invariant enforced in a screen is a suggestion, and an invariant enforced in two places is two future disagreements about what it means.
4. Authority. Who may cause which facts, and who may see which. Permission grain, record scope, field masking — chapter T's three patterns — are the whole of it. Authority is also a fact-producer: who decided is itself a fact worth appending.
That is the entire theory. A style master is facts about products. An order book is facts about promises plus invariants about their consistency. A WMS is a fact-capture device for the warehouse's four verbs. EDI is fact exchange with someone else's system. When a salesperson demos you a module, translate it into these four words and you will see instantly what it really does — and what it merely displays.
Stocks, flows and invariants
Systems thinking proper — the discipline Donella Meadows wrote the standard little book about — models any system as stocks (quantities that accumulate), flows (rates that fill or drain them), and feedback loops (stocks influencing their own flows). An apparel company is almost embarrassingly literal about it: warehouse units are a stock; receiving and shipping are its flows; the order book is a stock of promises filled by market and drained by fulfillment; cash is the stock everyone dies of, filled by collections and drained by everything.
The reason this framing earns its place in an engineering book: your database schema is a stock-and-flow diagram whether you drew one or not. Every table is either a stock (current state), a flow (events), or a decision record. The classic ERP design failure is storing a stock and discarding its flows — an on_hand column with no movement ledger — which is a bathtub whose water level you know but whose taps and drains you cannot see. Every reconciliation nightmare in chapter 9 traces to a stock without flows; every clean audit traces to flows with derived stocks.
Feedback loops complete the picture, because the company runs on them. Sell-through feeds the next line plan (CLEAR → PLAN, chapter U). ATS feeds the reps' selling, which drains ATS. A late factory feeds allocation decisions which feed fill rates which feed next season's buys from that retailer. The ERP is where those loops become visible — or where they stay invisible and the company steers by feel. When an owner says "the system paid for itself," they almost always mean one specific loop became visible in time to act on it.
Before adding any table, classify it out loud: stock, flow, or decision? If a stock — where are its flows, and can it be rebuilt from them? If a flow — is it append-only, and does it carry its actor? If a decision — does it record who and why? Fifteen seconds of taxonomy prevents years of archaeology.
Six disciplines of systems thinking
Principles are cheap when stated and expensive when practiced. These six are the practice, each one visible in the case study you just walked:
1. One fact, one home. Every fact has exactly one authoritative location; everything else is a cache or a view. Butter's credit limit (ledger decides, account column caches) and its refusal to keep an editable stock number are this discipline. The test: for any number on any screen, can you name its home in one sentence?
2. Make the wrong thing unrepresentable, then unreachable, then audited — in that order. Best: the schema cannot express the error (a SKU cannot point at another style's colorway if the keys forbid it). Next: the transaction refuses it (allocation capped at need). Last resort: it can happen but leaves a trail. Screens are the fourth line of defense, and the only one users can route around.
3. Derive, don't declare. Wherever a value can be computed from facts, compute it. A stored number that could have been derived is a future disagreement with its own inputs. (Corollary — snapshot deliberately: an invoice's prices are facts about the moment of sale, not derivations from the current price list. Knowing which of the two you are looking at is the whole skill.)
4. Design the seams before the rooms. The interfaces between subsystems — order book to inventory, production to inventory, app to auth — outlive every implementation behind them. Butter's inventory exposes transactional seams that orders and production write through, which is why its frontend can be rewritten wholesale (it is being rewritten, beside itself, right now) without touching a single invariant. Chapter 4 is this discipline applied to other people's systems.
5. Gall's law, taken literally. A complex system that works evolves from a simple system that worked; a complex system designed from scratch does not work and cannot be patched into working. The build order this book keeps insisting on — ledger, then orders, then production, then edges — is not modesty. It is the only known route. Every stage of Butter that works was once a smaller stage that worked.
6. The map is not the territory — reconcile anyway. The database says 24; the shelf says 23. Systems thinking does not pretend the model is reality; it builds the comparison into the system (counts as first-class deltas, chapter J) and treats every persistent divergence as information about a leaking process, not an annoyance to overwrite.
Decisions as records: the refusal
One more Butter habit deserves promotion to principle, because almost nobody does it and everybody should. Butter's repository records not only what it built but what it refused to build, with reasons, permanently: no editable stock cells (a fifth verb would move stock without a ledger line); no company-wide activity feed (a timeline read through its entity inherits the entity's permissions; a global feed would need its own); no uniqueness on buyers' PO numbers (it would refuse the trade's ordinary reuse to catch a typo); no renaming of a permission key after its module was replaced (every custom role granting the old name would silently lose it). Each refusal is a fence with its reason nailed to it.
Why this matters more than it looks: a system is defined as much by its refusals as its features. Features tell you what it does; refusals tell you what it understands. The next engineer who arrives at the fence does not have to re-litigate the field — they read the reason, and either the world has changed (remove the fence, on the record) or it has not (walk away in seconds). Compare the usual alternative: undocumented absence, re-argued annually, occasionally "fixed" by someone who could not know it was load-bearing. Write your refusals down. It is the cheapest architectural documentation that exists, and the only kind that prevents work instead of describing it.
What this means for your ERP
- Steal the walkthrough as your acceptance test. Whatever you build or buy, it should survive Butter's Tuesday: a palette-fast path to any record, credit gating the promise, masked costs that stay masked in totals, bulk allocation, a cut sheet that is a query, receipts that move two numbers in one transaction, history that obeys the reader's permissions.
- Keep your own gap list, statused and reasoned, and grade it by broken loops → missing screens → conveniences. A team that can recite what it lacks and why is ahead of a team that can only demo what it has.
- Classify every table: stock, flow, or decision. Stocks need flows behind them; flows need actors and append-only discipline; decisions need who and why. Refuse to add a table that will not say which it is.
- Put invariants in transactions, authority in queries, masking in view construction — and treat screens as replaceable clothing over all three. If a rule would not survive a frontend rewrite, it was never a rule.
- Sequence by Gall: the ledger before the orders, the orders before the edges, and every stage shippable and true before the next begins. Resist the demo-driven urge to build the impressive surfaces first; the surfaces are the easy part, and they are worthless over a hollow middle.
- Record your refusals next to your features, with reasons. Six months from now, the most valuable sentence in your repository will be one that starts with "We deliberately do not."
Field notes & further reading
- The Butter codebase explorer: an interactive map of the actual system this chapter describes — every table, route, module, flow, and the to-build list with its statuses and refusals. The gap table above is drawn from it. (It sits behind a password; the walkthrough personas and screens are the seeded demo workspace.)
- Donella Meadows, Thinking in Systems: A Primer: the standard short introduction to stocks, flows and feedback loops. Read it in an afternoon; you will never look at an
on_handcolumn the same way. - John Gall, Systemantics: the source of Gall's law and a bitterly funny catalog of how designed-from-scratch systems fail. Treat it as an inoculation, not a syllabus.
- Architecture Decision Records: the lightweight convention for writing decisions (and refusals) down next to the code. Butter's decision log is this pattern; chapter 12's decision log is this book's.
- Chapters 1 (the ledger), B (the full ERP map), and N (implementation) are this chapter's three nearest neighbors: the theory, the territory, and the rollout.
1. Run the Tuesday yourself. Get into a Butter demo workspace (or the nearest system you can access) and replay all four personas' mornings: find an account through search, read its credit story, attempt to confirm an order for a held account, allocate something, receive something, change a price, then pull the record's history. At each step, write down where the behavior lives — schema, transaction, query, or screen — and what would survive a frontend rewrite. Anything that lives only in a screen, flag.
2. Write your gap list and your refusals. For your own project (or the vendor you are evaluating): produce the honest table this chapter produced for Butter — what is missing, what it breaks, whether the schema already knows — and then a second list of at least five things you deliberately will not build, each with its reason. If you cannot produce the second list, you have not made decisions yet; you have only run out of time.
When you are done you should have: a persona replay log with each behavior's true home; a graded gap list for one real system; and a refusals document whose reasons a stranger could evaluate without asking you anything.