getConnection(); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); const ST_PROGRAMMATO = 6; const ST_PRODUZIONE = 2; const ST_PAUSA = 7; const ST_QUALITA = 3; const ST_PROBLEMA = 8; const ST_DA_COMPILARE = 11; function stOut(array $a) { while (ob_get_level()) ob_end_clean(); header('Content-Type: application/json; charset=utf-8'); echo json_encode($a); exit; } /** I tempi in tabella sono salvati in UTC (UTC_TIMESTAMP): li passo al browser in millisecondi. */ function stMs($dt) { if (!$dt) return null; return (new DateTime($dt, new DateTimeZone('UTC')))->getTimestamp() * 1000; } function stHas(PDO $pdo, $t) { static $c = []; if (!isset($c[$t])) { try { $pdo->query("SELECT 1 FROM `$t` LIMIT 0"); $c[$t] = true; } catch (Throwable $e) { $c[$t] = false; } } return $c[$t]; } /** Lavori con tutti i dati utili alla postazione. */ function stJobs(PDO $pdo, $where, array $params = []) { $tools = stHas($pdo, 'productiondata_tools') && stHas($pdo, 'inv_equipment') ? "(SELECT GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR '||') FROM productiondata_tools pt JOIN inv_equipment t ON t.id = pt.tool_id WHERE pt.productiondata_id = p.id)" : 'NULL'; $photos = stHas($pdo, 'production_photos') ? "(SELECT GROUP_CONCAT(CONCAT(ph.photo_type, ':', COALESCE(ph.param_position, 0))) FROM production_photos ph WHERE ph.production_id = p.id)" : 'NULL'; $st = $pdo->prepare(" SELECT p.id, p.id_status, p.id_linea, p.priority, p.conferma_ordine, p.idmatrice, m.nome AS matrice, m.photo, c.nome AS cliente, p.data_zibo, p.data_cliente, p.metri, p.kg_sp, p.kg_p, p.ore_previste, p.note_extra, p.note_operatore, p.start_time, s.nome AS status_nome, COALESCE(s.badge_color, '#64748b') AS status_color, (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, (SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)), 0) FROM production_pauses pp WHERE pp.id_production = p.id AND pp.end_pause IS NOT NULL) AS pause_sec, (SELECT pp.start_pause FROM production_pauses pp WHERE pp.id_production = p.id AND pp.end_pause IS NULL ORDER BY pp.start_pause DESC LIMIT 1) AS open_pause, (SELECT pr.name FROM production_pauses pp JOIN pause_reasons pr ON pr.id = pp.reason_id WHERE pp.id_production = p.id AND pp.end_pause IS NULL ORDER BY pp.start_pause DESC LIMIT 1) AS open_reason, $tools AS tools, $photos AS photos FROM productiondata p LEFT JOIN matrice m ON m.id = p.idmatrice LEFT JOIN clients c ON c.id = p.id_cliente LEFT JOIN production_status s ON s.id = p.id_status WHERE $where ORDER BY p.priority IS NULL, p.priority, p.id"); $st->execute($params); $out = []; foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $j) { $ph = ['lotto_mescola' => 0, 'parametri_macchina' => 0, 'problema' => 0, 'slots' => []]; foreach (array_filter(explode(',', (string)$j['photos'])) as $x) { [$type, $pos] = array_pad(explode(':', $x), 2, 0); if (isset($ph[$type])) $ph[$type]++; if ($type === 'parametri_macchina' && $pos) $ph['slots'][(int)$pos] = true; } $out[] = [ 'id' => (int)$j['id'], 'status' => (int)$j['id_status'], 'status_nome' => $j['status_nome'], 'status_color' => $j['status_color'], 'line' => (int)$j['id_linea'], 'matrice' => $j['matrice'], 'matrice_id' => (int)$j['idmatrice'], 'photo' => $j['photo'], 'cliente' => $j['cliente'], 'ordine' => $j['conferma_ordine'], 'data_zibo' => $j['data_zibo'], 'data_cliente' => $j['data_cliente'], 'metri' => (float)$j['metri'], 'kg_sp' => (float)$j['kg_sp'], 'kg_p' => (float)$j['kg_p'], 'ore' => (float)$j['ore_previste'], 'note' => (string)$j['note_extra'], 'note_operatore' => (string)$j['note_operatore'], 'mescole' => $j['mescole'] ? explode('||', $j['mescole']) : [], 'tools' => $j['tools'] ? explode('||', $j['tools']) : [], 'start_ms' => stMs($j['start_time']), 'pause_sec' => (int)$j['pause_sec'], 'open_pause_ms' => stMs($j['open_pause']), 'open_reason' => $j['open_reason'], 'photos' => $ph, ]; } return $out; } /** Secondi di occupazione linea (lordo) e di pausa chiusa, come la pagina di produzione. */ function stTimes(PDO $pdo, $id) { $st = $pdo->prepare("SELECT start_time FROM productiondata WHERE id = ?"); $st->execute([$id]); $start = $st->fetchColumn(); $occ = 0; if ($start) { $occ = max(0, time() - (new DateTime($start, new DateTimeZone('UTC')))->getTimestamp()); } $st = $pdo->prepare("SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, start_pause, end_pause)), 0) FROM production_pauses WHERE id_production = ? AND end_pause IS NOT NULL"); $st->execute([$id]); $pause = (int)$st->fetchColumn(); return [$occ, $pause]; } /* ===================== AJAX ===================== */ $a = $_GET['a'] ?? $_POST['a'] ?? ''; if ($a !== '') { try { // ---------- stato della postazione ---------- if ($a === 'state') { $lines = $pdo->query("SELECT id, name, COALESCE(NULLIF(color, ''), '#64748b') AS color FROM production_lines ORDER BY line_number, id")->fetchAll(PDO::FETCH_ASSOC); foreach ($lines as &$l) $l['id'] = (int)$l['id']; unset($l); $line = (int)($_GET['line'] ?? 0); $res = ['success' => true, 'now_ms' => time() * 1000, 'lines' => $lines]; // panoramica: in macchina e coda di ogni linea $res['overview'] = stJobs($pdo, 'p.id_status IN (' . ST_PRODUZIONE . ',' . ST_PAUSA . ',' . ST_PROBLEMA . ')'); $cnt = []; foreach ($pdo->query("SELECT id_linea, COUNT(*) n, SUM(ore_previste) h FROM productiondata WHERE id_status = " . ST_PROGRAMMATO . " GROUP BY id_linea") as $r) { $cnt[(int)$r['id_linea']] = ['n' => (int)$r['n'], 'h' => (float)$r['h']]; } $res['queue_count'] = $cnt; // primo lavoro in coda per ogni linea (per la panoramica) foreach ( $pdo->query("SELECT p.id_linea, m.nome FROM productiondata p JOIN matrice m ON m.id = p.idmatrice WHERE p.id_status = " . ST_PROGRAMMATO . " ORDER BY p.priority IS NULL, p.priority, p.id") as $r ) { foreach ($res['lines'] as &$l) if ($l['id'] === (int)$r['id_linea'] && !isset($l['next'])) $l['next'] = $r['nome']; unset($l); } if ($line) { $res['queue'] = stJobs($pdo, 'p.id_status = ' . ST_PROGRAMMATO . ' AND p.id_linea = ?', [$line]); $st = $pdo->prepare("SELECT position, short_label, label, icon FROM production_line_params WHERE line_id = ? ORDER BY position"); $st->execute([$line]); $res['params'] = $st->fetchAll(PDO::FETCH_ASSOC); } $res['reasons'] = $pdo->query("SELECT id, name, is_problem FROM pause_reasons ORDER BY is_problem, name")->fetchAll(PDO::FETCH_ASSOC); stOut($res); } // ---------- foto di un lavoro ---------- if ($a === 'photos') { $st = $pdo->prepare("SELECT id, photo_type, param_position, filename, created_at FROM production_photos WHERE production_id = ? ORDER BY created_at DESC, id DESC"); $st->execute([(int)($_GET['id'] ?? 0)]); $rows = $st->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as &$r) { $r['id'] = (int)$r['id']; $r['param_position'] = $r['param_position'] === null ? null : (int)$r['param_position']; } unset($r); stOut(['success' => true, 'rows' => $rows]); } // ---------- avvio (da programmato) ---------- if ($a === 'start') { $id = (int)($_POST['id'] ?? 0); $pdo->beginTransaction(); $st = $pdo->prepare("SELECT id_status, id_linea FROM productiondata WHERE id = ? FOR UPDATE"); $st->execute([$id]); $r = $st->fetch(PDO::FETCH_ASSOC); if (!$r) throw new RuntimeException('Lavoro non trovato'); if ((int)$r['id_status'] !== ST_PROGRAMMATO) throw new RuntimeException('Il lavoro non è più tra i programmati'); $st = $pdo->prepare("SELECT COUNT(*) FROM productiondata WHERE id_linea = ? AND id_status IN (" . ST_PRODUZIONE . "," . ST_PAUSA . ")"); $st->execute([$r['id_linea']]); if ($st->fetchColumn()) throw new RuntimeException('La linea è già occupata: termina prima il lavoro in macchina'); $pdo->prepare("UPDATE productiondata SET id_status = ?, start_time = UTC_TIMESTAMP(), end_time = NULL WHERE id = ? AND id_status = ?") ->execute([ST_PRODUZIONE, $id, ST_PROGRAMMATO]); $pdo->commit(); stOut(['success' => true]); } // ---------- pausa ---------- if ($a === 'pause') { $id = (int)($_POST['id'] ?? 0); $reason = (int)($_POST['reason_id'] ?? 0) ?: null; $note = trim((string)($_POST['note'] ?? '')); if ($reason) { $st = $pdo->prepare("SELECT is_problem FROM pause_reasons WHERE id = ?"); $st->execute([$reason]); $isProblem = $st->fetchColumn(); if ($isProblem === false) throw new RuntimeException('Motivo non trovato'); if ((int)$isProblem && $note === '') throw new RuntimeException('Per un problema tecnico scrivi una nota'); } $pdo->beginTransaction(); $st = $pdo->prepare("UPDATE productiondata SET id_status = ?, note_operatore = ? WHERE id = ? AND id_status = ?"); $st->execute([ST_PAUSA, $note ?: null, $id, ST_PRODUZIONE]); if ($st->rowCount()) { $pdo->prepare("INSERT INTO production_pauses (id_production, reason_id, start_pause, note) VALUES (?, ?, UTC_TIMESTAMP(), ?)") ->execute([$id, $reason, $note ?: null]); } $pdo->commit(); stOut(['success' => true]); } // ---------- ripresa ---------- if ($a === 'resume') { $id = (int)($_POST['id'] ?? 0); $reason = (int)($_POST['reason_id'] ?? 0); $pdo->beginTransaction(); $st = $pdo->prepare("SELECT reason_id FROM production_pauses WHERE id_production = ? AND end_pause IS NULL ORDER BY start_pause DESC LIMIT 1"); $st->execute([$id]); $has = $st->fetchColumn(); if (($has === false || $has === null) && $reason <= 0) throw new RuntimeException('Scegli il motivo della pausa per riprendere'); $pdo->prepare("UPDATE production_pauses SET end_pause = UTC_TIMESTAMP()" . ($reason > 0 ? ", reason_id = ?" : "") . " WHERE id_production = ? AND end_pause IS NULL ORDER BY start_pause DESC LIMIT 1") ->execute($reason > 0 ? [$reason, $id] : [$id]); $st = $pdo->prepare("UPDATE productiondata SET id_status = ?, end_time = NULL WHERE id = ? AND id_status = ?"); $st->execute([ST_PRODUZIONE, $id, ST_PAUSA]); if (!$st->rowCount()) throw new RuntimeException('Il lavoro non è in pausa'); $pdo->commit(); stOut(['success' => true]); } // ---------- fine produzione (dati finali o "compila dopo") ---------- if ($a === 'finish') { $id = (int)($_POST['id'] ?? 0); $reason = ($_POST['reason'] ?? 'fine') === 'problema' ? 'problema' : 'fine'; $later = !empty($_POST['later']); $kg = max(0, (float)str_replace(',', '.', (string)($_POST['kgprod'] ?? 0))); $mt = max(0, (float)str_replace(',', '.', (string)($_POST['mtprod'] ?? 0))); $sc = max(0, (float)str_replace(',', '.', (string)($_POST['scarto'] ?? 0))); $note = trim((string)($_POST['note'] ?? '')); if ($reason === 'problema' && $note === '') throw new RuntimeException('Descrivi il problema nella nota'); if (!$later && $kg <= 0 && $mt <= 0) throw new RuntimeException('Inserisci i kg o i metri prodotti, oppure "Compila dopo"'); $pdo->beginTransaction(); $pdo->prepare("UPDATE production_pauses SET end_pause = UTC_TIMESTAMP() WHERE id_production = ? AND end_pause IS NULL")->execute([$id]); [$occ, $pause] = stTimes($pdo, $id); $status = $later ? ST_DA_COMPILARE : ($reason === 'problema' ? ST_PROBLEMA : ST_QUALITA); $st = $pdo->prepare(" UPDATE productiondata SET kgprod = :kg, mtprod = :mt, scarto = :sc, scarto_perc = :scp, note_operatore = :note, hourprod = SEC_TO_TIME(:occ), tempo_totale_produzione = :occ2, seconds_pause = :pause, seconds_produzione = :netto, id_status = :status, end_time = COALESCE(end_time, UTC_TIMESTAMP()) WHERE id = :id AND id_status IN (" . ST_PRODUZIONE . "," . ST_PAUSA . ")"); $st->execute([ 'kg' => $later ? 0 : $kg, 'mt' => $later ? 0 : $mt, 'sc' => $later ? 0 : $sc, 'scp' => (!$later && $kg + $sc > 0) ? round($sc / ($kg + $sc) * 100, 2) : 0, 'note' => $note, 'occ' => $occ, 'occ2' => $occ, 'pause' => $pause, 'netto' => max(0, $occ - $pause), 'status' => $status, 'id' => $id, ]); if (!$st->rowCount()) throw new RuntimeException('Il lavoro non è più in macchina'); $pdo->commit(); stOut(['success' => true, 'status' => $status]); } // ---------- nuovo lavoro rapido sulla linea ---------- if ($a === 'matrici') { stOut([ 'success' => true, 'rows' => $pdo->query("SELECT id, nome FROM matrice WHERE TRIM(nome) <> '' ORDER BY nome")->fetchAll(PDO::FETCH_ASSOC), 'mescole' => $pdo->query("SELECT id, nome FROM mescole WHERE is_active = 1 ORDER BY nome")->fetchAll(PDO::FETCH_ASSOC) ]); } if ($a === 'matmescole') { $st = $pdo->prepare("SELECT me.id FROM matrici_mescole mm JOIN mescole me ON me.id = mm.idmescola WHERE mm.idmatrice = ? AND me.is_active = 1"); $st->execute([(int)($_GET['id'] ?? 0)]); stOut(['success' => true, 'ids' => array_map('intval', $st->fetchAll(PDO::FETCH_COLUMN))]); } if ($a === 'quick') { $idm = (int)($_POST['idmatrice'] ?? 0); $line = (int)($_POST['line'] ?? 0); $avvia = !empty($_POST['avvia']); if (!$idm) throw new RuntimeException('Scegli la matrice'); if (!$line) throw new RuntimeException('Scegli la linea'); $pdo->beginTransaction(); if ($avvia) { $st = $pdo->prepare("SELECT COUNT(*) FROM productiondata WHERE id_linea = ? AND id_status IN (" . ST_PRODUZIONE . "," . ST_PAUSA . ")"); $st->execute([$line]); if ($st->fetchColumn()) throw new RuntimeException('La linea è occupata: crealo in coda'); } $prio = (int)$pdo->query("SELECT COALESCE(MAX(priority), 0) + 1 FROM productiondata WHERE id_status = " . ST_PROGRAMMATO)->fetchColumn(); $pdo->prepare("INSERT INTO productiondata (Data, idmatrice, id_linea, note_extra, priority, id_status, start_time, end_time, fermomacchina, `hour`, hourprod, kgprod, mtprod, scarto) VALUES (CURDATE(), ?, ?, ?, ?, ?, " . ($avvia ? 'UTC_TIMESTAMP()' : 'NULL') . ", NULL, 0, '00:00:00', '00:00:00', 0, 0, 0)") ->execute([$idm, $line, trim((string)($_POST['descrizione'] ?? '')) ?: null, $prio, $avvia ? ST_PRODUZIONE : ST_PROGRAMMATO]); $id = (int)$pdo->lastInsertId(); $ins = $pdo->prepare("INSERT INTO productiondata_mescole (id_productiondata, id_mescola) VALUES (?, ?)"); foreach (array_unique(array_filter(array_map('intval', (array)($_POST['mescole'] ?? [])))) as $m) $ins->execute([$id, $m]); $pdo->commit(); stOut(['success' => true, 'id' => $id]); } stOut(['success' => false, 'message' => 'Azione sconosciuta']); } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); stOut(['success' => false, 'message' => $e->getMessage()]); } } // linea preferita dell'utente (come la pagina di produzione) $preferred = ''; if (!empty($iduserlogin)) { try { $st = $pdo->prepare("SELECT preferred_line_id FROM auth_users WHERE id = ?"); $st->execute([(int)$iduserlogin]); $preferred = (string)($st->fetchColumn() ?: ''); } catch (Throwable $e) { } } ?> Postazione linea - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
--:--