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
+59 -119
View File
@@ -1,131 +1,77 @@
{% extends "base.html" %}
{% block title %}Magazzino - Mago4 Demo{% endblock %}
{% block page_title %}Magazzino{% endblock %}
{% block title %}Giacenze - Mago4 Demo{% endblock %}
{% block window_tabs %}<a href="/warehouse" class="window-tab active"><i class="material-icons">inventory</i> Giacenze</a>{% endblock %}
{% block content %}
<div class="page-header">
<div class="module-bar">
<div class="module-title">Giacenze Articoli</div>
<div class="breadcrumb"><a href="/">Mago4</a><span class="sep">»</span><a href="/magazzino">Logistica</a><span class="sep">»</span><span class="current">Giacenze</span></div>
</div>
<div class="tab-strip">
<a href="/magazzino">Magazzino</a>
<a href="/inventory-movements">Movimenti</a>
<a href="/warehouse" class="active">Giacenze</a>
<a href="/inventory-reasons">Causali</a>
</div>
<div class="content-pad">
<div class="kpi-grid">
<div class="kpi-card" style="border-left-color: var(--c-blue)">
<div class="kpi-title">Articoli in Catalogo</div>
<div class="kpi-row"><div class="kpi-icon"><i class="material-icons" style="color:var(--c-blue)">inventory_2</i></div><div class="kpi-value">{{ products|length }}</div></div>
</div>
<div class="kpi-card" style="border-left-color: var(--c-red)">
<div class="kpi-title">Articoli Sotto Scorta</div>
<div class="kpi-row"><div class="kpi-icon"><i class="material-icons" style="color:var(--c-red)">warning</i></div><div class="kpi-value">{{ low_stock|length }}</div></div>
</div>
<div class="kpi-card" style="border-left-color: var(--c-purple)">
<div class="kpi-title">Valore Magazzino (a costo)</div>
<div class="kpi-row"><div class="kpi-icon"><i class="material-icons" style="color:var(--c-purple)">euro_symbol</i></div><div class="kpi-value">€ {{ "{:,.0f}".format(warehouse_value) }}</div></div>
</div>
</div>
{% if low_stock %}
<div class="alert alert-danger" style="flex: 1;">
<i class="material-icons">error</i>
<strong>Attenzione!</strong> {{ low_stock|length }} articolo/i in sottosorta
</div>
{% endif %}
</div>
<!-- Summary Stats -->
<div class="widget-row" style="margin-bottom: 20px;">
<div class="widget-card stat-card" style="border-left-color: var(--color-blue)">
<div class="widget-header">
<h3 class="widget-title">Articoli in Catalogo</h3>
</div>
<div class="widget-body stat-body">
<div class="stat-value">{{ products|length }}</div>
</div>
</div>
<div class="widget-card stat-card" style="border-left-color: var(--color-red)">
<div class="widget-header">
<h3 class="widget-title">Articoli in Sottosorta</h3>
</div>
<div class="widget-body stat-body">
<div class="stat-icon" style="color: var(--color-red)">
<i class="material-icons">warning</i>
</div>
<div class="stat-value">{{ low_stock|length }}</div>
</div>
</div>
</div>
<!-- Low Stock Alert -->
{% if low_stock %}
<div class="widget-card">
<div class="widget-header">
<h3 class="widget-title">
<i class="material-icons">warning</i> Articoli Sotto Soglia Riordino
</h3>
</div>
<div class="widget-body">
<div class="panel">
<div class="panel-header"><h3><i class="material-icons" style="color:var(--c-red)">warning</i> Articoli Sotto Scorta Minima</h3></div>
<table class="data-table">
<thead>
<tr>
<th>Codice</th>
<th>Descrizione</th>
<th>Giacenza</th>
<th>Soglia Riordino</th>
<th>Scostamento</th>
<th>Azioni</th>
</tr>
</thead>
<thead><tr><th>Codice</th><th>Descrizione</th><th class="text-right">Giacenza</th><th class="text-right">Scorta Min.</th><th class="text-right">Da Riordinare</th><th></th></tr></thead>
<tbody>
{% for product in low_stock %}
<tr class="highlight-danger">
<td><strong>{{ product.code }}</strong></td>
<td>{{ product.description }}</td>
<td class="text-danger text-bold">{{ product.quantity_in_stock }}</td>
<td class="text-right">{{ product.reorder_level }}</td>
<td class="text-right text-danger">
-{{ product.reorder_level - product.quantity_in_stock }} unità
</td>
<td>
<button class="btn-small btn-primary" onclick="createPOForProduct({{ product.id }})">
<i class="material-icons">add_shopping_cart</i>
</button>
</td>
{% for p in low_stock %}
<tr class="low-stock">
<td class="text-bold mono">{{ p.code }}</td>
<td>{{ p.description }}</td>
<td class="text-right text-danger text-bold">{{ p.quantity_in_stock }} {{ p.uom }}</td>
<td class="text-right">{{ p.reorder_level }} {{ p.uom }}</td>
<td class="text-right text-danger">{{ p.max_stock - p.quantity_in_stock }} {{ p.uom }}</td>
<td class="text-center"><button class="btn-icon-sm act-cart" title="Genera ordine" onclick="alert('Genera ordine di acquisto (demo)')"><i class="material-icons">add_shopping_cart</i></button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% endif %}
<!-- All Products -->
<div class="widget-card">
<div class="widget-header">
<h3 class="widget-title">Inventario Completo</h3>
</div>
<div class="widget-body">
<div class="panel">
<div class="panel-header"><h3><i class="material-icons">inventory</i> Inventario Completo — Deposito MAG01</h3></div>
<table class="data-table">
<thead>
<tr>
<th>Codice</th>
<th>Descrizione</th>
<th>Categoria</th>
<th>Giacenza</th>
<th>Soglia Riordino</th>
<th>Prezzo Unit.</th>
<th>Valore Magazzino</th>
<th>Stato</th>
</tr>
</thead>
<thead><tr><th>Codice</th><th>Descrizione</th><th>Categoria</th><th class="text-right">Giacenza</th><th class="text-right">Scorta Min.</th><th class="text-right">Costo Unit.</th><th class="text-right">Valore</th><th class="text-center">Stato</th></tr></thead>
<tbody>
{% for product in products %}
<tr class="{% if product.quantity_in_stock <= product.reorder_level %}low-stock{% endif %}">
<td><strong>{{ product.code }}</strong></td>
<td>{{ product.description }}</td>
<td>{{ product.category }}</td>
<td class="text-right {% if product.quantity_in_stock <= product.reorder_level %}text-danger text-bold{% endif %}">
{{ product.quantity_in_stock }}
</td>
<td class="text-right">{{ product.reorder_level }}</td>
<td class="text-right">€ {{ "%.2f"|format(product.unit_price) }}</td>
<td class="text-right text-bold">
€ {{ "%.2f"|format(product.quantity_in_stock * product.unit_price) }}
</td>
<td>
{% if product.quantity_in_stock <= product.reorder_level %}
<span class="badge badge-danger">
<i class="material-icons">warning</i> SOTTOSORTA
</span>
{% elif product.quantity_in_stock <= product.reorder_level + 20 %}
<span class="badge badge-warning">
<i class="material-icons">info</i> PROSSIMA SOGLIA
</span>
{% for p in products %}
<tr class="{% if p.quantity_in_stock <= p.reorder_level %}low-stock{% endif %}">
<td class="text-bold mono">{{ p.code }}</td>
<td>{{ p.description }}</td>
<td>{{ p.category }}</td>
<td class="text-right {% if p.quantity_in_stock <= p.reorder_level %}text-danger text-bold{% endif %}">{{ p.quantity_in_stock }} {{ p.uom }}</td>
<td class="text-right">{{ p.reorder_level }} {{ p.uom }}</td>
<td class="text-right text-mute">€ {{ "%.2f"|format(p.purchase_price) }}</td>
<td class="text-right text-bold">{{ "%.2f"|format(p.quantity_in_stock * p.purchase_price) }}</td>
<td class="text-center">
{% if p.quantity_in_stock <= p.reorder_level %}
<span class="badge badge-danger"><i class="material-icons">warning</i> Sottoscorta</span>
{% elif p.quantity_in_stock <= p.reorder_level + 20 %}
<span class="badge badge-warning"><i class="material-icons">info</i> In esaurimento</span>
{% else %}
<span class="badge badge-success">
<i class="material-icons">check</i> OK
</span>
<span class="badge badge-success"><i class="material-icons">check</i> OK</span>
{% endif %}
</td>
</tr>
@@ -134,10 +80,4 @@
</table>
</div>
</div>
<script>
function createPOForProduct(productId) {
alert('Creazione ordine di acquisto per articolo #' + productId + ' (da implementare)');
}
</script>
{% endblock %}