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:
2026-06-17 11:47:32 +02:00
co-authored by Claude Sonnet 4.6
parent 8a9fb5085a
commit b6b6759b9a
26 changed files with 2900 additions and 2125 deletions
+173 -99
View File
@@ -1,106 +1,180 @@
{% extends "base.html" %}
{% block title %}Dashboard - Mago4 Demo{% endblock %}
{% block page_title %}Dashboard{% endblock %}
{% set status_badge = {'overdue':'badge-danger','due_soon':'badge-warning','open':'badge-info','paid':'badge-success'} %}
{% set status_label = {'overdue':'Scaduta','due_soon':'In scadenza','open':'Aperta','paid':'Saldata'} %}
{% block content %}
<div class="dashboard-container">
<!-- First Row - KPI Cards -->
<div class="widget-row">
{% for widget in widgets[0] %}
<div class="widget-card stat-card" style="border-left-color: var(--color-{{ widget.color }})">
<div class="widget-header">
<h3 class="widget-title">{{ widget.title }}</h3>
</div>
<div class="widget-body stat-body">
<div class="stat-icon" style="color: var(--color-{{ widget.color }})">
<i class="material-icons">{{ widget.icon }}</i>
</div>
<div class="stat-value">{{ widget.value }}</div>
</div>
</div>
{% endfor %}
</div>
<!-- Second Row - Data Tables -->
<div class="widget-row">
{% for widget in widgets[1] %}
<div class="widget-card">
<div class="widget-header">
<h3 class="widget-title">{{ widget.title }}</h3>
{% if widget.subtitle %}<p class="widget-subtitle">{{ widget.subtitle }}</p>{% endif %}
</div>
<div class="widget-body grid-body">
<table class="data-table">
<thead>
<tr>
{% if "Vendita" in widget.title %}
<th>Data</th>
<th>Numero Ordine</th>
<th>Cliente</th>
<th>Importo</th>
<th>Stato</th>
{% else %}
<th>Data</th>
<th>Numero Ordine</th>
<th>Fornitore</th>
<th>Importo</th>
<th>Stato</th>
{% endif %}
</tr>
</thead>
<tbody>
{% if "Vendita" in widget.title %}
{% for order in widget.data.orders %}
<tr>
<td>{{ order.order_date }}</td>
<td><strong>{{ order.order_number }}</strong></td>
<td>{{ order.customer_name }}</td>
<td class="text-right">{{ order.total_amount }}</td>
<td>
<span class="badge status-{{ order.status }}">{{ order.status }}</span>
</td>
</tr>
{% endfor %}
{% else %}
{% for order in widget.data.orders %}
<tr>
<td>{{ order.order_date }}</td>
<td><strong>{{ order.order_number }}</strong></td>
<td>{{ order.supplier_name }}</td>
<td class="text-right">{{ order.total_amount }}</td>
<td>
<span class="badge status-{{ order.status }}">{{ order.status }}</span>
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
</div>
{% endfor %}
</div>
<!-- Summary Stats -->
<div class="summary-stats">
<div class="stat-summary">
<div class="stat-label">Totale Clienti</div>
<div class="stat-number">{{ stats.total_customers }}</div>
</div>
<div class="stat-summary">
<div class="stat-label">Totale Fornitori</div>
<div class="stat-number">{{ stats.total_suppliers }}</div>
</div>
<div class="stat-summary">
<div class="stat-label">Articoli in Catalogo</div>
<div class="stat-number">{{ stats.total_products }}</div>
</div>
<div class="stat-summary">
<div class="stat-label">Debiti Fornitori</div>
<div class="stat-number text-orange">€ {{ "%.2f"|format(stats.total_payable) }}</div>
</div>
</div>
<div class="module-bar">
<div class="module-title">Cruscotto Aziendale</div>
<div class="breadcrumb"><a href="/">Mago4</a><span class="sep">»</span><span class="current">Dashboard</span></div>
</div>
<div class="content-pad">
<!-- Banner allerta scadenze -->
{% if stats.overdue_pay_count or stats.overdue_recv_count %}
<div class="alert alert-danger">
<i class="material-icons">notification_important</i>
<span>
Attenzione: hai
{% if stats.overdue_pay_count %}<b>{{ stats.overdue_pay_count }} pagamenti scaduti</b> (€ {{ "%.2f"|format(stats.overdue_pay_amount) }}){% endif %}
{% if stats.overdue_pay_count and stats.overdue_recv_count %} e {% endif %}
{% if stats.overdue_recv_count %}<b>{{ stats.overdue_recv_count }} incassi scaduti</b> (€ {{ "%.2f"|format(stats.overdue_recv_amount) }}){% endif %}.
<a href="/scadenzario" style="color:inherit;font-weight:700;text-decoration:underline;">Vai allo scadenzario »</a>
</span>
</div>
{% endif %}
<!-- KPI: focus scadenze -->
<div class="kpi-grid">
<div class="kpi-card" style="border-left-color: var(--c-red)">
<div class="kpi-title">Pagamenti Scaduti</div>
<div class="kpi-row">
<div class="kpi-icon"><i class="material-icons" style="color:var(--c-red)">payments</i></div>
<div><div class="kpi-value">€ {{ "{:,.0f}".format(stats.overdue_pay_amount) }}</div>
<div class="text-mute" style="font-size:11px;">{{ stats.overdue_pay_count }} partite</div></div>
</div>
</div>
<div class="kpi-card" style="border-left-color: var(--c-orange)">
<div class="kpi-title">Incassi Scaduti</div>
<div class="kpi-row">
<div class="kpi-icon"><i class="material-icons" style="color:var(--c-orange)">request_quote</i></div>
<div><div class="kpi-value">€ {{ "{:,.0f}".format(stats.overdue_recv_amount) }}</div>
<div class="text-mute" style="font-size:11px;">{{ stats.overdue_recv_count }} partite</div></div>
</div>
</div>
<div class="kpi-card" style="border-left-color: var(--c-blue)">
<div class="kpi-title">Ordini da Evadere</div>
<div class="kpi-row">
<div class="kpi-icon"><i class="material-icons" style="color:var(--c-blue)">local_shipping</i></div>
<div class="kpi-value">{{ stats.sales_to_fulfill }}</div>
</div>
</div>
<div class="kpi-card" style="border-left-color: var(--c-purple)">
<div class="kpi-title">Articoli da Ordinare</div>
<div class="kpi-row">
<div class="kpi-icon"><i class="material-icons" style="color:var(--c-purple)">production_quantity_limits</i></div>
<div class="kpi-value">{{ stats.low_stock_count }}</div>
</div>
</div>
</div>
<!-- Colonna sinistra: scadenze | destra: cose da fare + cashflow -->
<div class="dash-grid-2">
<!-- Scadenze imminenti -->
<div class="panel">
<div class="panel-header">
<h3><i class="material-icons">event_note</i> Scadenze Imminenti (30 giorni)</h3>
<a href="/scadenzario" class="text-blue" style="font-size:12px;text-decoration:none;">Vedi tutto »</a>
</div>
<table class="data-table">
<thead><tr><th>Scadenza</th><th>Tipo</th><th>Soggetto</th><th class="text-right">Importo</th><th class="text-center">Stato</th></tr></thead>
<tbody>
{% for s in upcoming %}
{% set st = sched_status(s) %}
<tr class="row-link {% if st == 'overdue' %}low-stock{% endif %}" onclick="location.href='/scadenzario?type={{ s.type.value }}'">
<td class="text-bold">{{ s.due_date.strftime("%d/%m") }}</td>
<td>
{% if s.type == RECEIVABLE %}<span class="badge badge-load"><i class="material-icons">south_west</i> Inc.</span>
{% else %}<span class="badge badge-unload"><i class="material-icons">north_east</i> Pag.</span>{% endif %}
</td>
<td>{{ s.custsupp_name }}</td>
<td class="text-right text-bold">€ {{ "%.0f"|format(s.amount) }}</td>
<td class="text-center"><span class="badge {{ status_badge[st] }}">{{ status_label[st] }}</span></td>
</tr>
{% endfor %}
{% if not upcoming %}<tr><td colspan="5" class="text-center text-mute" style="padding:20px;">Nessuna scadenza nei prossimi 30 giorni</td></tr>{% endif %}
</tbody>
</table>
</div>
<div>
<!-- Cose da fare -->
<div class="panel">
<div class="panel-header"><h3><i class="material-icons">checklist</i> Cose da Fare</h3></div>
<div class="todo-list">
{% for t in todos %}
<div class="todo-item">
<div class="todo-icon c-{{ t.color }}"><i class="material-icons">{{ t.icon }}</i></div>
<div class="todo-body">
<div class="todo-text">{{ t.text }}</div>
{% if t.amount %}<div class="todo-amount">€ {{ "%.2f"|format(t.amount) }}</div>{% endif %}
</div>
<a href="{{ t.link }}" class="todo-action">{{ t.action }}</a>
</div>
{% endfor %}
{% if not todos %}
<div class="todo-empty"><i class="material-icons">check_circle</i>Tutto sotto controllo!<br>Nessuna attività urgente.</div>
{% endif %}
</div>
</div>
<!-- Previsione di cassa -->
<div class="panel">
<div class="panel-header"><h3><i class="material-icons">account_balance_wallet</i> Previsione di Cassa (30 gg)</h3></div>
<div class="cashflow-box">
<div class="cashflow-row">
<div class="cf-label"><i class="material-icons text-success">south_west</i> Incassi previsti</div>
<div class="cf-value text-success">€ {{ "{:,.2f}".format(cashflow.inflow) }}</div>
</div>
<div class="cashflow-row">
<div class="cf-label"><i class="material-icons text-danger">north_east</i> Pagamenti previsti</div>
<div class="cf-value text-danger"> € {{ "{:,.2f}".format(cashflow.outflow) }}</div>
</div>
<div class="cashflow-row cf-net">
<div class="cf-label">Saldo previsto</div>
<div class="cf-value {% if cashflow.net >= 0 %}text-success{% else %}text-danger{% endif %}">€ {{ "{:,.2f}".format(cashflow.net) }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- Riga inferiore: fido superato + ultimi documenti -->
<div class="dash-grid-2">
<div class="panel">
<div class="panel-header"><h3><i class="material-icons">credit_card</i> Controllo Fido Clienti</h3></div>
<table class="data-table">
<thead><tr><th>Cliente</th><th class="text-right">Fido</th><th class="text-right">Esposizione</th><th class="text-center">Stato</th></tr></thead>
<tbody>
{% for c in credit_exceeded %}
<tr class="low-stock">
<td>{{ c.name }}</td>
<td class="text-right">€ {{ "%.0f"|format(c.credit_limit) }}</td>
<td class="text-right text-danger text-bold">€ {{ "%.0f"|format(c.balance) }}</td>
<td class="text-center"><span class="badge badge-danger"><i class="material-icons">warning</i> Superato</span></td>
</tr>
{% endfor %}
{% if not credit_exceeded %}<tr><td colspan="4" class="text-center text-mute" style="padding:20px;">Nessun cliente ha superato il fido</td></tr>{% endif %}
</tbody>
</table>
</div>
<div class="panel">
<div class="panel-header"><h3><i class="material-icons">description</i> Ultimi Documenti di Vendita</h3></div>
<table class="data-table">
<thead><tr><th>Data</th><th>Numero</th><th>Cliente</th><th class="text-right">Totale</th><th>Stato</th></tr></thead>
<tbody>
{% for i in last_invoices %}
<tr class="row-link" onclick="location.href='/invoices/{{ i.id }}'">
<td>{{ i.doc_date.strftime("%d/%m/%Y") }}</td>
<td class="text-bold mono">{{ i.doc_no }}</td>
<td>{{ i.customer_name }}</td>
<td class="text-right">€ {{ "%.2f"|format(i.total) }}</td>
<td><span class="badge status-{{ i.status.value }}">{{ doc_status_label(i.status) }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- KPI riepilogo -->
<div class="kpi-grid" style="margin-top:16px;">
<div class="kpi-card"><div class="kpi-title">Fatturato Emesso</div><div class="kpi-value text-success">€ {{ "{:,.0f}".format(stats.invoiced_total) }}</div></div>
<div class="kpi-card" style="border-left-color: var(--c-teal)"><div class="kpi-title">Crediti Aperti</div><div class="kpi-value">€ {{ "{:,.0f}".format(stats.open_recv_amount) }}</div></div>
<div class="kpi-card" style="border-left-color: var(--c-orange)"><div class="kpi-title">Debiti Aperti</div><div class="kpi-value">€ {{ "{:,.0f}".format(stats.open_pay_amount) }}</div></div>
<div class="kpi-card" style="border-left-color: var(--c-purple)"><div class="kpi-title">Valore Magazzino</div><div class="kpi-value">€ {{ "{:,.0f}".format(stats.warehouse_value) }}</div></div>
</div>
</div>
{% endblock %}