2025—2026
Completed
Restaurant ERP
Sales, stock and customers in one system. Registering a sale moves inventory, and every dashboard number is derived from that same record.
Overview
A management system for a small restaurant: products and stock, a customer registry, sales, and a dashboard of what happened. It runs on Flask with SQLAlchemy, SQLite while developing and MySQL in the configured production setup.
Problem
The version of this that people actually use is a spreadsheet, where stock and sales are typed twice. They disagree within a week. Once they disagree, nothing derived from them — revenue, low-stock warnings, best sellers — can be trusted.
Solution
One operation writes once. Registering a sale creates the sale record and reduces the stock of every product in it, in the same request. The dashboard does not store any numbers of its own: revenue, sales count, low-stock alerts, and top products are all queries over the sales and products tables.
My role
I built the whole thing — the models, the routes, JWT authentication for the private routes, and the Jinja2 templates on top.
Architecture
Technologies
- LanguagePython 3.12
- FrameworkFlask 3
- ORMSQLAlchemy 2
- AuthJWT
- DatabaseMySQL · SQLite
- ViewsJinja2 · Bootstrap 5
Challenges
A sale touches two things at once. If the sale is written and the stock update fails, the system starts lying — so the two have to move together.
Running the same models against SQLite locally and MySQL in production without the schema drifting apart.
Deciding which routes are public and which need a token, and making the failure obvious instead of silent.
What I learned
Reports are a reading problem, not a writing problem. Every metric I was tempted to store turned out to be a query I could write instead, and the version that stores nothing is the one that never goes stale.
Next