Selected work / Chapter 04

Tawzee توزيع

Arabic for “distribution”

Wholesale distribution

B2B wholesale distribution and route-to-market

A route-to-market platform for GCC wholesale distributors: an offline-first sales-rep app with geo-verified visits and instant credit checks, a head-office credit portal and a warehouse console with scan-verified picking, all on a double-entry ledger.

Open live demo Demo password Tawzee#2026 · roles to try
Used by
Sales reps, Sales director, Credit control, Warehouse
Built with
Angular 21 · Django 5.2 LTS · Django REST Framework · PostgreSQL 16 · Celery + Redis · Docker Compose · Dexie (IndexedDB) · MiniSearch
Languages
English and Arabic, with full right-to-left layouts
Source
Private repository — available on request by email
Tawzee credit-control overview with customer exposure, aging buckets and accounts on credit stop.
Fig. 1 Credit control overview

The problemWhat the operation is up against.

Wholesale distributors in Saudi Arabia and the UAE sell on credit to thousands of small outlets — groceries, hypermarkets, auto-parts shops, electrical traders. Their reps drive fixed routes, take orders at the counter and collect cash, transfers and post-dated cheques, often with poor or no mobile coverage.

Head office has to stop orders from customers who are over their limit or past their terms without stopping the business, and a cheque is not money until the bank clears it: when one bounces, the customer must be locked again before the next delivery leaves the warehouse. Prices differ by tier and promotion, VAT differs by country, and the warehouse must ship the right pack size of the right variant.

Who uses it4 roles, one system.

  • Sales reps

    Follow today’s route on a phone, check in at each outlet (verified by GPS), see the customer’s credit position instantly, take orders and collect cash or cheques — offline.

  • Sales director

    Approves or declines orders that broke credit rules, one order at a time.

  • Credit control

    Takes post-dated cheques from deposit to clearing or bounce, and lifts credit stops.

  • Warehouse

    Picks released orders from aisle-sorted lists, verifying every line by barcode, then stages and dispatches.

What was builtThe working parts.

  • An offline-first rep app

    One call downloads today’s route and every assigned customer with a credit snapshot. Check-ins, orders and collections queue in an outbox and sync when the signal returns; refused items are parked for the rep, and an order refused for credit can be resent as an override request.

  • Geo-verified visits

    Check-ins are compared with the outlet’s registered location (200 m by default) on the phone and again on the server. A far-away check-in is kept and flagged, never bounced.

  • An instant credit snapshot

    Balance, limit, available credit, open orders, cheques in hand and aging buckets (0–30, 31–60, 61–90, over 90 days) with a credit-stop banner — adjusted offline for the phone’s own unsynced orders.

  • Pricing that agrees to the halala

    Multi-tier price books in SAR and AED, and promotions with line and mix-and-match breaks. TypeScript and Python engines compute in integer halalas against one shared contract file, so an offline quote and the server’s re-pricing agree exactly.

  • Search over 10,311 SKUs on the phone

    A MiniSearch index with prefix and fuzzy matching, exact barcode and SKU hits and Arabic normalisation; queries take a few milliseconds, and a test guards search speed over 12,000 SKUs.

  • Cheques and credit control

    Post-dated cheques with photos and a lifecycle from safe to deposited to cleared or bounced; a bounce reverses the receipt and puts the customer on credit stop automatically. Credit limits are enforced in the order serializer under a row lock.

  • Scan-to-verify picking

    Pick lists sorted by aisle and bin in walking order. A scan of the wrong item from the same family is diagnosed precisely — wrong pack size or wrong variant — with audible feedback. Dispatch posts a bilingual tax invoice naming both parties’ VAT and CR numbers.

  • Double-entry receivables

    Invoices, receipts, cheque clearances and bounces are balanced journal entries; a deferred PostgreSQL trigger re-checks every entry at commit, and corrections are reversing entries. A nightly job re-ages every customer.

Design rulesThe rules it keeps.

The decisions that make the system trustworthy, enforced on the server and in the database rather than left to the screen.

A cheque is not cash until it clears.

Receiving a post-dated cheque credits the customer’s statement, but credit exposure adds uncleared cheques back, so a cheque dated six months out does not free up credit today.

Overrides are per order.

The Sales Director lets one order through; the stop stays, and the approval, its reason and the position at the time are kept.

Prices are honoured as quoted.

Promotions are evaluated at the order’s device date, so an order taken offline on the last day of a promotion keeps its discount when it syncs the next morning.

Field facts versus commitments.

A check-in from the wrong place is recorded and flagged; an order that breaks credit or stock rules is refused, or held for approval.

ArchitectureHow it fits together.

An Angular PWA for reps and a back-office console, a Django REST API with credit and stock guards, and Celery for the nightly credit review — on PostgreSQL with row-level security and ledger triggers.

Front end
Angular 21 (standalone, zoneless, signals), Tailwind CSS 4, CDK BiDi, a Dexie outbox, a MiniSearch catalogue index and TypeScript twins of the pricing and credit engines.
API
Django 5.2 LTS and Django REST Framework: a credit guard in serializer validation that locks the customer row, and an all-or-nothing stock allocator that locks balances in product order.
Ledger
Double-entry accounts receivable with one-sided lines, a deferred constraint trigger that re-checks every entry at commit, FIFO aging and a trial balance.
Jobs
Celery beat at 00:00 UTC re-ages every customer and sets or lifts overdue credit holds.
Data
PostgreSQL 16 with row-level security per tenant and CHECK constraints that make negative stock impossible.
Delivery
Docker Compose with NGINX serving the PWA and protected cheque photos, Gunicorn, Celery worker and beat. CI runs pytest against PostgreSQL, the Angular tests and build, and a Trivy scan.

Quality evidenceTested where it matters.

  • 111 backend tests (pytest), including the row-level-security, ledger-trigger and row-lock tests that run against PostgreSQL in CI; ruff lint and format checks and a migration-drift check.
  • 28 frontend unit tests (Vitest), including the pricing contract shared with the server, offline credit evaluation and search speed over 12,000 SKUs.
  • GitHub Actions runs all of it, a production build with bundle budgets, and builds both container images and scans them with Trivy.

ScreensOn the desk and in the field.

A “commercial ledger” direction: figures are the primary visual element — tabular IBM Plex Sans figures in ruled tables, aging as a ledger of buckets — with one ledger-green accent; red and amber are reserved for credit risk and always paired with text. The rep app is built for one hand in the sun: 16 px base type, 44 px targets, the connection state always spelled out and the main actions within thumb reach.

Try the demoSign in as any role.

The demo runs on realistic seeded data, with a second tenant to show that one customer never sees another’s records.

The sign-in page offers each demo role in one click, and the demo data resets every night at 00:30 UTC.

Demo users
UserRoleWhat to try
repSales rep, RiyadhOn a phone-sized window: check in at stop 1, go offline and order milk and juice (3 % mix-and-match), send stop 2’s order for approval, record a cheque with a photo at stop 3.
directorSales directorCredit dashboard; approve or decline the orders waiting in Approvals.
financeCredit controlCheques: deposit a matured cheque, clear one, bounce one and watch the customer lock; lift a stop.
warehouseWarehouse, Riyadh DCPicking: start the urgent order, scan a wrong pack size, finish, stage and dispatch.
adminAdministratorEverything except the director’s approval.
rep_jed, rep_dxbReps in Jeddah and DubaiTheir own routes and customers; Dubai bills in AED with 5 % VAT.
gst_adminAdmin of a second companySees none of the first company’s data.

Password for every demo user Tawzee#2026

  • All customers, products and amounts are generated demo data for a fictitious distributor.

Out of scopeNot built yet — on purpose.

  • ZATCA Phase 2 e-invoicing — VAT and tax invoice numbers are posted; the documents are not generated.
  • Short picks and back-orders — allocation is all-or-nothing.
  • Bank statement import for clearing cheques and transfers.
  • Route optimisation and a map view.

Work togetherNeed a platform like Tawzee?

Tell me about the operation and the rules it can’t break.

Also on GitHub