getConnection(); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); /* ===================== IMPOSTAZIONI ===================== */ const PB_STATUS_TODO = 1; // Da programmare const PB_STATUS_PLAN = 6; // Programmato const PB_STATUS_ACTIVE = [2, 7, 8]; // In produzione, Pausa, Problema: "in macchina" const PB_SHIFT_START = 6; // ora di inizio turno per le linee che non lavorano 24 ore const PB_WORK_DAYS = [1, 2, 3, 4, 5]; // giorni lavorativi per le stime (1 = lunedì … 7 = domenica) const PB_DEFAULT_HOURS = 24; // ore al giorno se la linea non ha max_hours_per_day if (!userCan('production.programming.view')) { if (isset($_GET['action']) || isset($_POST['action'])) { MagoSalesStats::jsonOut(['success' => 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; } function pbTableExists(PDO $pdo, $t) { try { $pdo->query("SELECT 1 FROM `$t` LIMIT 0"); return true; } catch (Throwable $e) { return false; } } function pbLines(PDO $pdo) { $out = []; foreach ($pdo->query("SELECT * FROM production_lines ORDER BY line_number, id")->fetchAll(PDO::FETCH_ASSOC) as $l) { $out[] = [ 'id' => (int)$l['id'], 'name' => $l['name'], 'color' => !empty($l['color']) ? $l['color'] : '#64748b', 'hours' => !empty($l['max_hours_per_day']) && (float)$l['max_hours_per_day'] > 0 ? min(24, (float)$l['max_hours_per_day']) : PB_DEFAULT_HOURS, 'active' => !isset($l['status']) || $l['status'] === '' || $l['status'] === 'active', ]; } return $out; } /* ===================== AJAX ===================== */ $action = $_GET['action'] ?? $_POST['action'] ?? ''; if ($action !== '') { try { // ---------- tabellone ---------- if ($action === 'board') { $hasQueue = pbTableExists($pdo, 'mago_prod_queue'); $mago = $hasQueue ? "LEFT JOIN (SELECT productiondata_id, MAX(mago_changed) AS mago_changed, MAX(mago_changes) AS mago_changes FROM mago_prod_queue WHERE productiondata_id IS NOT NULL GROUP BY productiondata_id) q ON q.productiondata_id = p.id" : ''; $magoCols = $hasQueue ? 'q.productiondata_id IS NOT NULL AS from_mago, COALESCE(q.mago_changed, 0) AS mago_changed, q.mago_changes' : '0 AS from_mago, 0 AS mago_changed, NULL AS mago_changes'; $in = implode(',', array_merge([PB_STATUS_TODO, PB_STATUS_PLAN], PB_STATUS_ACTIVE)); $jobs = $pdo->query(" SELECT p.id, p.id_status, p.id_linea, p.priority, p.conferma_ordine, p.idmatrice, m.nome AS matrice, m.photo, p.id_cliente, 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.start_time, p.Data AS created, s.nome AS status_nome, COALESCE(s.badge_color, '#6c757d') 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 GROUP_CONCAT(pm.id_mescola) FROM productiondata_mescole pm WHERE pm.id_productiondata = p.id) AS mescole_ids, (SELECT GROUP_CONCAT(ml.idlinea) FROM matrici_lines ml WHERE ml.idmatrice = p.idmatrice) AS matrice_lines, $magoCols 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 $mago WHERE p.id_status IN ($in) ORDER BY p.priority IS NULL, p.priority, p.id ")->fetchAll(PDO::FETCH_ASSOC); foreach ($jobs as &$j) { foreach (['id', 'id_status', 'id_linea', 'idmatrice', 'id_cliente', 'from_mago', 'mago_changed'] as $k) $j[$k] = $j[$k] === null ? null : (int)$j[$k]; foreach (['metri', 'kg_sp', 'kg_p', 'ore_previste'] as $k) $j[$k] = (float)$j[$k]; $j['mescole'] = $j['mescole'] ? explode('||', $j['mescole']) : []; $j['mescole_ids'] = $j['mescole_ids'] ? array_map('intval', explode(',', $j['mescole_ids'])) : []; $j['matrice_lines'] = $j['matrice_lines'] ? array_map('intval', explode(',', $j['matrice_lines'])) : []; } unset($j); $nuoviMago = $hasQueue ? (int)$pdo->query("SELECT COUNT(*) FROM mago_prod_queue WHERE queue_status = 'nuovo' OR (queue_status = 'importato' AND mago_changed = 1)")->fetchColumn() : 0; MagoSalesStats::jsonOut([ 'success' => true, 'lines' => pbLines($pdo), 'jobs' => $jobs, 'nuoviMago' => $nuoviMago, 'now' => date('Y-m-d H:i:s'), ]); } // ---------- liste per il modulo ---------- if ($action === 'options') { MagoSalesStats::jsonOut([ 'success' => true, 'matrici' => $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), 'clienti' => $pdo->query("SELECT id, nome FROM clients ORDER BY nome")->fetchAll(PDO::FETCH_ASSOC), ]); } // ---------- dati tecnici della matrice (linee, mescole, velocità) ---------- if ($action === 'matinfo') { MagoSalesStats::jsonOut(['success' => true, 'info' => MagoProduction::matriceInfo($pdo, (int)($_GET['id'] ?? 0))]); } // ---------- nuovo / modifica ---------- if ($action === 'save') { $id = (int)($_POST['id'] ?? 0); $idm = (int)($_POST['idmatrice'] ?? 0); $lin = (int)($_POST['id_linea'] ?? 0); if (!$idm) throw new RuntimeException('Scegli la matrice'); if (!$lin) throw new RuntimeException('Scegli la linea'); $status = (int)($_POST['id_status'] ?? PB_STATUS_TODO); if (!in_array($status, [PB_STATUS_TODO, PB_STATUS_PLAN], true)) $status = PB_STATUS_TODO; $date = function ($k) { $v = trim((string)($_POST[$k] ?? '')); return MagoSalesStats::isValidDate($v) ? $v : null; }; $num = function ($k, $max = 99999999) { return min($max, round((float)str_replace(',', '.', (string)($_POST[$k] ?? 0)), 2)); }; $f = [ 'conferma_ordine' => mb_substr(trim((string)($_POST['conferma_ordine'] ?? '')), 0, 50) ?: null, 'idmatrice' => $idm, 'id_linea' => $lin, 'id_cliente' => (int)($_POST['id_cliente'] ?? 0) ?: null, 'data_zibo' => $date('data_zibo'), 'data_cliente' => $date('data_cliente'), 'metri' => $num('metri'), 'kg_sp' => $num('kg_sp'), 'kg_p' => $num('kg_p'), 'ore_previste' => $num('ore_previste', 999.99), 'note_extra' => trim((string)($_POST['note_extra'] ?? '')) ?: null, ]; $pdo->beginTransaction(); if ($id) { $st = $pdo->prepare("SELECT id_status FROM productiondata WHERE id = ? FOR UPDATE"); $st->execute([$id]); $cur = $st->fetchColumn(); if ($cur === false) throw new RuntimeException('Lavoro non trovato'); $cur = (int)$cur; $set = implode(', ', array_map(fn($k) => "$k = :$k", array_keys($f))); // lo stato si cambia da qui solo tra Da programmare e Programmato; chi cambia stato va in fondo if (in_array($cur, [PB_STATUS_TODO, PB_STATUS_PLAN], true) && $status !== $cur) { $set .= ', id_status = :st, priority = :pr'; $f['st'] = $status; $f['pr'] = MagoProduction::nextPriority($pdo, $status); } $f['id'] = $id; $pdo->prepare("UPDATE productiondata SET $set WHERE id = :id")->execute($f); } else { $f['id_status'] = $status; $f['priority'] = MagoProduction::nextPriority($pdo, $status); $cols = array_keys($f); $pdo->prepare("INSERT INTO productiondata (Data, " . implode(', ', $cols) . ", fermomacchina, `hour`, hourprod, kgprod, mtprod, scarto) VALUES (CURDATE(), :" . implode(', :', $cols) . ", 0, '00:00:00', '00:00:00', 0, 0, 0)")->execute($f); $id = (int)$pdo->lastInsertId(); } $pdo->prepare("DELETE FROM productiondata_mescole WHERE id_productiondata = ?")->execute([$id]); $mescole = array_values(array_unique(array_filter(array_map('intval', (array)($_POST['mescole'] ?? []))))); $ins = $pdo->prepare("INSERT INTO productiondata_mescole (id_productiondata, id_mescola) VALUES (?, ?)"); foreach ($mescole as $m) $ins->execute([$id, $m]); $pdo->commit(); MagoSalesStats::jsonOut(['success' => true, 'id' => $id]); } // ---------- ordine dopo un trascinamento ---------- if ($action === 'order') { $p = json_decode((string)($_POST['payload'] ?? ''), true); if (!is_array($p)) throw new RuntimeException('Dati non validi'); $upd = $pdo->prepare("UPDATE productiondata SET id_status = ?, id_linea = ?, priority = ? WHERE id = ? AND id_status IN (" . PB_STATUS_TODO . "," . PB_STATUS_PLAN . ")"); $updTodo = $pdo->prepare("UPDATE productiondata SET id_status = ?, priority = ? WHERE id = ? AND id_status IN (" . PB_STATUS_TODO . "," . PB_STATUS_PLAN . ")"); $pdo->beginTransaction(); // i programmati sono numerati in un'unica sequenza, linea dopo linea (come la vista classica) $n = 0; foreach ((array)($p['lines'] ?? []) as $l) { foreach ((array)($l['ids'] ?? []) as $id) $upd->execute([PB_STATUS_PLAN, (int)$l['line'], ++$n, (int)$id]); } $n = 0; foreach ((array)($p['backlog'] ?? []) as $id) $updTodo->execute([PB_STATUS_TODO, ++$n, (int)$id]); $pdo->commit(); MagoSalesStats::jsonOut(['success' => true]); } // ---------- elimina (solo lavori non ancora avviati) ---------- if ($action === 'delete') { $id = (int)($_POST['id'] ?? 0); $pdo->beginTransaction(); $st = $pdo->prepare("SELECT id_status FROM productiondata WHERE id = ? FOR UPDATE"); $st->execute([$id]); $cur = $st->fetchColumn(); if ($cur === false) throw new RuntimeException('Lavoro non trovato'); if (!in_array((int)$cur, [PB_STATUS_TODO, PB_STATUS_PLAN], true)) throw new RuntimeException('Si possono eliminare solo i lavori non ancora avviati'); if (pbTableExists($pdo, 'mago_prod_queue')) { // la riga Mago torna tra i "Nuovi da Mago" $pdo->prepare("UPDATE mago_prod_queue SET queue_status = 'nuovo', productiondata_id = NULL, imported_at = NULL, mago_changed = 0, mago_changes = '', updated_at = NOW() WHERE productiondata_id = ?")->execute([$id]); } $pdo->prepare("DELETE FROM productiondata_mescole WHERE id_productiondata = ?")->execute([$id]); $pdo->prepare("DELETE FROM productiondata WHERE id = ?")->execute([$id]); $pdo->commit(); MagoSalesStats::jsonOut(['success' => true]); } MagoSalesStats::jsonOut(['success' => false, 'message' => 'Azione sconosciuta']); } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); $msg = $e->getMessage(); if (strpos($msg, '1451') !== false) $msg = 'Il lavoro ha dati collegati (foto, registrazioni): eliminalo dalla vista classica'; MagoSalesStats::jsonOut(['success' => false, 'message' => $msg]); } } ?> Programmazione - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?>
Programmazione produzione
Trascina i lavori nelle linee: l'ordine nella colonna è l'ordine di produzione
fine stimata oltre la data Zibo margine sotto 1 giorno
Caricamento…