Case study 06 · Retail operations software · Build, rebuild, run
Seven years running a retail group's entire operation, then rebuilding it from two diverging copies into one platform
A platform where opening a new store is a settings screen, not a third copy.
Written by Hesham Ghoniem, lead engineer · ·
- Sector
- Multi-site specialist retail · regulated sale
- Engagement
- Long-running · design, build, rebuild, maintain
- Scope
- Full operation: shop floor, back office, legal paperwork, payroll, public websites
- Stack
- Laravel 9
- Vue 3
- Inertia
- Vite
- MySQL
- PHP 8.0
- 160 — Files that had silently drifted apart between two stores
- 62 — Database tables rebuilt from scratch
- 6 — Checks between a stranger and a printed legal contract
- 9 — Legal documents generated from one click
- 193 — Screens in the system being replaced
Overview
A copy is not a shared system. It is a system that has started drifting.
A multi-site specialist retailer was running each of its stores on its own private copy of the same software. It worked, and for years that was enough. But a copy is not a shared system: it is a system that has started drifting. Every fix had to be made twice, every new rule typed twice, and by the time we measured it, 160 files had diverged between the two copies. The same button no longer did the same thing in both stores, and nobody could say exactly why.
The business also could not grow sideways. Opening a third store meant a third copy, a third set of fixes, and a third opportunity for the answer to diverge again.
We built the replacement: a single platform where every record knows which store it belongs to, where the commercial rules a manager cares about live in settings rather than in code, and where six separate checks stand between a person at a keyboard and a printed legal contract. The old systems kept running the whole time.
The challenges
Six problems, hidden inside a system that still worked.
Two systems that had stopped being the same system
Each store's system ran around 165 top-level screens over more than 500 program files. A file-by-file comparison across all of them found 160 that had diverged: 135 that existed in both copies but no longer matched, and 25 that existed in only one.
That number is the whole problem in one figure. Nobody could confidently describe the system's behaviour, because the system had two behaviours. Every improvement cost double, carried the risk of fixing one store while breaking the other, and widened the gap a little further.
Left alone, the two copies would eventually have become two products with one support contract.
Every store had its own commercial rules, and the software didn't know that
Commission brackets, store fees, tax exemption, approved vaccine suppliers, accepted financing companies, which staff can see which screen: all of it differed store to store. In the old systems those differences lived inside the code.
So changing a commission rate was a developer task, not a manager task. The business could not adjust its own commercial terms without booking engineering time, which meant in practice that terms got adjusted late, or not at all.
The paperwork is the business, and it was fragile
Selling a single animal in this sector is a regulated transaction. It legally requires a purchase agreement under regional statute, an official veterinary health certificate, a complete vaccine record naming every manufacturer and batch number, a supplier source disclosure and a registration application, among others.
One of the tools producing this bundle was a single page 1,758 lines long that printed nine documents back to back. It worked, and it had worked for years.
But a one-line mistake in that file is not a bug report, it is a legal exposure. And because it was one long page rather than a set of parts, nobody could safely change any of it.
Shared computers on a busy shop floor
This is a retail counter, not an office. Three things are true at once:
- Staff step away mid-sale with a customer standing in front of them.
- A salesperson cannot type a long password while a customer watches.
- Contracts must never be generated from somebody's kitchen table.
A conventional login screen solves none of those three problems, and the third one is a compliance issue rather than a convenience one.
Sourcing was recorded from whatever was written on the delivery note
Every animal must be traceable to a licensed breeder, identified by their national licence number. In the old systems that number was typed in by hand from paperwork that arrived with the animal.
Typed numbers are wrong sometimes. A wrong licence number on a contract is a wrong legal disclosure, and it is not discovered at the time. It is discovered later, by an inspector.
Nothing recorded who changed what
The old systems stored the current state of a record and nothing else. When a breeder assignment turned out to be wrong, there was no way to establish when it changed, or who changed it.
In a regulated sale, "we don't know" is the worst available answer.
Three generations · the contract packet
The 75,000-byte file that became zero bytes
The contract packet, the nine legal documents printed at the counter when an animal is sold, exists in the repositories in three generations. They were not planned as a sequence. Each one is what we understood at the time, and together they are an unusually honest record of a system being brought under control.
Three bars shrinking to nothing. Generation one, 1,758 lines, standalone. Generation two, 75,854 bytes, after removing its own login and permissions. Generation three, 0 bytes, drawn as an empty outline, after removing the last copy of the contract wording.
Generation one
A standalone tool, 1,758 lines long.
One page printing nine documents back to back with page-break styling. Its own login page, its own database queries and its own copy of every piece of contract wording. It worked, and it was the single riskiest file in the business.
Generation two
A shared login, and its own copy of the wording.
Rebuilt for the second store with proper routing, and with the important change: it stopped being standalone. It borrowed the staff system's login, its permission matrix and its page-access rules rather than keeping its own, so there was one source of truth for who is allowed to do what. But the contract wording was still its own copy: a view file of 75,854 bytes.
Generation three
Zero bytes, on purpose.
The third store's version of that same file is 0 bytes. Not deleted, not missing: empty, on purpose. Its controller is fifty-six lines long, and all it does is gather the data and hand over to the staff system's own contract template, the one place the wording now lives. Change a clause once and every store prints the new clause.
That is the arc of the whole engagement in three files.
1,758 lines → a shared login → zero bytes. Each generation removed a copy of something. The third generation is the point at which the contract wording stopped being duplicated anywhere in the business.
And it does one more thing.
While assembling the contract, generation three quietly writes a fresh session token into the supplier transparency network's database. The salesperson can tap straight through from the contract screen to the breeder's profile, already signed in, without typing a password in front of the customer. Two systems, one continuous conversation.
Related case study: Supplier Transparency Network
Where the token leads: the supplier profile on the tablet.
A file that shrinks to nothing is usually a mistake. This one is the deliverable.
What we built
Five chapters, one merge we advised against, and a store in every row.
Chapter one · The legacy estate
The back office that ran the shop
Long before any rebuild, the system customers never see was doing the real work: stock and housing inventory with fifteen-digit identifier validation, full medical records, breeder, broker and transporter records, the sales pipeline, financing applications across five lenders, customer records and post-sale follow-up.
It ran the staff side too: weekly clock-in and clock-out, sales commissions, closer commissions, days owed, holiday requests and approvals. And it held legally mandated holding notices, treatment schedules, grooming lists and the content management for the public website: 193 screens in all.
This is the system the rebuild had to replace, and its size is the reason the rebuild was staged rather than attempted in one go.
It also printed everything the shop uses
- Cage cards, four to a page
- Quick identification cards, sixteen to a page
- Exam sheets
- Medication sheets
- Source certificates
- Vet coupons
- Insurance recommendation sheets
Chapter two · Advice against the obvious
The decision not to merge
The obvious move with two diverged copies is to merge them. We advised against it. Merging two systems that have drifted 160 files apart produces a third system that nobody trusts and that still contains both sets of assumptions.
Instead we rebuilt the foundation underneath. We catalogued what the business actually holds and rebuilt it as 62 clean database tables.
What the business actually holds
- Animals
- Breeders
- Brokers
- Transporters
- Vaccines and their manufacturers
- Medical repairs
- Staff
- Roles
- Page permissions
- Fees
- Commission brackets
- Payment methods
- Sale types
- Salespeople
Chapter three · Multi-store by design
The decision that makes it multi-store
Every single record carries the store it belongs to. Not a setting read at start-up, not a filter applied in one place and forgotten in another: the store is part of the record. 23 of the 62 tables exist purely to link records together, and 16 of those bind a record to its store: which vaccine suppliers this store approves, which payment methods it accepts, which staff work there, which computers it trusts.
That one modelling decision is what turns a system into a platform. The code never has to ask which store it is running for, because every row already knows.
Side by side. On the left, four rows with no store attached, an approved vaccine supplier, an accepted payment method, a staff member and a trusted computer, which every query has to remember to filter. On the right, the same four rows, each carrying the store it belongs to: store A or store B.
Chapter four · Settings, not code
The rules moved out of the code
Commission brackets became a table with a percentage and a price band, editable by a manager. Store fees became records with their own tax-exempt flag. Approved vaccine suppliers became a checklist. Accepted payment and financing companies became a per-store selection. Sale types carry their own flags controlling which reports they appear in.
Adding a tenth store now touches no code at all.
Chapter five · Verified sourcing
Sourcing verified against the national register
The most consequential integration is invisible. When staff type a breeder's name, the platform searches a live mirror of the national licensing authority's register of licensed breeders, dealers and carriers. CompertMe's own market-intelligence platform maintains that mirror, filtered to currently active licences only.
Staff pick the real licensed entity from the real register. They do not retype a number off a delivery note.
And when the breeder is not yet verified, the animal is filed against a pending record and flagged rather than blocked. A manager promotes it later, and who did it and when is written to an audit table. The shop floor is never stopped by an administrative gap, and the gap is never lost.
Related case study: Competitor Price Intelligence Platform
Where the register behind this search comes from.
Technical deep-dive · the security model
Inside the security model: six gates on a shop floor
Most business software authenticates a person. This platform has to authenticate a person, at a store, on a machine, on premises, and then keep doing it while that person walks away from the counter.
A person at a keyboard passes six gates in order, right person, right store, right machine, password, second factor and unlocked screen, before a printed legal contract comes out. An arrow branches off the third gate to an alert for a manager, which fires when the machine check refuses the login.
Gate one
The username carries the store.
Staff sign in as name@store. The store is not a dropdown to be selected and mis-selected; it is part of the credential.
Gate two
The link is checked.
The person must be attached to that store. A valid employee of one store cannot sign in to another, even with a correct password.
Gate three
The machine is checked.
The request's network address must appear in the union of two allow-lists: the addresses that store trusts, and the addresses that individual is personally trusted from. Fail this and the login is refused and a notification is dispatched naming the person and the attempt. Silence is not an option here: an unauthorised login attempt is exactly the event nobody would think to look for.
Gate four
The password.
In the ordinary way, and only after the first three have passed. A correct password on an unapproved machine never reaches the account.
Gate five
A second factor.
A time-based code from Google Authenticator, or a one-time code sent by SMS.
Gate six
The screen lock.
This one exists because of the shop floor rather than because of security theory. Sessions carry a screen-locked flag and a four-digit PIN that is deliberately not the password. Step away, and the screen locks; come back, and four digits let you in. A salesperson never types a password in front of a customer, and an unattended counter terminal is not an open session.
And the exception is a workflow, not a back door.
Staff sometimes genuinely need access from outside the shop. Rather than handing out a bypass, there is a request table: who is asking, from which address, from what location, a written reason, which store, and a status of pending, approved or rejected.
An approval records who granted it and when, and issues a trusted-address entry that can carry an expiry date, so temporary access is temporary by construction rather than by someone remembering to revoke it.
Most systems ask "is this the right password?" This one asks "is this the right person, at the right shop, on the right machine, right now?"
How we work
Eight rules for replacing a system the business can't switch off.
Read the whole thing before replacing any of it
We catalogued all 543 program files across the two diverged copies and listed every behaviour that differed, before writing a line of the replacement. You cannot safely replace what you have not fully read, and the 160-file figure only exists because somebody counted.
Advise against the obvious plan when it is wrong
The client's expectation was a merge. We recommended a rebuild of the foundation instead, and explained why merging two drifted systems produces a third. That decision belonged to the client; our job was to make sure it was an informed decision rather than a default one.
Model the data before designing a single screen
The 62-table structure came first and was checked against real shop paperwork: actual contracts, actual health certificates, actual vaccine records. Screens are cheap to change. A wrong data model is not.
Make the store part of the record, not part of the query
Multi-tenancy enforced by remembering to filter is multi-tenancy that fails the first time someone forgets. Put the tenant in the row.
Commercial rules belong to the business, not to us
Anything a manager might reasonably want to change, from rates and fees to approved suppliers, accepted payment methods and permissions, lives in a settings screen. If changing a commission rate requires a deployment, we have built the wrong thing.
Security is the first thing built, not the last
The six-gate login existed before the screens behind it. Retrofitting a security model onto a finished application is how gaps get left.
Never stop the shop
Every old system stayed live and supported throughout. Nothing was switched off until its replacement was demonstrably doing the same job better.
Record who did it
Anywhere a record can be changed in a way that later matters legally, the change is attributed. "We don't know" is not an acceptable answer in a regulated sale.
Technologies used
Eight layers, and what each one is for.
Application
The engine running the business rules and the data. A mainstream framework with a large hiring pool, so the client is not locked to us. Both versions are now past end-of-life; see the status note.
- PHP 8.0
- Laravel 9.19
Interface
56 screens that change instantly instead of reloading. Feels like an app, deploys like a website.
- Vue 3
- Inertia
- Vite
- Pinia
Data
The 62-table store of record, with every row tagged to its store.
- MySQL
Second factor
A second factor at sign-in: an authenticator app set up by scanning a code, or one-time codes by SMS for staff without one.
- Google Authenticator (TOTP)
- QR provisioning
- Twilio
System alerts and account mail, with delivery actually tracked.
- Postmark
Addresses
Address lookup and verification, so an address is typed once and correctly.
- Loqate
Licence register
Live licensed breeder, dealer and carrier records from the national register.
- CompertMe's own market-intelligence platform
Legacy estate
The 193-screen back office still running the shop floor today, including every printed legal document.
- Custom PHP MVC
- FastRoute
- mPDF
- PHPSpreadsheet
- Chart.js
Automated testing
Automated tests check each change before it reaches production.
How the platform works
Seven steps, from a sign-in at the counter to the week's payroll.
Step 1
Sign in
Staff enter name@store. The platform checks the person, the store link, and whether the machine is one the store trusts, then the password, then a second factor. A failed location check alerts a manager automatically.
Step 2
Step away
The screen locks behind a four-digit PIN. Returning takes a second, in front of a customer, without a password.
Step 3
An animal arrives
Staff type the breeder's name, search the live national licence register and select the real licensed breeder. If that breeder is not yet verified in the system, the animal is filed as pending and flagged: work continues, nothing is lost.
Step 4
A manager verifies
The pending record is promoted, and the audit table records which user did it and when.
Step 5
The medical record builds up
Every vaccine carries its manufacturer, batch number, expiry date and the date it was given. Medical treatments attach to the animal. This is the record the legal certificate is generated from, so nothing is retyped later.
Step 6
The sale happens
One action produces the full nine-document packet, populated from the animal's own record: the contract, the official health certificate, the source disclosure, the registration application, the warranty terms, the financing options and the lineage record.
Step 7
The week closes
Commission is calculated against that store's own brackets. Clock-in and clock-out hours, commissions and days owed feed the payroll sheets.
Step six, in full: nine legal documents from one action
- Purchase agreement under regional statute
- Health warranty
- Extended warranty option
- Registration application
- Insurance recommendations
- Veterinary coupon
- Official veterinary inspection certificate, every vaccine and batch
- Finance provider options
- Lineage record, with parent photographs
And in the background, approved suppliers, accepted payment methods, fees, tax exemption and staff permissions are all per-store settings a manager edits, not code.
Results
What the business can do now that it couldn't before.
Make a change once and have it be true everywhere
Two diverging systems became one model on the new platform. The 160-file gap cannot reopen there, because there is no second copy to drift.
Open a store without a developer
Fees, commission brackets, staff, roles, permissions, approved vaccine suppliers, accepted payment methods and trusted machines are all settings.
Set its own commercial terms
Commission brackets and store fees moved out of code and into a screen a manager owns.
Prove where an animal came from
Sourcing is selected from the live government licence register rather than typed from a delivery note, and filtered to active licences.
Answer "who changed this, and when"
Promotions from pending to verified are attributed in an audit table.
Refuse a contract to the wrong person, place or machine
Six independent checks, with an alert on the one that matters most.
Grant off-site access safely
A written request, a named approver, a recorded decision and an expiry date, instead of an informal exception nobody revokes.
Change a contract clause once
The third-generation sales tool holds no contract wording of its own. There is one template, and every store prints from it.
The engineering, in numbers
- 160 Files that had diverged between the two store copies
- 543 Program files catalogued before the rebuild began
- 62 Database tables in the new model
- 23 Join tables in the model
- 16 Of those, tables binding a record to its store
- 56 Screens in the new application
- 54 Shared interface components
- 193 Screens in the legacy system still running the shop
- 11 Printable document templates in the legacy estate
- 6 Independent checks before a contract can be printed
- 9 Legal documents produced by one action
- 1,758 Lines in the original contract tool
- 0 Bytes in its third-generation replacement
Honest status note
The foundation is live, and the estate is being moved across.
This is what a staged replacement of a business-critical system actually looks like from the inside, and the shops never stopped trading.
In production · being extended
The new platform has not replaced the old one
It is a real, working, deployed application, and what it covers it covers properly. What it does not yet cover still runs on the legacy system described in chapter one, which is live and maintained.
Covered by the new platform
- Stores
- Users
- Roles
- Page permissions
- The six-gate security model
- Notifications
- All reference data
- Fees
- Commission rates
- The animal record and its medical history
- Register-verified sourcing
Not yet covered: still on the legacy system
- The sales pipeline
- Customer records
- The contract and document engine
- End-of-day reporting
- Commission sheets
- Payroll
Past end-of-life
And the versions are now old
The code this page was counted from was last touched in May 2023. PHP 8.0 lost security support in November 2023 and Laravel 9 in February 2024, so the platform's foundations have been unsupported for over two years. That is a statement about a code snapshot, not a criticism of the client.
Related work
Competitor Price Intelligence Platform
Retail · Long-running · Build, run, maintain
188 — Competitor websites monitored
White-Label Storefront Engine
Retail · Multi-brand · Build, extend, maintain
8 — Files that diverge between two live brands, of 60
Next step
Running your business on copies that have drifted apart?
Work with an engineering team that counts before it proposes, tells you when the obvious plan is wrong, and switches nothing off until its replacement is doing the same job better.
Client anonymised · every figure counted from the source code, last modified May 2023 · Hesham has worked as a full-stack developer since 2016 and founded CompertMe in 2020 · October 2026