from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse from starlette.requests import Request from jinja2 import Environment, FileSystemLoader from datetime import datetime import os from models import ( Customer, Supplier, Product, SalesOrder, PurchaseOrder, OrderLine, OrderStatus, Dashboard, DashboardWidget ) from database import db app = FastAPI( title="Mago4 Demo", description="Demo gestionale ERP con Python FastAPI", version="1.0.0" ) # Setup static e template files BASE_DIR = os.path.dirname(os.path.abspath(__file__)) os.makedirs(os.path.join(BASE_DIR, "static"), exist_ok=True) os.makedirs(os.path.join(BASE_DIR, "static", "css"), exist_ok=True) os.makedirs(os.path.join(BASE_DIR, "static", "js"), exist_ok=True) os.makedirs(os.path.join(BASE_DIR, "templates"), exist_ok=True) app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "static")), name="static") # Workaround Python 3.14 + Jinja2: disable template cache (globals dict not hashable as cache key) _jinja_env = Environment( loader=FileSystemLoader(os.path.join(BASE_DIR, "templates")), autoescape=True, cache_size=0, ) templates = Jinja2Templates(env=_jinja_env) # ==================== DASHBOARD ==================== @app.get("/", response_class=HTMLResponse) async def dashboard(request: Request): stats = db.get_dashboard_stats() widgets = [ [ DashboardWidget( title="Ordini Vendita da Evadere", style="stats", size="small", icon="shopping_cart", color="blue", value=str(stats["sales_to_fulfill"]) ), DashboardWidget( title="Ordini Acquisto da Ricevere", style="stats", size="small", icon="local_shipping", color="orange", value=str(stats["purchase_to_receive"]) ), DashboardWidget( title="Crediti Clienti", style="stats", size="small", icon="euro_symbol", color="green", value=f"€ {stats['total_receivable']:,.2f}", format="money" ), DashboardWidget( title="Articoli Sotto Soglia", style="stats", size="small", icon="warning", color="red", value=str(stats["low_stock_count"]) ), ], [ DashboardWidget( title="Ultimi Ordini Vendita", subtitle="per data ordine", style="grid", size="medium", data={"orders": [ { "order_number": o.order_number, "order_date": o.order_date.strftime("%d/%m/%Y"), "customer_name": o.customer_name, "total_amount": f"€ {o.total_amount:,.2f}", "status": o.status.value } for o in sorted(db.get_sales_orders(), key=lambda x: x.order_date, reverse=True)[:5] ]} ), DashboardWidget( title="Ultimi Ordini Acquisto", subtitle="per data ordine", style="grid", size="medium", data={"orders": [ { "order_number": o.order_number, "order_date": o.order_date.strftime("%d/%m/%Y"), "supplier_name": o.supplier_name, "total_amount": f"€ {o.total_amount:,.2f}", "status": o.status.value } for o in sorted(db.get_purchase_orders(), key=lambda x: x.order_date, reverse=True)[:5] ]} ), ] ] return templates.TemplateResponse(request, "dashboard.html", { "widgets": widgets, "stats": stats }) # ==================== CUSTOMERS ==================== @app.get("/api/customers", tags=["Customers"]) async def list_customers(): return db.get_customers() @app.get("/api/customers/{customer_id}", tags=["Customers"]) async def get_customer(customer_id: int): customer = db.get_customer(customer_id) if not customer: raise HTTPException(status_code=404, detail="Cliente non trovato") return customer @app.post("/api/customers", tags=["Customers"]) async def create_customer(customer: Customer): return db.create_customer(customer) @app.put("/api/customers/{customer_id}", tags=["Customers"]) async def update_customer(customer_id: int, customer_data: dict): result = db.update_customer(customer_id, customer_data) if not result: raise HTTPException(status_code=404, detail="Cliente non trovato") return result @app.get("/customers", response_class=HTMLResponse) async def customers_page(request: Request): return templates.TemplateResponse(request, "customers.html", { "customers": db.get_customers() }) # ==================== SUPPLIERS ==================== @app.get("/api/suppliers", tags=["Suppliers"]) async def list_suppliers(): return db.get_suppliers() @app.get("/api/suppliers/{supplier_id}", tags=["Suppliers"]) async def get_supplier(supplier_id: int): supplier = db.get_supplier(supplier_id) if not supplier: raise HTTPException(status_code=404, detail="Fornitore non trovato") return supplier @app.post("/api/suppliers", tags=["Suppliers"]) async def create_supplier(supplier: Supplier): return db.create_supplier(supplier) @app.get("/suppliers", response_class=HTMLResponse) async def suppliers_page(request: Request): return templates.TemplateResponse(request, "suppliers.html", { "suppliers": db.get_suppliers() }) # ==================== PRODUCTS ==================== @app.get("/api/products", tags=["Products"]) async def list_products(): return db.get_products() @app.get("/api/products/{product_id}", tags=["Products"]) async def get_product(product_id: int): product = db.get_product(product_id) if not product: raise HTTPException(status_code=404, detail="Articolo non trovato") return product @app.post("/api/products", tags=["Products"]) async def create_product(product: Product): return db.create_product(product) @app.get("/products", response_class=HTMLResponse) async def products_page(request: Request): products = db.get_products() return templates.TemplateResponse(request, "products.html", { "products": products, "low_stock": [p for p in products if p.quantity_in_stock <= p.reorder_level] }) # ==================== SALES ORDERS ==================== @app.get("/api/sales-orders", tags=["Sales Orders"]) async def list_sales_orders(): return db.get_sales_orders() @app.get("/api/sales-orders/{order_id}", tags=["Sales Orders"]) async def get_sales_order(order_id: int): order = db.get_sales_order(order_id) if not order: raise HTTPException(status_code=404, detail="Ordine vendita non trovato") return order @app.post("/api/sales-orders", tags=["Sales Orders"]) async def create_sales_order(order: SalesOrder): return db.create_sales_order(order) @app.get("/sales-orders", response_class=HTMLResponse) async def sales_orders_page(request: Request): orders = db.get_sales_orders() return templates.TemplateResponse(request, "sales_orders.html", { "orders": orders, "pending": [o for o in orders if o.status != OrderStatus.RECEIVED] }) # ==================== PURCHASE ORDERS ==================== @app.get("/api/purchase-orders", tags=["Purchase Orders"]) async def list_purchase_orders(): return db.get_purchase_orders() @app.get("/api/purchase-orders/{order_id}", tags=["Purchase Orders"]) async def get_purchase_order(order_id: int): order = db.get_purchase_order(order_id) if not order: raise HTTPException(status_code=404, detail="Ordine acquisto non trovato") return order @app.post("/api/purchase-orders", tags=["Purchase Orders"]) async def create_purchase_order(order: PurchaseOrder): return db.create_purchase_order(order) @app.get("/purchase-orders", response_class=HTMLResponse) async def purchase_orders_page(request: Request): orders = db.get_purchase_orders() return templates.TemplateResponse(request, "purchase_orders.html", { "orders": orders, "pending": [o for o in orders if o.status != OrderStatus.RECEIVED] }) # ==================== WAREHOUSE ==================== @app.get("/api/warehouse/stats", tags=["Warehouse"]) async def warehouse_stats(): stats = db.get_dashboard_stats() return { "total_products": stats["total_products"], "low_stock_count": stats["low_stock_count"], "low_stock_items": stats["low_stock_items"], "all_products": db.get_products() } @app.get("/warehouse", response_class=HTMLResponse) async def warehouse_page(request: Request): stats = db.get_dashboard_stats() return templates.TemplateResponse(request, "warehouse.html", { "products": db.get_products(), "low_stock": stats["low_stock_items"] }) # ==================== INFO ==================== @app.get("/api/info", tags=["Info"]) async def api_info(): stats = db.get_dashboard_stats() return { "name": "Mago4 Demo", "version": "1.0.0", "status": "active", "database_type": "in-memory", "statistics": stats } if __name__ == "__main__": import uvicorn port = int(os.environ.get("PORT", 8000)) uvicorn.run(app, host="127.0.0.1", port=port)