Inventra is a single Next.js 15 application on MongoDB. The central idea is that every stock operation goes through one engine that writes an append-only movement, updates the stock-level cache and the cost layers, and posts the balanced journal entry — all in one transaction.
Key design decisions
- Stock is a ledger. 11 movement types (receipt, reserve, release, dispatch, transfers, adjustments, counts, returns, POS sales, put-away) are recorded append-only with their value change and reference document.
- Concurrency by conditional updates. Reservations and dispatches use
$inc with $gte guards rather than read-then-write, so two orders can never take the same unit.
- Accounting wired into operations. Receipts, dispatches, invoices, payments, adjustments and consignment ownership transfers post balanced journals; a period lock protects closed months.
- Integer money. Minor units everywhere, currency exponents respected, and a stored rate on each document.
- Fail-closed multi-tenancy. A Mongoose plugin throws on any query without a tenant filter; every server action passes one guarded wrapper (session → tenant → permission scope → validation → audit), enforced by a repo script.
- Offline where it matters. The POS till and field-sales app queue writes in IndexedDB with idempotency keys; the server de-duplicates on a unique index. The back office stays online-only.
- AI as an optional assistant. Imports land in a staged review grid with per-field confidence; commits are all-or-nothing and can be rolled back.
Architecture
React Server Components and a shadcn/Radix UI sit on server actions and /api/v1/[tenantSlug] route handlers, which call a service layer of more than 40 modules. Mongoose models on a MongoDB replica set carry tenant and read-only plugins. PDFs are rendered server-side and labels are emitted as ZPL; Cloudinary, SMTP, optional Upstash Redis and an optional AI API sit beside the services, and a protected cron endpoint recalculates re-order points and rebalancing.