// case-study.md

WMS v2 · Inventory Rewrite
Laravel 12 + Vue 3 rewrite of a legacy enterprise warehouse system

Active modernization of a multi-warehouse, multi-tenant inventory + WMS platform — replacing a Laravel 8 / Nuxt 2 legacy with a Laravel 12 / Vue 3 architecture organized around a single canonical V3 list-page pattern (receiving.vue), a BaseCrudController + BaseCrudService backend foundation, online schema migrations, and CI-enforced quality gates.

client Confidential · logistics operator under NDA
last touched
role Full-Stack Developer / Architecture Lead
period 2025 — Present
status Active rewrite · parity rollout in progress
scope ~103 models · ~76 controllers · ~121 migrations · ~130 pages
Laravel 12 Vue 3 Spatie Permissions AG Grid Enterprise (V3) Playwright PHPStan ESLint MySQL online DDL Karpathy guidelines
// receipts · tl;dr
domain models
~103
migrations
~121
ui pages
~130
canonical pattern
v3 receiving
// outcome · what changed
BEFORE

Legacy inventory was faster to extend by writing a new system than by editing the old one.

AFTER

Parity rewrite shipped online while the old system kept production traffic served.

BEFORE

Ad-hoc patches with thin test coverage; every release was a hold-your-breath moment.

AFTER

Playwright module suite + canonical V3 receiving pattern means new modules copy a known-good template.

BEFORE

Changes flew under the radar in 100K+ row tables; audit was an afterthought.

AFTER

Targeted indexes + cursor-based exports + exhaustive wms_audit_logs table.

What it is

A ground-up rewrite of an enterprise WMS that handles receiving, picking, packing, dispatch, validation, count-sheets, stock movement, and audit trails across multiple warehouses and tenants. The rewrite replaces a ~113-controller Laravel 8 backend and a ~130-page Nuxt 2 frontend with Laravel 12 + Vue 3, organized around one canonical pattern per layer — mirror that pattern, don’t reinvent.

Five binding requirements drive every decision: parity (every legacy capability replicated), flexibility (schema- and config-driven, not hardcoded), maintainability (single canonical pattern), scalability (DB + API + FE ready for 10× data without redesign), and performance (fast first paint and page interaction at scale). Plus the operational constraint that all migrations run online (ALGORITHM=INPLACE, LOCK=NONE) and ship an EXPLAIN snapshot in the docblock.

The bottleneck

The legacy WMS works — in production daily, handling real multi-warehouse traffic — but it’s carrying compounding cost:

The ask: same workflows, modern stack, one canonical pattern. A rewrite the team can both maintain by hand and let AI agents extend confidently — without breaking production during the transition.

How I broke it down

What I built

Foundation in place (Phase 0):

Modules in the rewrite (each follows the canonical pattern):

The canonical receiving view — the template every list page mirrors:

~/wms_v2/src/views/inbound/receiving.vue
<template>
  <Layout>
    <div class="v3-page v3-view-inner">
      <div class="ph">
        <div class="ph-left">{{ title }}</div>
        <div class="ph-actions"><WmsWorkflowButtons :workflow="schema.workflow"/></div>
      </div>

      <WmsStatCards :cards="schema.statCards" :summary="summary" />

      <div class="v3-card">
        <div class="toolbar"></div>
        <WmsFilterPanel :filters="schema.filters" v-model:value="filters" />
        <V3DataTable
          :flat="true"
          :columns="schema.grid"
          :datasource="datasource"
          :permissions="perms"
        />
      </div>
    </div>
  </Layout>
</template>

<script setup>
const schema      = useModuleSchema('receiving')
const { rows, total, filters, summary } = useV3ListPage(schema)
const datasource  = computed(() => buildV3Datasource({ module: 'receiving', filters }))
const perms       = usePermissions('receiving')
</script>

That’s the entire shape for a new module: a schema, a list page, a datasource. The work happens in the composables and the backend; the view stays declarative.

WMS v2 receiving view mock — header with workflow buttons, four stat cards, filter panel, and a V3DataTable showing anonymized receiving records with status pills, serial counts, and per-row actions.
MOCKCanonical receiving view · ph header with workflow actions, four stat cards (Inbound today / Open / Validated / Posted), filter panel, and the V3DataTable showing anonymized receiving records. Inventory volumes illustrative.

Tech

Results

domain models
~103
migrations
~121
ui pages
~130
canonical pattern
v3 receiving

A new module on v2 now takes a fraction of the time the legacy code required — the agent (human or AI) reads the canonical receiving.vue + ReceivingController + ReceivingService, copies the shape, points the schema at a new table. The CI guardrails catch “helpful” inline column defs or raw axios calls before they hit the repo. Migrations ship online, so the team can extend schema during business hours without windows.

Parity rollout is module-by-module behind a toggle; the legacy system remains the fallback per module until v2 verification ticks the parity matrix to verified. Specific rollout dates and business figures stay with the client; happy to discuss specifics on request.

What I’d do again — and differently

Worked well:

Would tighten: