getConnection(); // --- LISTE LINEE --- $linee = $pdo->query("SELECT id, name, color FROM production_lines ORDER BY line_number")->fetchAll(); // --- LINEA PREFERITA DELL'UTENTE (NULL = Tutte) --- $preferredLineId = null; if (!empty($iduserlogin)) { $stmtPref = $pdo->prepare("SELECT preferred_line_id FROM auth_users WHERE id = :uid LIMIT 1"); $stmtPref->execute(['uid' => (int)$iduserlogin]); $val = $stmtPref->fetchColumn(); if ($val !== false && $val !== null) { $preferredLineId = (int)$val; } } // --- STATUS DINAMICI --- $statusProgrammato = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status WHERE id = 6 LIMIT 1")->fetch(); $statusProduzione = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status WHERE id = 2 LIMIT 1")->fetch(); $statusPausa = $pdo->query("SELECT id, nome, badge_color, line_color FROM production_status WHERE id = 7 LIMIT 1")->fetch(); $statusQualita = $pdo->query("SELECT id FROM production_status WHERE id = 3 LIMIT 1")->fetchColumn(); if (!$statusProgrammato || !$statusProduzione || !$statusPausa || !$statusQualita) { die("Errore: uno o più status non trovati in production_status"); } $statusProgrammatoId = $statusProgrammato['id']; $statusProduzioneId = $statusProduzione['id']; $statusPausaId = $statusPausa['id']; // --- PAUSE REASONS --- $pauseReasons = $pdo->query("SELECT id, name, is_problem FROM pause_reasons ORDER BY name")->fetchAll(); // --- DATI PER MODALE "NUOVO" (riga libera) --- $allMatrici = $pdo->query("SELECT id, nome FROM matrice ORDER BY nome")->fetchAll(); $allMescole = $pdo->query("SELECT id, nome FROM mescole WHERE is_active = 1 ORDER BY nome")->fetchAll(); // --- AVVIO / RIPRESA PRODUZIONE --- if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['start_production'])) { $id = (int)$_POST['id']; try { // 1) Leggo stato attuale $sql = "SELECT id_status FROM productiondata WHERE id = :id LIMIT 1"; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $id]); $currentStatus = $stmt->fetchColumn(); if ($currentStatus === false) { echo json_encode(['success' => false, 'msg' => 'Record produzione non trovato.']); exit; } // 2) Primo avvio: da PROGRAMMATO (6) → PRODUZIONE (2) + set start_time if ((int)$currentStatus === (int)$statusProgrammatoId) { $sqlUpdate = "UPDATE productiondata SET id_status = :status, start_time = UTC_TIMESTAMP(), end_time = NULL WHERE id = :id AND id_status = :programmato"; $stmtUpdate = $pdo->prepare($sqlUpdate); $stmtUpdate->execute([ 'status' => $statusProduzioneId, 'id' => $id, 'programmato' => $statusProgrammatoId ]); // 3) Ripresa: da PAUSA (7) → PRODUZIONE (2) **senza toccare start_time** } elseif ((int)$currentStatus === (int)$statusPausaId) { // Il motivo è obbligatorio alla ripresa: lo ricevo dal form $resumeReasonId = isset($_POST['reason_id']) ? (int)$_POST['reason_id'] : 0; // Verifico se la pausa aperta ha già un motivo $stmtChk = $pdo->prepare("SELECT reason_id FROM production_pauses WHERE id_production = :id AND end_pause IS NULL ORDER BY start_pause DESC LIMIT 1"); $stmtChk->execute(['id' => $id]); $existingReason = $stmtChk->fetchColumn(); // Se manca il motivo (sia sulla pausa che nel form) → blocco if (($existingReason === null || $existingReason === false) && $resumeReasonId <= 0) { echo json_encode(['success' => false, 'msg' => 'Seleziona il motivo della pausa prima di riprendere.']); exit; } // Chiudo l'ultima pausa aperta + scrivo il motivo se fornito ora $sqlClose = "UPDATE production_pauses SET end_pause = UTC_TIMESTAMP()" . ($resumeReasonId > 0 ? ", reason_id = :reason_id" : "") . " WHERE id_production = :id AND end_pause IS NULL ORDER BY start_pause DESC LIMIT 1"; $stmtClose = $pdo->prepare($sqlClose); $paramsClose = ['id' => $id]; if ($resumeReasonId > 0) $paramsClose['reason_id'] = $resumeReasonId; $stmtClose->execute($paramsClose); $sqlUpdate = "UPDATE productiondata SET id_status = :status, end_time = NULL WHERE id = :id AND id_status = :pausa"; $stmtUpdate = $pdo->prepare($sqlUpdate); $stmtUpdate->execute([ 'status' => $statusProduzioneId, 'id' => $id, 'pausa' => $statusPausaId ]); } else { echo json_encode([ 'success' => false, 'msg' => 'Lo stato corrente non consente avvio/ripresa (stato id: ' . $currentStatus . ')' ]); exit; } echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'msg' => $e->getMessage()]); } exit; } // --- PAUSA PRODUZIONE (crea record in production_pauses + cambia stato) --- // --- PAUSA PRODUZIONE (crea record in production_pauses + cambia stato) --- if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['pause_production'])) { $id = (int)$_POST['id']; $reasonId = isset($_POST['reason_id']) ? (int)$_POST['reason_id'] : 0; $note = trim($_POST['note'] ?? ''); try { // Motivo pausa ora OPZIONALE: se non lo sanno, salvano NULL e lo mettono al riprendi if ($reasonId > 0) { $sql = "SELECT is_problem FROM pause_reasons WHERE id = :id LIMIT 1"; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $reasonId]); $isProblem = $stmt->fetchColumn(); if ($isProblem === false) { echo json_encode(['success' => false, 'msg' => 'Motivo pausa non trovato.']); exit; } $isProblem = (int)$isProblem; } else { $reasonId = null; // nessun motivo scelto ora $isProblem = 0; } // ❌ PRIMA – sbagliato per la tua logica: // $nextStatus = $isProblem ? 8 : $statusPausaId; // 8 = Problema in produzione // ✅ ORA – in PAUSA lo stato è SEMPRE pausa (7) $nextStatus = $statusPausaId; // Aggiorno lo stato sulla produzione SOLO se è in produzione (id_status = produzione) $sql = "UPDATE productiondata SET id_status = :status, note_operatore = :note WHERE id = :id AND id_status = :produzione"; $stmtUpdate = $pdo->prepare($sql); $stmtUpdate->execute([ 'status' => $nextStatus, 'note' => $note ?: null, 'id' => $id, 'produzione' => $statusProduzioneId ]); // Se effettivamente ho cambiato lo stato, registro la pausa if ($stmtUpdate->rowCount() > 0) { $sqlPause = "INSERT INTO production_pauses (id_production, reason_id, start_pause, note) VALUES (:id_production, :reason_id, UTC_TIMESTAMP(), :note)"; $stmtPause = $pdo->prepare($sqlPause); $stmtPause->execute([ 'id_production' => $id, 'reason_id' => $reasonId, 'note' => $note ?: null ]); } echo json_encode(['success' => true]); } catch (Exception $e) { echo json_encode(['success' => false, 'msg' => $e->getMessage()]); } exit; } // --- STOP PRODUZIONE (vai a Qualità + salva tempo finale) --- if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['stop_production'])) { $id = (int)$_POST['id']; $reason = $_POST['reason'] ?? 'fine'; $note = trim($_POST['note'] ?? ''); try { $pdo->beginTransaction(); // Se sto stoppando mentre è in pausa, chiudo la pausa aperta $sqlClose = "UPDATE production_pauses SET end_pause = UTC_TIMESTAMP() WHERE id_production = :id AND end_pause IS NULL"; $pdo->prepare($sqlClose)->execute(['id' => $id]); // Leggo dati produzione $sql = "SELECT start_time, id_status FROM productiondata WHERE id = :id"; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $id]); $row = $stmt->fetch(); // Tempo di occupazione linea (LORDO, pause incluse): start -> ora $occupatoSec = 0; if ($row && $row['start_time']) { $start = new DateTime($row['start_time'], new DateTimeZone('UTC')); $now = new DateTime('now', new DateTimeZone('UTC')); $occupatoSec = max(0, $now->getTimestamp() - $start->getTimestamp()); } // Somma totale delle pause di questa produzione (in secondi) $sqlPause = "SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, start_pause, end_pause)), 0) FROM production_pauses WHERE id_production = :id AND end_pause IS NOT NULL"; $stmtP = $pdo->prepare($sqlPause); $stmtP->execute(['id' => $id]); $pauseSec = (int)$stmtP->fetchColumn(); // Stato di destinazione $nextStatus = ($reason === 'problema') ? 8 : $statusQualita; // 8 = Problema in produzione $sql = "UPDATE productiondata SET id_status = :status, end_time = UTC_TIMESTAMP(), note_operatore = :note, tempo_totale_produzione = :occupato, seconds_pause = :pause, seconds_produzione = :netto WHERE id = :id AND id_status IN ($statusProduzioneId, $statusPausaId)"; $stmt = $pdo->prepare($sql); $stmt->execute([ 'status' => $nextStatus, 'note' => $note ?: null, 'occupato' => $occupatoSec, 'pause' => $pauseSec, 'netto' => max(0, $occupatoSec - $pauseSec), 'id' => $id, ]); $pdo->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); echo json_encode(['success' => false, 'msg' => $e->getMessage()]); } exit; } // --- SALVATAGGIO DATI FINE PRODUZIONE --- if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data'])) { $id = (int)$_POST['id']; $kgprod = (float)$_POST['kgprod']; $mtprod = (float)$_POST['mtprod']; $scarto = (float)$_POST['scarto']; $note = trim($_POST['note'] ?? ''); $reason = $_POST['reason'] ?? 'fine'; try { $pdo->beginTransaction(); // Chiudo eventuali pause ancora aperte $pdo->prepare("UPDATE production_pauses SET end_pause = UTC_TIMESTAMP() WHERE id_production = :id AND end_pause IS NULL") ->execute(['id' => $id]); // Leggo start_time per calcolare l'occupazione $stmt = $pdo->prepare("SELECT start_time FROM productiondata WHERE id = :id"); $stmt->execute(['id' => $id]); $startTime = $stmt->fetchColumn(); // Occupazione LORDA (start -> ora), pause incluse $occupatoSec = 0; if ($startTime) { $start = new DateTime($startTime, new DateTimeZone('UTC')); $now = new DateTime('now', new DateTimeZone('UTC')); $occupatoSec = max(0, $now->getTimestamp() - $start->getTimestamp()); } // Somma pause chiuse $stmtP = $pdo->prepare("SELECT COALESCE(SUM(TIMESTAMPDIFF(SECOND, start_pause, end_pause)), 0) FROM production_pauses WHERE id_production = :id AND end_pause IS NOT NULL"); $stmtP->execute(['id' => $id]); $pauseSec = (int)$stmtP->fetchColumn(); $seconds = $occupatoSec; // hourprod = tempo di occupazione (come da tua logica) // Calcolo scarto % $totaleKg = $kgprod + $scarto; $scartoPerc = 0.00; if ($totaleKg > 0) { $scartoPerc = round(($scarto / $totaleKg) * 100, 2); } // "Compila dopo" → stato 11 (dati da inserire), altrimenti flusso normale $compilaDopo = !empty($_POST['compila_dopo']); if ($compilaDopo) { $nextStatus = 11; // Da compilare dati } else { $nextStatus = ($reason === 'problema') ? 8 : $statusQualita; } // Update finale $sql = "UPDATE productiondata SET kgprod = :kgprod, mtprod = :mtprod, scarto = :scarto, scarto_perc = :scarto_perc, note_operatore = :note, hourprod = SEC_TO_TIME(:seconds), tempo_totale_produzione = :occupato, seconds_pause = :pause, seconds_produzione = :netto, id_status = :status, end_time = COALESCE(end_time, UTC_TIMESTAMP()) WHERE id = :id"; $stmt = $pdo->prepare($sql); $stmt->execute([ 'kgprod' => $kgprod, 'mtprod' => $mtprod, 'scarto' => $scarto, 'scarto_perc' => $scartoPerc, 'note' => $note, 'seconds' => $seconds, 'occupato' => $occupatoSec, 'pause' => $pauseSec, 'netto' => max(0, $occupatoSec - $pauseSec), 'status' => $nextStatus, 'id' => $id ]); $pdo->commit(); echo json_encode(['success' => true]); } catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); echo json_encode(['success' => false, 'msg' => $e->getMessage()]); } exit; } // --- CREAZIONE RIGA LIBERA (Nuovo) + avvio immediato --- if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['create_quick'])) { $idMatrice = (int)($_POST['id_matrice'] ?? 0); $idLinea = (int)($_POST['id_linea'] ?? 0); $mescRaw = $_POST['id_mescole'] ?? ''; $mescIds = $mescRaw !== '' ? array_values(array_unique(array_filter(array_map('intval', explode(',', $mescRaw))))) : []; $descr = trim($_POST['descrizione'] ?? ''); try { if ($idMatrice <= 0) { echo json_encode(['success' => false, 'msg' => 'Seleziona una matrice.']); exit; } if ($idLinea <= 0) { echo json_encode(['success' => false, 'msg' => 'Seleziona una linea.']); exit; } $avvia = !empty($_POST['avvia']); $pdo->beginTransaction(); // Priorità: in fondo alla coda della linea (max+1) $stmtPri = $pdo->prepare("SELECT COALESCE(MAX(priority),0)+1 FROM productiondata WHERE id_linea = :linea AND id_status = :programmato"); $stmtPri->execute(['linea' => $idLinea, 'programmato' => $statusProgrammatoId]); $nextPriority = (int)$stmtPri->fetchColumn(); // Se "avvia": nasce in PRODUZIONE (2) con start_time. // Altrimenti: PROGRAMMATO (6), pronto per essere avviato dopo. $statusIns = $avvia ? $statusProduzioneId : $statusProgrammatoId; $startExpr = $avvia ? 'UTC_TIMESTAMP()' : 'NULL'; $sqlIns = "INSERT INTO productiondata (Data, idmatrice, id_linea, note_extra, priority, id_status, start_time, end_time, fermomacchina, hour, hourprod, kgprod, mtprod, scarto) VALUES (CURDATE(), :idmatrice, :id_linea, :note_extra, :priority, :status, $startExpr, NULL, 0, '00:00:00', '00:00:00', 0, 0, 0)"; $stmtIns = $pdo->prepare($sqlIns); $stmtIns->execute([ 'idmatrice' => $idMatrice, 'id_linea' => $idLinea, 'note_extra' => $descr ?: null, 'priority' => $nextPriority, 'status' => $statusIns, ]); $newId = (int)$pdo->lastInsertId(); if (!empty($mescIds)) { $stmtM = $pdo->prepare("INSERT INTO productiondata_mescole (id_productiondata, id_mescola) VALUES (:pid, :mid)"); foreach ($mescIds as $mid) { if ($mid > 0) { $stmtM->execute(['pid' => $newId, 'mid' => $mid]); } } } $pdo->commit(); echo json_encode(['success' => true, 'id' => $newId]); } catch (Exception $e) { if ($pdo->inTransaction()) $pdo->rollBack(); echo json_encode(['success' => false, 'msg' => $e->getMessage()]); } exit; } // --- AJAX: la pausa aperta di questa produzione ha già un motivo? --- if (!empty($_GET['check_pause_reason'])) { header('Content-Type: application/json; charset=utf-8'); $pid = (int)($_GET['id'] ?? 0); if ($pid <= 0) { echo json_encode(['success' => false]); exit; } $stmt = $pdo->prepare("SELECT reason_id FROM production_pauses WHERE id_production = :id AND end_pause IS NULL ORDER BY start_pause DESC LIMIT 1"); $stmt->execute(['id' => $pid]); $rid = $stmt->fetchColumn(); // has_reason = true se esiste una pausa aperta con reason_id non NULL echo json_encode([ 'success' => true, 'has_reason' => ($rid !== false && $rid !== null) ]); exit; } // --- AJAX: mescole collegate a una matrice --- if (!empty($_GET['mescole_by_matrice'])) { header('Content-Type: application/json; charset=utf-8'); $idMatrice = (int)($_GET['id_matrice'] ?? 0); if ($idMatrice <= 0) { echo json_encode(['success' => false, 'rows' => []]); exit; } $stmt = $pdo->prepare(" SELECT me.id, me.nome FROM matrici_mescole mm JOIN mescole me ON me.id = mm.idmescola WHERE mm.idmatrice = :idm AND me.is_active = 1 ORDER BY me.nome "); $stmt->execute(['idm' => $idMatrice]); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); // Linee associate a questa matrice $stmtL = $pdo->prepare(" SELECT ml.idlinea AS id FROM matrici_lines ml WHERE ml.idmatrice = :idm "); $stmtL->execute(['idm' => $idMatrice]); $lineIds = array_map('intval', $stmtL->fetchAll(PDO::FETCH_COLUMN)); echo json_encode(['success' => true, 'rows' => $rows, 'lines' => $lineIds]); exit; } // --- AJAX PRINCIPALE: carica i record della data selezionata --- if (!empty($_GET['ajax'])) { $lineRaw = $_GET['line'] ?? ''; $lineArray = $lineRaw !== '' ? explode(',', $lineRaw) : []; // Linee già occupate da una produzione attiva (in produzione=2 o in pausa=7). // Usato per bloccare l'avvio di una seconda produzione sulla stessa linea. $lineeOccupate = []; $stmtOcc = $pdo->query("SELECT DISTINCT id_linea FROM productiondata WHERE id_status IN (2, 7)"); foreach ($stmtOcc->fetchAll(PDO::FETCH_COLUMN) as $lid) { $lineeOccupate[(int)$lid] = true; } // --- RECORD IN PRODUZIONE (2,7,8) // --- RECORD IN PRODUZIONE (2,7,8) $sql = "SELECT p.*, m.id AS matrice_id, m.nome AS matrice, m.photo AS matrice_photo, ( SELECT GROUP_CONCAT(mes.nome ORDER BY mes.nome SEPARATOR ' | ') FROM productiondata_mescole pm JOIN mescole mes ON mes.id = pm.id_mescola WHERE pm.id_productiondata = p.id ) AS mescole_list, l.name AS linea, c.nome AS cliente, s.nome AS status_nome, s.badge_color, s.line_color, p.tempo_totale_produzione, -- 🔧 ADD: tools list & count ( 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 ) AS tools_list, ( SELECT COUNT(*) FROM productiondata_tools pt WHERE pt.productiondata_id = p.id ) AS tools_count FROM productiondata p LEFT JOIN matrice m ON p.idmatrice = m.id LEFT JOIN production_lines l ON p.id_linea = l.id LEFT JOIN clients c ON p.id_cliente = c.id LEFT JOIN production_status s ON p.id_status = s.id WHERE p.id_status IN (2, 7, 8) " . (!empty($lineArray) ? " AND p.id_linea IN (" . implode(',', array_map('intval', $lineArray)) . ")" : "") . " ORDER BY l.line_number, p.Data"; $stmt = $pdo->prepare($sql); $stmt->execute(); $records = $stmt->fetchAll(); foreach ($records as $r) { // --- PARAMETRI LINEA (slot dinamici) $sqlParams = "SELECT position, short_label, icon FROM production_line_params WHERE line_id = :line ORDER BY position ASC"; $stmtP = $pdo->prepare($sqlParams); $stmtP->execute(['line' => $r['id_linea']]); $paramsLinea = $stmtP->fetchAll(); $r['param_slots'] = $paramsLinea; include __DIR__ . "/render_production_card.php"; } // --- RECORD IN STATO 6 ORDINATI PER PRIORITY $sql2 = "SELECT p.*, m.id AS matrice_id, m.nome AS matrice, m.photo AS matrice_photo, ( SELECT GROUP_CONCAT(mes.nome ORDER BY mes.nome SEPARATOR ' | ') FROM productiondata_mescole pm JOIN mescole mes ON mes.id = pm.id_mescola WHERE pm.id_productiondata = p.id ) AS mescole_list, l.name AS linea, c.nome AS cliente, s.nome AS status_nome, s.badge_color, s.line_color, p.tempo_totale_produzione, -- 🔧 ADD: tools list & count ( 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 ) AS tools_list, ( SELECT COUNT(*) FROM productiondata_tools pt WHERE pt.productiondata_id = p.id ) AS tools_count FROM productiondata p LEFT JOIN matrice m ON p.idmatrice = m.id LEFT JOIN production_lines l ON p.id_linea = l.id LEFT JOIN clients c ON p.id_cliente = c.id LEFT JOIN production_status s ON p.id_status = s.id WHERE p.id_status = 6 " . (!empty($lineArray) ? " AND p.id_linea IN (" . implode(',', array_map('intval', $lineArray)) . ")" : "") . " ORDER BY p.priority ASC "; $stmt2 = $pdo->prepare($sql2); $stmt2->execute(); $recordsPriority = $stmt2->fetchAll(); if (!empty($recordsPriority)) { echo '