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; } $canSync = userCan('sales.sync.manage'); /* ============================================================ * AJAX * ============================================================ */ $action = $_GET['action'] ?? ($_POST['action'] ?? ''); if ($action === 'sync' && $_SERVER['REQUEST_METHOD'] === 'POST') { if (!$canSync) { MagoSalesStats::jsonOut(['ok' => false, 'message' => 'Non hai i permessi per sincronizzare i dati da Mago']); } set_time_limit(600); try { $mode = $_POST['mode'] ?? 'incremental'; $opts = []; if ($mode === 'test') $opts['limit'] = max(1, min(500, (int)($_POST['limit'] ?? 50))); if ($mode === 'full') $opts['full'] = true; $sync = new MagoSync($pdo, MagoClient::fromEnv()); $report = $sync->run($opts); $report['info'] = MagoSalesStats::syncInfo($pdo); MagoSalesStats::jsonOut($report); } catch (Throwable $e) { MagoSalesStats::jsonOut(['ok' => false, 'message' => $e->getMessage()]); } } if ($action === 'filters') { MagoSalesStats::jsonOut(MagoSalesStats::filterOptions($pdo)); } if ($action === 'data') { try { [$where, $params] = MagoSalesStats::buildWhere(); $from = MagoSalesStats::baseFrom(); // Metrica dei grafici: numero ordini oppure valore (imponibile, annullati esclusi) $metric = (($_GET['metric'] ?? '') === 'value') ? 'value' : 'count'; $m = $metric === 'value' ? "CASE WHEN x.status <> 'annullato' THEN x.taxable_amount ELSE 0 END" : "1"; $agg = "SUM($m)"; $val = "CASE WHEN x.status <> 'annullato' THEN x.taxable_amount ELSE 0 END"; // --- KPI --- $kpi = MagoSalesStats::fetchAllQ($pdo, " SELECT COUNT(*) AS orders, SUM(x.status = 'aperto') AS open_orders, SUM(x.status = 'evaso') AS delivered, SUM(x.status = 'annullato') AS cancelled, SUM(x.status = 'bloccato') AS blocked, SUM(x.is_late) AS late, SUM(x.is_late_client) AS late_client, SUM(x.status IN ('aperto','bloccato') AND x.late_lines > 0 AND x.late_lines < x.open_lines) AS late_partial, SUM(CASE WHEN x.status IN ('aperto','bloccato') THEN x.late_lines ELSE 0 END) AS late_lines, SUM(x.status IN ('aperto','bloccato') AND x.week_lines > 0) AS week_orders, SUM(CASE WHEN x.status IN ('aperto','bloccato') THEN x.week_lines ELSE 0 END) AS week_lines, SUM(x.delivered = 1 AND x.invoiced = 0 AND x.cancelled = 0) AS to_invoice, COUNT(DISTINCT x.customer) AS customers, AVG(CASE WHEN x.status <> 'annullato' AND x.due_date IS NOT NULL AND x.due_date >= x.order_date THEN DATEDIFF(x.due_date, x.order_date) END) AS avg_lead_days, COALESCE(SUM($val), 0) AS value_total, COALESCE(SUM(CASE WHEN x.status IN ('aperto','bloccato') THEN x.taxable_amount ELSE 0 END), 0) AS value_open, COALESCE(SUM(CASE WHEN x.is_late = 1 THEN x.taxable_amount ELSE 0 END), 0) AS value_late, COALESCE(SUM(CASE WHEN x.delivered = 1 AND x.invoiced = 0 AND x.cancelled = 0 THEN x.taxable_amount ELSE 0 END), 0) AS value_to_invoice, AVG(CASE WHEN x.status <> 'annullato' THEN x.taxable_amount END) AS value_avg, SUM(x.summary_tb_modified IS NULL) AS missing_amounts FROM $from WHERE $where ", $params)[0]; // --- Confronto con lo STESSO PERIODO dell'anno precedente --- $kpi['prev_orders'] = null; $kpi['prev_value'] = null; $fromD = $_GET['from'] ?? ''; $toD = $_GET['to'] ?? ''; if (MagoSalesStats::isValidDate($fromD) && MagoSalesStats::isValidDate($toD) && $fromD <= $toD) { $prevFrom = date('Y-m-d', strtotime($fromD . ' -1 year')); $prevTo = date('Y-m-d', strtotime($toD . ' -1 year')); [$pw, $pp] = MagoSalesStats::buildWhere(['from' => $prevFrom, 'to' => $prevTo]); $prev = MagoSalesStats::fetchAllQ($pdo, "SELECT COUNT(*) AS n, COALESCE(SUM($val), 0) AS v FROM $from WHERE $pw", $pp)[0]; $kpi['prev_orders'] = (int)$prev['n']; $kpi['prev_value'] = (float)$prev['v']; $kpi['prev_from'] = date('d/m/Y', strtotime($prevFrom)); $kpi['prev_to'] = date('d/m/Y', strtotime($prevTo)); } // --- Andamento mensile per stato --- $monthly = MagoSalesStats::fetchAllQ($pdo, " SELECT DATE_FORMAT(x.order_date, '%Y-%m') AS ym, SUM(CASE WHEN x.status = 'aperto' THEN $m ELSE 0 END) AS aperto, SUM(CASE WHEN x.status = 'bloccato' THEN $m ELSE 0 END) AS bloccato, SUM(CASE WHEN x.status = 'evaso' THEN $m ELSE 0 END) AS evaso, SUM(CASE WHEN x.status = 'annullato' THEN " . ($metric === 'value' ? '0' : '1') . " ELSE 0 END) AS annullato, $agg AS totale FROM $from WHERE $where AND x.order_date IS NOT NULL GROUP BY ym ORDER BY ym ", $params); // --- Top clienti --- $topCustomers = MagoSalesStats::fetchAllQ($pdo, " SELECT x.customer AS code, x.company_name AS name, $agg AS n, SUM(CASE WHEN x.status IN ('aperto','bloccato') THEN $m ELSE 0 END) AS open_n FROM $from WHERE $where GROUP BY x.customer, x.company_name ORDER BY n DESC LIMIT 10 ", $params); // --- Per area / agente --- $byArea = MagoSalesStats::fetchAllQ($pdo, " SELECT CASE WHEN x.area = '' THEN '(nessuna)' ELSE x.area END AS label, x.area AS code, $agg AS n FROM $from WHERE $where GROUP BY x.area ORDER BY n DESC LIMIT 12 ", $params); $bySalesperson = MagoSalesStats::fetchAllQ($pdo, " SELECT CASE WHEN x.salesperson = '' THEN '(nessuno)' ELSE x.salesperson END AS label, x.salesperson AS code, $agg AS n FROM $from WHERE $where GROUP BY x.salesperson ORDER BY n DESC LIMIT 12 ", $params); // --- Scadenze ordini aperti --- $dueBuckets = MagoSalesStats::fetchAllQ($pdo, " SELECT bucket, SUM(mv) AS n FROM ( SELECT CASE WHEN x.is_late = 1 THEN '1_scaduti' WHEN x.open_due IS NULL THEN '6_senza' WHEN YEARWEEK(x.open_due, 3) = YEARWEEK(CURDATE(), 3) THEN '2_settimana' WHEN YEARWEEK(x.open_due, 3) = YEARWEEK(CURDATE() + INTERVAL 7 DAY, 3) THEN '3_prossima' WHEN x.open_due <= CURDATE() + INTERVAL 30 DAY THEN '4_30gg' ELSE '5_oltre' END AS bucket, $m AS mv FROM $from WHERE $where AND x.status IN ('aperto','bloccato') ) b GROUP BY bucket ORDER BY bucket ", $params); // --- Top articoli (dalle righe, annullati esclusi) --- $topItems = MagoSalesStats::fetchAllQ($pdo, " SELECT d.item AS code, MAX(d.description) AS name, MAX(d.uom) AS uom, SUM(d.qty) AS qty, SUM(d.taxable_amount) AS value, COUNT(DISTINCT d.sale_ord_id) AS orders FROM mago_sale_ord_detail d JOIN $from ON x.sale_ord_id = d.sale_ord_id WHERE $where AND d.item <> '' AND d.cancelled = 0 AND x.status <> 'annullato' GROUP BY d.item ORDER BY " . ($metric === 'value' ? 'value' : 'orders') . " DESC LIMIT 10 ", $params); // --- Elenco ordini --- $rows = MagoSalesStats::fetchAllQ($pdo, " SELECT x.sale_ord_id, x.internal_ord_no, x.external_ord_no, x.order_date, x.due_date, x.customer, x.company_name, x.salesperson, x.area, x.status, x.invoiced, x.your_reference, x.taxable_amount, x.total_amount, x.zibo_date, x.client_date, x.open_lines, x.late_lines, x.late_lines_client, x.open_dates, x.week_lines, x.is_late_client, (x.summary_tb_modified IS NULL) AS no_amount, x.is_late FROM $from WHERE $where ORDER BY x.order_date DESC, x.sale_ord_id DESC LIMIT 5000 ", $params); MagoSalesStats::jsonOut([ 'success' => true, 'metric' => $metric, 'kpi' => $kpi, 'monthly' => $monthly, 'top_customers' => $topCustomers, 'by_area' => $byArea, 'by_salesperson' => $bySalesperson, 'due_buckets' => $dueBuckets, 'top_items' => $topItems, 'rows' => $rows, ]); } catch (Throwable $e) { MagoSalesStats::jsonOut(['success' => false, 'message' => $e->getMessage()]); } } if ($action === 'order') { try { $id = (int)($_GET['id'] ?? 0); $st = $pdo->prepare("SELECT x.* FROM " . MagoSalesStats::baseFrom() . " WHERE x.sale_ord_id = ?"); $st->execute([$id]); $o = $st->fetch(PDO::FETCH_ASSOC); if (!$o) MagoSalesStats::jsonOut(['success' => false, 'message' => 'Ordine non trovato']); $raw = json_decode((string)$o['raw_json'], true) ?: []; unset($o['raw_json']); $stL = $pdo->prepare(" SELECT d.line, d.item, d.description, d.uom, d.qty, d.delivered_qty, d.invoiced_qty, d.unit_value, GREATEST(d.qty - d.delivered_qty, 0) AS residual_qty, d.taxable_amount, d.delivered, d.invoiced, d.cancelled, d.notes, " . MagoSalesStats::ziboDate('d') . " AS zibo_date, " . MagoSalesStats::clientDate('d') . " AS client_date FROM mago_sale_ord_detail d WHERE d.sale_ord_id = ? ORDER BY d.position, d.line, d.sub_id "); $stL->execute([$id]); $lines = $stL->fetchAll(PDO::FETCH_ASSOC); MagoSalesStats::jsonOut([ 'success' => true, 'lines' => $lines, 'order' => $o, 'extra' => [ 'Cliente fatturazione' => $raw['InvoicingCustomer'] ?? '', 'Pagamento' => $raw['Payment'] ?? '', 'Listino' => $raw['PriceList'] ?? '', 'Trasporto' => $raw['Transport'] ?? '', 'Porto' => $raw['Port'] ?? '', 'Imballo' => $raw['Package'] ?? '', 'Magazzino' => $raw['StoragePhase1'] ?? '', 'Nostro riferimento' => $raw['OurReference'] ?? '', 'Vostro riferimento' => $raw['YourReference'] ?? '', 'Commessa' => $raw['Job'] ?? '', 'Stampato' => !empty($raw['Printed']) ? 'Sì' : 'No', 'Fatturato' => !empty($raw['Invoiced']) ? 'Sì' : 'No', 'Consegna tassativa' => MagoClient::toMysqlDate($raw['CompulsoryDeliveryDate'] ?? null), 'Creato in Mago' => MagoClient::toMysqlDate($raw['TbCreated'] ?? null, true), 'Modificato in Mago' => MagoClient::toMysqlDate($raw['TbModified'] ?? null, true), ], ]); } catch (Throwable $e) { MagoSalesStats::jsonOut(['success' => false, 'message' => $e->getMessage()]); } } ?> Statistiche Ordini Cliente - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
Statistiche Ordini Cliente
Dati Mago4
Filtri
Ordini
—
 
Valore ordinato
—
 
Da evadere
—
 
Aperti
—
 
In ritardo
—
data ZIBO superata
 
Evasi
—
 
Da fatturare
—
evasi non fatturati
Clienti attivi
—
 
Grafici in
>
Elenco ordini
Clicca una riga per il dettaglio
N° ordine Data Cliente Rif. cliente Consegna (ZIBO / cliente) Imponibile Stato Fatt. Agente Area
Andamento mensile ordini
Clicca un mese per filtrarlo
Ripartizione per stato
Clicca una fetta per filtrare
Top 10 clienti per numero ordini
Clicca un cliente per vedere solo i suoi ordini
Scadenze ordini aperti
In base alla data di consegna prevista
Top 10 articoli per numero ordini
Dalle righe ordine · passa sopra una barra per quantità e valore
Ordini per area
Ordini per agente
Caricamento…