Ritzy One • Technical Documentation
What a technical evaluator, IT manager or hosting partner needs before deploying Ritzy One — the technology stack, the architecture, the security model, server and client requirements, ports, deployment options and the backup strategy.
Ritzy One is a browser-based ERP suite, a re-platform of the long-running Ritzy desktop application. It is a single Node.js API process talking to a PostgreSQL database, with a React single-page application for the desk and a family of installable phone apps (PWAs) for the field.
| Attribute | Value |
|---|---|
| Application type | Single-page application over a REST API — no application server licence, no Java or .NET runtime |
| Deployment model | On-premise, private VM/VPS, or Docker — one instance per customer |
| Tenancy | Single-tenant per install; one PostgreSQL database per install holds every company, each company's rows isolated by Row-Level Security |
| Language | TypeScript end to end, strict mode (including noUncheckedIndexedAccess) |
| Codebase | npm-workspaces monorepo — 16 packages, roughly 140,000 lines of first-party TypeScript |
| Licensed modules | The Accounts core plus ten independently licensed modules: Cargo, Liner, Marine, Warehousing, Transport, Shipchandling, Inventory, Payroll, CRM, Real Estate |
pg, without pg-native). There is no libpq to match, no compiler toolchain needed at install time, and no compiled add-on to rebuild when Node or the platform changes. That is what lets the same build run unchanged on Windows, on Linux and inside a container.| Layer | Technology | Version | Notes |
|---|---|---|---|
| Runtime | Node.js | 18.x or 20.x LTS (20 recommended) | The Windows installer fetches Node 20 LTS if it is absent |
| API framework | Express | 4.21 | REST over JSON; no GraphQL |
| Language / build | TypeScript | 5.7 | tsc for the API; tsc -b plus Vite for the front ends |
| Database | PostgreSQL | 18 (17 minimum) | Installed by the Windows installer if missing, or bundled inside the Docker stack |
| Database driver | pg (node-postgres) | 8.23 | Pure-JavaScript PostgreSQL client with a connection pool; no native bindings |
| Front end | React + Ant Design | React 18.3, antd 5.22 | React Router 7, Axios, Day.js, Recharts |
| Bundler | Vite | 6.0 | Static output served by nginx (or by the API itself in the container build) |
| Authentication | JSON Web Tokens + TOTP | jsonwebtoken 9 | Passwords hashed with bcryptjs |
| Documents | docxtemplater + Handlebars | 3.69 / 4.7 | Real Word .docx templates; PDF through LibreOffice where installed |
| Mail / push | nodemailer, web-push | 9.0 / 3.6 | SMTP held per company in the database; VAPID keys for push |
| AI | Anthropic Claude SDK | 0.106 | Entirely optional — see §14 |
| Process manager | PM2 (Windows service via NSSM) / systemd / Docker | — | Survives reboot on every platform |
| Web server | nginx | current stable | Serves the static builds and proxies /api/ to the one API process |
There is exactly one API process. Every front end — the desk application and all twelve phone apps — is a separate static bundle that nginx publishes on the one address — the desk application at /, each phone app under its own path (/track/, /crm/ …) — proxying /api/ back to that single process. One host name and one certificate cover them all. Adding a phone app adds a static site, not a service.
| Package | Role |
|---|---|
packages/api | The Express REST API — 67 route modules, the posting engines, reporting, document generation, licensing and the scheduled jobs |
packages/web | The full ERP desk application (React + Ant Design) |
packages/*-pwa × 12 | Track, Dues, CRM, Delivery, Notify, Ask, Support, Tenant, Jobs, Inspect, Letting, Collect |
packages/support | Shared client library behind the support assistant in every front end |
packages/license-server | The vendor-side licence issuing and tracking service — it runs at Ritzy, not at the customer |
The API keeps no session state in memory — authentication is a signed token and session records live in the database — so it can be restarted without logging anyone out. Scaling is nonetheless vertical by design: the bottleneck in this workload is database I/O rather than Node CPU, and one PostgreSQL server per customer is ample. The multi-customer answer is one instance per customer, not a shared cluster.
Every company in an install lives in one PostgreSQL database. The companies table is the company registry and drives the login page's company dropdown. Every business table starts with company_id, and Row-Level Security limits each connection to the company it was opened for; consolidated group reports switch it to read every company. Attachments and scanned documents are held in the same database rather than in a separate file.
| Item | Detail |
|---|---|
| Database | PostgreSQL 18, UTF-8, created with the built-in C.UTF-8 locale so codes and names sort the same way on every operating system |
| Schema | About 400 tables (394 in the baseline, the rest added by later migrations), 357 baseline foreign keys, 35 views |
| Keys | Each table has a surrogate key; business numbers (voucher, account code, job number) are unique columns; document numbering uses sequences plus a locked series table |
| Types | Money is exact NUMERIC (never floating point), flags are boolean, dates are date or timestamptz, text is text, binary is bytea |
| Logins | PG_URL (superuser) prepares and upgrades the database on start; PG_APP_URL (ritzy_app) serves every request and is subject to Row-Level Security |
<RITZY_ROOT> | Install-wide state files under Ritzy\System, and the default backup target Backups\PostgreSQL |
On start the application creates its own database, schema, application login and upgrades from PG_URL, so there is no separate database-setup step. The schema is a baseline; later changes are numbered SQL migrations applied in order, each in its own transaction and recorded with a checksum in schema_migrations. The same runner upgrades an existing install when a new build is deployed, so the database and the application version stay in lockstep. A new company is seeded with its chart of accounts and defaults in one transaction, and adding a module is a data change, not a schema change.
The desktop application kept much of its logic in Firebird stored procedures and triggers. In Ritzy One that logic — voucher posting, ledger and party balances, bill allocation, inventory costing and stock posting — runs in TypeScript services, inside the same database transaction as the document it posts, and is covered by regression tests against the desktop application's results. Only a few read-only queries remain as SQL functions.
PostgreSQL is open-source with no per-seat or per-core licence cost, runs identically on Windows, Linux and in containers, and is supported by every cloud provider as a managed service. Exact numeric types, real foreign keys and Row-Level Security give the data stronger guarantees than the desktop schema had, and standard tools — pg_dump, pg_restore, streaming replication — cover backup and standby.
Desktop customers' data is in Firebird .FDB files. A one-off import converts each company into the PostgreSQL database: from the administration console (Import from Firebird, as a new company or replacing an existing one) or with npm run pg:convert. Problem rows are logged rather than silently dropped. This import is the only part of Ritzy One that reads Firebird; the running server does not need Firebird installed.
/api, across 67 registered route modules (/api/auth, /api/accounts-tran, /api/reports, /api/realestate, /api/payroll and so on).{ rows, totals } — a fixed contract the grid components rely on.GET /api/version is unauthenticated and reports the running build marker. It is the canonical way to confirm which build is live after a deployment.A gated module must be both installed in the company database and entitled by the subscription licence before its routes answer, and the check is on the server rather than only in the menu. It deliberately fails open if the entitlement lookup itself errors, so an infrastructure problem can never lock a paying customer out of their own data.
| Control | Implementation |
|---|---|
| Authentication | Username and password against the company database, issuing a signed JWT. Access token lasts 8 hours by default, the refresh token 7 days; both configurable per install. |
| Token signing | HMAC with a per-install secret generated randomly by the installer — never a shipped default. |
| Passwords | Hashed with bcrypt. Existing desktop accounts keep working through the original cipher, so no password reset is forced at cut-over. |
| Two-factor | TOTP (RFC 6238, any authenticator app), enrolled per user, and mandatory on the internal support account. |
| Authorisation | The desktop application's own rights model is preserved: per-group, per-form view / add / edit / delete rights, enforced server-side on every call and used to build the menu. |
| Database access | PostgreSQL listens locally and logs in with SCRAM-SHA-256. Requests run as a dedicated application login (ritzy_app) that does not own the tables, so Row-Level Security confines every query to the signed-in company; the superuser login is used only to prepare and upgrade the database on start. Where the database is on another host, the connection uses TLS (sslmode in the connection URL). |
| Browser transport | TLS terminates at nginx; a ready configuration ships in the bundle. Let's Encrypt/certbot on Linux, IIS ARR or a certificate bound in nginx on Windows. |
| CORS | Restricted to the configured origins. |
| Session control | Sessions are recorded per device in the database and can be ended centrally. A device that has been disabled, or whose app has been withdrawn, is rejected at the next token refresh rather than only at the next login. |
| Privileged tooling | The support console that can run SQL and replace database files sits behind three independent gates: a dedicated internal account, mandatory two-factor on it, and a rotating support password re-checked on the server for every change it makes. |
Licensing is a signed-token scheme. A licence is issued by the vendor-side Ritzy License Server, signed with a private key, and verified locally against the matching public key — so entitlement checks keep working with no internet connection.
| Platform | Status | How it is installed |
|---|---|---|
| Windows Server 2016+, or Windows 10/11 Pro | Primary, in production | install.ps1 — PM2 plus NSSM services, nginx, firewall rules |
| Linux (Ubuntu LTS, Debian, AlmaLinux) | Supported | install-linux.sh — systemd and nginx |
| Docker / Docker Compose (Linux or WSL2) | Supported, self-contained | docker compose up -d; PostgreSQL 18 is inside the bundle, so there is nothing to install |
| Profile | vCPU | RAM | Storage | Suits |
|---|---|---|---|---|
| Minimum / evaluation | 2 | 4 GB | 40 GB SSD | Pilot, one company, a handful of users |
| Recommended production | 4 | 8 GB | 100 GB NVMe | One company, 10–30 concurrent users, normal reporting |
| Report-heavy or multi-company | 4–8 | 16 GB | 250 GB NVMe | Several companies, long histories, heavy trial-balance and ageing runs |
Component footprint on a lean install: idle Linux around 0.5–1 GB, the Node API 0.3–0.5 GB, and PostgreSQL with a useful cache 1–2 GB. A 3 GB tenant VM is comfortable and 4 GB generous — which is why a 32 GB host runs roughly six to eight fully isolated tenant VMs with headroom.
pg_restore and a report run. Oversubscribed budget-VPS disks are the commonest cause of disappointing performance.| Requirement | Version | Notes |
|---|---|---|
| PostgreSQL | 18 (17 minimum) | Installed by the Windows installer if missing (EDB installer, silent; service postgresql-x64-18); an older local PostgreSQL stops the installer until it is moved to 18. Logins use SCRAM-SHA-256; data checksums on. One database holds every company. The app creates the database, schema, upgrades and its own login on start, from PG_URL. Inside the Docker bundle already. |
| Firebird | — | Not needed by the server. Only the one-off import of a Ritzy desktop customer's data reads their .FDB files (the administration console's Import from Firebird, or npm run pg:convert), on a machine that has Firebird. |
| Node.js | 20.x LTS or later | Installed automatically by the Windows installer if missing. |
| nginx | current stable | Downloaded by the Windows installer if absent. |
| LibreOffice | any current release | Optional. Only needed to render Word quotation and purchase-order templates to PDF. |
| Client | Requirement |
|---|---|
| Desk browser | Any current evergreen browser — Chrome, Edge, Firefox or Safari. No plug-in, no Java, no ActiveX, nothing installed on the workstation. |
| Screen | 1366×768 or better for the desk application; the phone apps are built for phone widths. |
| Phones | Android with Chrome, or iPhone with Safari. The apps install to the home screen straight from the browser — no app store, no MDM package. |
| Phone features used | Camera (QR scanning, photos, visiting-card capture), microphone (spoken questions), location and signature capture — each behind the browser's own permission prompt. Several field apps hold work offline and sync when a signal returns. |
| Network | LAN, VPN or public HTTPS. Bandwidth needs are modest: JSON payloads against a cached static bundle, with no thick-client chatter and no database traffic to the workstation. |
| Push notifications | Standard Web Push (VAPID). Requires HTTPS, and on iPhone that the app has been added to the home screen. |
The installer opens these inbound TCP rules. For an internet-facing deployment, publish 443 only and put nginx in front of everything.
| Port | Service |
|---|---|
| 80 / 443 | Desk web application and every phone app under its path (nginx) |
| 3001 | The API process — proxied by nginx, does not need to be public |
| 5432 | PostgreSQL — local to the server; never expose it |
Outbound traffic is entirely optional and each item can be omitted: the licence heartbeat, company registration and the support assistant to the Ritzy License Server over HTTPS, SMTP for sending documents, the tax authority's endpoint where e-invoicing is in use, and the browser push services.
Deployment is a compiled bundle, not a source checkout. The build produces a deployment zip; extracting it over the install folder and restarting the process manager is the whole update. The installer installs PostgreSQL 18 when it is missing, sets up dependencies, writes the API's .env with the two database logins and a freshly generated secret, schedules a nightly pg_dump, registers the PM2 process and the Windows services, configures nginx and opens the firewall.
install.ps1 on Windows or install-linux.sh on Linux — it prompts once for the base folder (and, when PostgreSQL was already installed, its superuser password).install-app.ps1 — no prompts, no .env changes; the app applies any database upgrades itself on start. Or simply extract the zip and restart the service..env is never overwritten by a re-install.GET /api/version returns the live build marker.For a new standalone install the container bundle is the simplest path: two services, postgres (PostgreSQL 18) and app, each with its own volume. Updates are pull then up -d. A Ritzy desktop customer, whose data is in Firebird, is moved onto a stack with docker/migrate/: the companies are converted on the Windows side by the importer and restored into the stack as a pg_dump.
A brand-new server has no database and no company registry, so there is nothing to log in to yet. The install therefore starts in first-run mode: a setup wizard — reachable only while zero companies exist, and additionally protected by the support password — creates the first company in the database (which the app has already created, with its schema and upgrades) and seeds the chart of accounts and defaults. Once a company exists the wizard is gone for good.
pg_dump. It reads one consistent snapshot of the running database. The Windows installer schedules it nightly (pg_dump -Fc, 14 days kept under <RITZY_ROOT>\Backups\PostgreSQL); the Docker bundle's backup-stacks.sh does the same per stack. One company is backed up and restored from the administration console (.pgbk: download to a PC, upload, restore).pg_restore plus a service restart. The application holds no state of its own outside the database and its .env — keep a copy of that file with the backups.| Integration | Detail |
|---|---|
| ZATCA e-invoicing (Saudi Arabia) | Phase-2 (Fatoora) clearance and reporting through the official SDK on the server. Invoices, cash memos, credit and debit notes are submitted on finalisation, with a locked invoice-hash and counter chain and a full submissions log. Enabled for Saudi installs only. |
| Bahrain customs manifest | The WCO-format sea cargo manifest, generated from Liner jobs and submitted to OFOQ/TFP with per-submission tracking. Requires the Liner module and the customer's own API keys. |
| SMTP host, port, credentials and sender address are held per company in the database, so changing them is an application setting rather than a server file edit. | |
| Web push | A standard VAPID key pair in the API's .env. Without it the notification inbox still works and only the push toggle is unavailable. |
| Documents | Word .docx templates with merge fields for quotations and purchase orders; HTML and Handlebars templates, editable inside the application, for invoices and other billing documents. QR codes are generated server-side. |
| Data import | CSV import for masters, opening balances and bank statements, with an AI-assisted column mapper. |
| Payments | PayPal, on the vendor's licence server only — not something a customer install talks to. |
The AI features use Anthropic's Claude models and are strictly opt-in. They fall into two groups with different privacy profiles.
.env. The key is the customer's, requests go straight from their server to the model API, and they carry only the text or image being processed. With no key configured these features report that AI is not set up and everything else carries on unchanged.The management briefing is deliberately constrained: the model chooses which figures to show, but every number is computed by the server from the database. The model never produces a figure itself.
| Dimension | Position |
|---|---|
| Concurrent users | Governed by the licence seat count rather than a technical ceiling. Ten to thirty concurrent users on the recommended profile is routine. |
| Companies per install | Several — all in the one database, separated by Row-Level Security, and chosen at login. Bounded by disk and RAM, not by the application. |
| Tenants per host | One VM per customer is the recommended isolation boundary; roughly six to eight 3 GB tenant VMs fit a 32 GB host. |
| Report performance | The heaviest work is trial balance, general ledger and ageing across long histories. These are sort- and I/O-bound, so NVMe storage and enough RAM for PostgreSQL's cache (shared_buffers plus the OS file cache) matter far more than CPU count. |
| Known constraints | The application is designed for one PostgreSQL server per customer and does not use read replicas or database scale-out. Plan capacity vertically and isolate per customer. (Standard PostgreSQL also opens the door to streaming replicas for standby and to managed PostgreSQL services, where a customer prefers them.) |
Everything environment-specific lives in one file, packages\api\.env, which is why the same build runs unchanged on Windows, on Linux and in a container. The keys that matter most:
# Paths and database
RITZY_ROOT=C:\Ritzy Solutions # the BASE folder -- not ...\Ritzy
PG_URL=postgres://postgres:********@localhost:5432/ritzy # superuser: makes the database ready on start
PG_APP_URL=postgres://ritzy_app:********@localhost:5432/ritzy # the login every request uses
RITZY_BACKUP_DIR=
# API
PORT=3001
JWT_SECRET=<generated at install>
JWT_EXPIRES_IN=8h
JWT_REFRESH_EXPIRES_IN=7d
CORS_ORIGIN=https://erp.example.com
# Optional features
ANTHROPIC_API_KEY= # AI data features
VAPID_PUBLIC_KEY= # web push
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=
SOFFICE_PATH= # LibreOffice, for PDF rendering
FATOORA_HOME= # ZATCA -- Saudi installs only
# Licensing (inert unless enabled)
LICENSING_ENABLED=false
LICENSE_PUBLIC_KEY=
LICENSE_HEARTBEAT_URL=
RITZY_ROOT is the base folder that contains Ritzy\ and Backups\ — the application appends the rest itself, so it is C:\Ritzy Solutions, not C:\Ritzy Solutions\Ritzy. And PG_APP_URL must be the ritzy_app login, not the superuser: a superuser bypasses Row-Level Security, and the application refuses a second company on such a login.