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
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
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 });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.
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.
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.
OrdersEntered or imported in bulkPackingShipment planning kept consistent in one transactionInvoicingLocked numbering per company, GST calculationDocumentsStatutory 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