Shop management for small Algerian retailers Django REST API + Arabic RTL frontend
# Hanouti shop management for small Algerian retailers
A point-of-sale and credit-tracking system built for corner shops (*hanout*),
where inventory is counted by eye and customer debt lives in a paper notebook.
Most small retailers in Algeria track credit — the *carnet* — by hand. This
project digitises that notebook alongside stock and sales, in Arabic, and is
designed from the schema up to keep working when the internet does not.
---
## Screens
| Screen | What it does |
|---|---|
| البيع (Sell) | Tap a product to add it to the basket, settle in cash or on credit |
| المخزون (Stock) | Current quantities, cost and margin per item, low-stock warnings |
| الكارني (Carnet) | Every credit customer with their running balance |
The interface is right-to-left Arabic, since the people using it are
shopkeepers, not developers.
---
## Stack
- **Backend** — Django 6.1, Django REST Framework
- **Auth** — JWT (`djangorestframework-simplejwt`)
- **Database** — SQLite in development, PostgreSQL in production
- **Frontend** — single-file HTML/CSS/JS, served by Django
---
## Design decisions
These are the parts worth reading the code for.
### Stock is a ledger, not a number
`StockMovement` is append-only: purchases, sales, breakage and corrections are
all rows. `Product.qty_on_hand` is a cached total, updated inside the same
transaction that writes the movement.
Reads stay fast, but the history remains authoritative. If the cache ever
drifts, `StockMovement.rebuild_qty()` recomputes it — and the ledger explains
when and why it went wrong.
### Prices are snapshotted at sale time
`SaleLine` stores `sell_at_sale` and `buy_at_sale` rather than referencing the
product's current price. Raising the price of an item tomorrow must not
silently rewrite last month's revenue and profit figures.
`name_at_sale` serves the same purpose: a discontinued product can be deleted
without erasing what was sold.
### Balances are derived, never stored
`Customer.balance` is computed as …