Files
MagoRevisited/database.py
T
Alby96andClaude Sonnet 4.6 8a9fb5085a Add Mago4 demo ERP with Python FastAPI
Implements a working in-memory ERP demo inspired by Mago4, covering
the core modules: dashboard with KPI widgets, customers, suppliers,
products, sales orders, purchase orders, and warehouse inventory.

- FastAPI backend with Pydantic models and REST API (auto-docs at /docs)
- Jinja2 HTML templates with Mago4-style UI (dark sidebar, blue/orange/
  green/red palette, Material Design icons)
- In-memory fake database seeded with sample customers, suppliers,
  products, and orders — ready to swap for SQLAlchemy + SQL Server
- Workarounds for Python 3.14 + Starlette 1.3 / Jinja2 compatibility
  (cache_size=0, new TemplateResponse(request, name, ctx) signature)
- launch.json for one-click preview; run.bat for manual startup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 10:17:57 +02:00

288 lines
12 KiB
Python

from datetime import datetime, timedelta
from models import (
Customer, Supplier, Product, OrderLine, SalesOrder, PurchaseOrder, OrderStatus
)
from typing import List, Optional
class FakeDatabase:
def __init__(self):
self.customers = self._init_customers()
self.suppliers = self._init_suppliers()
self.products = self._init_products()
self.sales_orders = self._init_sales_orders()
self.purchase_orders = self._init_purchase_orders()
self.next_customer_id = max([c.id for c in self.customers]) + 1
self.next_supplier_id = max([s.id for s in self.suppliers]) + 1
self.next_product_id = max([p.id for p in self.products]) + 1
self.next_so_id = max([o.id for o in self.sales_orders]) + 1
self.next_po_id = max([o.id for o in self.purchase_orders]) + 1
def _init_customers(self) -> List[Customer]:
return [
Customer(
id=1, code="CUST001", name="Acme Corporation",
email="info@acme.com", phone="+39 02 1234567",
address="Via Roma 10", city="Milano", country="IT",
credit_limit=50000, balance=15000
),
Customer(
id=2, code="CUST002", name="TechFlow Industries",
email="contact@techflow.de", phone="+49 30 555888",
address="Hauptstrasse 45", city="Berlin", country="DE",
credit_limit=75000, balance=32000
),
Customer(
id=3, code="CUST003", name="European Logistics",
email="sales@eulog.fr", phone="+33 1 4520 3580",
address="Avenue des Champs 20", city="Paris", country="FR",
credit_limit=100000, balance=8500
),
Customer(
id=4, code="CUST004", name="Alpine Manufacturing",
email="orders@alpmfg.ch", phone="+41 44 5678900",
address="Industriestrasse 12", city="Zurich", country="CH",
credit_limit=60000, balance=22000
),
]
def _init_suppliers(self) -> List[Supplier]:
return [
Supplier(
id=1, code="SUPP001", name="Global Components Ltd",
email="sales@globalcomp.com", phone="+44 20 7946 0958",
address="123 Oxford Street", city="London", country="UK",
payment_terms_days=30, balance=5000
),
Supplier(
id=2, code="SUPP002", name="Asia Electronics Co.",
email="export@asiaelec.com", phone="+886 2 8797 3000",
address="Taipei Industrial Park", city="Taipei", country="TW",
payment_terms_days=45, balance=18000
),
Supplier(
id=3, code="SUPP003", name="Nordic Materials AB",
email="info@nordicmat.se", phone="+46 8 123 4567",
address="Industrígatan 5", city="Stockholm", country="SE",
payment_terms_days=60, balance=3500
),
]
def _init_products(self) -> List[Product]:
return [
Product(
id=1, code="PROD001", description="Industrial Widget A",
category="Electronics", unit_price=125.50,
quantity_in_stock=250, reorder_level=50, supplier_id=1
),
Product(
id=2, code="PROD002", description="Premium Connector Set",
category="Hardware", unit_price=45.00,
quantity_in_stock=180, reorder_level=30, supplier_id=2
),
Product(
id=3, code="PROD003", description="Steel Bracket Type B",
category="Structural", unit_price=22.75,
quantity_in_stock=500, reorder_level=100, supplier_id=3
),
Product(
id=4, code="PROD004", description="Precision Bearing Set",
category="Mechanical", unit_price=89.99,
quantity_in_stock=120, reorder_level=40, supplier_id=1
),
Product(
id=5, code="PROD005", description="Control Module MCU-32",
category="Electronics", unit_price=234.50,
quantity_in_stock=75, reorder_level=20, supplier_id=2
),
]
def _init_sales_orders(self) -> List[SalesOrder]:
now = datetime.now()
return [
SalesOrder(
id=1, order_number="SO-2024-0001",
order_date=now - timedelta(days=10),
customer_id=1, customer_name="Acme Corporation",
status=OrderStatus.CONFIRMED,
lines=[
OrderLine(id=1, product_id=1, product_code="PROD001",
product_description="Industrial Widget A",
quantity=10, unit_price=125.50, total_amount=1255.00),
OrderLine(id=2, product_id=4, product_code="PROD004",
product_description="Precision Bearing Set",
quantity=5, unit_price=89.99, total_amount=449.95),
],
total_amount=1704.95,
delivery_date=now + timedelta(days=15)
),
SalesOrder(
id=2, order_number="SO-2024-0002",
order_date=now - timedelta(days=5),
customer_id=2, customer_name="TechFlow Industries",
status=OrderStatus.CONFIRMED,
lines=[
OrderLine(id=3, product_id=5, product_code="PROD005",
product_description="Control Module MCU-32",
quantity=8, unit_price=234.50, total_amount=1876.00),
],
total_amount=1876.00,
delivery_date=now + timedelta(days=10)
),
SalesOrder(
id=3, order_number="SO-2024-0003",
order_date=now - timedelta(days=2),
customer_id=3, customer_name="European Logistics",
status=OrderStatus.DRAFT,
lines=[
OrderLine(id=4, product_id=2, product_code="PROD002",
product_description="Premium Connector Set",
quantity=20, unit_price=45.00, total_amount=900.00),
OrderLine(id=5, product_id=3, product_code="PROD003",
product_description="Steel Bracket Type B",
quantity=15, unit_price=22.75, total_amount=341.25),
],
total_amount=1241.25,
delivery_date=now + timedelta(days=20)
),
]
def _init_purchase_orders(self) -> List[PurchaseOrder]:
now = datetime.now()
return [
PurchaseOrder(
id=1, order_number="PO-2024-0001",
order_date=now - timedelta(days=15),
supplier_id=1, supplier_name="Global Components Ltd",
status=OrderStatus.PARTIALLY_RECEIVED,
lines=[
OrderLine(id=1, product_id=1, product_code="PROD001",
product_description="Industrial Widget A",
quantity=100, unit_price=110.00, total_amount=11000.00),
],
total_amount=11000.00,
expected_delivery_date=now + timedelta(days=5)
),
PurchaseOrder(
id=2, order_number="PO-2024-0002",
order_date=now - timedelta(days=8),
supplier_id=2, supplier_name="Asia Electronics Co.",
status=OrderStatus.CONFIRMED,
lines=[
OrderLine(id=2, product_id=5, product_code="PROD005",
product_description="Control Module MCU-32",
quantity=50, unit_price=195.00, total_amount=9750.00),
],
total_amount=9750.00,
expected_delivery_date=now + timedelta(days=25)
),
PurchaseOrder(
id=3, order_number="PO-2024-0003",
order_date=now - timedelta(days=1),
supplier_id=3, supplier_name="Nordic Materials AB",
status=OrderStatus.DRAFT,
lines=[
OrderLine(id=3, product_id=3, product_code="PROD003",
product_description="Steel Bracket Type B",
quantity=200, unit_price=20.00, total_amount=4000.00),
],
total_amount=4000.00,
expected_delivery_date=now + timedelta(days=30)
),
]
# CRUD Operations
def get_customers(self) -> List[Customer]:
return self.customers
def get_customer(self, customer_id: int) -> Optional[Customer]:
return next((c for c in self.customers if c.id == customer_id), None)
def create_customer(self, customer: Customer) -> Customer:
customer.id = self.next_customer_id
self.next_customer_id += 1
self.customers.append(customer)
return customer
def update_customer(self, customer_id: int, customer_data: dict) -> Optional[Customer]:
customer = self.get_customer(customer_id)
if customer:
for key, value in customer_data.items():
if hasattr(customer, key) and value is not None:
setattr(customer, key, value)
return customer
def get_suppliers(self) -> List[Supplier]:
return self.suppliers
def get_supplier(self, supplier_id: int) -> Optional[Supplier]:
return next((s for s in self.suppliers if s.id == supplier_id), None)
def create_supplier(self, supplier: Supplier) -> Supplier:
supplier.id = self.next_supplier_id
self.next_supplier_id += 1
self.suppliers.append(supplier)
return supplier
def get_products(self) -> List[Product]:
return self.products
def get_product(self, product_id: int) -> Optional[Product]:
return next((p for p in self.products if p.id == product_id), None)
def create_product(self, product: Product) -> Product:
product.id = self.next_product_id
self.next_product_id += 1
self.products.append(product)
return product
def get_sales_orders(self) -> List[SalesOrder]:
return self.sales_orders
def get_sales_order(self, order_id: int) -> Optional[SalesOrder]:
return next((o for o in self.sales_orders if o.id == order_id), None)
def create_sales_order(self, order: SalesOrder) -> SalesOrder:
order.id = self.next_so_id
self.next_so_id += 1
self.sales_orders.append(order)
return order
def get_purchase_orders(self) -> List[PurchaseOrder]:
return self.purchase_orders
def get_purchase_order(self, order_id: int) -> Optional[PurchaseOrder]:
return next((o for o in self.purchase_orders if o.id == order_id), None)
def create_purchase_order(self, order: PurchaseOrder) -> PurchaseOrder:
order.id = self.next_po_id
self.next_po_id += 1
self.purchase_orders.append(order)
return order
# Dashboard statistics
def get_dashboard_stats(self):
sales_to_fulfill = sum(1 for o in self.sales_orders if o.status in [OrderStatus.CONFIRMED, OrderStatus.DRAFT])
purchase_to_receive = sum(1 for o in self.purchase_orders if o.status in [OrderStatus.CONFIRMED, OrderStatus.DRAFT, OrderStatus.PARTIALLY_RECEIVED])
total_receivable = sum(c.balance for c in self.customers)
total_payable = sum(s.balance for s in self.suppliers)
low_stock_items = [p for p in self.products if p.quantity_in_stock <= p.reorder_level]
return {
"sales_to_fulfill": sales_to_fulfill,
"purchase_to_receive": purchase_to_receive,
"total_receivable": total_receivable,
"total_payable": total_payable,
"low_stock_count": len(low_stock_items),
"low_stock_items": low_stock_items,
"total_customers": len(self.customers),
"total_suppliers": len(self.suppliers),
"total_products": len(self.products),
}
# Global database instance
db = FakeDatabase()