Restyle UI to match Mago4 and add invoicing, warehouse, scadenzario
Rework the demo into a faithful Mago4 look-and-feel and expand it with core ERP modules modelled on the real Mago4 database schema. UI (ispirata a Mago4): - Blue header with logo/search/operation date, desktop-style window tabs - White sidebar with coloured module icons (Anagrafiche, Vendite, Acquisti, Amministrazione, Logistica) - Card-based module landing pages with breadcrumb + tab strip - Document detail forms with left tabs, line grid and totals box Fatturazione (InvoiceMng / MA_SaleDoc): - Document types (Fattura Immediata, DDT, Nota di Credito, ProForma) - Lines with discount/IVA, automatic taxable/tax/total computation - Document states (draft -> printed -> issued / posted_to_inventory) Magazzino (Inventory / MA_InventoryEntries + MA_InventoryReasons): - Inventory movements with load/unload reasons (DebitCreditSign) - Posting a movement actually updates item balances - Causali and stock valuation pages Amministrazione / Scadenzario (AP_AR / MA_PyblsRcvbls): - Open items (receivables/payables) with due date, method, state - Settle action closes the item and recomputes the cash forecast Dashboard riprogettata (focus su scadenze e to-do): - Overdue alert banner, KPI for overdue payables/receivables - Upcoming deadlines table, prioritised "Cose da fare" action list - 30-day cash-flow forecast and customer credit-limit check Adds REST endpoints for invoices, movements, schedules and dashboard data. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,24 +8,16 @@ from datetime import datetime
|
||||
import os
|
||||
|
||||
from models import (
|
||||
Customer, Supplier, Product, SalesOrder, PurchaseOrder,
|
||||
OrderLine, OrderStatus, Dashboard, DashboardWidget
|
||||
Customer, Supplier, Product, SalesOrder, PurchaseOrder, OrderStatus,
|
||||
Invoice, InventoryMovement, DocumentType, DocumentStatus,
|
||||
PaymentSchedule, PaymentScheduleType,
|
||||
DOCUMENT_TYPE_LABELS, DOCUMENT_STATUS_LABELS, PAYMENT_SCHEDULE_TYPE_LABELS, MovementSign,
|
||||
)
|
||||
from database import db
|
||||
|
||||
app = FastAPI(
|
||||
title="Mago4 Demo",
|
||||
description="Demo gestionale ERP con Python FastAPI",
|
||||
version="1.0.0"
|
||||
)
|
||||
app = FastAPI(title="Mago4 Demo", description="Demo gestionale ERP — Python FastAPI", version="2.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)
|
||||
@@ -34,268 +26,295 @@ _jinja_env = Environment(
|
||||
autoescape=True,
|
||||
cache_size=0,
|
||||
)
|
||||
# Helper esposti ai template
|
||||
_jinja_env.globals["doc_type_label"] = lambda t: DOCUMENT_TYPE_LABELS.get(t, str(t))
|
||||
_jinja_env.globals["doc_status_label"] = lambda s: DOCUMENT_STATUS_LABELS.get(s, str(s))
|
||||
_jinja_env.globals["sched_type_label"] = lambda t: PAYMENT_SCHEDULE_TYPE_LABELS.get(t, str(t))
|
||||
_jinja_env.globals["sched_status"] = db.schedule_status
|
||||
_jinja_env.globals["sched_days"] = db.schedule_days_to_due
|
||||
templates = Jinja2Templates(env=_jinja_env)
|
||||
|
||||
|
||||
# ==================== DASHBOARD ====================
|
||||
def ctx(request: Request, active_module: str, **extra):
|
||||
base = {
|
||||
"request": request,
|
||||
"active_module": active_module,
|
||||
"operation_date": datetime.now().strftime("%d/%m/%Y"),
|
||||
}
|
||||
base.update(extra)
|
||||
return base
|
||||
|
||||
|
||||
def render(request: Request, name: str, active_module: str, **extra):
|
||||
return templates.TemplateResponse(request, name, ctx(request, active_module, **extra))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 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
|
||||
})
|
||||
upcoming = db.get_upcoming_schedules(30)
|
||||
todos = db.get_todo_items()
|
||||
cashflow = db.get_cashflow_forecast(30)
|
||||
credit_exceeded = db.get_credit_exceeded_customers()
|
||||
last_invoices = sorted(db.get_invoices(), key=lambda x: x.doc_date, reverse=True)[:5]
|
||||
return render(request, "dashboard.html", "dashboard",
|
||||
stats=stats, upcoming=upcoming, todos=todos, cashflow=cashflow,
|
||||
credit_exceeded=credit_exceeded, last_invoices=last_invoices,
|
||||
RECEIVABLE=PaymentScheduleType.RECEIVABLE, PAYABLE=PaymentScheduleType.PAYABLE)
|
||||
|
||||
|
||||
# ==================== 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
|
||||
# ============================================================
|
||||
# ANAGRAFICHE
|
||||
# ============================================================
|
||||
@app.get("/anagrafiche", response_class=HTMLResponse)
|
||||
async def anagrafiche(request: Request):
|
||||
return render(request, "anagrafiche.html", "anagrafiche")
|
||||
|
||||
|
||||
@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)
|
||||
return render(request, "customers.html", "anagrafiche", customers=db.get_customers())
|
||||
|
||||
|
||||
@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)
|
||||
return render(request, "suppliers.html", "anagrafiche", suppliers=db.get_suppliers())
|
||||
|
||||
|
||||
@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]
|
||||
})
|
||||
return render(request, "products.html", "anagrafiche", 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)
|
||||
# ============================================================
|
||||
# VENDITE
|
||||
# ============================================================
|
||||
@app.get("/vendite", response_class=HTMLResponse)
|
||||
async def vendite(request: Request):
|
||||
return render(request, "vendite.html", "vendite")
|
||||
|
||||
|
||||
@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]
|
||||
})
|
||||
return render(request, "sales_orders.html", "vendite", 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("/invoices", response_class=HTMLResponse)
|
||||
async def invoices_page(request: Request, type: str = "all"):
|
||||
invoices = db.get_invoices()
|
||||
selected = "all"
|
||||
if type != "all":
|
||||
try:
|
||||
dt = DocumentType(type)
|
||||
invoices = [i for i in invoices if i.doc_type == dt]
|
||||
selected = type
|
||||
except ValueError:
|
||||
pass
|
||||
invoices = sorted(invoices, key=lambda x: x.doc_date, reverse=True)
|
||||
return render(request, "invoices.html", "vendite", invoices=invoices,
|
||||
selected_type=selected, doc_types=list(DocumentType))
|
||||
|
||||
|
||||
@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.get("/invoices/{invoice_id}", response_class=HTMLResponse)
|
||||
async def invoice_detail_page(request: Request, invoice_id: int):
|
||||
invoice = db.get_invoice(invoice_id)
|
||||
if not invoice:
|
||||
raise HTTPException(status_code=404, detail="Documento non trovato")
|
||||
return render(request, "invoice_detail.html", "vendite", invoice=invoice)
|
||||
|
||||
|
||||
@app.post("/api/purchase-orders", tags=["Purchase Orders"])
|
||||
async def create_purchase_order(order: PurchaseOrder):
|
||||
return db.create_purchase_order(order)
|
||||
# ============================================================
|
||||
# ACQUISTI
|
||||
# ============================================================
|
||||
@app.get("/acquisti", response_class=HTMLResponse)
|
||||
async def acquisti(request: Request):
|
||||
return render(request, "acquisti.html", "acquisti")
|
||||
|
||||
|
||||
@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]
|
||||
})
|
||||
return render(request, "purchase_orders.html", "acquisti", 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()
|
||||
}
|
||||
# ============================================================
|
||||
# AMMINISTRAZIONE / SCADENZARIO (AP_AR)
|
||||
# ============================================================
|
||||
@app.get("/amministrazione", response_class=HTMLResponse)
|
||||
async def amministrazione(request: Request):
|
||||
return render(request, "amministrazione.html", "amministrazione")
|
||||
|
||||
|
||||
@app.get("/scadenzario", response_class=HTMLResponse)
|
||||
async def scadenzario_page(request: Request, type: str = "all"):
|
||||
schedules = db.get_payment_schedules()
|
||||
selected = "all"
|
||||
if type in ("receivable", "payable"):
|
||||
st = PaymentScheduleType(type)
|
||||
schedules = [s for s in schedules if s.type == st]
|
||||
selected = type
|
||||
# ordina: prima le aperte per data scadenza, poi le chiuse
|
||||
schedules = sorted(schedules, key=lambda s: (s.closed, s.due_date))
|
||||
return render(request, "scadenzario.html", "amministrazione",
|
||||
schedules=schedules, selected_type=selected,
|
||||
RECEIVABLE=PaymentScheduleType.RECEIVABLE, PAYABLE=PaymentScheduleType.PAYABLE)
|
||||
|
||||
|
||||
@app.post("/scadenzario/{schedule_id}/settle")
|
||||
async def settle_schedule_action(schedule_id: int):
|
||||
s = db.settle_schedule(schedule_id)
|
||||
if not s:
|
||||
raise HTTPException(status_code=404, detail="Partita non trovata o già saldata")
|
||||
return {"status": "ok", "schedule_id": schedule_id, "closed": s.closed}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAGAZZINO / LOGISTICA
|
||||
# ============================================================
|
||||
@app.get("/magazzino", response_class=HTMLResponse)
|
||||
async def magazzino(request: Request):
|
||||
return render(request, "magazzino.html", "magazzino")
|
||||
|
||||
|
||||
@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"]
|
||||
})
|
||||
return render(request, "warehouse.html", "magazzino", products=db.get_products(),
|
||||
low_stock=stats["low_stock_items"], warehouse_value=stats["warehouse_value"])
|
||||
|
||||
|
||||
# ==================== INFO ====================
|
||||
@app.get("/inventory-movements", response_class=HTMLResponse)
|
||||
async def inventory_movements_page(request: Request):
|
||||
movements = sorted(db.get_inventory_movements(), key=lambda m: m.posting_date, reverse=True)
|
||||
return render(request, "inventory_movements.html", "magazzino", movements=movements)
|
||||
|
||||
|
||||
@app.get("/inventory-movements/{movement_id}", response_class=HTMLResponse)
|
||||
async def movement_detail_page(request: Request, movement_id: int):
|
||||
movement = db.get_inventory_movement(movement_id)
|
||||
if not movement:
|
||||
raise HTTPException(status_code=404, detail="Movimento non trovato")
|
||||
return render(request, "movement_detail.html", "magazzino", movement=movement)
|
||||
|
||||
|
||||
@app.post("/inventory-movements/{movement_id}/post")
|
||||
async def post_movement_action(movement_id: int):
|
||||
m = db.post_movement(movement_id)
|
||||
if not m:
|
||||
raise HTTPException(status_code=404, detail="Movimento non trovato o già confermato")
|
||||
return {"status": "ok", "movement_id": movement_id, "posted": m.posted}
|
||||
|
||||
|
||||
@app.get("/inventory-reasons", response_class=HTMLResponse)
|
||||
async def inventory_reasons_page(request: Request):
|
||||
return render(request, "inventory_reasons.html", "magazzino", reasons=db.get_inventory_reasons())
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API REST
|
||||
# ============================================================
|
||||
@app.get("/api/customers", tags=["Customers"])
|
||||
async def api_customers(): return db.get_customers()
|
||||
|
||||
@app.get("/api/customers/{cid}", tags=["Customers"])
|
||||
async def api_customer(cid: int):
|
||||
c = db.get_customer(cid)
|
||||
if not c: raise HTTPException(404, "Cliente non trovato")
|
||||
return c
|
||||
|
||||
@app.post("/api/customers", tags=["Customers"])
|
||||
async def api_create_customer(customer: Customer): return db.create_customer(customer)
|
||||
|
||||
@app.get("/api/suppliers", tags=["Suppliers"])
|
||||
async def api_suppliers(): return db.get_suppliers()
|
||||
|
||||
@app.get("/api/suppliers/{sid}", tags=["Suppliers"])
|
||||
async def api_supplier(sid: int):
|
||||
s = db.get_supplier(sid)
|
||||
if not s: raise HTTPException(404, "Fornitore non trovato")
|
||||
return s
|
||||
|
||||
@app.get("/api/products", tags=["Products"])
|
||||
async def api_products(): return db.get_products()
|
||||
|
||||
@app.get("/api/products/{pid}", tags=["Products"])
|
||||
async def api_product(pid: int):
|
||||
p = db.get_product(pid)
|
||||
if not p: raise HTTPException(404, "Articolo non trovato")
|
||||
return p
|
||||
|
||||
@app.get("/api/sales-orders", tags=["Sales Orders"])
|
||||
async def api_sales_orders(): return db.get_sales_orders()
|
||||
|
||||
@app.get("/api/sales-orders/{oid}", tags=["Sales Orders"])
|
||||
async def api_sales_order(oid: int):
|
||||
o = db.get_sales_order(oid)
|
||||
if not o: raise HTTPException(404, "Ordine non trovato")
|
||||
return o
|
||||
|
||||
@app.get("/api/purchase-orders", tags=["Purchase Orders"])
|
||||
async def api_purchase_orders(): return db.get_purchase_orders()
|
||||
|
||||
@app.get("/api/purchase-orders/{oid}", tags=["Purchase Orders"])
|
||||
async def api_purchase_order(oid: int):
|
||||
o = db.get_purchase_order(oid)
|
||||
if not o: raise HTTPException(404, "Ordine non trovato")
|
||||
return o
|
||||
|
||||
@app.get("/api/invoices", tags=["Invoices"])
|
||||
async def api_invoices(): return db.get_invoices()
|
||||
|
||||
@app.get("/api/invoices/{iid}", tags=["Invoices"])
|
||||
async def api_invoice(iid: int):
|
||||
i = db.get_invoice(iid)
|
||||
if not i: raise HTTPException(404, "Documento non trovato")
|
||||
return i
|
||||
|
||||
@app.post("/api/invoices", tags=["Invoices"])
|
||||
async def api_create_invoice(invoice: Invoice): return db.create_invoice(invoice)
|
||||
|
||||
@app.get("/api/inventory-movements", tags=["Warehouse"])
|
||||
async def api_movements(): return db.get_inventory_movements()
|
||||
|
||||
@app.get("/api/inventory-movements/{mid}", tags=["Warehouse"])
|
||||
async def api_movement(mid: int):
|
||||
m = db.get_inventory_movement(mid)
|
||||
if not m: raise HTTPException(404, "Movimento non trovato")
|
||||
return m
|
||||
|
||||
@app.post("/api/inventory-movements", tags=["Warehouse"])
|
||||
async def api_create_movement(movement: InventoryMovement): return db.create_inventory_movement(movement)
|
||||
|
||||
@app.get("/api/inventory-reasons", tags=["Warehouse"])
|
||||
async def api_reasons(): return db.get_inventory_reasons()
|
||||
|
||||
@app.get("/api/payment-schedules", tags=["Scadenzario"])
|
||||
async def api_schedules(): return db.get_payment_schedules()
|
||||
|
||||
@app.get("/api/payment-schedules/{sid}", tags=["Scadenzario"])
|
||||
async def api_schedule(sid: int):
|
||||
s = db.get_payment_schedule(sid)
|
||||
if not s: raise HTTPException(404, "Partita non trovata")
|
||||
return s
|
||||
|
||||
@app.get("/api/dashboard/todos", tags=["Dashboard"])
|
||||
async def api_todos(): return db.get_todo_items()
|
||||
|
||||
@app.get("/api/dashboard/cashflow", tags=["Dashboard"])
|
||||
async def api_cashflow(days: int = 30): return db.get_cashflow_forecast(days)
|
||||
|
||||
@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
|
||||
}
|
||||
return {"name": "Mago4 Demo", "version": "2.0.0", "status": "active",
|
||||
"database_type": "in-memory", "statistics": db.get_dashboard_stats()}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user