fixed nuovo

This commit is contained in:
2026-09-07 15:02:36 +02:00
parent 8425a7d90c
commit c394d92ef3
16 changed files with 2629 additions and 70 deletions
+368 -34
View File
@@ -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'])) {
<title>Linea Produzione - Tablet</title>
<?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
<style>
/* Select2 dentro il modale + z-index sopra l'overlay */
.select2-container {
width: 100% !important;
}
.select2-container--default .select2-selection--single {
height: auto;
padding: 0.6rem 0.6rem;
border: 1px solid #cbd5e1;
border-radius: 0.6rem;
font-size: 1.15rem;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 100%;
}
.select2-dropdown {
z-index: 2000;
}
.select2-container--open {
z-index: 2000;
}
/* Select2 MULTIPLO: chip grandi e comodi per tablet */
.select2-container--default .select2-selection--multiple {
min-height: 3.2rem;
padding: 0.3rem 0.5rem;
border: 1px solid #cbd5e1;
border-radius: 0.6rem;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
font-size: 1.05rem;
padding: 0.35rem 0.7rem;
border-radius: 0.5rem;
margin-top: 0.35rem;
}
.select2-container--default .select2-results__option {
font-size: 1.1rem;
padding: 0.7rem 0.9rem;
}
</style>
<style>
* {
@@ -1124,7 +1319,13 @@ if (!empty($_GET['ajax'])) {
<div class="card-header">
<h5 style="color:#ffffff;">Linea di Produzione</h5>
<button class="back-btn" onclick="location.href='production_dashboard.php'">Torna</button>
<div style="display:flex; gap:0.8rem; align-items:center;">
<button class="back-btn" id="btnNuovoQuick"
style="background:rgba(16,185,129,0.9); border-color:rgba(16,185,129,1);">
+ Nuovo
</button>
<button class="back-btn" onclick="location.href='production_dashboard.php'">Torna</button>
</div>
</div>
<div class="filters">
@@ -1159,6 +1360,54 @@ if (!empty($_GET['ajax'])) {
<?php include('include/footer.php'); ?>
</div>
<!-- MODALE NUOVO -->
<div id="quickCreateModal" class="custom-modal">
<div class="modal-content" style="max-width:900px; width:92%; text-align:left;">
<h3 style="text-align:center;">Nuovo avvio rapido</h3>
<label style="font-weight:600; display:block; margin-top:1rem;">Descrizione</label>
<input type="text" id="qcDescrizione" class="input-big" placeholder="Descrizione...">
<div style="display:flex; gap:1.2rem; flex-wrap:wrap;">
<div style="flex:1; min-width:260px;">
<label style="font-weight:600; display:block;">Matrice *</label>
<select id="qcMatrice" class="input-big">
<option value="">Seleziona matrice</option>
<?php foreach ($allMatrici as $mx): ?>
<option value="<?= (int)$mx['id'] ?>"><?= htmlspecialchars($mx['nome']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div style="flex:1; min-width:260px;">
<label style="font-weight:600; display:block;">Linea *</label>
<select id="qcLinea" class="input-big">
<option value="">Seleziona linea</option>
<?php foreach ($linee as $l): ?>
<option value="<?= (int)$l['id'] ?>"><?= htmlspecialchars($l['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<label style="font-weight:600; display:block;">Mescole (una o più, opzionale)</label>
<select id="qcMescola" class="input-big" multiple>
<?php foreach ($allMescole as $me): ?>
<option value="<?= (int)$me['id'] ?>"><?= htmlspecialchars($me['nome']) ?></option>
<?php endforeach; ?>
</select>
<div class="modal-buttons" style="flex-wrap:wrap;">
<button class="modal-btn modal-cancel" id="qcCancel">Annulla</button>
<button class="modal-btn modal-confirm" id="qcCreateQueue"
style="background:#3b82f6;">Crea in coda</button>
<button class="modal-btn modal-confirm" id="qcCreateStart">Crea e avvia</button>
</div>
</div>
</div>
<!-- Notifica sonora -->
<audio id="beepSound" src="beep.wav" preload="auto"></audio>
@@ -1271,6 +1520,7 @@ if (!empty($_GET['ajax'])) {
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
function showPhotoSuccess() {
$("#photoMessageError").hide();
@@ -1998,13 +2248,9 @@ if (!empty($_GET['ajax'])) {
break;
case 'stop':
if (reason === 'fine') {
// Apertura modale dati finali
openFinalDataModal(id, reason, note);
} else {
// Problema → stop diretto
stopProduction(id, reason, note);
}
// Sia "fine" che "problema" aprono il modale dati finali.
// Il reason viene passato e conservato per il salvataggio.
openFinalDataModal(id, reason, note);
break;
}
});
@@ -2166,6 +2412,94 @@ if (!empty($_GET['ajax'])) {
// --- NUOVO AVVIO RAPIDO ---
function initQuickSelects() {
$('#qcMatrice').select2({
dropdownParent: $('#quickCreateModal .modal-content'),
placeholder: 'Cerca matrice...',
allowClear: true,
width: '100%'
});
$('#qcMescola').select2({
dropdownParent: $('#quickCreateModal .modal-content'),
placeholder: 'Cerca e aggiungi mescole...',
allowClear: true,
width: '100%',
closeOnSelect: false
});
}
function destroyQuickSelects() {
if ($('#qcMatrice').data('select2')) $('#qcMatrice').select2('destroy');
if ($('#qcMescola').data('select2')) $('#qcMescola').select2('destroy');
}
function closeQuickModal() {
destroyQuickSelects();
$('#quickCreateModal').removeClass('active');
}
$('#btnNuovoQuick').on('click', function() {
$('#qcDescrizione').val('');
$('#qcMatrice').val('');
$('#qcMescola').val(null);
$('#qcLinea').val('');
$('#quickCreateModal').addClass('active');
initQuickSelects();
});
$('#qcCancel').on('click', closeQuickModal);
$(document).on('click', '#quickCreateModal', function(e) {
if ($(e.target).is('#quickCreateModal')) closeQuickModal();
});
function submitQuickCreate(avvia) {
const idMatrice = $('#qcMatrice').val();
const idLinea = $('#qcLinea').val();
const mescoleArr = $('#qcMescola').val() || []; // array di id
const descr = $('#qcDescrizione').val().trim();
if (!idMatrice) {
alert('Seleziona una matrice.');
return;
}
if (!idLinea) {
alert('Seleziona una linea.');
return;
}
$('#qcCreateQueue, #qcCreateStart').prop('disabled', true);
$.post('', {
create_quick: 1,
avvia: avvia ? 1 : 0,
id_matrice: idMatrice,
id_linea: idLinea,
id_mescole: mescoleArr.join(','), // CSV di id
descrizione: descr
}, function(r) {
$('#qcCreateQueue, #qcCreateStart').prop('disabled', false);
if (r.success) {
closeQuickModal();
playBeep();
loadRecords();
} else {
alert(r.msg || 'Errore durante la creazione.');
}
}, 'json').fail(function() {
$('#qcCreateQueue, #qcCreateStart').prop('disabled', false);
alert('Errore di comunicazione con il server.');
});
}
$('#qcCreateQueue').on('click', function() {
submitQuickCreate(false);
});
$('#qcCreateStart').on('click', function() {
submitQuickCreate(true);
});
loadRecords();
});