
About the project
An ERP covering finance, sales, purchasing, stock and company structure, built on the rules Tunisian distribution actually runs on. First deployed to replace an outgrown Sage installation without interrupting daily operations.
My role: Functional analyst and full stack developer. I wrote the specification of every module before building it, then ran the migration off the legacy system.
I built this ERP to replace a Sage installation a Tunisian electronics distributor had outgrown. The company runs distribution, retail and installation at the same time, and every workaround around the old system, spreadsheets for credit control, manual bank reconciliation, stock counted separately, had turned into a daily cost. I wrote the functional specification of each module before building it, which is why the system implements the rules the business actually works with rather than the ones a generic package assumes.
The system covers five functional domains. Finance handles invoices, receipts and payments, lettering, cheques and promissory notes, withholding tax, expense claims, bank reconciliation and credit blocking. Sales runs the full quotation to order to delivery note to invoice chain, with credit notes, pricing rules and a CRM pipeline. Purchasing adds three way matching between purchase order, goods reception and supplier invoice. Stock carries multiple warehouses, lot and expiry tracking, weighted average and FIFO valuation, reservations and bills of materials.
Those rules belong to Tunisian distribution, not to one company: withholding tax, lettering, three way matching and dual stock valuation are the same wherever the sector operates, which is what makes the foundation worth redeploying rather than rewriting. The stack is React and TypeScript on Vite, a Firebase Functions API and PostgreSQL on Cloud SQL. I gave the cutover as much attention as the features: a gap analysis against the legacy system, a migration plan and a switchover runbook let a company leave Sage without losing history. The ERP also ingests orders from an online storefront, so the shop and the back office share one stock and one customer record.
Main features
- 💰 Finance module: Invoices, receipts, payments, lettering, cheques and promissory notes, withholding tax and bank reconciliation in one ledger.
- 🧾 Quote to invoice chain: Quotation, order, delivery note and invoice flow into each other, with credit notes and returns handled end to end.
- 🔍 Purchasing with three way matching: Purchase order, goods reception and supplier invoice are reconciled before any payment goes out.
- 📦 Dual stock valuation: Weighted average cost and FIFO run side by side, with lot tracking, expiry dates, reservations and multi warehouse transfers.
- 🏗️ Bills of materials: Assembled products consume their components, so installation kits stay accurate against real stock.
- 🚦 Automatic credit control: Customer credit limits block orders on their own, replacing the spreadsheet a team otherwise maintains by hand.
- 🔁 Sage migration kit: A gap analysis, a migration plan and a cutover runbook let a company leave the legacy system without losing history.
- 🌐 Web order ingestion: Orders placed on an online storefront land directly in the ERP, sharing one stock and one customer record.
- 🔐 Status and permission matrix: Each module defines who can validate, modify or only read every document type.
- 🚦 Status today: One distributor runs it in production. The functional core is sector wide rather than company specific, so a second deployment is a scoping exercise on the chart of accounts and pricing rules, not a rebuild.
Problems and solutions
Problem: A distributor running on a legacy Sage installation ends up surrounded by spreadsheets. Credit control, bank reconciliation and stock counts live outside the system, so the same figure exists in three places and none of them agree.
Solution: I wrote the functional specification of each module before writing code, then built the ERP around the real business rules. Credit limits, lettering and valuation now happen inside the system, and a documented cutover runbook moves a company off Sage without losing history.
Problem: Generic ERP packages are built for rules that do not match Tunisian distribution, so withholding tax, lettering and dual stock valuation get bolted on locally until the bolt ons become the real system. Building bespoke instead is how every distributor ends up paying for the same modules twice.
Solution: The modules encode the sector's rules rather than one company's habits, which is what allows the same foundation to serve another distributor. What stays specific to each deployment is the chart of accounts, the pricing rules and the legacy data, and those are scoped per project rather than rewritten.


