Case study 01 · Multi-entity ERP

AJA Savory Food Services

One ERP running three food-service businesses — subscription meal delivery, multi-branch retail with POS, and the shared corporate back-office — on a single Laravel + Filament panel.

Sole architect & engineer Mar 2024 – Aug 2026, still active
Laravel 12.66 PHP 8.4 Filament v5.7 Livewire 4 Tailwind 4 MySQL Redis · Horizon Reverb Sanctum OpenAI Meta Graph API AWS S3
~97k lines of PHP
142 Eloquent models
421 migrations
1,358 commits · 2y 5m
70+51 resources + widgets
89 test files

The problem

Three related food-service businesses shared owners, staff, and supplies — but not systems. A subscription meal-delivery brand (SDD), a multi-branch retail operation (STG), and the corporate office handling HR, finance, and procurement for both each ran on their own spreadsheets and ad-hoc processes. Consolidated reporting across the group meant manual reconciliation, and shared resources like employees and inventory were tracked in duplicate or not at all.

The requirement was one system that respects the boundaries between businesses where they matter — sales, subscriptions, operations — and deliberately crosses them where they don’t: people, supplies, and the consolidated financial picture.

The system

One schema, one panel, three businesses separated by a business_code discriminator. 70 Filament resources, 18 analytics pages, and 51 dashboard widgets sit on top of a domain layer of 142 models, 83 policies, and 66 enums.

SDDsubscriptions · weekly menus · deliveries · AI marketing
STGmulti-branch retail · Loyverse POS sync · branch analytics
CorporateHR & payroll · finance · procurement
Shared domain layer — employees · supplies · accounting · policies enforcing business_code boundaries
PlatformLaravel 12 · Filament v5 · MySQL · Redis + Horizon · Reverb websockets · Telescope
IntegrationsOpenAI · Meta Graph API · Loyverse POS · Brevo · AWS S3 · Browsershot/DomPDF

Engineering deep-dives

Four decisions that made a three-business system sustainable for one engineer.

01

Multi-tenancy without multi-database

Three businesses share one schema, separated by a business_code discriminator. All 83 authorization policies enforce the boundary; shared entities — staff, supplies — are exempt by design, because the corporate office genuinely serves both businesses.

The alternative, one database per business, would have made the core requirement — consolidated cross-business reporting — a data-warehousing project. One schema keeps it a query.

Trade-off, taken deliberately: the boundary is enforced by code discipline rather than the database. Simpler to report across and cheaper to run, in exchange for policies that must never be skipped.

02

AI content pipeline for marketing

A service chain — StrategyEngine, CopyGenerator, ImageGenerator, ImageRenderer, RecipePhotoGenerator, MetaPublisher, MetaSyncService — generates day-aware social captions and food photography, composites posts via headless Chrome, and publishes to Facebook and Instagram through the Meta Graph API. Published posts sync back, become read-only, and are deduplicated on re-sync.

Recipe selection is constrained to the menu actually scheduled for that date, so the marketing never advertises food the kitchen isn’t making. Both the text prompt and the image prompt are persisted per post, making every generated asset auditable and reproducible.

The first image model produced stylised output unsuitable for food photography; switching models mid-pipeline was cheap precisely because prompts and rendering were separate persisted stages.

03

Philippine labor-compliance payroll

Not a generic payroll module: night-differential and holiday-pay calculators, statutory leave entitlement tracking, 13th-month pay, daily time records reconciled against shift schedules, cash advances, government loan deductions, and an incident-report workflow.

Payroll is generated and recomputable, because real payroll gets corrected after the fact — a late time-record or an approved override must be able to flow into a corrected run without hand-editing totals.

Compliance logic lives in dedicated calculators rather than inline in the payroll run, so a change in statutory rules is one class, not an archaeology project.

04

Event-driven inventory and finance

Domain events — DeliveryFinalized, SupplyPulloutCommitted, PaymentWasMade, SupplyAdjustmentApproved, FundWasDisbursed — drive listeners that create invoices, deduct stock, and update supplier pricing. The accounting consequence of an operational action cannot be forgotten by a caller, because no caller is responsible for it.

On top of that sit journal entries, account titles, accounts payable, bank reconciliation, a cash-flow overview, and an SDD income statement — the consolidated financial picture the single-schema decision was made for.

Events make the write path harder to trace than a linear service call; Telescope in production and a full audit trail on financial records are the counterweight.

Stack & practices

Outcome & hindsight

Two and a half years in, the system runs all three businesses’ daily operations — payroll, inventory, subscriptions, retail reconciliation, and marketing — and is still actively developed, with 1,358 commits by one engineer.

What I’d do differently: the subscription engine originally updated plans by deleting and recreating subscription details, which silently destroyed per-day meal customisations. The fix — reconciliation that preserves them — is documented in the codebase as a hard-won lesson. I’d reach for reconciliation over replace-on-write from the start.