Build everything, ship nothing
Teams attempt eleven modules at once and have nothing sellable two years later. The scope has to be narrow while the foundation stays wide.
We had delivered enough accounting systems to know why they fail: a schema that fits the first module and nothing after it. So we designed the full ERP data model first, then shipped the accounting product on top of it — with a separate database for every customer company.
The situation
We started this because both failure modes were visible in systems we had been asked to rescue. The architecture is a direct answer to them.
Teams attempt eleven modules at once and have nothing sellable two years later. The scope has to be narrow while the foundation stays wide.
An accounting product succeeds, then inventory cannot be added because the schema never anticipated it, and the rewrite costs more than the original build.
Most SaaS puts every customer in one database with a tenant column. One bad query, one migration mistake, and the exposure is everybody's data at once.
ZATCA bolted on beside the ledger means two sources of truth, reconciliation work every month and a painful audit.
Decisions taken before the first screen was designed, because none of them can be retrofitted cheaply.
Each customer company is provisioned its own database, so an export, a restore, a private deployment or a data-residency requirement is a normal operation rather than a project.
Identity, subscriptions, provisioning and fleet orchestration live in one place, while business data never leaves the tenant's own database.
Every journal line carries a cost centre and project from the start, and products are typed as stockable long before inventory ships — so later modules are additions, not migrations.
Every business event — an invoice, a bill, a payment, a depreciation run — produces its journal entry through one engine, which is what keeps the ledger trustworthy.
Invoice XML is signed with the company's certificate and cleared as part of issuing the invoice, with the QR code and the audit trail stored against the same record.
Built bilingual from the first component, with right-to-left layouts designed rather than mirrored, because an accountant should not be the one adapting.
What changed
The accounting product is complete and in production; inventory, assets, projects, payroll and CRM sit on the same schema. The cost of that decision was paid once, at the beginning.
Modules on one shared data model
Tenant migrations, applied per company
API route groups behind one gateway
Deliberately boring where it matters: a relational core, explicit migrations, and no framework that would be hard to hire for.
Multi-tenancy, provisioning and a data model that survives the second module are decisions best made before the first one. We have made them.