Any billing app can show yesterday’s sales. None of them can tell an owner whether the shop earned anything, and eight years of bespoke code had the same blind spot.
The system recorded sales, purchases and stock, but posted only one leg of each transaction, so the ledger never balanced and a healthy shop could look like it was losing millions. Stock lived in two tables that disagreed. All of it had to become a multi-tenant product a skeptical shopkeeper could try from a phone.
One Laravel application, nine bounded-context packages, one shared database. Every financial side-effect travels as a domain event into an accounting package that alone is allowed to write the ledger.
Sales and POS, stock and catalogue, purchasing, replenishment, discounts, accounting, identity, demo, insights and storefront each own their routes, migrations, permissions, React SPA and tests. A written context map records where legacy code still crosses a boundary, so the split stays mechanical.
A sale, purchase, return, revaluation or expense fires a domain event; posting listeners in the accounting package write balanced journal entries keyed on a source reference, so retries post nothing twice. The POS refuses any sale that cannot balance its own journal.
One authoritative batch store with oldest-first stock-out, a derived movement log, and a daily rollup that stock reports read at actual cost. Depleted batches are retained and alerts are never hidden, so expiry value by month and low-stock worklists stay honest.
Organisation-to-shop scoping is enforced where a record is fetched, never after, over five access tiers and a 125-slug permission registry. Record-state rules live in Laravel policies. A salesperson bills without ever seeing a margin.
Name, pharmacy and mobile number provision a private tenant in about two minutes, seeded through the live services so every demo transaction posts real books. Customers order by QR code with OTP-verified tracking, and a Flutter sales app shares the same API.
Seventeen live pharmacies moved onto balanced books without closing a counter, and a shopkeeper can now try the product from a phone before talking to anyone.
The rehearsal clone was 1.78M out before reconciliation, and one shop showed a 9.1M loss on a healthy 1.7M gross profit because legacy purchases had been expensed periodically. Rather than patch figures, we wrote a fourteen-step idempotent migration command, rehearsed it twice on production clones, and made go-live conditional on a reconciliation that passed clean.
Tell us where the friction is. We will map it with you and propose a clear place to start.