getConnection(); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // --- Permessi: pagina visibile solo con 'sales.stats.view' --- if (!userCan('sales.stats.view')) { if (isset($_GET['action']) || isset($_POST['action'])) { MagoSalesStats::jsonOut(['success' => false, 'ok' => false, 'message' => 'Non hai i permessi per questa pagina']); } while (ob_get_level()) ob_end_clean(); http_response_code(403); echo '

Accesso non consentito

Non hai i permessi per visualizzare questa pagina.

Torna alla Dashboard
'; exit; } /* ============================================================ * AJAX * ============================================================ */ $action = $_GET['action'] ?? ''; if ($action === 'filters') { MagoSalesStats::jsonOut(MagoSalesStats::filterOptions($pdo)); } // Elenco fatture / note di credito degli anni scelti (tabella a scomparsa sotto i grafici del fatturato) if ($action === 'invdocs') { try { [$where, $params] = MagoSalesStats::buildWhere(['from' => '', 'to' => '']); $iWhere = preg_replace('/\bx\./', 'i.', $where); $iYtd = !empty($_GET['ytd']) ? " AND DATE_FORMAT(i.document_date, '%m-%d') <= DATE_FORMAT(CURDATE(), '%m-%d')" : ''; $years = array_values(array_unique(array_map('intval', MagoSalesStats::arrParam('years')))); $years = array_filter($years, function ($y) { return $y >= 2000 && $y <= 2100; }); if (!$years) MagoSalesStats::jsonOut(['success' => true, 'docs' => []]); $ph = []; foreach (array_values($years) as $k => $y) { $ph[] = ':dy' . $k; $params['dy' . $k] = $y; } $docs = MagoSalesStats::fetchAllQ($pdo, " SELECT i.document_date, i.doc_no, i.document_type, i.is_credit_note, i.customer, i.company_name, i.salesperson, i.area, i.revenue, i.no_amount FROM " . MagoSalesStats::invoiceFrom() . " WHERE $iWhere AND YEAR(i.document_date) IN (" . implode(',', $ph) . ") $iYtd ORDER BY i.document_date DESC, i.doc_no DESC LIMIT 20000 ", $params); MagoSalesStats::jsonOut(['success' => true, 'docs' => $docs, 'types' => MagoSalesStats::INVOICE_TYPE_LABELS]); } catch (Throwable $e) { MagoSalesStats::jsonOut(['success' => false, 'message' => $e->getMessage()]); } } if ($action === 'data') { try { $from = MagoSalesStats::baseFrom(); // Filtri cliente / agente / area (le date qui non si usano: si ragiona per anni) [$where, $params] = MagoSalesStats::buildWhere(['from' => '', 'to' => '']); // "A pari data": ogni anno considerato solo fino al giorno/mese di oggi $ytd = !empty($_GET['ytd']); $ytdCond = $ytd ? " AND DATE_FORMAT(x.order_date, '%m-%d') <= DATE_FORMAT(CURDATE(), '%m-%d')" : ''; // Metrica: numero ordini o valore (imponibile); gli annullati sono sempre esclusi $metric = (($_GET['metric'] ?? '') === 'value') ? 'value' : 'count'; $m = $metric === 'value' ? "CASE WHEN x.status <> 'annullato' THEN x.taxable_amount ELSE 0 END" : "CASE WHEN x.status <> 'annullato' THEN 1 ELSE 0 END"; // --- Riepilogo per TUTTI gli anni (serve anche l'anno prima del primo selezionato per la variazione) --- $summary = MagoSalesStats::fetchAllQ($pdo, " SELECT YEAR(x.order_date) AS y, SUM(x.status <> 'annullato') AS orders, COALESCE(SUM(CASE WHEN x.status <> 'annullato' THEN x.taxable_amount END), 0) AS value, COUNT(DISTINCT CASE WHEN x.status <> 'annullato' THEN x.customer END) AS customers, SUM(x.status = 'annullato') AS cancelled, SUM(x.summary_tb_modified IS NULL) AS missing_amounts FROM $from WHERE $where AND x.order_date IS NOT NULL $ytdCond GROUP BY y ORDER BY y ", $params); $available = array_map(function ($r) { return (int)$r['y']; }, $summary); // Anni scelti (validati); default: gli ultimi 3 disponibili $years = array_values(array_intersect(array_map('intval', MagoSalesStats::arrParam('years')), $available)); if (!$years) $years = array_slice($available, -3); sort($years); $monthly = []; $customers = []; $yearIn = '1=0'; $yp = $params; if ($years) { $ph = []; $yp = $params; foreach ($years as $i => $y) { $ph[] = ':yy' . $i; $yp['yy' . $i] = $y; } $yearIn = 'YEAR(x.order_date) IN (' . implode(',', $ph) . ')'; // --- Mensile per anno --- $monthly = MagoSalesStats::fetchAllQ($pdo, " SELECT YEAR(x.order_date) AS y, MONTH(x.order_date) AS m, SUM($m) AS v FROM $from WHERE $where AND $yearIn $ytdCond GROUP BY y, m ORDER BY y, m ", $yp); // --- Clienti per anno --- $rows = MagoSalesStats::fetchAllQ($pdo, " SELECT x.customer AS code, x.company_name AS name, YEAR(x.order_date) AS y, SUM($m) AS v FROM $from WHERE $where AND $yearIn $ytdCond AND x.customer <> '' GROUP BY x.customer, x.company_name, y ", $yp); foreach ($rows as $r) { $k = $r['code']; if (!isset($customers[$k])) { $customers[$k] = ['code' => $r['code'], 'name' => $r['name'], 'v' => []]; } $customers[$k]['v'][(int)$r['y']] = (float)$r['v']; } $customers = array_values($customers); } // --- FATTURATO per data documento (fatture + acconti - note di credito) --- $invoiced = ['available' => false, 'summary' => [], 'monthly' => []]; $iFrom = MagoSalesStats::invoiceFrom(); $iWhere = preg_replace('/\bx\./', 'i.', $where); // stessi filtri cliente/agente/area try { $iYtd = $ytd ? " AND DATE_FORMAT(i.document_date, '%m-%d') <= DATE_FORMAT(CURDATE(), '%m-%d')" : ''; $invoiced['summary'] = MagoSalesStats::fetchAllQ($pdo, " SELECT YEAR(i.document_date) AS y, COALESCE(SUM(i.revenue), 0) AS value, SUM(i.is_credit_note = 0) AS docs, SUM(i.is_credit_note = 1) AS credit_notes, COALESCE(SUM(CASE WHEN i.is_credit_note = 1 THEN -i.revenue END), 0) AS credit_value, SUM(i.no_amount) AS missing_amounts FROM $iFrom WHERE $iWhere AND i.document_date IS NOT NULL $iYtd GROUP BY y ORDER BY y ", $params); if ($years) { $invoiced['monthly'] = MagoSalesStats::fetchAllQ($pdo, " SELECT YEAR(i.document_date) AS y, MONTH(i.document_date) AS m, SUM(i.revenue) AS v FROM $iFrom WHERE $iWhere AND " . str_replace('x.order_date', 'i.document_date', $yearIn) . " $iYtd GROUP BY y, m ORDER BY y, m ", $yp); } $invoiced['available'] = (bool)$pdo->query("SELECT 1 FROM mago_sale_doc LIMIT 1")->fetchColumn(); } catch (Throwable $e) { $invoiced['error'] = 'Fatturato non disponibile: eseguire la migrazione e la sincronizzazione'; } // ===== CRUSCOTTO DIREZIONE: situazione ad oggi (rispetta solo i filtri cliente / agente / area) ===== $curY = (int)date('Y'); $val = "CASE WHEN x.status <> 'annullato' THEN x.taxable_amount ELSE 0 END"; $dash = []; // portafoglio da consegnare = valore residuo delle righe aperte $rWhere = preg_replace('/\bx\./', 'r.', $where); $dash['backlog'] = MagoSalesStats::fetchAllQ($pdo, " SELECT COALESCE(SUM(r.residual_value), 0) AS v, COUNT(*) AS n_lines, COUNT(DISTINCT r.sale_ord_id) AS n_orders, COALESCE(SUM(CASE WHEN r.due_date < CURDATE() THEN r.residual_value END), 0) AS late_v FROM " . MagoSalesStats::openLinesFrom() . " WHERE $rWhere ", $params)[0]; // ordinato: ultimi 90 giorni, mesi (24) e anni per la previsione $dash['ordered_90'] = (float)MagoSalesStats::fetchAllQ($pdo, " SELECT COALESCE(SUM($val), 0) AS v FROM $from WHERE $where AND x.order_date > CURDATE() - INTERVAL 90 DAY ", $params)[0]['v']; $since24 = date('Y-m-01', strtotime('first day of -23 months')); $dash['since'] = $since24; $dash['ordered_months'] = MagoSalesStats::fetchAllQ($pdo, " SELECT DATE_FORMAT(x.order_date, '%Y-%m') AS ym, SUM($val) AS v FROM $from WHERE $where AND x.order_date >= '$since24' GROUP BY ym ORDER BY ym ", $params); $ordYears = MagoSalesStats::fetchAllQ($pdo, " SELECT YEAR(x.order_date) AS y, SUM($val) AS full, SUM(CASE WHEN DATE_FORMAT(x.order_date, '%m-%d') <= DATE_FORMAT(CURDATE(), '%m-%d') THEN $val ELSE 0 END) AS ytd FROM $from WHERE $where AND x.order_date >= '" . ($curY - 3) . "-01-01' GROUP BY y ", $params); $dash['ordered_forecast'] = MagoSalesStats::yearForecast($ordYears, $curY); // fatturato (se i documenti sono sincronizzati) $dash['invoiced_90'] = null; $dash['invoiced_months'] = []; $dash['invoiced_forecast'] = null; if (!empty($invoiced['available'])) { $dash['invoiced_90'] = (float)MagoSalesStats::fetchAllQ($pdo, " SELECT COALESCE(SUM(i.revenue), 0) AS v FROM $iFrom WHERE $iWhere AND i.document_date > CURDATE() - INTERVAL 90 DAY ", $params)[0]['v']; $dash['invoiced_months'] = MagoSalesStats::fetchAllQ($pdo, " SELECT DATE_FORMAT(i.document_date, '%Y-%m') AS ym, SUM(i.revenue) AS v FROM $iFrom WHERE $iWhere AND i.document_date >= '$since24' GROUP BY ym ORDER BY ym ", $params); $invYears = MagoSalesStats::fetchAllQ($pdo, " SELECT YEAR(i.document_date) AS y, SUM(i.revenue) AS full, SUM(CASE WHEN DATE_FORMAT(i.document_date, '%m-%d') <= DATE_FORMAT(CURDATE(), '%m-%d') THEN i.revenue ELSE 0 END) AS ytd FROM $iFrom WHERE $iWhere AND i.document_date >= '" . ($curY - 3) . "-01-01' GROUP BY y ", $params); $dash['invoiced_forecast'] = MagoSalesStats::yearForecast($invYears, $curY); } // ===== CLIENTI DA RICHIAMARE ===== $dormRows = MagoSalesStats::fetchAllQ($pdo, " SELECT x.customer AS code, x.company_name AS name, x.order_date, x.taxable_amount AS v, x.salesperson, x.area, x.internal_ord_no AS ord_no FROM $from WHERE $where AND x.status <> 'annullato' AND x.customer <> '' AND x.order_date >= CURDATE() - INTERVAL " . (int)MagoSalesStats::DORMANT_WINDOW_YEARS . " YEAR ", $params); $dormant = MagoSalesStats::dormantCustomers($dormRows); MagoSalesStats::jsonOut([ 'success' => true, 'dash' => $dash, 'dormant' => $dormant, 'dormant_cfg' => [ 'years' => MagoSalesStats::DORMANT_WINDOW_YEARS, 'min_orders' => MagoSalesStats::DORMANT_MIN_ORDERS, 'watch' => MagoSalesStats::DORMANT_WATCH, 'sleep' => MagoSalesStats::DORMANT_SLEEP, 'lost' => MagoSalesStats::DORMANT_LOST, ], 'invoiced' => $invoiced, 'metric' => $metric, 'ytd' => $ytd, 'today' => date('Y-m-d'), 'available' => $available, 'years' => $years, 'summary' => $summary, 'monthly' => $monthly, 'customers' => $customers, ]); } catch (Throwable $e) { MagoSalesStats::jsonOut(['success' => false, 'message' => $e->getMessage()]); } } ?> Confronto anni - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
Statistiche Ordini Cliente
Dati Mago4
Situazione ad oggi non dipende dagli anni scelti · segue i filtri cliente, agente e area
Ordinato vs fatturato · ultimi 24 mesi
Barre: valori del mese · linea: book-to-bill su 3 mesi (ordinato ÷ fatturato: sopra 1 il portafoglio cresce, sotto 1 si consuma)
Andamento mensile
Stagionalità: un colore per anno
Progressivo dell'anno
Somma da gennaio: dice se si è avanti o indietro rispetto agli anni scorsi
Fatturato per mese
Imponibile fatture + acconti − note di credito, per data fattura
Fatturato progressivo dell'anno
Somma da gennaio del fatturato, anno su anno
Clienti da richiamare
 
Clienti a confronto
Clicca un cliente per filtrare tutta la pagina