A production-quality mini ERP system for managing Vendors, Products, and Purchase Orders. Built with FastAPI + PostgreSQL + SQLAlchemy + Vanilla JS.
- Python 3.10+
- PostgreSQL 14+
- Git
git clone <your-repo-url>
cd po-management
# Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt# Create the database
createdb po_management
# Load schema + seed data
psql -U postgres -d po_management -f schema.sqlcp .env.example .env
# Edit .env and set your DATABASE_URLuvicorn backend.main:app --reload --host 0.0.0.0 --port 8000Open your browser at http://localhost:8000
po-management/
β
βββ backend/
β βββ main.py # FastAPI app entry point
β βββ database.py # SQLAlchemy engine & session
β βββ models/
β β βββ models.py # ORM table definitions
β βββ schemas/
β β βββ schemas.py # Pydantic request/response models
β βββ routes/
β β βββ vendors.py # GET/POST /api/vendors
β β βββ products.py # GET/POST /api/products
β β βββ purchase_orders.py# GET/POST /api/purchase-orders
β βββ services/
β βββ vendor_service.py # Vendor business logic
β βββ product_service.py# Product business logic
β βββ po_service.py # PO business logic (tax, stock)
β
βββ frontend/
β βββ css/
β β βββ styles.css # Dark industrial theme
β βββ js/
β β βββ api.js # Shared API client + helpers
β βββ pages/
β βββ index.html # Login page
β βββ dashboard.html # PO list + stats
β βββ create_po.html # Dynamic PO creation form
β βββ receipt.html # Order confirmation/receipt
β
βββ schema.sql # PostgreSQL DDL + seed data
βββ requirements.txt
βββ .env.example
βββ README.md
vendors (1) βββββββββββ (N) purchase_orders (1) βββββββββββ (N) purchase_order_items
β
products (1) βββββββββββββββββββββββββββββββββββββββββββββββββββββββ (N) β
| Decision | Reasoning |
|---|---|
purchase_order_items.price stores snapshot |
Product prices change over time. Snapshotting at order creation preserves accurate billing history |
total_amount stored (not computed) |
Avoids recalculating tax on every read; ensures the agreed total is immutable |
ON DELETE RESTRICT for vendor FK |
Prevents accidental deletion of a vendor that has open orders |
ON DELETE CASCADE for PO items |
Deleting a PO removes its line items atomically |
| Stock validated before any DB write | Prevents partial orders; all-or-nothing stock deduction |
| Random 4-digit suffix in ref no | Avoids sequential enumeration of PO IDs via public reference |
vendors β Supplier master
id Β· name Β· contact Β· rating (0β5)
products β Product catalog
id Β· name Β· sku (UNIQUE) Β· unit_price Β· stock_level Β· description
purchase_orders β PO header
id Β· reference_no (UNIQUE) Β· vendor_id (FK) Β· total_amount Β· status Β· created_at
purchase_order_items β PO line items
id Β· po_id (FK) Β· product_id (FK) Β· quantity Β· price (snapshot)
Base URL: http://localhost:8000/api
Interactive docs: http://localhost:8000/api/docs
| Method | Endpoint | Description |
|---|---|---|
GET |
/vendors |
List all vendors. ?search= for filtering |
GET |
/vendors/{id} |
Get single vendor |
POST |
/vendors |
Create vendor |
Create Vendor
POST /api/vendors
{
"name": "Acme Supplies",
"contact": "sales@acme.com",
"rating": 4.5
}| Method | Endpoint | Description |
|---|---|---|
GET |
/products |
List all products |
GET |
/products/{id} |
Get single product |
POST |
/products |
Create product |
POST |
/products/ai-description |
Generate AI description |
Create Product
POST /api/products
{
"name": "Industrial Drill Bit",
"sku": "DRILL-001",
"unit_price": 2999.00,
"stock_level": 100
}AI Description
POST /api/products/ai-description
{
"product_name": "Industrial Drill Bit",
"category": "Tools"
}| Method | Endpoint | Description |
|---|---|---|
GET |
/purchase-orders |
List all POs (with vendor + items) |
GET |
/purchase-orders/{id} |
Get single PO |
POST |
/purchase-orders |
Create new PO |
Create PO
POST /api/purchase-orders
{
"vendor_id": 1,
"items": [
{ "product_id": 1, "quantity": 5 },
{ "product_id": 3, "quantity": 2 }
]
}Response includes auto-calculated totals:
{
"id": 1,
"reference_no": "PO-20240115-7823",
"total_amount": 16537.50,
"status": "Pending",
...
}subtotal = Ξ£ (quantity Γ unit_price)
tax = subtotal Γ 0.05 β 5% tax
total_amount = subtotal + tax
For each item in order:
1. Fetch product from DB
2. If product.stock_level < requested quantity β return HTTP 400
After all items pass validation:
3. Create PO record
4. Create all line items
5. Deduct stock atomically
6. Commit transaction
| URL | Page |
|---|---|
/ |
Login (demo credentials pre-filled) |
/dashboard |
All POs, stats, vendor/product panels |
/create-po |
Dynamic PO form with add/remove rows |
/receipt/{id} |
Order confirmation with printable receipt |
- Full CRUD for Vendors and Products
- Purchase Order creation with multiple line items
- Auto 5% tax calculation stored in
total_amount - Stock validation and atomic deduction
- Dynamic add/remove product rows in frontend
- Real-time order summary panel (subtotal, tax, total)
- Vendor search (debounced, case-insensitive)
- AI product description via Anthropic API (with fallback)
- Receipt page with printable view
- Dark industrial UI theme
- 20% discount banner (first-time user UI)
- Toast notifications for all actions
- API documentation at
/api/docs
The system includes a simple session-based demo login. For production:
- Set
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRETin.env - Install
authlibandpython-jose[cryptography] - Implement the OAuth2 flow in
routes/auth.py
The frontend currently uses sessionStorage to simulate an auth session for demo purposes.
- GitHub repository with clean commits
-
schema.sqlwith DDL + seed data -
README.mdwith setup instructions - API documented at
/api/docs(Swagger UI) - 2-minute video demo (record using OBS or Loom)
Built with FastAPI, PostgreSQL, SQLAlchemy, Bootstrap (dark theme), and Vanilla JS.