diff --git a/public/userarea/class/MagoProduction.php b/public/userarea/class/MagoProduction.php index a837d93e..7a386899 100644 --- a/public/userarea/class/MagoProduction.php +++ b/public/userarea/class/MagoProduction.php @@ -332,6 +332,21 @@ class MagoProduction return rtrim(rtrim(number_format((float)$v, 3, ',', '.'), '0'), ','); } + /** Riconosce la matrice di un valore Matrice/Misura (indice caricato una volta per richiesta). [idmatrice|null, match_status, match_info] */ + public static function lookup(PDO $pdo, $raw) + { + static $idx = null; + if ($idx === null) { + try { + $idx = self::matriceIndex($pdo); + } catch (Throwable $e) { + $idx = ['exact' => [], 'base' => [], 'num' => [], 'map' => []]; + } + } + [$id, $ms, $mi] = self::resolve($idx, $raw); + return [$id, $ms, $mi]; + } + private static function dt($v) { return $v ? date('d/m/Y', strtotime($v)) : '—'; diff --git a/public/userarea/sales_orders_stats.php b/public/userarea/sales_orders_stats.php index fb1ca7a9..a5e0648a 100644 --- a/public/userarea/sales_orders_stats.php +++ b/public/userarea/sales_orders_stats.php @@ -226,19 +226,98 @@ if ($action === 'order') { $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 - "); + // righe + (se presenti) matrice, coda "Nuovi da Mago" e lavoro di produzione collegato + $has = function ($t) use ($pdo) { + $q = $pdo->prepare("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"); + $q->execute([$t]); + return (bool)$q->fetchColumn(); + }; + $colQ = $pdo->prepare("SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'mago_sale_ord_detail' AND COLUMN_NAME = 'matrice_misura'"); + $colQ->execute(); + $hasMM = (bool)$colQ->fetchColumn(); + $hasQ = $hasMM && $has('mago_prod_queue') && $has('productiondata'); + + $sel = "d.sub_id, 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"; + $join = ''; + if ($hasMM) $sel .= ", d.matrice_misura, d.cod_art_cli"; + if ($hasQ) { + $sel .= ", q.queue_status, q.match_status, q.match_info, q.discard_reason, q.mago_changed, q.mago_changes, + q.productiondata_id, COALESCE(p.idmatrice, q.idmatrice) AS idmatrice, + m.nome AS matrice, m.photo AS matrice_photo, + p.id_status AS prod_status, s.nome AS prod_status_name, COALESCE(s.badge_color, '#6c757d') AS prod_status_color, + p.id_linea, l.name AS linea, l.color AS linea_color, p.metri AS prod_metri, p.data_produzione, + (SELECT GROUP_CONCAT(ms.nome ORDER BY ms.nome SEPARATOR ', ') FROM productiondata_mescole pm + JOIN mescole ms ON ms.id = pm.id_mescola WHERE pm.id_productiondata = p.id) AS mescole"; + $join = "LEFT JOIN mago_prod_queue q ON q.sale_ord_id = d.sale_ord_id AND q.sub_id = d.sub_id + LEFT JOIN productiondata p ON p.id = q.productiondata_id + LEFT JOIN matrice m ON m.id = COALESCE(p.idmatrice, q.idmatrice) + LEFT JOIN production_status s ON s.id = p.id_status + LEFT JOIN production_lines l ON l.id = p.id_linea"; + } + $stL = $pdo->prepare("SELECT $sel FROM mago_sale_ord_detail d $join WHERE d.sale_ord_id = ? ORDER BY d.position, d.line, d.sub_id"); $stL->execute([$id]); $lines = $stL->fetchAll(PDO::FETCH_ASSOC); + // righe non (ancora) in coda: provo comunque a riconoscere la matrice dal valore Mago + if ($hasMM) { + if (!class_exists('MagoProduction') && is_file(__DIR__ . '/class/MagoProduction.php')) require_once __DIR__ . '/class/MagoProduction.php'; + foreach ($lines as &$l) { + $l['mm'] = null; + if (!class_exists('MagoProduction') || trim((string)$l['matrice_misura']) === '') continue; + $l['mm'] = MagoProduction::parse($l['matrice_misura']); + $l['mm']['tipo_label'] = MagoProduction::TIPI[$l['mm']['tipo']] ?? ''; + if (empty($l['idmatrice'])) { + [$idm, $ms, $mi] = MagoProduction::lookup($pdo, $l['matrice_misura']); + if ($idm) { + $mt = $pdo->prepare("SELECT nome, photo FROM matrice WHERE id = ?"); + $mt->execute([$idm]); + $r = $mt->fetch(PDO::FETCH_ASSOC) ?: []; + $l['idmatrice'] = $idm; + $l['matrice'] = $r['nome'] ?? ''; + $l['matrice_photo'] = $r['photo'] ?? ''; + } + if (empty($l['match_status'])) { + $l['match_status'] = $ms; + $l['match_info'] = $mi; + } + } + } + unset($l); + + // linee e mescole associate alla matrice in anagrafica (scheda di lavoro attiva, altrimenti matrici_mescole) + if ($has('matrici_lines') && class_exists('MagoProduction')) { + $lq = $pdo->prepare("SELECT l.name, l.color FROM matrici_lines ml JOIN production_lines l ON l.id = ml.idlinea WHERE ml.idmatrice = ? ORDER BY l.line_number, l.id"); + $mq = $pdo->prepare("SELECT nome FROM mescole WHERE id = ?"); + $cache = []; + foreach ($lines as &$l) { + $l['matrice_lines'] = []; + $l['matrice_mescole'] = []; + $idm = (int)($l['idmatrice'] ?? 0); + if (!$idm) continue; + if (!isset($cache[$idm])) { + $lq->execute([$idm]); + $ml = $lq->fetchAll(PDO::FETCH_ASSOC); + $mx = []; + try { + $info = MagoProduction::matriceInfo($pdo, $idm); + foreach ($info['mescole'] as $idmx) { + $mq->execute([$idmx]); + if (($n = $mq->fetchColumn()) !== false) $mx[] = $n; + } + } catch (Throwable $e) { + } + $cache[$idm] = [$ml, array_values(array_unique($mx))]; + } + [$l['matrice_lines'], $l['matrice_mescole']] = $cache[$idm]; + } + unset($l); + } + } + MagoSalesStats::jsonOut([ 'success' => true, 'lines' => $lines, @@ -877,6 +956,272 @@ if ($action === 'order') { } } + /* modale larga: quasi tutto lo schermo su PC, a tutto schermo su tablet in verticale e telefono */ + .od-dialog.modal-xl { + --bs-modal-width: min(1480px, 96vw); + } + + @media (max-width: 991.98px) { + .od-dialog .modal-content { + border-radius: 0 !important; + } + } + + @media (min-width: 768px) { + .od-dialog .ro-grid { + grid-template-columns: 150px 1fr 150px 1fr; + } + } + + @media (min-width: 1200px) { + .od-dialog .ro-grid { + grid-template-columns: 170px 1fr 170px 1fr; + } + } + + /* righe ordine */ + .ol-head, + .ol-row { + display: grid; + grid-template-columns: minmax(190px, 1.6fr) minmax(200px, 1.5fr) minmax(150px, 1fr) minmax(150px, 1fr) minmax(120px, .8fr) minmax(200px, 1.4fr); + gap: 10px 16px; + align-items: start; + } + + .ol-head { + background: #f1f5f9; + border-radius: 10px; + padding: 8px 14px; + font-size: .78rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: .03em; + color: #475569; + } + + .ol-row { + border: 1px solid #e2e8f0; + border-left: 5px solid #3b82f6; + border-radius: 12px; + padding: 12px 14px; + margin-top: 8px; + background: #fff; + font-size: .9rem; + } + + .ol-row.done { + border-left-color: #22c55e; + } + + .ol-row.cancel { + border-left-color: #94a3b8; + opacity: .7; + } + + .ol-row.late { + border-left-color: #dc3545; + background: #fff7f7; + } + + .ol-k { + display: none; + font-size: .7rem; + font-weight: 700; + text-transform: uppercase; + color: #64748b; + letter-spacing: .03em; + margin-bottom: 2px; + } + + .ol-n { + display: inline-block; + min-width: 26px; + padding: 1px 6px; + margin-right: 6px; + border-radius: 6px; + background: #e2e8f0; + color: #334155; + font-size: .75rem; + font-weight: 700; + text-align: center; + } + + .ol-item { + font-weight: 800; + color: #0f172a; + font-size: .98rem; + } + + .ol-sub { + font-size: .8rem; + color: #64748b; + white-space: normal; + } + + .ol-mat { + display: flex; + gap: 10px; + align-items: flex-start; + } + + .ol-mat img { + width: 56px; + height: 56px; + object-fit: cover; + border-radius: 10px; + border: 1px solid #e2e8f0; + background: #f8fafc; + cursor: zoom-in; + flex: 0 0 auto; + } + + .ol-mname { + font-weight: 800; + color: #0f172a; + } + + .ol-raw { + display: inline-block; + font-family: ui-monospace, Menlo, Consolas, monospace; + font-size: .78rem; + background: #f1f5f9; + border-radius: 6px; + padding: 1px 6px; + color: #334155; + } + + .ol-chip { + display: inline-flex; + align-items: center; + gap: 4px; + border-radius: 999px; + padding: 2px 9px; + font-size: .74rem; + font-weight: 700; + line-height: 1.5; + margin-top: 3px; + background: color-mix(in srgb, var(--c, #64748b) 14%, #fff); + color: var(--c, #64748b); + border: 1px solid color-mix(in srgb, var(--c, #64748b) 35%, #fff); + } + + .ol-qty b { + font-size: 1rem; + } + + .ol-bar { + height: 6px; + border-radius: 99px; + background: #e2e8f0; + overflow: hidden; + margin: 5px 0 3px; + } + + .ol-bar>span { + display: block; + height: 100%; + background: #22c55e; + border-radius: 99px; + } + + .ol-amt { + text-align: right; + } + + .ol-amt .amount { + font-weight: 800; + font-size: 1rem; + } + + .ol-lpill { + display: inline-block; + border-radius: 8px; + padding: 2px 8px; + margin: 2px 4px 0 0; + font-size: .76rem; + font-weight: 700; + background: color-mix(in srgb, var(--c, #64748b) 18%, #fff); + color: #1e293b; + border: 1px solid color-mix(in srgb, var(--c, #64748b) 45%, #fff); + } + + .ol-mrow { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 2px 0; + margin-top: 5px; + } + + .ol-mrow>span:first-child { + font-size: .68rem; + font-weight: 700; + text-transform: uppercase; + color: #64748b; + letter-spacing: .03em; + margin-right: 6px; + } + + .ol-mx { + display: inline-block; + border-radius: 8px; + padding: 2px 8px; + margin: 2px 4px 0 0; + font-size: .76rem; + font-weight: 600; + background: #f5f3ff; + color: #5b21b6; + border: 1px solid #ddd6fe; + } + + .ol-note { + grid-column: 1 / -1; + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: 8px; + padding: 6px 10px; + font-size: .84rem; + white-space: pre-wrap; + color: #713f12; + } + + .ol-warn { + grid-column: 1 / -1; + font-size: .82rem; + color: #b45309; + font-weight: 600; + } + + @media (max-width: 1199.98px) { + .ol-head { + display: none; + } + + .ol-row { + grid-template-columns: 1fr 1fr; + } + + .ol-k { + display: block; + } + + .ol-art, + .ol-mat, + .ol-prod { + grid-column: 1 / -1; + } + + .ol-amt { + text-align: left; + } + } + + @media (max-width: 575.98px) { + .ol-row { + grid-template-columns: 1fr; + } + } + .sc-link { color: #198754; margin-left: 6px; @@ -1179,7 +1524,7 @@ if ($action === 'order') {