Requirements · Architecture · Cost

Your own ERP,
costed honestly.

Every screen, every table, every monthly line item — and a working demo you can click through, upload a spreadsheet into, and export a PDF out of, right now.

Screens specified91

Across 11 modules. 43 of them are the phase-1 MVP.

Clickable in the demo43

Real state, real validation, real file import and export.

Infrastructure, month one~$25/mo

One VPS running app, database and queue. Scales to ~$640 at 100+ users.

What Odoo would cost at 25 users$777/mo

Per seat, rising to $972 after year one. Yours is flat.

Straight answers first

Everything you asked, in one line each.

One thing I could not do

Your Storylane link — app.storylane.io/share/lddvudombcyf — is blocked by the network policy on the machine I'm running on, so I could not watch the demo and copy its exact screens. Everything below is built to the general shape of an SME ERP that handles clients and inventory, which is what you described. If you send me a screen recording, a PDF export of the Storylane, or just a list of its tab names, I will re-cut the screen inventory to match it exactly — the structure below is designed to be edited, not rewritten.

Your questionThe short answer
What's the full IT stack? Next.js + TypeScript on the front, Node/tRPC API, PostgreSQL as the single database, Redis for queues, S3-compatible object storage for documents. Details in the stack section.
How many screens? 91 across 11 modules. 43 are phase 1 (weeks 1–8), 37 are phase 2, 11 are phase 3. Full list here.
What's the data behind the screens? About 45 tables. The field-by-field spec for the six screens that matter most is in the data model section; the rest follow the same pattern.
Can I get a mock-up of the whole thing? Better than a mock-up — a working demo. Click any row, edit a record, drop a spreadsheet in, download a PDF. It keeps state.
Upload PDF, Excel and CSV? Yes, built in — and working in the demo today. Map → validate → review errors → commit. How it works.
Download the data too? CSV, Excel, PDF and JSON from every screen, plus scheduled exports that email themselves. Masked fields never reach the file.
Security features? MFA, role-based access enforced in the database (not the UI), field-level masking, an immutable audit trail, encryption at rest and in transit, tested backups. Fourteen controls listed.
What database, and where do I host it? PostgreSQL. Start on Supabase or a single Hetzner VPS; move to AWS me-central-1 (UAE) if you need data residency. Three options compared.
Total monthly cost? ~$25/mo lean, ~$135/mo at 10–30 users, ~$640/mo at 100+ users and three entities. Flat, not per seat. Full breakdown.
Can Claude Code actually build this? Yes — the demo you're about to open was built by it. The code is the easy part; the accounting rules and the data migration are the hard parts. Honest assessment.
Layer by layer

The full IT stack.

Nothing exotic. Every choice below is boring, widely hired for, and something you can walk away from — which matters more than performance when the thing has to run your company for ten years.

LayerChoiceWhy this one
Front endNext.js 15 (App Router) · React 19 · TypeScript Server components keep the big tables fast; one language front to back; the largest hiring pool of any framework.
UI kitTailwind CSS · shadcn/ui · TanStack Table & Query You own the component code rather than renting a design system. TanStack Table handles 50k-row grids with sorting, filtering and virtualisation.
ChartsRecharts, or hand-rolled SVG The demo's charts are hand-rolled SVG — zero dependencies, works offline. Recharts once the dashboards get complicated.
APItRPC (typed) + REST for outside callers tRPC gives you compile-time errors instead of runtime 500s when a field is renamed. A thin REST surface stays for integrations.
ORM / schemaPrisma or Drizzle + versioned SQL migrations The schema is the contract. Migrations are reviewed like code, run in CI, and never applied by hand to production.
DatabasePostgreSQL 16 Real transactions (non-negotiable for double entry), row-level security, JSONB for the fields every business wants and no one predicted, full-text search built in. More on why not the alternatives.
Cache & queueRedis + BullMQ (or Inngest if you'd rather not run it) Imports, PDF generation, emails and scheduled reports all run off the request cycle so a 12,000-row spreadsheet never times out a browser.
File storageS3-compatible — Cloudflare R2 or Supabase Storage Invoices, receipts, passports and trade licences live in object storage with expiring signed URLs, never in the database.
AuthSupabase Auth, Clerk or Auth.js — TOTP MFA, SSO-ready Never build session handling yourself. Google Workspace SSO the day you have more than ten staff.
SearchPostgres full-text + pg_trgm; Meilisearch later Postgres covers you to roughly a million rows. Add a search engine when it actually hurts, not before.
File parsingPapaParse (CSV) · SheetJS/ExcelJS (XLSX) · pdf-parse Server-side and streamed, so memory stays flat regardless of file size.
Document AIClaude API or AWS Textract for scanned PDFs Only for documents with no text layer. Returns confidence per field; anything under the gate stops for a human.
PDF outputPuppeteer (server) or React-PDF Server-side so an invoice looks identical whether it was emailed by a cron job or printed from a phone.
Email / SMSResend or AWS SES · Twilio · WhatsApp Business API Invoice delivery, statement runs, approval requests, expiry alerts.
ObservabilitySentry · Better Stack or Axiom · Vercel Analytics You want to hear about the failed GRN post from a pager, not from your storekeeper three days later.
CI/CDGitHub Actions — typecheck, lint, tests, migration dry-run Nothing reaches production without the migration having been replayed against a copy of production first.
TestingVitest (unit) · Playwright (end-to-end) Accounting logic gets unit tests with known-answer fixtures. The critical click-paths get Playwright.
Edge / WAFCloudflare — DNS, WAF, rate limiting, DDoS Free at your volume and it sits in front of everything.
CLIENTS BrowserDesktop · tablet · phone Warehouse scannerBarcode → GRN screen Email / WhatsAppInvoices & alerts out EDGE Cloudflare DNSWAF Rate limitDDoS TLS 1.3 APPLICATION Next.js + tRPC Server components · typed API RBAC middleware Validation (Zod) — one copy, shared by UI, import and API Workers: import · PDF · email · scheduled reports DATA PostgreSQL 16 45 tables · row-level security · audit triggers · PITR backup RedisQueue · cache · rate limits Object storage (S3 / R2)Receipts · PDFs · staff documents INTEGRATIONS — added as needed, never on day one Bank feedStatement → reconciliation WPS / bank payrollSIF file out Claude / TextractScanned PDF → fields POS / e-commerceSales → stock ledger FTA / e-invoicePhase 3 OPERATIONS GitHub Actions — test, migrate, deploy Sentry — errors & performance Daily backup + monthly restore test Audit log — 7-year retention
How the pieces sit together — six boxes you can actually name and replace

The one architectural rule that matters

Validation lives in exactly one place, next to the schema. A value typed into a form, pasted in from Excel, or pushed through the API all pass through the same rules. The moment you have two copies of "what a valid invoice looks like", your data starts drifting — and every ERP that has ever gone bad, went bad exactly there.

The inventory

All 91 screens, and when each one gets built.

P1 · weeks 1–8 · 43 screensP2 · weeks 9–16 · 37 screens P3 · later · 11 screens
Phase 1 is deliberately the smallest thing that can run the business end to end: take an order, hold stock, buy more, invoice it, get paid, see the number. Everything else waits.

Core & access 8

  1. Login / SSO
  2. MFA challenge
  3. Forgot & reset password
  4. Company setup wizard
  5. Role-aware dashboard
  6. Global search
  7. Notification centre
  8. My profile & preferences

Clients & CRM 8

  1. Client list
  2. Client 360 detail
  3. Create / edit client
  4. Contacts at client
  5. Lead & opportunity pipeline
  6. Activity & follow-up log
  7. Client statement of account
  8. Credit-limit review

Sales 10

  1. Quotation list
  2. Quotation builder
  3. Sales order list
  4. Sales order detail
  5. Delivery note / dispatch
  6. Invoice list
  7. Invoice builder (VAT)
  8. Credit note
  9. Receipts & payment collection
  10. AR ageing

Inventory 11

  1. Item master list
  2. Item detail — stock by location
  3. Create / edit item
  4. Categories, UoM & attributes
  5. Warehouses & bins
  6. Stock ledger
  7. Stock transfer
  8. Stock adjustment / write-off
  9. Physical stock count
  10. Reorder alerts
  11. Batch / serial & expiry

Purchasing 8

  1. Supplier list
  2. Supplier detail
  3. Purchase request
  4. Purchase order
  5. Goods receipt note
  6. Purchase invoice / bill
  7. Three-way match exceptions
  8. Supplier payments & AP ageing

Finance 10

  1. Chart of accounts
  2. Journal entries
  3. Bank accounts
  4. Bank reconciliation
  5. Expenses & petty cash
  6. VAT return schedule
  7. Trial balance
  8. Profit & loss
  9. Balance sheet
  10. Cash flow

People 8

  1. Employee list
  2. Employee detail & documents
  3. Attendance
  4. Leave requests
  5. Payroll run & payslips
  6. WPS SIF export
  7. Document expiry alerts
  8. Recruitment pipeline

Vessels & Jobs 11

  1. Vessel register
  2. Installed base per vessel
  3. Job list — new-build, retrofit, service
  4. Job detail & scope
  5. Service call / work order
  6. Engineer dispatch board
  7. Commissioning & sea-trial checklist
  8. Class certification & survey tracker
  9. Warranty & service contract register
  10. Timesheets & offshore allowances
  11. Job costing — budget vs actual

Data in / out 5

  1. Import wizard
  2. Field mapping
  3. Validation & error queue
  4. Export centre
  5. Scheduled reports

Admin & security 8

  1. Users
  2. Roles & permission matrix
  3. Audit trail
  4. Approval workflows
  5. Document vault
  6. Integrations & API keys
  7. Backup & restore
  8. System settings

Reporting 4

  1. Report library
  2. Report builder
  3. KPI dashboards
  4. Alert & rule engine

Screen count is not the same as effort

Forty of those eighty-four are the same screen with different columns — a list, a filter bar, a detail drawer, a form. Build that pattern once, generate the rest from the schema, and the 91 collapses to roughly 15 genuinely distinct pieces of work. The screens that actually cost you time are the invoice builder, the GRN posting, the payroll run, the import wizard and the VAT schedule. Budget accordingly.

What sits behind the glass

The data model — field by field.

Roughly 45 tables. Below are the six that everything else hangs off, with the exact columns, types and rules. The remaining tables follow the same shape: an id, a tenant, the business fields, and the four audit columns every row carries.

1 · client — the customer master

ColumnTypeRuleNotes
iduuidprimary keyNever the display code. Codes get reused; ids never do.
codetextunique per tenantWhat humans say out loud — CL-1001.
nametextrequired, unique per tenantThe registered legal name, not the trading name.
trntext15 digits, starts with 1Prints on the tax invoice. Wrong TRN = rejected input claim for your customer.
segment, emirateenumfrom a lookup tableLookups, not free text — otherwise you get "Dubai", "DUBAI" and "dxb".
payment_termsenumAdvance · Net 15/30/45/60Drives the due date on every invoice automatically.
credit_limitnumeric(14,2)≥ 0Zero means cash only. Enforced at order entry, not discovered at month end.
balancenumeric(14,2)derivedNever stored as a typed number — computed from the ledger, or kept in a materialised view.
statusenumActive · Prospect · On Hold · Dormant"On Hold" blocks new orders at the API, not with a warning dialog.
owner_user_iduuid → usernullableDrives the row-level security rule for the Sales role.
created_at · created_by · updated_at · updated_bytimestamptz / uuidset by triggerOn every table. No exceptions.

2 · item + stock_level + stock_move

ColumnTypeRuleNotes
item.skutextunique per tenantThe natural key. Imports match on this, which is what makes a re-import safe.
item.barcodetextunique, nullableNullable and unique — Postgres allows many NULLs, one of each value.
item.uomenumPCS · KG · LTR · MTR · BOXChanging the UoM on a live item is forbidden; you clone the item instead.
item.costnumeric(14,4)≥ 0, restrictedFour decimals — rounding at two will drift on high-volume, low-value stock. Hidden from the Sales role.
item.pricenumeric(14,2)≥ 0Hidden from the Warehouse role.
item.reorder_levelinteger≥ 0Feeds the reorder screen and the nightly alert.
stock_level(item, warehouse, bin) → qtycomposite uniqueQuantity lives per location, never on the item. The item's "on hand" is a sum.
stock_moveappend-onlyno UPDATE, no DELETEEvery in and out, with its source document. This is the table an auditor reads.
stock_move.qty_deltanumeric(14,4)≠ 0Signed. A transfer is two rows that net to zero, written in one transaction.

The mistake almost everyone makes here

Storing qty_on_hand as an editable column on the item, and updating it from five different places. Six months later the number is wrong and nobody can prove when it went wrong. Stock is a ledger: you append movements, and the balance is derived. It is slightly more work on day one and it saves the entire system on day four hundred.

3 · invoice + invoice_line

ColumnTypeRuleNotes
numbertextunique, gaplessAllocated by a database sequence inside the transaction. A cancelled invoice is voided, never deleted and never reissued.
client_iduuid → clientrequiredRestrict on delete. A client with invoices can never be removed, only archived.
issue_date · due_datedatedue ≥ issueDue date defaults from the client's payment terms.
currency · fx_ratechar(3) / numericrate frozen at issueFreeze the rate on the document. Re-deriving it later silently rewrites your history.
line.qty · unit_price · discount_pctnumericqty > 0Per line, not per invoice.
line.tax_rate · tax_amountnumeric5% UAE standardVAT is computed per line and then summed. Computing it on the invoice total gives you rounding differences that the FTA will notice.
subtotal · tax_total · grand_totalnumeric(14,2)derived, checkedStored for speed, but a constraint asserts they still equal the sum of lines.
amount_paid · balancenumeric(14,2)derived from receiptsA receipt can be split across several invoices — that's a join table, not a column.
statusenumDraft→Sent→Part Paid→Paid / Overdue / VoidA posted invoice is immutable. Corrections are credit notes.

4 · purchase_order → goods_receipt → bill

TableWrites toThe rule that matters
purchase_order + linesnothing financialA PO is a promise, not a transaction. It moves no stock and books no cost.
goods_receipt + linesstock_move + GRNI accrualThe moment stock becomes yours. One transaction writes the movement and the accrual, or neither.
bill (supplier invoice)accounts_payable, reverses GRNIThree-way match: PO price × GRN quantity × bill total. Any mismatch above tolerance stops for a human.
supplier_paymentbank + APCannot be raised against an unmatched bill. That single constraint is most of your fraud control.

5 · employee — the table with the sharp edges

ColumnHandlingWhy
emirates_id · passport_noencrypted at column levelPersonal identifiers under the UAE PDPL. Encrypted with a key held outside the database, so a stolen dump is useless.
ibanencrypted, masked in UIShows as AE•• •••• 4471 everywhere except the payroll run itself.
basic_salary · allowancesrow-level securityVisible to Owner, Finance and HR. The API never sends it to anyone else, so it cannot leak through an export.
visa_expiry · passport_expiry · labour_card_expiryindexed, alertedA nightly job raises alerts at 90, 60, 30 and 0 days. This one field pays for the system on its own.
person_id · employer_idrequired for WPSThe bank's SIF file rejects the whole batch if one is malformed. Validate on entry, not on payday.

6 · audit_log — the table nobody can write to

ColumnTypeNotes
occurred_attimestamptzDatabase clock, not the browser's.
actor_user_id · actor_roleuuid / enumThe role in force at the time, not the role they hold today.
actiontextINVOICE.VOID, ITEM.PRICE_UPDATE, EXPORT.CSV …
entity_table · entity_idtext / uuidPoints at the row that changed.
before · afterjsonbThe changed fields only, not the whole row. Enough to reconstruct, small enough to keep for seven years.
ip · user_agent · request_idinet / textTies a change to a session and a support ticket.

How it stays immutable

Rows are written by a database trigger inside the same transaction as the change they describe — so a change cannot exist without its audit row. The application's database role is granted INSERT only: no UPDATE, no DELETE, not even for the owner's account. Doctoring the log requires superuser access to the database itself, which is a different and much louder kind of event.

Data in, data out

Upload PDF, Excel, CSV — and get it all back out.

This is the feature that decides whether the ERP gets adopted or quietly abandoned. If your team cannot get their existing spreadsheets in on day one, they will keep the spreadsheets — and then you have two systems instead of one.

1

Drop the file

CSV with any delimiter (comma, semicolon, tab, pipe — sniffed automatically), .xlsx, .xls, or a PDF. Quoted fields, embedded line breaks, Excel's BOM and Arabic text are all handled. Files stream to storage; nothing is held in memory.

2

Map the columns — mostly automatically

"TRN Number", "Tax Reg No" and "VAT NUMBER" all resolve to the same field, with a confidence score shown beside each match. You only correct the ones it got wrong. In the demo, a seven-column Excel file with headers like PURCHASE_PRICE and MIN_QTY maps 7 out of 7 with no help.

3

Validate before anything is written

Required fields, number formats, date parsing, enum membership, duplicate natural keys within the file, and duplicates against what is already in the database. Failures go to an error queue with the file line number and a plain-English reason — "Cost Price is not a number (“NOT-A-PRICE”) on line 5" — downloadable as a CSV you can hand back to whoever produced the file.

4

Commit in one transaction

Good rows are written together or not at all. Each row stores a hash of its source line, so re-running the same file is a no-op instead of a duplicate — which is the single most common way an import destroys an inventory. The commit writes one IMPORT.COMMIT row to the audit trail.

The PDF path is different — and it should be

A spreadsheet is structured; a PDF is a picture of structure. Two cases:

Kind of PDFHow it's readWhat you get
Has a text layer
Exported from any accounting system
Text extracted directly, then fields matched by pattern Supplier name, invoice number, date, TRN, currency, VAT and grand total — plus the line items — each with a confidence score. The demo does this in your browser, live.
A scan or a photo
Someone photographed a paper receipt
OCR server-side — AWS Textract or the Claude API Same shape of result, same confidence scores, costing roughly 1–4 US cents per page.

Never auto-post a PDF

Extraction is probabilistic. Your ledger is not. Fields above the confidence gate (85% is a sensible starting point) are pre-filled; everything else stops for a human. The record is created as a draft and still has to pass the three-way match against the purchase order and the goods receipt before anyone can pay it. Skip that gate to save thirty seconds and you will eventually pay a supplier twice.

Getting data out

FormatProduced byUse it for
CSVStreamed, UTF-8 with BOMAnything that will be re-imported somewhere else. Opens correctly in Excel including Arabic.
ExcelExcelJS server-side, typed cellsHanding a workbook to your accountant with numbers that are actually numbers, not text.
PDFPuppeteer, server-sideInvoices, statements, the ageing report your bank asks for. Identical output whether emailed by a cron job or printed from a phone.
JSONREST endpointFeeding a dashboard, a Power BI file, or another system.
ScheduledQueue + emailAR ageing to you every Monday 07:00. Stock valuation to the auditor on the 1st. Nobody has to remember.

Masking happens before the file exists

When the Sales role exports the item list, the cost column is not hidden in the spreadsheet — it was never selected from the database. Same for salaries, IBANs and Emirates IDs. Verify it yourself: open the demo, switch the role to Sales Executive, open Item Master, export the CSV, and look at the header row.

Not an afterthought

Fourteen security controls, and which ones are non-negotiable.

#ControlPriorityWhat it actually means
1MFA on every write roleDay one TOTP app codes. A stolen password alone gets nobody in. Read-only roles can be exempt; anyone who can post a payment cannot.
2Role-based access in the databaseDay one Postgres row-level security. The Sales role's query for other reps' clients returns zero rows — there is no endpoint to forget to protect.
3Field-level maskingDay one Cost prices, salaries, IBANs, Emirates IDs. Enforced in the query, so the UI, the API and every export agree.
4Immutable audit trailDay one Written by trigger, insert-only grant, seven-year retention. Who, what, when, from where, before and after.
5Encryption in transit and at restDay one TLS 1.3 everywhere, AES-256 volume encryption, HSTS, secure cookies.
6Column encryption for identifiersDay one Emirates ID, passport, IBAN encrypted with a key held in a KMS outside the database. A leaked backup is then just noise.
7Tested backupsDay one Daily snapshot plus point-in-time recovery, stored in a different region. An automated monthly restore that alerts on failure — an untested backup is a rumour.
8Maker-checker approvalsWeek 6 Supplier payments, price changes, credit notes, stock write-offs and user role changes need a second person. This is where internal fraud actually happens.
9Session policyWeek 4 30-minute idle timeout, visible device list, forced re-authentication before payment actions.
10Rate limiting & lockoutWeek 4 Five failed sign-ins locks the account for fifteen minutes and alerts the owner. Per-IP limits on the API.
11Signed, expiring document URLsWeek 4 A link to a passport scan works for five minutes for one authenticated user. Object storage is never public.
12Export logging & thresholdsWeek 8 Every export names the user, dataset, row count and columns. Bulk exports above a threshold can require a second approver.
13Dependency & secret scanningDay one Dependabot, secret scanning in CI, a vault for credentials. No .env in the repository, ever.
14Penetration test before go-liveBefore cutover One external review, roughly $3,000–8,000 in the UAE. Cheaper than the first breach by several orders of magnitude.

On UAE data protection

The federal PDPL (Decree-Law 45 of 2021) governs personal data — your employees' documents far more than your stock records. There is no blanket residency mandate for a private trading company, but regulated sectors (healthcare, finance, some government contracts) and DIFC/ADGM entities have their own rules. If you are unsure, or you expect to bid for government work, host in me-central-1 (AWS UAE) or Azure UAE North and the question stops being a question. Confirm your specific obligations with a UAE lawyer before go-live — I am describing the general landscape, not giving you legal advice.

Where it lives

Database and hosting, three ways.

Why PostgreSQL, and not the others

OptionVerdictReasoning
PostgreSQLUse this Real ACID transactions — mandatory the moment you have double entry. Row-level security is the cleanest RBAC you will find. JSONB absorbs the custom fields every business turns out to need. Full-text search included. Runs anywhere, and every developer you will ever hire knows it.
MySQL / MariaDBWorkable Perfectly capable, but weaker JSON handling and no row-level security, so permissions move back into application code where they are easier to get wrong.
MongoDBWrong tool An ERP is relentlessly relational — an invoice line references an item references a supplier. Enforcing that by hand in application code is a decade of bugs you have volunteered for.
Airtable / Google SheetsFine to start, fatal to stay No transactions, no real permissions, no audit trail, and it degrades badly past a few tens of thousands of rows. Excellent for validating the idea; it cannot be the system of record.
SQLiteOnly for the demo Superb single-user database. Concurrent writers from a warehouse and an accounts desk is not its job.

Three hosting options

A · One VPS

Hetzner CPX31, Docker, Coolify. App, Postgres, Redis and backups on one box you control completely. Cheapest by a wide margin and genuinely fine for a single company under about 20 users.

Trade-off: you are the sysadmin. Patching, monitoring and the restore drill are yours. Budget two hours a month, and know that a bad night is a long night.

~$25/moEU regions

B · Managed platform — recommended

Vercel + Supabase (or Neon) + Cloudflare R2. Postgres, authentication, storage, row-level security and point-in-time recovery arrive configured. You write features instead of Terraform.

Trade-off: costs more, and you inherit someone else's outage. Both are still plain Postgres underneath, so leaving is a pg_dump, not a rewrite.

~$135/moFrankfurt / Singapore

C · UAE cloud

AWS me-central-1 or Azure UAE North. RDS Postgres multi-AZ, ECS or App Runner, S3, KMS. The answer when data residency is a contractual requirement or you are selling to government.

Trade-off: roughly four to five times option B, and it needs someone who genuinely knows AWS. Do not pick this one for comfort — pick it because a contract made you.

~$640/mo+UAE residency

What I would actually do

Start on B. It gets you to a working system fastest, and every part of it is standard Postgres, so nothing you build gets stranded. Move to C the day a customer contract or a regulator asks for UAE residency — that migration is a dump, a restore and a DNS change, roughly a weekend. Choose A only if the monthly cost genuinely matters more than your weekends.

The number you actually asked for

What it costs, every month.

List prices as published in August 2026, in US dollars. Providers change these — treat them as accurate to about ±15% and re-check at signup. Everything below excludes VAT.

Tier A — lean · up to 5 users

Line item$ / monthNote
Hetzner CPX31 VPS — 4 vCPU / 8 GB, running app + Postgres + Redis18Rising to ~25 on the announced 2026 increase
Off-site backup storage (1 TB Storage Box)4Nightly encrypted dumps
Cloudflare R2 object storage — ~100 GB of documents2No egress charges
Domain + Cloudflare DNS & WAF1WAF is free at this volume
Transactional email (Resend free tier, 3k/mo)0$20 above 50k
Sentry & uptime monitoring (free tiers)0
Total~25≈ AED 92 / month

Tier B — growth · 10–30 users, multi-branch ← most likely you

Line item$ / monthNote
Vercel Pro — 1 seat20Includes 10M edge requests; overage $2/M
Supabase Pro — 8 GB database, 100 GB storage, 100k MAU25Includes a $10 compute credit
Supabase compute upgrade to Small5Difference above the included Micro
Cloudflare R2 — ~500 GB8
Upstash Redis — queues, cache, rate limits10Pay-per-request
Resend — 50k emails20Invoices, statements, alerts
Sentry Team26
Background jobs (Inngest, or self-hosted BullMQ at $0)20
Total~134≈ AED 492 / month
Optional: Supabase 7-day point-in-time recovery+100Worth it once the ledger is real money
Optional: PDF/OCR extraction, ~2,000 documents+30–80Only for scans with no text layer

Tier C — scale · 50–150 users, multiple entities, UAE residency

Line item$ / monthNote
Application hosting + seats + usage80
Managed Postgres, multi-AZ with read replica250RDS me-central-1 or Neon Scale
Managed Redis40
Object storage ~2 TB + egress35
Email, SMS and WhatsApp Business60
Observability — errors, logs, traces90
Disaster recovery — second region, PITR60
Cloudflare Business — WAF, bot management25
Total~640≈ AED 2,350 / month

Against buying it off the shelf — at 25 users

Option$ / month$ / yearWhat you get, and what you don't
Your own, Tier B1341,608 Flat. Adding a 26th user costs nothing. You own the code and the data outright.
Odoo Standard7789,330 $31.10/user first year, then $38.90 — so year two is $11,670. Mature and huge; you rent it forever, and customisation means an Odoo partner.
Odoo Custom1,52518,300 $61/user first year, then $76.20. Adds Studio, multi-company and API access.
Zoho One92511,100 $37 per employee per month on the all-employee plan — note that is every employee, not every user.
SAP Business One (Professional)2,27527,300 $91/user/month, plus implementation typically $20k–80k. Genuine enterprise depth you will not use.
Oracle NetSuite3,47441,690 ~$999 base + ~$99/user, plus a six-figure implementation. Wrong shape at your size.

The real argument, in one sentence

Commercial ERP is priced per seat; your own is priced per server. At five users the packaged products are cheaper and you should buy one. At twenty-five users you are paying roughly $9,000 a year to rent software you cannot change. At a hundred users you are paying $37,000 a year — and by then the build has paid for itself several times over and you own the asset.

The build cost — the number people leave out

How you build itPhase 1 (34 screens)ElapsedHonest read
You + Claude Code$100–200/mo subscription + your time10–16 weeks part-time Real, and this page is the evidence — but you personally have to review the accounting logic, and you carry the risk.
One developer + Claude Code ← best value$10,000–25,0008–12 weeks Roughly 250–450 hours of human time, mostly review, integration and migration. This is where the economics genuinely work.
Offshore dev shop, no AI$27,000–84,0004–7 months900–1,400 hours at $30–60. Quality varies enormously.
UAE agency$60,000–150,0005–9 monthsYou are buying accountability and a local phone number.

Add, on top of any of those: a one-off penetration test ($3,000–8,000), an accountant to sign off the chart of accounts and VAT treatment ($1,000–3,000), and — the item everyone forgets — data migration from whatever you run today. Budget two to four weeks for the migration alone, and run both systems in parallel for one full month before you switch anything off.

Can Claude Code really do this?

Yes — and here is exactly where it stops.

The demo linked from this page was built by Claude Code in a single session: the schema, all 29 working screens, the CSV parser, the XLSX unzipper, the PDF text extractor, the role engine, the export pipeline and the browser tests that verify them. So the question is not whether it can write the code. It is which parts you must not delegate.

Genuinely fast with AI

✓ Schema design and migrations
✓ Every list / detail / form screen
✓ Import parsing, mapping and validation
✓ Export in all four formats
✓ Role and permission plumbing
✓ Standard reports and dashboards
✓ Test suites and CI pipelines
✓ The boring 70% that makes a shop quote six figures

Where a human has to own it

! Double-entry correctness — an accountant signs the chart of accounts, not a model
! VAT edge cases — reverse charge, designated zones, blocked input tax
! WPS SIF format — the bank rejects the batch, not the row
! Concurrency on stock — two people picking the last unit needs a real lock
! Migrations against live data — rehearsed on a copy, always
! Your data migration — nobody else knows what your legacy fields mean
! The security review — an outside pair of eyes before go-live

Sixteen weeks, in order

WeekBuildDone means
1Schema, auth, roles, app shellYou can sign in as three roles and see three different navigations.
2–3Clients, Items, Suppliers + the import wizardYour real master data is in, imported from your existing spreadsheets.
4–5Purchase order → GRN → stock ledgerA real shipment is received and the stock number is correct and provable.
6–7Quotation → invoice → receipt → AR ageingYou raise a real VAT invoice and record a real payment against it.
8Reports, export centre, PDF pipelinePhase 1 complete — the business could run on it.
9Expenses, chart of accounts, journalEvery module posts to the ledger; the trial balance nets to zero.
10VAT schedule + accountant reviewYour accountant confirms the quarter's numbers against their own working.
11Employees, payroll, WPS exportThe bank accepts a test SIF file.
12Projects and job costingYou can see which jobs lost money.
13Approvals, audit hardening, backup drillYou restore the database from a backup and it works.
14Penetration test and remediationFindings closed, retested.
15Data migration + parallel run beginsBoth systems produce the same closing balances.
16Training, cutoverThe old system is read-only, not deleted. Keep it for a year.

The failure mode to actually plan against

It is never the code. It is week 15. Every ERP project that goes wrong goes wrong at migration: the legacy stock quantities don't reconcile, three years of half-finished purchase orders have no clean equivalent, and somebody wants to go live anyway because the date was promised. Run in parallel for a full month. Both systems, same transactions, and compare the closing trial balance. If they disagree, do not cut over — no matter what the date says.

Now go and click it

43 of the 91 screens are clickable, across six roles that genuinely change what you can see, with real file import and export. Drop a spreadsheet into it. Export a PDF. Switch to the Sales role and try to find the cost price.

Open the Tritek Marine Services ERP demo