Jaloliddin Musayev
Back to projects
Actively developed2025 — 2026ERP & finance

Multi-tenant ERP platform

One codebase serving many companies: sales, receivables, invoicing and reliable hand-off of orders to the warehouse system.

584
source files
23
DB migrations
4
access levels
2
tenant modes

Problem

Building a separate system per company is expensive and unmaintainable. But putting everyone in one shared database risks one company seeing another's data. On top of that, receivables lived in Excel — there was no single source of truth for who owed what and when an invoice was actually settled.

Solution

Tenant context travels inside the JWT, and a company can run in `shared` or `dedicated` mode without rewriting the domain layer. Access is two-dimensional: the role decides what you may do, the data scope decides what you may see — the same endpoint returns different rows to a branch manager and a field rep. Receivables are modelled properly: invoice lifecycle, allocation of payments across invoices, aging by due date, and a client statement.

Key capabilities

  • Multi-tenancy: a company runs on a shared or a dedicated database, and the choice never reaches the domain code
  • Four data-scope levels: all data / own branch / own department / own records only
  • The complete sales-order lifecycle, from draft through to fulfilment
  • Receivables: invoice draft → issued → paid, payment allocation, and an aging report bucketed by due date
  • Client statements: chronological invoice and payment lines with allocation audit notes
  • Outbox pattern: orders reach the warehouse system through a queue, with `FOR UPDATE SKIP LOCKED` keeping parallel workers apart
  • Crash recovery: jobs stuck mid-flight are automatically returned to the queue after a timeout
  • 23 Alembic migrations — the schema history is fully versioned and reversible
  • A Next.js admin panel and a Flutter mobile app for field sales

Architecture

A request pulls its tenant and access boundary out of the JWT, and domain services run inside that context. Every outbound event is written to a queue table first, so an order survives a dropped network.

Clients

Next.js admin panelFlutter mobile app

API & access control

ALL_DATA / OWN_BRANCH / OWN_DEPARTMENT / OWN_RECORDS

FastAPIJWT tenant contextRBACData scope

Domain modules

Sales ordersInvoices & ARPaymentsCatalog & clients

Integration

SKIP LOCKED, retries, stale-job recovery

Outbox tableIntegration workerWMS adapter

Data

PostgreSQLAlembic (23 revisions)

Stack

Backend

Python 3.12FastAPISQLAlchemy 2.xPostgreSQLAlembicPydantic v2JWT / bcryptpytest

Web

Next.jsReact 19TypeScriptTanStack QueryTanStack TableZustandRadix UI

Mobile

FlutterDartDioSecure storage

Infrastructure

Outbox workerAlembic migrationsCron / long-running worker

My role

Backend architecture and the entire domain layer, the RBAC and data-scope model, the integration worker, the admin panel and the Flutter mobile app.

Client names, domains and server addresses are withheld for confidentiality.