diff --git a/public/userarea/class/MagoSalesStats.php b/public/userarea/class/MagoSalesStats.php
index 5c31c637..50504e22 100644
--- a/public/userarea/class/MagoSalesStats.php
+++ b/public/userarea/class/MagoSalesStats.php
@@ -17,9 +17,27 @@ class MagoSalesStats
*
* Per invertirle basta scambiare i due valori qui sotto.
*/
+ // Verificato sui dati reali (13/10/2026): la data ZIBO è ExpectedDeliveryDate, la data cliente è ConfirmedDeliveryDate
const ZIBO_DATE_COL = 'expected_delivery_date'; // Mago: ExpectedDeliveryDate ("Data ZIBO")
const CLIENT_DATE_COL = 'confirmed_delivery_date'; // Mago: ConfirmedDeliveryDate ("DATA CLIENTE")
+ /*
+ * DATA ZIBO = SETTIMANA DI CONSEGNA
+ * In Mago la data ZIBO è il primo giorno della settimana: per il cliente significa "entro quella settimana".
+ * La scadenza reale è quindi la fine di quella settimana; durante la settimana compare un avviso.
+ * ZIBO_IS_WEEK = false → la data ZIBO torna a valere come giorno preciso
+ * ZIBO_WEEK_END_DAYS = giorni dopo il lunedì: 6 = domenica, 4 = venerdì
+ */
+ const ZIBO_IS_WEEK = true;
+ const ZIBO_WEEK_END_DAYS = 6;
+
+ /*
+ * ORDINI ESCLUSI da tutte le statistiche: parole cercate (senza distinzione maiuscole/minuscole) in
+ * numero ordine esterno, nostro/vostro riferimento, note di testata, descrizioni e note delle righe.
+ * Lasciare l'elenco vuoto per non escludere nulla.
+ */
+ const EXCLUDE_KEYWORDS = ['listino'];
+
/** Data ZIBO "valida" di una riga/testata: in Mago, se non compilata, resta uguale alla data ordine. */
public static function ziboDate($a)
{
@@ -33,10 +51,40 @@ class MagoSalesStats
return "CASE WHEN $c > $a.order_date THEN $c END";
}
- /** Scadenza effettiva di una riga: data ZIBO, se manca la data cliente. */
+ /** Scadenza della data ZIBO: fine della settimana indicata (o il giorno stesso se ZIBO_IS_WEEK = false). */
+ public static function ziboDeadline($a)
+ {
+ $z = self::ziboDate($a);
+ if (!self::ZIBO_IS_WEEK) return $z;
+ return "DATE_ADD($z, INTERVAL (" . (int)self::ZIBO_WEEK_END_DAYS . " - WEEKDAY($z)) DAY)";
+ }
+
+ /** Scadenza effettiva di una riga: fine settimana ZIBO, se manca la data cliente. */
public static function lineDue($a)
{
- return "COALESCE(" . self::ziboDate($a) . ", " . self::clientDate($a) . ")";
+ return "COALESCE(" . self::ziboDeadline($a) . ", " . self::clientDate($a) . ")";
+ }
+
+ /**
+ * Condizione SQL che ESCLUDE gli ordini contenenti una delle parole in EXCLUDE_KEYWORDS
+ * (alias della testata: $a). Stringa vuota se non c'è nulla da escludere.
+ */
+ public static function excludeCondition($a)
+ {
+ $words = array_filter(array_map('trim', self::EXCLUDE_KEYWORDS));
+ if (!$words) return '1=1';
+
+ $parts = [];
+ foreach ($words as $w) {
+ $like = "'%" . str_replace(["\\", "'", '%', '_'], ["\\\\", "''", '\\%', '\\_'], $w) . "%'";
+ foreach (['external_ord_no', 'our_reference', 'your_reference', 'notes'] as $col) {
+ $parts[] = "COALESCE($a.$col, '') LIKE $like"; // COALESCE: un campo NULL non deve escludere l'ordine
+ }
+ $parts[] = "EXISTS (SELECT 1 FROM mago_sale_ord_detail ex
+ WHERE ex.sale_ord_id = $a.sale_ord_id
+ AND (COALESCE(ex.description, '') LIKE $like OR COALESCE(ex.notes, '') LIKE $like))";
+ }
+ return 'NOT (' . implode(' OR ', $parts) . ')';
}
/** Aggregati per ordine calcolati dalle righe (alias l). */
@@ -56,7 +104,8 @@ class MagoSalesStats
MIN($due) AS first_due,
COUNT(DISTINCT CASE WHEN $open THEN $due END) AS open_dates,
SUM($open AND $due < CURDATE()) AS late_lines,
- SUM($open AND " . self::clientDate('d') . " < CURDATE()) AS late_lines_client
+ SUM($open AND " . self::clientDate('d') . " < CURDATE()) AS late_lines_client,
+ SUM($open AND $due >= CURDATE() AND YEARWEEK($due, 3) = YEARWEEK(CURDATE(), 3)) AS week_lines
FROM mago_sale_ord_detail d
WHERE d.item <> '' OR d.qty <> 0
GROUP BY d.sale_ord_id
@@ -110,13 +159,14 @@ class MagoSalesStats
-- (o alla data cliente se la data ZIBO non è compilata)
COALESCE(
l.next_due, l.first_due,
- " . self::ziboDate('o') . ", " . self::clientDate('o') . ", o.compulsory_delivery_date
+ " . self::ziboDeadline('o') . ", " . self::clientDate('o') . ", o.compulsory_delivery_date
) AS due_date,
COALESCE(l.lines_total, 0) AS lines_total,
COALESCE(l.open_lines, 0) AS open_lines,
COALESCE(l.open_dates, 0) AS open_dates,
COALESCE(l.late_lines, 0) AS late_lines,
COALESCE(l.late_lines_client, 0) AS late_lines_client,
+ COALESCE(l.week_lines, 0) AS week_lines,
CASE
WHEN o.cancelled = 1 THEN 'annullato'
WHEN o.delivered = 1 THEN 'evaso'
@@ -127,6 +177,7 @@ class MagoSalesStats
LEFT JOIN mago_cust_supp c
ON c.cust_supp_type = " . self::CUSTOMER_TYPE . " AND c.cust_supp = o.customer
LEFT JOIN " . self::lineAggregates() . " ON l.sale_ord_id = o.sale_ord_id
+ WHERE " . self::excludeCondition('o') . "
) x";
}
@@ -201,6 +252,7 @@ class MagoSalesStats
'bloccato' => "x.status = 'bloccato'",
'ritardo' => "(x.status IN ('aperto','bloccato') AND x.due_date < CURDATE())",
'ritardo_cliente' => "(x.status IN ('aperto','bloccato') AND x.client_date < CURDATE())",
+ 'settimana' => "(x.status IN ('aperto','bloccato') AND x.week_lines > 0)", // righe da consegnare questa settimana
'da_fatturare' => "(x.delivered = 1 AND x.invoiced = 0 AND x.cancelled = 0)",
'fatturato' => "x.invoiced = 1",
];
diff --git a/public/userarea/sales_orders_deliveries.php b/public/userarea/sales_orders_deliveries.php
index 4a485472..7297890e 100644
--- a/public/userarea/sales_orders_deliveries.php
+++ b/public/userarea/sales_orders_deliveries.php
@@ -501,6 +501,18 @@ if ($action === 'data') {
font-style: italic;
}
+ .week-badge {
+ display: inline-block;
+ background: #fef3c7;
+ color: #b45309;
+ font-size: .66rem;
+ font-weight: 800;
+ border-radius: 999px;
+ padding: 1px 7px;
+ margin-left: 5px;
+ vertical-align: middle;
+ }
+
.late-days {
display: inline-block;
background: #fde8ea;
@@ -628,7 +640,7 @@ if ($action === 'data') {
- Data ZIBO = scadenza comunicata (rossa se superata)
+ Data ZIBO = settimana di consegna comunicata (rossa a settimana finita)Data cliente = desiderata (arancione se superata)
@@ -826,11 +838,37 @@ if ($action === 'data') {
const fmtMetric = v => metric === 'value' ? fmtEur(v) : fmtNum(Math.round(v));
const fmtAxis = v => metric === 'value' ? fmtEurShort(v) : fmtNum(Math.round(v));
+ // Data ZIBO = settimana di consegna (impostazioni in MagoSalesStats)
+ const ZIBO_WEEK = = MagoSalesStats::ZIBO_IS_WEEK ? 'true' : 'false' ?>;
+ const ZIBO_END_DAYS = = (int)MagoSalesStats::ZIBO_WEEK_END_DAYS ?>;
+
+ // scadenza della data ZIBO: fine della settimana indicata
+ function ziboEnd(z) {
+ if (!z || !ZIBO_WEEK) return z;
+ const d = new Date(String(z).substring(0, 10) + 'T00:00:00');
+ d.setDate(d.getDate() - ((d.getDay() + 6) % 7) + ZIBO_END_DAYS);
+ return iso(d);
+ }
+
+ // stato della data ZIBO: 'late' (settimana finita), 'week' (settimana in corso), '' (futura)
+ function ziboState(z, open) {
+ if (!z || !open) return '';
+ const today = iso(new Date());
+ if (ziboEnd(z) < today) return 'late';
+ if (ZIBO_WEEK && String(z).substring(0, 10) <= today) return 'week';
+ return '';
+ }
+
+ function ziboHtml(z, open, extra) {
+ const st = ziboState(z, open);
+ const title = ZIBO_WEEK ? ` title="Settimana del ${fmtDate(z)}: consegna entro ${fmtDate(ziboEnd(z))}"` : '';
+ return `