From c394d92ef3831214c478144177b98a7a558ca887 Mon Sep 17 00:00:00 2001 From: solocla Date: Mon, 7 Sep 2026 15:02:36 +0200 Subject: [PATCH] fixed nuovo --- ...04095755_add_category_to_pause_reasons.php | 21 + ...add_daily_capacity_to_production_lines.php | 22 + ...30003_add_preferred_line_to_auth_users.php | 26 + db/seeds/PauseReasonsPlannedSeeder.php | 22 + public/userarea/edit_linea.php | 7 + public/userarea/include/navbar.php | 8 + public/userarea/linee.php | 14 +- public/userarea/pause_stats.php | 831 ++++++++++++++++ public/userarea/photos/14-18-1788520038.jpg | Bin 0 -> 23891 bytes public/userarea/production_dashboard.php | 51 +- public/userarea/production_line_view2.php | 402 +++++++- public/userarea/production_stats.php | 929 ++++++++++++++++++ public/userarea/render_production_card.php | 26 +- public/userarea/save_linea.php | 9 +- public/userarea/stats.php | 300 ++++++ public/userarea/update_linea.php | 31 +- 16 files changed, 2629 insertions(+), 70 deletions(-) create mode 100644 db/migrations/20260904095755_add_category_to_pause_reasons.php create mode 100644 db/migrations/20260904095814_add_daily_capacity_to_production_lines.php create mode 100644 db/migrations/20260907130003_add_preferred_line_to_auth_users.php create mode 100644 db/seeds/PauseReasonsPlannedSeeder.php create mode 100644 public/userarea/pause_stats.php create mode 100644 public/userarea/photos/14-18-1788520038.jpg create mode 100644 public/userarea/production_stats.php create mode 100644 public/userarea/stats.php diff --git a/db/migrations/20260904095755_add_category_to_pause_reasons.php b/db/migrations/20260904095755_add_category_to_pause_reasons.php new file mode 100644 index 00000000..82f8b1c2 --- /dev/null +++ b/db/migrations/20260904095755_add_category_to_pause_reasons.php @@ -0,0 +1,21 @@ +table('pause_reasons') + ->addColumn('category', 'enum', [ + 'values' => ['planned', 'unplanned'], + 'null' => false, + 'default' => 'unplanned', + 'after' => 'is_problem', + 'comment' => 'planned = fermo pianificato (cambio, pulizia, mensa); unplanned = guasto, mancanza materiale, qualità', + ]) + ->update(); + } +} diff --git a/db/migrations/20260904095814_add_daily_capacity_to_production_lines.php b/db/migrations/20260904095814_add_daily_capacity_to_production_lines.php new file mode 100644 index 00000000..784befdc --- /dev/null +++ b/db/migrations/20260904095814_add_daily_capacity_to_production_lines.php @@ -0,0 +1,22 @@ +table('production_lines') + ->addColumn('max_hours_per_day', 'decimal', [ + 'precision' => 4, + 'scale' => 2, + 'null' => false, + 'default' => 24.00, + 'after' => 'color', + 'comment' => 'Ore massime di disponibilita giornaliera della linea (default 24)', + ]) + ->update(); + } +} diff --git a/db/migrations/20260907130003_add_preferred_line_to_auth_users.php b/db/migrations/20260907130003_add_preferred_line_to_auth_users.php new file mode 100644 index 00000000..1e25d13d --- /dev/null +++ b/db/migrations/20260907130003_add_preferred_line_to_auth_users.php @@ -0,0 +1,26 @@ +table('auth_users') + ->addColumn('preferred_line_id', 'integer', [ + 'signed' => true, // production_lines.id è int(11) SIGNED → deve combaciare + 'limit' => 11, + 'null' => true, + 'default' => null, + 'after' => 'notify_webapp', + 'comment' => 'Linea di produzione preferita per la Line View (NULL = Tutte)', + ]) + ->addForeignKey('preferred_line_id', 'production_lines', 'id', [ + 'delete' => 'SET_NULL', + 'update' => 'NO_ACTION', + ]) + ->update(); + } +} diff --git a/db/seeds/PauseReasonsPlannedSeeder.php b/db/seeds/PauseReasonsPlannedSeeder.php new file mode 100644 index 00000000..18e3cf5d --- /dev/null +++ b/db/seeds/PauseReasonsPlannedSeeder.php @@ -0,0 +1,22 @@ + 'Cambio turno', 'is_problem' => 0, 'category' => 'planned'], + ['name' => 'Aggiunta / cambio mescola', 'is_problem' => 0, 'category' => 'planned'], + ['name' => 'Cambio produzione', 'is_problem' => 0, 'category' => 'planned'], + ['name' => 'Attrezzaggio / cambio stampo', 'is_problem' => 0, 'category' => 'planned'], + ['name' => 'Pulizia impianto', 'is_problem' => 0, 'category' => 'planned'], + ['name' => 'Manutenzione programmata', 'is_problem' => 0, 'category' => 'planned'], + ]; + + $this->table('pause_reasons')->insert($data)->saveData(); + } +} diff --git a/public/userarea/edit_linea.php b/public/userarea/edit_linea.php index cae8e68e..0a47889a 100644 --- a/public/userarea/edit_linea.php +++ b/public/userarea/edit_linea.php @@ -121,6 +121,13 @@ value=""> +
+ + + Capacità teorica giornaliera. Default 24 (h24). +
+
+ Capacità teorica giornaliera. Default 24 (h24). +
+
diff --git a/public/userarea/pause_stats.php b/public/userarea/pause_stats.php new file mode 100644 index 00000000..0e0ec27e --- /dev/null +++ b/public/userarea/pause_stats.php @@ -0,0 +1,831 @@ +getConnection(); + +$linee = $pdo->query("SELECT id, name, color FROM production_lines ORDER BY line_number")->fetchAll(); +$matrici = $pdo->query("SELECT id, nome FROM matrice ORDER BY nome")->fetchAll(); +$mescole = $pdo->query("SELECT id, nome FROM mescole ORDER BY nome")->fetchAll(); + +if (!empty($_GET['ajax'])) { + header('Content-Type: application/json'); + + $dateFrom = $_GET['date_from'] ?? ''; + $dateTo = $_GET['date_to'] ?? ''; + $lineRaw = $_GET['lines'] ?? ''; + $lineArr = $lineRaw !== '' ? array_values(array_filter(array_map('intval', explode(',', $lineRaw)))) : []; + $matId = isset($_GET['matrice_id']) && $_GET['matrice_id'] !== '' ? (int)$_GET['matrice_id'] : null; + $mescId = isset($_GET['mescola_id']) && $_GET['mescola_id'] !== '' ? (int)$_GET['mescola_id'] : null; + + $where = ["pp.end_pause IS NOT NULL"]; + $params = []; + if ($dateFrom !== '') { + $where[] = "p.Data >= :date_from"; + $params['date_from'] = $dateFrom; + } + if ($dateTo !== '') { + $where[] = "p.Data <= :date_to"; + $params['date_to'] = $dateTo; + } + if (!empty($lineArr)) { + $where[] = "p.id_linea IN (" . implode(',', $lineArr) . ")"; + } + if ($matId !== null) { + $where[] = "p.idmatrice = :mat_id"; + $params['mat_id'] = $matId; + } + if ($mescId !== null) { + $where[] = "EXISTS (SELECT 1 FROM productiondata_mescole pm + WHERE pm.id_productiondata = p.id AND pm.id_mescola = :mesc_id)"; + $params['mesc_id'] = $mescId; + } + $whereSql = implode(' AND ', $where); + + try { + $sqlKpi = "SELECT + COUNT(*) AS n_pause, COUNT(DISTINCT pp.id_production) AS n_prod_con_pause, + COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS tot_s, + COALESCE(AVG(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS media_s, + COALESCE(MAX(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS max_s, + COALESCE(SUM(CASE WHEN pr.is_problem=1 THEN 1 ELSE 0 END),0) AS n_problemi, + COALESCE(SUM(CASE WHEN pr.category='planned' THEN TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause) ELSE 0 END),0) AS planned_s, + COALESCE(SUM(CASE WHEN pr.category='unplanned' THEN TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause) ELSE 0 END),0) AS unplanned_s + FROM production_pauses pp + JOIN productiondata p ON p.id = pp.id_production + JOIN pause_reasons pr ON pr.id = pp.reason_id + WHERE $whereSql"; + $stmt = $pdo->prepare($sqlKpi); + $stmt->execute($params); + $kpi = $stmt->fetch(PDO::FETCH_ASSOC); + + $sqlMotivo = "SELECT pr.id, pr.name AS motivo, pr.is_problem, pr.category, + COUNT(pp.id) AS n_pause, + COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS durata_s, + COALESCE(AVG(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS media_s + FROM production_pauses pp + JOIN productiondata p ON p.id = pp.id_production + JOIN pause_reasons pr ON pr.id = pp.reason_id + WHERE $whereSql GROUP BY pr.id ORDER BY durata_s DESC"; + $stmt = $pdo->prepare($sqlMotivo); + $stmt->execute($params); + $perMotivo = $stmt->fetchAll(PDO::FETCH_ASSOC); + $totDurata = array_sum(array_map(fn($x) => (int)$x['durata_s'], $perMotivo)); + $cum = 0; + foreach ($perMotivo as &$m) { + $cum += (int)$m['durata_s']; + $m['cum_perc'] = ($totDurata > 0) ? round($cum / $totDurata * 100, 1) : 0; + $m['perc'] = ($totDurata > 0) ? round($m['durata_s'] / $totDurata * 100, 1) : 0; + } + unset($m); + + $sqlLinea = "SELECT l.name AS linea, l.color, + COUNT(pp.id) AS n_pause, + COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS durata_s, + COALESCE(SUM(CASE WHEN pr.is_problem=1 THEN 1 ELSE 0 END),0) AS n_problemi + FROM production_pauses pp + JOIN productiondata p ON p.id = pp.id_production + JOIN production_lines l ON l.id = p.id_linea + JOIN pause_reasons pr ON pr.id = pp.reason_id + WHERE $whereSql GROUP BY l.id ORDER BY durata_s DESC"; + $stmt = $pdo->prepare($sqlLinea); + $stmt->execute($params); + $perLinea = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $sqlGiorno = "SELECT p.Data AS giorno, + COUNT(pp.id) AS n_pause, + COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS durata_s + FROM production_pauses pp + JOIN productiondata p ON p.id = pp.id_production + JOIN pause_reasons pr ON pr.id = pp.reason_id + WHERE $whereSql GROUP BY p.Data ORDER BY p.Data"; + $stmt = $pdo->prepare($sqlGiorno); + $stmt->execute($params); + $perGiorno = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $sqlDett = "SELECT pp.id, pp.start_pause, pp.end_pause, pp.note, + TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause) AS durata_s, + pr.name AS motivo, pr.is_problem, pr.category, + l.name AS linea, m.nome AS matrice, p.id AS prod_id + FROM production_pauses pp + JOIN productiondata p ON p.id = pp.id_production + LEFT JOIN production_lines l ON l.id = p.id_linea + LEFT JOIN matrice m ON m.id = p.idmatrice + JOIN pause_reasons pr ON pr.id = pp.reason_id + WHERE $whereSql ORDER BY pp.start_pause DESC LIMIT 500"; + $stmt = $pdo->prepare($sqlDett); + $stmt->execute($params); + $dettaglio = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode([ + 'success' => true, + 'kpi' => [ + 'n_pause' => (int)$kpi['n_pause'], + 'n_prod_con_pause' => (int)$kpi['n_prod_con_pause'], + 'tot_s' => (int)$kpi['tot_s'], + 'media_s' => (int)$kpi['media_s'], + 'max_s' => (int)$kpi['max_s'], + 'n_problemi' => (int)$kpi['n_problemi'], + 'planned_s' => (int)$kpi['planned_s'], + 'unplanned_s' => (int)$kpi['unplanned_s'], + ], + 'per_motivo' => $perMotivo, + 'per_linea' => $perLinea, + 'per_giorno' => $perGiorno, + 'dettaglio' => $dettaglio, + ]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'msg' => $e->getMessage()]); + } + exit; +} +?> + + + + + + + Statistiche Pause & Fermi + + + + + + + +
+ + + +
+
+ +
+

Statistiche Pause & Fermi

+ +
+ +
+
+
+
+ +
+ + + +
+
+
+
+
+ + +
+
+ +
+
Caricamento…
+
+ + + + + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/public/userarea/photos/14-18-1788520038.jpg b/public/userarea/photos/14-18-1788520038.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a6d80f734112dfbafb60448723b4ec0515b4feb GIT binary patch literal 23891 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%0A@0!vsGmnaM?(3~DgNgjLdY62@ksBw=Na zd|aSy!`wV6y-n8$a!%}Up6R5)!z8QjyLg`@;{O@smY<8pP=8*G8|t9`+$qe`|Cd$O zcyF)!jVkHYSG~OtYO17-aTDY|eCs-6DAr}^!kx`^T^$sgjV6D76;G0;DkaidlmBbA zCVzh}2~S0SlI~2xKouVp$xHXJtouRs0SiuqU-^gwGuk2kI3%S);`1B{D>u+WE`7Rv6_yZqQ zLdVMPfBI#hZxDLVWO8BnDMunLDKNJ9y_^?=ATQ@$x1n6|Ce$UZjKBK-kaho?vituQ H-~Rp&#`CIL literal 0 HcmV?d00001 diff --git a/public/userarea/production_dashboard.php b/public/userarea/production_dashboard.php index 4c376596..ec3dde58 100644 --- a/public/userarea/production_dashboard.php +++ b/public/userarea/production_dashboard.php @@ -23,12 +23,42 @@ $dashboardSections = [ 'permission' => 'production.line_view.view', ], [ - 'label' => 'Statistiche', + 'label' => 'Magazzino', + 'icon' => '📦', + 'class' => 'btn-magazzino', + 'url' => 'warehouse_dashboard.php', + 'permission' => 'warehouse.dashboard.view', + ], + [ + 'label' => 'Smart-Alert', + 'icon' => '⏰', + 'class' => 'btn-scadenziario', + 'url' => 'scadenzario/index.php', + 'permission' => 'deadlines.view', + ], + ], + ], + [ + 'id' => 'secDirezione', + 'title' => 'Direzione', + 'subtitle' => 'Statistiche, analisi fermi e strumenti di supervisione', + 'icon' => '📊', + 'open' => false, + 'buttons' => [ + [ + 'label' => 'Statistiche Produzione', 'icon' => '📈', 'class' => 'btn-statistiche', 'url' => 'production_stats.php', 'permission' => 'production.stats.view', ], + [ + 'label' => 'Pause & Fermi', + 'icon' => '🛑', + 'class' => 'btn-pause-stats', + 'url' => 'pause_stats.php', + 'permission' => 'production.stats.view', + ], [ 'label' => 'Manager', 'icon' => '👔', @@ -43,20 +73,6 @@ $dashboardSections = [ 'url' => 'manager_stats.php', 'permission' => 'production.manager_stats.view', ], - [ - 'label' => 'Magazzino', - 'icon' => '📦', - 'class' => 'btn-magazzino', - 'url' => 'warehouse_dashboard.php', - 'permission' => 'warehouse.dashboard.view', - ], - [ - 'label' => 'Smart-Alert', - 'icon' => '⏰', - 'class' => 'btn-scadenziario', - 'url' => 'scadenzario/index.php', - 'permission' => 'deadlines.view', - ], ], ], [ @@ -421,6 +437,11 @@ $dashboardSections = [ background: linear-gradient(135deg, #d2f7d9, #e7ffea); } + .btn-pause-stats { + background: linear-gradient(135deg, #fca5a5, #fecaca); + color: #7f1d1d; + } + .btn-magazzino { background: linear-gradient(135deg, #b9e3ffff, #d7f1ffff); } diff --git a/public/userarea/production_line_view2.php b/public/userarea/production_line_view2.php index 142759c2..51c91434 100644 --- a/public/userarea/production_line_view2.php +++ b/public/userarea/production_line_view2.php @@ -26,6 +26,10 @@ $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'])) { @@ -62,6 +66,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['start_production'])) // 3) Ripresa: da PAUSA (7) → PRODUZIONE (2) **senza toccare start_time** } elseif ((int)$currentStatus === (int)$statusPausaId) { + // Chiudo l'ultima pausa aperta di questa produzione + $sqlClose = "UPDATE production_pauses + SET end_pause = UTC_TIMESTAMP() + WHERE id_production = :id AND end_pause IS NULL + ORDER BY start_pause DESC + LIMIT 1"; + $stmtClose = $pdo->prepare($sqlClose); + $stmtClose->execute(['id' => $id]); + $sqlUpdate = "UPDATE productiondata SET id_status = :status, end_time = NULL @@ -162,36 +175,61 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['stop_production'])) $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(); - $seconds = 0; - if ($row && $row['start_time'] && $row['id_status'] == $statusProduzioneId) { + + // 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')); - $seconds = $now->getTimestamp() - $start->getTimestamp(); + $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 - WHERE id = :id AND id_status IN ($statusProduzioneId, $statusPausaId)"; + 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, - 'seconds' => $seconds, - 'note' => $note ?: null, - 'id' => $id, - 'produzione' => $statusProduzioneId, - 'pausa' => $statusPausaId + '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; @@ -208,11 +246,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data'])) $reason = $_POST['reason'] ?? 'fine'; try { - // Recupero tempo totale già calcolato - $sql = "SELECT tempo_totale_produzione FROM productiondata WHERE id = :id"; - $stmt = $pdo->prepare($sql); + $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]); - $seconds = (int)$stmt->fetchColumn(); + $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; @@ -227,14 +288,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data'])) // Update finale $sql = "UPDATE productiondata - SET kgprod = :kgprod, - mtprod = :mtprod, - scarto = :scarto, - scarto_perc = :scarto_perc, - note_operatore = :note, - hourprod = SEC_TO_TIME(:seconds), - id_status = :status, - end_time = COALESCE(end_time, UTC_TIMESTAMP()) + 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); @@ -245,12 +309,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data'])) '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; @@ -258,12 +327,91 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['save_final_data'])) +// --- 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 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 @@ -396,6 +544,53 @@ if (!empty($_GET['ajax'])) { Linea Produzione - Tablet + + + + + +
+ + + +
+
+ +
+

Statistiche Produzione

+ +
+ +
+
+
+ +
+ +
+ + + +
+
+ +
+
+ +
+ +
+ + +
+
+ +
+ + +
+
+ +
+
Caricamento…
+
+ + + + + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/public/userarea/render_production_card.php b/public/userarea/render_production_card.php index 8e567bce..a8346654 100644 --- a/public/userarea/render_production_card.php +++ b/public/userarea/render_production_card.php @@ -81,6 +81,13 @@ if ($matricePhoto) { + +
+ Descrizione + +
+ +
Linea
Matrice
@@ -258,9 +265,22 @@ if ($matricePhoto) {
- + + + + + + 24) $maxHours = 24; if (!$lineNumber || !$name) { echo json_encode(['success' => false, 'message' => 'Numero linea e nome sono obbligatori.']); @@ -19,10 +22,10 @@ try { } $stmt = $pdo->prepare(" - INSERT INTO production_lines (line_number, name, model, brand, status, color) - VALUES (?, ?, ?, ?, ?, ?) + INSERT INTO production_lines (line_number, name, model, brand, status, color, max_hours_per_day) + VALUES (?, ?, ?, ?, ?, ?, ?) "); - $stmt->execute([$lineNumber, $name, $model, $brand, $status, $color]); + $stmt->execute([$lineNumber, $name, $model, $brand, $status, $color, $maxHours]); echo json_encode(['success' => true]); } catch (Exception $e) { diff --git a/public/userarea/stats.php b/public/userarea/stats.php new file mode 100644 index 00000000..9dedcbc2 --- /dev/null +++ b/public/userarea/stats.php @@ -0,0 +1,300 @@ +getConnection(); + +/* 2=produzione, 3=qualità(fine ok), 7=pausa, 8=problema. "Concluse"=3,8 */ +$STATI_CONCLUSI = [3, 8]; + +$linee = $pdo->query("SELECT id, name FROM production_lines ORDER BY line_number")->fetchAll(); +$matrici = $pdo->query("SELECT id, nome FROM matrice ORDER BY nome")->fetchAll(); +$mescole = $pdo->query("SELECT id, nome FROM mescole ORDER BY nome")->fetchAll(); + +if (!empty($_GET['ajax'])) { + header('Content-Type: application/json'); + + $dateFrom = $_GET['date_from'] ?? ''; + $dateTo = $_GET['date_to'] ?? ''; + $lineId = isset($_GET['line_id']) && $_GET['line_id'] !== '' ? (int)$_GET['line_id'] : null; + $matId = isset($_GET['matrice_id']) && $_GET['matrice_id'] !== '' ? (int)$_GET['matrice_id'] : null; + $mescId = isset($_GET['mescola_id']) && $_GET['mescola_id'] !== '' ? (int)$_GET['mescola_id'] : null; + + $where = ["p.id_status IN (" . implode(',', $STATI_CONCLUSI) . ")"]; + $params = []; + if ($dateFrom !== '') { $where[] = "p.Data >= :date_from"; $params['date_from'] = $dateFrom; } + if ($dateTo !== '') { $where[] = "p.Data <= :date_to"; $params['date_to'] = $dateTo; } + if ($lineId !== null) { $where[] = "p.id_linea = :line_id"; $params['line_id'] = $lineId; } + if ($matId !== null) { $where[] = "p.idmatrice = :mat_id"; $params['mat_id'] = $matId; } + if ($mescId !== null) { + $where[] = "EXISTS (SELECT 1 FROM productiondata_mescole pm + WHERE pm.id_productiondata = p.id AND pm.id_mescola = :mesc_id)"; + $params['mesc_id'] = $mescId; + } + $whereSql = implode(' AND ', $where); + + try { + $sqlKpi = "SELECT + COUNT(*) AS n_produzioni, + COALESCE(SUM(p.tempo_totale_produzione),0) AS tot_occupato_s, + COALESCE(SUM(p.seconds_pause),0) AS tot_pause_s, + COALESCE(SUM(p.seconds_produzione),0) AS tot_netto_s, + COALESCE(SUM(p.kgprod),0) AS tot_kg, + COALESCE(SUM(p.mtprod),0) AS tot_mt, + COALESCE(SUM(p.scarto),0) AS tot_scarto + FROM productiondata p WHERE $whereSql"; + $stmt = $pdo->prepare($sqlKpi); $stmt->execute($params); + $kpi = $stmt->fetch(PDO::FETCH_ASSOC); + + $effTempo = ($kpi['tot_occupato_s'] > 0) ? round($kpi['tot_netto_s'] / $kpi['tot_occupato_s'] * 100, 1) : 0; + $denomScarto = $kpi['tot_kg'] + $kpi['tot_scarto']; + $scartoPercTot = ($denomScarto > 0) ? round($kpi['tot_scarto'] / $denomScarto * 100, 2) : 0; + + $sqlLinea = "SELECT l.id, l.name AS linea, l.max_hours_per_day, COUNT(*) AS n_prod, + COALESCE(SUM(p.tempo_totale_produzione),0) AS occupato_s, + COALESCE(SUM(p.seconds_pause),0) AS pause_s, + COALESCE(SUM(p.seconds_produzione),0) AS netto_s, + COALESCE(SUM(p.kgprod),0) AS kg, COALESCE(SUM(p.scarto),0) AS scarto + FROM productiondata p JOIN production_lines l ON l.id = p.id_linea + WHERE $whereSql GROUP BY l.id ORDER BY l.line_number"; + $stmt = $pdo->prepare($sqlLinea); $stmt->execute($params); + $perLinea = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($perLinea as &$row) { + $row['eff_tempo'] = ($row['occupato_s'] > 0) ? round($row['netto_s'] / $row['occupato_s'] * 100, 1) : 0; + $den = $row['kg'] + $row['scarto']; + $row['scarto_perc'] = ($den > 0) ? round($row['scarto'] / $den * 100, 2) : 0; + } unset($row); + + $sqlGiorno = "SELECT p.Data AS giorno, + COALESCE(SUM(p.tempo_totale_produzione),0) AS occupato_s, + COALESCE(SUM(p.seconds_pause),0) AS pause_s, + COALESCE(SUM(p.seconds_produzione),0) AS netto_s, + COALESCE(SUM(p.kgprod),0) AS kg, COALESCE(SUM(p.scarto),0) AS scarto + FROM productiondata p WHERE $whereSql GROUP BY p.Data ORDER BY p.Data"; + $stmt = $pdo->prepare($sqlGiorno); $stmt->execute($params); + $perGiorno = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($perGiorno as &$g) { + $g['eff_tempo'] = ($g['occupato_s'] > 0) ? round($g['netto_s'] / $g['occupato_s'] * 100, 1) : 0; + $den = $g['kg'] + $g['scarto']; + $g['scarto_perc'] = ($den > 0) ? round($g['scarto'] / $den * 100, 2) : 0; + } unset($g); + + $sqlCat = "SELECT pr.category AS category, + COALESCE(SUM(TIMESTAMPDIFF(SECOND, pp.start_pause, pp.end_pause)),0) AS durata_s + FROM production_pauses pp + JOIN productiondata p ON p.id = pp.id_production + JOIN pause_reasons pr ON pr.id = pp.reason_id + WHERE pp.end_pause IS NOT NULL AND $whereSql GROUP BY pr.category"; + $stmt = $pdo->prepare($sqlCat); $stmt->execute($params); + $plannedS = 0; $unplannedS = 0; + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $c) { + if ($c['category'] === 'planned') $plannedS = (int)$c['durata_s']; else $unplannedS += (int)$c['durata_s']; + } + + $sqlDett = "SELECT p.id, p.Data, p.conferma_ordine, p.note_extra, + l.name AS linea, m.nome AS matrice, c.nome AS cliente, + p.tempo_totale_produzione AS occupato_s, p.seconds_pause AS pause_s, p.seconds_produzione AS netto_s, + p.kgprod, p.mtprod, p.scarto, p.scarto_perc, p.id_status, + (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 + FROM productiondata p + LEFT JOIN production_lines l ON l.id = p.id_linea + LEFT JOIN matrice m ON m.id = p.idmatrice + LEFT JOIN clients c ON c.id = p.id_cliente + WHERE $whereSql ORDER BY p.Data DESC, p.id DESC LIMIT 500"; + $stmt = $pdo->prepare($sqlDett); $stmt->execute($params); + $dettaglio = $stmt->fetchAll(PDO::FETCH_ASSOC); + + echo json_encode([ + 'success' => true, + 'kpi' => [ + 'n_produzioni'=>(int)$kpi['n_produzioni'],'tot_occupato_s'=>(int)$kpi['tot_occupato_s'], + 'tot_pause_s'=>(int)$kpi['tot_pause_s'],'tot_netto_s'=>(int)$kpi['tot_netto_s'], + 'tot_kg'=>(float)$kpi['tot_kg'],'tot_mt'=>(float)$kpi['tot_mt'],'tot_scarto'=>(float)$kpi['tot_scarto'], + 'eff_tempo'=>$effTempo,'scarto_perc_tot'=>$scartoPercTot,'planned_s'=>$plannedS,'unplanned_s'=>$unplannedS, + ], + 'per_linea'=>$perLinea,'per_giorno'=>$perGiorno,'dettaglio'=>$dettaglio, + ]); + } catch (Exception $e) { + echo json_encode(['success' => false, 'msg' => $e->getMessage()]); + } + exit; +} +?> + + + + + + Statistiche Produzione + + + + + + + + +
+

Statistiche Produzione

+ +
+ +
+
+
+
+
+
+
+ + +
+
+ +
Caricamento…
+ + + + + + + + + + \ No newline at end of file diff --git a/public/userarea/update_linea.php b/public/userarea/update_linea.php index f01835de..fa7be282 100644 --- a/public/userarea/update_linea.php +++ b/public/userarea/update_linea.php @@ -18,6 +18,9 @@ try { $brand = trim($_POST['brand'] ?? ''); $status = trim($_POST['status'] ?? ''); $color = trim($_POST['color'] ?? '#dc2626'); + $maxHours = isset($_POST['max_hours_per_day']) ? (float)$_POST['max_hours_per_day'] : 24; + if ($maxHours < 0) $maxHours = 0; + if ($maxHours > 24) $maxHours = 24; if ($lineNumber === '' || $lineName === '') { echo json_encode(['success' => false, 'message' => 'Numero linea e nome sono obbligatori.']); @@ -26,24 +29,26 @@ try { $sql = " UPDATE production_lines - SET line_number = :line_number, - name = :name, - model = :model, - brand = :brand, - status = :status, - color = :color + SET line_number = :line_number, + name = :name, + model = :model, + brand = :brand, + status = :status, + color = :color, + max_hours_per_day = :max_hours_per_day WHERE id = :id "; $stmt = $pdo->prepare($sql); $stmt->execute([ - ':line_number' => $lineNumber, - ':name' => $lineName, - ':model' => $model, - ':brand' => $brand, - ':status' => $status, - ':color' => $color, - ':id' => $id + ':line_number' => $lineNumber, + ':name' => $lineName, + ':model' => $model, + ':brand' => $brand, + ':status' => $status, + ':color' => $color, + ':max_hours_per_day' => $maxHours, + ':id' => $id ]); echo json_encode(['success' => true]);