All work
ERP
Business operations
Shipped · iterating

Codesloci ERP

Retail back office on one Flutter codebase

Platform guide

Stock, orders and reporting for product-led retail. Twelve resources behind one versioned Express API, and a Flutter client that deliberately surfaces four of them — dashboard, products, orders, settings — as one adaptive layout that serves a phone, a tablet and a desktop browser from a single widget tree.

The problem

Retail operators outgrow spreadsheets fast. Stock drifts from reality, purchase orders live in a supplier’s inbox, and nobody can say what margin looked like last month without an afternoon of reconciliation. The moment a second location opens, a single stock number stops meaning anything at all.

Our approach

One Flutter codebase for every surface, with a single adaptive shell rather than a layout tree per platform: a bottom navigation bar under 600dp, a collapsed rail under 1024, an extended rail above. Behind it an Express and MongoDB API where all twelve resources sit under one router with the auth guard mounted once — the previous backend attached its check to four routes out of fifty-five, and mounting it at the boundary is what stops the next module shipping unprotected. Totals and stock are the server’s business: the client posts product ids and quantities, and a client-supplied total is rejected as an unknown field.

The outcome

A back office that runs as a web app, a phone app and a desktop app from one build, over an API where a stock position can be explained and an order cannot be moved to a status the business does not allow.

What is inside

Twelve API resources: products, categories, customers, suppliers, stores, countries, orders, sales, purchases, returns, discounts and users

A four-destination Flutter client — dashboard, products, orders, settings — adaptive across phone, tablet and desktop

Order lifecycle as an explicit state machine, with the permitted transitions declared per status

Stock consumed on order creation and released exactly once on cancel or refund

Role-based access — administrator, manager, cashier, staff — enforced server-side and mirrored in the UI so no button returns a 403

JWT access tokens with server-side refresh tokens, so a password change can invalidate other sessions

One dashboard request backing the whole overview: summary, revenue trend, top products and customers, low stock, orders by status


Engineering notes · 4 min read

Stock is a ledger, not a number

12

Modules

one folder each

Movements

Stock writes

never a running total

Many

Stores

per-store positions

The single most common way retail software loses trust is storing stock as a mutable quantity. Someone edits it to fix a discrepancy, the discrepancy is now invisible, and three months later nobody can explain the position.

Every change here is written as a movement with a cause: goods received, sale, return, adjustment, transfer. The position is derived. That makes the stock ledger the screen everything else is judged against — if the ledger and the dashboard disagree, the dashboard is wrong.

Module-per-domain, and what that actually buys

  • Adding a module means adding a folder, not reworking the shell
  • A module owns its routes, its grid columns and its permissions in one place
  • Role-based access is declared per module, so a clerk’s build of the app is genuinely smaller
  • Reporting reads the same movements the operational screens write — no export step, no BI layer

Virtualised grids were a requirement, not an optimisation

An operator with 20,000 SKUs scrolls. A non-virtualised table renders 20,000 rows into the DOM and the browser stops responding, which reads as "the system is broken" rather than "the table is naive". MUI X DataGrid handles windowing so the interaction stays flat as the catalogue grows.

The lifecycle the demo walks through
StepWritesVisible in
Purchase order receivedGoods-in movementStock ledger, stock value KPI
SaleSale movementStock ledger, revenue chart
ReturnReturn movementStock ledger, returns rate
AdjustmentAdjustment movement with a reasonStock ledger only — never silent

If you cannot explain a stock position from the movements that produced it, you do not have inventory management. You have a number somebody typed.

Technology

Client

Flutter
Dart
Riverpod
GoRouter
Material 3

Back end

Express
Node.js
Mongoose
zod
JWT
bcryptjs
Helmet
pino

Data

MongoDB

Platforms

Web · iOS · Android · Desktop

Discuss a similar build

Need a platform like Codesloci ERP?

We can walk you through how this was built, what it cost to run, and what the equivalent looks like for your business.

Or email us directly at [email protected]