Jay GajjarSenior Software Engineer
All work

Manufacturing and exportIn production

Export operations ERP

One ERP for a group of companies, where no two documents share a number and no company sees another's data.

Role
Technical lead
Status
In production
Stack
PHP 8.2, Laravel 12, MySQL, Role-based permissions, PDF and spreadsheet generation, Queues
Three companies, one system: separate data, separate numbering.

The challenge

Several companies run on one system. Each needs its own document numbering and private data, while many users work on the same orders, shipments and invoices at once, under GST rules for domestic and export sales.

The solution

A Laravel 12 ERP where numbering happens inside locked transactions, every query is scoped to the current company, and packing changes update shipment data in the same transaction.

My role

  • Led technical development: design, deployment and production support
  • Built the numbering, tax and data-isolation foundations
  • Reviewed a junior developer's pull requests

At a glance

Statutory documents generated
8
GST scenarios handled
3

Challenges and solutions

  1. Unique numbers under concurrency

    Challenge

    Two users creating invoices at the same moment could both read the same next number.

    Solution

    Each company's number series is read under a row-level lock inside a transaction, so concurrent requests queue for the next number instead of colliding.

    Trade-offInvoice creation is serialised per company, which is exactly the point.

    Illustrative Laravel pattern (simplified, not client code)php
    DB::transaction(function () use ($companyId) {
        $series = DocumentSeries::where('company_id', $companyId)
            ->where('type', 'invoice')
            ->lockForUpdate()
            ->firstOrFail();
    
        $series->increment('last_number');
    
        // ...create the invoice with $series->last_number
    });
  2. Keeping each company's data private

    Challenge

    With every company in the same database, one missing filter could show one company another's records.

    Solution

    A global query scope adds the current company to every query on company-owned data, and role-based permissions control what each user can do inside it.

    Trade-offEvery new model has to opt in to the scope, so it becomes a standing code-review check.

  3. Tax that always adds up

    Challenge

    Domestic and export invoices follow different GST rules, and totals must match line items exactly.

    Solution

    Each case is calculated with precise decimal arithmetic instead of floating point, so rounding never drifts.

  4. Packing changes without half-updated shipments

    Challenge

    Splitting or regrouping packed goods affects shipment data that other users rely on.

    Solution

    Model observers update the dependent shipment data inside the same transaction, so a change either completes everywhere or not at all.

What was delivered

  • Order-to-invoice workflow for export operations
  • Packing and shipment planning
  • GST-compliant invoicing for domestic and export cases
  • Statutory shipping documents generated as PDFs
  • Role-based access for each company
  • Bulk import for large order batches

How it fits together

One ERP serves a group of export companies, covering purchase orders, packing, shipping, invoicing and the documents every shipment needs.

  1. OrdersEntered or imported in bulk
  2. PackingShipment planning kept consistent in one transaction
  3. InvoicingLocked numbering per company, GST calculation
  4. DocumentsStatutory shipping documents as PDFs

Constraints

  • Separate document numbering per company
  • Strict data separation between companies
  • GST rules for domestic and export invoices
  • Many users on the same records at once