This is a complete inventory management system in Python that also handles several warehouses: purchases, sales, transfers, internal requisitions, low-stock alerts and a reorder list. It is a web app built with Flask and SQLite, it runs with two commands, and the full source code is free to download below. We wrote and tested every part of it ourselves.
If you need a final-year project you can actually explain in a viva, or a small system you can adapt for a real shop, this is built for that. This page also explains the database design and the one piece of logic most student projects get wrong: stock that never goes negative. Prefer a desktop app? Our older Warehouse Management System in C# is still available.

The dashboard: stock value, 14 days of sales and purchases, and what is running low.
Project at a Glance
Features of the Inventory Management System
- Products and categories with SKU, unit, cost price, sale price and a reorder level per product
- Suppliers, customers and warehouses with search, add, edit and safe delete
- Purchases (stock in) with several products on one document
- Sales (stock out) with automatic prices, a running total and a printable invoice
- Transfers between warehouses that never change the total stock
- Internal requisitions: staff request items, an admin approves or rejects, stock is issued on approval
- Stock by warehouse report with value at cost and a low-stock filter
- Movement ledger: every stock change, with filters and CSV download
- Reorder list that suggests how much to buy from the last 30 days of sales
- Two roles (admin and staff), hashed passwords and CSRF protection on every form
- Database backup download and a read-only JSON API
- 45 days of demo data loaded on first run, so every screen has something to show

A sale with two product lines. Prices fill in automatically and the total updates as you type.
How to Run the Project (3 Steps)
You need Python installed (get it from python.org; on Windows tick “Add Python to PATH”). New to Python? Start with our Python beginner to advanced notes.
Step 1: Download and extract the zip
The download button is at the end of this page. Extract the zip anywhere, then open a terminal inside the extracted folder.
Step 2: Install Flask
Step 3: Start the app
* Serving Flask app ‘app’
* Debug mode: off
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
Open http://127.0.0.1:5000 in your browser and log in as admin / admin123. The database file wms.db is created for you on the first run.
✏️ Try it: run python app.py --reset any time to start again with fresh demo data.
Inventory Management System Database Design
The database has 13 tables and 1 view. The diagram shows how they connect.

ER diagram of the 13 tables. The yellow table is the stock ledger.
The design decision that matters: a stock ledger
Most student projects keep a quantity column on the products table and add or subtract from it. That works until two things update it at once, or one update fails halfway, and then the number is wrong forever with no way to find out why.
This project never stores stock as a number. Every change is a row in stock_movements: +60 for a purchase, -4 for a sale. Current stock is simply the sum of those rows, which this view calculates:
SELECT p.id AS product_id, w.id AS warehouse_id, COALESCE(SUM(m.qty), 0) AS qty
FROM products p
CROSS JOIN warehouses w
LEFT JOIN stock_movements m ON m.product_id = p.id AND m.warehouse_id = w.id
GROUP BY p.id, w.id;
Code Walkthrough: A Sale That Cannot Oversell
Two short pieces of app.py do the important work. First, a helper that makes a block of database work all-or-nothing:
def transaction():
“””All-or-nothing block. BEGIN IMMEDIATE takes the write lock first, so two
cashiers cannot both sell the last unit of the same product.”””
db = get_db()
db.execute(“BEGIN IMMEDIATE”)
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
Then the sale itself. It checks every line first and only then writes anything. If one product is short, the whole sale is cancelled, including the lines that were fine:
lines = merge_lines(lines)
with transaction() as db:
for product_id, qty, _ in lines: # check everything first…
require_stock(db, product_id, warehouse_id, qty)
cur = db.execute(
“INSERT INTO sales (customer_id, warehouse_id, total, note, created_by) VALUES (?, ?, ?, ?, ?)”,
(customer_id, warehouse_id, sum(q * p for _, q, p in lines), note, user_id))
sale_id = cur.lastrowid
for product_id, qty, price in lines: # …then write everything
db.execute(“INSERT INTO sale_items (sale_id, product_id, qty, unit_price) VALUES (?, ?, ?, ?)”,
(sale_id, product_id, qty, price))
move(db, product_id, warehouse_id, -qty, “sale”, f”SALE-{sale_id}”, user_id)
return sale_id
Here is what happens when someone tries to sell two power banks while the stock is zero. Nothing is saved:

The sale form after an oversell attempt. The stock and the sales table are unchanged.
Proof It Works: 20 Automated Tests
The zip includes test_app.py. Run it with one command:
test_same_product_on_two_lines_is_checked_together … ok
test_transfer_moves_stock_without_changing_the_total … ok
test_requisition_flow … ok
test_staff_cannot_open_admin_pages … ok
test_post_without_csrf_token_is_blocked … ok
test_backup_is_a_real_sqlite_file … ok
test_stock_never_negative_anywhere … ok
…
———————————————————————-
Ran 20 tests in 12.707s
OK
✏️ Viva tip: if your examiner asks “how do you know it works?”, run this in front of them.
The Reorder List: A Simple Sales Forecast
The reorder page answers “what should I buy today, and how much?” using a formula you can explain in one breath:
- Daily sales = units sold in the last 30 days, divided by 30
- Reorder point = the larger of the product’s own reorder level and 10 days of sales (7 days supplier lead time plus 3 safety days)
- Suggested quantity = 30 days of sales + reorder point – stock on hand
The reorder list: days of stock left and how much to order now.
The same numbers are available as JSON, so a mobile app or another system can read them:
{“id”:11,”name”:”Hammer 500g”,”on_hand”:2,”reorder_level”:8,”sku”:”HW-001″,”unit”:”pcs”},
{“id”:12,”name”:”Screwdriver Set”,”on_hand”:3,”reorder_level”:8,”sku”:”HW-002″,”unit”:”set”}]
The API key is switched on by setting the WMS_API_KEY environment variable before you start the app. Without a key or a login the API answers 401.
More Screens

Stock by warehouse, with value at cost. Admins can correct a count here.

Requisitions: staff ask, an admin approves or rejects.

Every sale gets a printable invoice.
5 Common Errors (With the Exact Messages)
1. No module named ‘flask’
ModuleNotFoundError: No module named ‘flask’
You skipped step 2. Run pip install -r requirements.txt. If you have several Python versions, use python -m pip install -r requirements.txt so Flask goes into the same Python that runs the app.
2. Port 5000 is in use
Port 5000 is in use by another program. Either identify and stop that program, or start the server with a different port.
The app is already running in another terminal, or another program holds the port (on macOS, AirPlay Receiver uses 5000). Close the other copy, or start on another port: on Windows set PORT=5050 then python app.py; on macOS or Linux PORT=5050 python app.py.
3. ‘python’ is not recognized
operable program or batch file.
Python is not on PATH. Reinstall Python and tick “Add Python to PATH”, or use py app.py instead.
4. Error 400: form expired
Form expired or CSRF token missing. Go back, refresh the page and try again.
You logged out in another tab, or the form was open for a long time. Go back, refresh and submit again. This check is deliberate: it stops other websites from submitting forms on your behalf.
5. Not enough stock
This is the system doing its job. Receive stock with a purchase, transfer it from another warehouse, or (as admin) correct the count on the Stock by warehouse page.
Ideas to Extend It (Future Work for Your Report)
- Barcode scanning: a USB scanner types the SKU, so a search box on the sale form is enough
- PDF invoices and email alerts when a product reaches its reorder point
- Purchase orders created directly from the reorder list
- PostgreSQL or MySQL for many users at once; the SQL is standard and needs few changes
- Deploy it free on PythonAnywhere or Render, after changing the default passwords and the
WMS_SECRETkey - Batch and expiry dates for medicines or food, like our pharmacy POS in C#
For Your Project Report
Related Projects
- Desktop version: Warehouse Management System in C# with source code
- Sales focus: Sale and Inventory Management System in C#
- More Python: Python projects, beginner to advanced
📥 Download the Source Code
TechPro WMS: Inventory & Warehouse Management System in Python
app.py, schema.sql, 17 templates, 20 tests, Bootstrap and Chart.js, README. Our own code, MIT licence, 135 KB, no sign-up.
Run it. Break it. Extend it.
Download the zip, run the 20 tests, then add one feature of your own. That is a project you can defend. Get the source code
Frequently Asked Questions
Yes. The code is released under the MIT licence, so you can use it, change it and submit it. Make it your own: add one of the extensions above, and be ready to explain the stock ledger and the transaction code.
It uses SQLite, which is built into Python. There is nothing to install or configure. The file wms.db is created the first time you run python app.py.
Yes. Open app.py, find CURRENCY="Rs." near the top and change it to your symbol. Every page picks it up.
Yes. After Flask is installed, everything runs on your own computer. Bootstrap and Chart.js are inside the zip instead of being loaded from a CDN.
For a small business on one computer or a local network, yes, after you change the default passwords and the WMS_SECRET key and set up regular backups. For many users at the same time, move the database to PostgreSQL or MySQL first.
Inventory management tracks what you have and what it is worth. Warehouse management adds where it is and how it moves: locations, transfers and internal requests. This project covers both, with stock kept separately for each warehouse.



