added multiple imballaggi

This commit is contained in:
2026-09-09 14:59:26 +02:00
parent c394d92ef3
commit da5c4b8777
5 changed files with 373 additions and 46 deletions
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateWorkSheetPackages extends AbstractMigration
{
public function change(): void
{
$this->table('work_sheet_packages', ['id' => 'id'])
->addColumn('worksheet_id', 'integer', ['limit' => 11])
->addColumn('package_code', 'integer', ['limit' => 11, 'comment' => 'FK -> packaging_items.id'])
->addColumn('package_position', 'integer', ['default' => 1])
->addColumn('created_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->addColumn('updated_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP'])
->addIndex(['worksheet_id'], ['name' => 'idx_wsp_ws'])
->addIndex(['package_code'], ['name' => 'idx_wsp_pkg'])
->addIndex(['worksheet_id', 'package_code'], ['unique' => true, 'name' => 'uk_wsp_ws_pkg'])
->addIndex(['worksheet_id', 'package_position'], ['unique' => true, 'name' => 'uk_wsp_ws_position'])
->addForeignKey('worksheet_id', 'work_sheets', 'id', ['delete' => 'CASCADE', 'update' => 'CASCADE'])
->addForeignKey('package_code', 'packaging_items', 'id', ['update' => 'CASCADE'])
->create();
}
}
+8
View File
@@ -61,3 +61,11 @@ require_once(__DIR__ . '/../../languages/en/general.php');
//include("generalsettings.php");
require_once __DIR__ . '/permissions_helper.php';
require_once(__DIR__ . '/popup-moved.php');
// Timbra la sessione dell'utente a ogni pagina (rolling manuale)
try {
$pdoSess = DBHandlerSelect::getInstance()->getConnection();
$pdoSess->prepare("UPDATE auth_sessions SET last_activity = ? WHERE user_id = ? ORDER BY last_activity DESC LIMIT 1")
->execute([time(), $iduserlogin]);
} catch (\Throwable $e) { /* non bloccare la pagina */
}
+240 -2
View File
@@ -327,6 +327,68 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['ajax']) && $_POST['aj
exit;
}
// Add package row
if ($action === 'save_package_row') {
$worksheet_id = (int)($_POST['worksheet_id'] ?? 0);
if ($worksheet_id <= 0) {
echo json_encode(['success' => false, 'message' => 'Salva prima il foglio (testata)']);
exit;
}
$package_code = (int)($_POST['package_code'] ?? 0);
if ($package_code <= 0) {
echo json_encode(['success' => false, 'message' => 'Seleziona un imballo']);
exit;
}
$pdo->beginTransaction();
$stmtPos = $pdo->prepare("
SELECT IFNULL(MAX(package_position), 0) + 1
FROM work_sheet_packages
WHERE worksheet_id = ?
FOR UPDATE
");
$stmtPos->execute([$worksheet_id]);
$pos = (int)$stmtPos->fetchColumn();
if ($pos <= 0) $pos = 1;
try {
$stmt = $pdo->prepare("
INSERT INTO work_sheet_packages
(worksheet_id, package_code, package_position)
VALUES (?, ?, ?)
");
$stmt->execute([$worksheet_id, $package_code, $pos]);
} catch (PDOException $ex) {
$pdo->rollBack();
if ((int)$ex->errorInfo[1] === 1062) {
echo json_encode(['success' => false, 'message' => 'Imballo già presente in questo foglio']);
exit;
}
throw $ex;
}
$pdo->commit();
echo json_encode(['success' => true]);
exit;
}
// Delete package row
if ($action === 'delete_package_row') {
$row_id = (int)($_POST['row_id'] ?? 0);
if ($row_id <= 0) {
echo json_encode(['success' => false, 'message' => 'ID riga non valido']);
exit;
}
$stmt = $pdo->prepare("DELETE FROM work_sheet_packages WHERE id = ?");
$stmt->execute([$row_id]);
echo json_encode(['success' => true]);
exit;
}
echo json_encode(['success' => false, 'message' => 'Azione sconosciuta']);
exit;
} catch (Exception $e) {
@@ -406,6 +468,7 @@ $mescole = $pdo->query("
// Load worksheet (edit)
$worksheet = null;
$mixRows = [];
$packageRows = [];
if ($worksheet_id > 0) {
$stmt = $pdo->prepare("
@@ -430,6 +493,20 @@ if ($worksheet_id > 0) {
");
$stmt->execute([$worksheet_id]);
$mixRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("
SELECT
wsp.*,
pi.item_name AS package_name,
pi.item_code AS package_item_code,
pi.category AS package_category
FROM work_sheet_packages wsp
LEFT JOIN packaging_items pi ON pi.id = wsp.package_code
WHERE wsp.worksheet_id = ?
ORDER BY wsp.package_position ASC, wsp.id ASC
");
$stmt->execute([$worksheet_id]);
$packageRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$worksheet_id = 0;
}
@@ -1079,10 +1156,56 @@ $isEdit = ($worksheet_id > 0);
<div class="col-lg-4">
<label class="form-label">Elemento imballo</label>
<select class="form-select" name="requested_package_code" id="requested_package_code">
<select class="form-select" id="requested_package_code">
<option value="">-- Prima scegli una categoria --</option>
</select>
<div class="input-tip">Il valore salvato è il codice dell'elemento</div>
<div class="input-tip">Scegli l'elemento e premi "Aggiungi imballo"</div>
</div>
<div class="col-lg-4">
<label class="form-label d-none d-lg-block">&nbsp;</label>
<button type="button" class="btn w-100" id="btnAddPackage" disabled
style="background:linear-gradient(135deg,#ea580c,#f97316);color:#fff;border:0;border-radius:12px;min-height:46px;font-weight:800;box-shadow:0 10px 24px rgba(249,115,22,0.18);">
➕ Aggiungi imballo
</button>
</div>
<div class="col-12">
<div class="table-wrap">
<table class="table table-striped table-bordered mb-0">
<thead>
<tr>
<th style="width:70px;">Pos</th>
<th>Categoria</th>
<th>Imballo</th>
<th style="width:120px;">Azioni</th>
</tr>
</thead>
<tbody id="packageRowsBody">
<?php foreach ($packageRows as $pr): ?>
<tr>
<td><?= (int)$pr['package_position'] ?></td>
<td><?= h($pr['package_category'] ?? '-') ?></td>
<td>
<div class="fw-bold"><?= h($pr['package_name'] ?? '-') ?></div>
<?php if (!empty($pr['package_item_code'])): ?>
<div class="text-muted small"><?= h($pr['package_item_code']) ?></div>
<?php endif; ?>
</td>
<td>
<button type="button" class="btn btn-sm btn-outline-danger delete-package"
data-id="<?= (int)$pr['id'] ?>">🗑️</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="input-tip mt-2">Puoi aggiungere più imballi. I dati "metri per confezione", tolleranza, note, ecc. restano unici per il foglio.</div>
</div>
<div class="col-12">
<hr class="my-1">
</div>
<div class="col-lg-4 col-md-6">
@@ -1613,6 +1736,121 @@ $isEdit = ($worksheet_id > 0);
});
});
});
// ---- IMBALLI ----
const btnAddPackage = $('#btnAddPackage');
// Sblocca il bottone imballo se la testata è salvata
function enablePackageButtonIfSaved() {
const id = parseInt(wsIdInput.val() || '0', 10);
btnAddPackage.prop('disabled', id <= 0);
}
enablePackageButtonIfSaved();
// Ogni volta che salvi la testata, riabilita anche questo bottone
const _origEnableMix = enableMixButtonIfSaved;
enableMixButtonIfSaved = function() {
_origEnableMix();
enablePackageButtonIfSaved();
};
enableMixButtonIfSaved();
// Click: aggiungi imballo
btnAddPackage.on('click', function() {
const wsId = parseInt(wsIdInput.val() || '0', 10);
if (wsId <= 0) {
Swal.fire({
icon: 'warning',
title: 'Salva prima la testata',
timer: 1500
});
return;
}
const packageCode = $('#requested_package_code').val();
if (!packageCode) {
Swal.fire({
icon: 'warning',
title: 'Seleziona un imballo',
timer: 1500
});
return;
}
fetch('', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `ajax=1&action=save_package_row&worksheet_id=${wsId}&package_code=${encodeURIComponent(packageCode)}`
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: 'Imballo aggiunto!',
timer: 800
})
.then(() => location.reload());
} else {
Swal.fire({
icon: 'error',
title: 'Errore',
text: data.message
});
}
})
.catch(err => {
Swal.fire({
icon: 'error',
title: 'Errore',
text: err.message
});
});
});
// Click: elimina imballo
$(document).on('click', '.delete-package', function() {
const rowId = $(this).data('id');
Swal.fire({
title: 'Eliminare questo imballo dal foglio?',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Sì, elimina',
cancelButtonText: 'Annulla'
}).then(result => {
if (!result.isConfirmed) return;
fetch('', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `ajax=1&action=delete_package_row&row_id=${rowId}`
})
.then(r => r.json())
.then(data => {
if (data.success) {
Swal.fire({
icon: 'success',
title: 'Eliminato!',
timer: 800
})
.then(() => location.reload());
} else {
Swal.fire({
icon: 'error',
title: 'Errore',
text: data.message
});
}
});
});
});
});
</script>
</body>
+71 -43
View File
@@ -9,6 +9,17 @@ $pdo = $db->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();
@@ -988,6 +999,38 @@ if (!empty($_GET['ajax'])) {
color: #475569 !important;
}
/* --- TAB LINEE (selezione singola) --- */
.line-tab {
flex: 1 1 auto;
min-width: 130px;
padding: 0.9rem 0.5rem;
font-size: 1.3rem;
font-weight: 800;
border: 2px solid #e2e8f0;
border-radius: 0.9rem 0.9rem 0 0;
background: #f1f5f9;
color: #64748b;
cursor: pointer;
transition: 0.15s;
letter-spacing: 0.5px;
}
.line-tab:hover {
background: #e2e8f0;
}
.line-tab.active {
background: var(--line-color, #1d4ed8);
color: #fff;
border-color: transparent;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
/* il tab "TUTTI" attivo usa un blu neutro */
.line-tab[data-line="all"].active {
background: #1d4ed8;
}
/* Modale più grande e ottimizzata */
.custom-modal .modal-content.final-wide {
@@ -1329,25 +1372,21 @@ if (!empty($_GET['ajax'])) {
</div>
<div class="filters">
<div class="filter-group">
<label>Linee</label>
<div id="lineButtons" style="display:flex; gap:0.8rem;">
<div class="filter-group" style="flex:1 1 100%;">
<div id="lineTabs" style="display:flex; gap:0.6rem; flex-wrap:wrap;">
<button class="line-tab" data-line="all">TUTTI</button>
<?php foreach ($linee as $l): ?>
<button
class="line-btn active"
data-line="<?= $l['id'] ?>"
<button class="line-tab"
data-line="<?= (int)$l['id'] ?>"
style="--line-color: <?= htmlspecialchars($l['color']) ?>;">
<?= htmlspecialchars($l['name']) ?>
</button>
<?php endforeach; ?>
</div>
<!-- Campo nascosto che userà JS per passare le linee selezionate -->
<input type="hidden" id="filterLine" value="">
<input type="hidden" id="preferredLine" value="<?= $preferredLineId !== null ? (int)$preferredLineId : '' ?>">
</div>
</div>
<div id="recordsContainer">
@@ -1671,43 +1710,32 @@ if (!empty($_GET['ajax'])) {
beep.play().catch(() => {});
}
// --- BOTTONI LINEE ---
let selectedLines = [];
// --- TAB LINEE (selezione singola) ---
// Imposta il filtro nascosto in base al tab: "all" = vuoto (tutte), altrimenti l'id.
function setActiveTab($tab) {
$('#lineTabs .line-tab').removeClass('active');
$tab.addClass('active');
const line = $tab.data('line');
$('#filterLine').val(line === 'all' ? '' : String(line));
}
// inizialmente selezioniamo tutte
$('#lineButtons .line-btn').each(function() {
selectedLines.push($(this).data('line'));
});
$('#filterLine').val(selectedLines.join(','));
// logica pulsanti
$(document).on('click', '.line-btn', function() {
const id = $(this).data('line');
if ($(this).hasClass('active')) {
// se la deseleziono
$(this).removeClass('active').addClass('inactive');
selectedLines = selectedLines.filter(l => l !== id);
} else {
// se la seleziono
$(this).removeClass('inactive').addClass('active');
selectedLines.push(id);
}
// assicurati che non sia vuoto
if (selectedLines.length === 0) {
// se deselezionano tutto → riseleziona tutto
$('#lineButtons .line-btn').removeClass('inactive').addClass('active');
selectedLines = $('#lineButtons .line-btn').map(function() {
return $(this).data('line');
}).get();
}
$('#filterLine').val(selectedLines.join(','));
// Click sui tab
$(document).on('click', '.line-tab', function() {
setActiveTab($(this));
loadRecords();
});
// Tab iniziale: preferita dell'utente se impostata, altrimenti "TUTTI"
(function initLineTab() {
const pref = $('#preferredLine').val();
let $start = $('#lineTabs .line-tab[data-line="all"]');
if (pref !== '') {
const $prefTab = $('#lineTabs .line-tab[data-line="' + pref + '"]');
if ($prefTab.length) $start = $prefTab;
}
setActiveTab($start);
})();
function escapeHtml(str) {
return String(str || '')
.replace(/&/g, '&amp;')
+29 -1
View File
@@ -35,6 +35,15 @@ try {
$countries = [];
}
// Load production lines (per la preferenza "Linea preferita").
$productionLines = [];
try {
$productionLines = $pdo->query("SELECT id, name FROM production_lines ORDER BY line_number ASC")
->fetchAll(PDO::FETCH_ASSOC);
} catch (Exception $e) {
$productionLines = [];
}
// Load current user.
$stmtProfileUser = $pdo->prepare("
SELECT
@@ -48,6 +57,7 @@ $stmtProfileUser = $pdo->prepare("
address,
country_id,
birthday,
preferred_line_id,
role_id,
status,
last_login
@@ -126,6 +136,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$address = trim($_POST['address'] ?? '');
$countryId = $_POST['country_id'] !== '' ? (int)$_POST['country_id'] : null;
$birthday = trim($_POST['birthday'] ?? '');
$preferredLineId = (isset($_POST['preferred_line_id']) && $_POST['preferred_line_id'] !== '')
? (int)$_POST['preferred_line_id']
: null;
$currentPassword = $_POST['current_password'] ?? '';
$newPassword = $_POST['new_password'] ?? '';
@@ -240,7 +253,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$errorMessage) {
try {
$pdo->beginTransaction();
$stmtUpdate = $pdo->prepare("
UPDATE auth_users
SET
@@ -252,6 +264,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
address = :address,
country_id = :country_id,
birthday = :birthday,
preferred_line_id = :preferred_line_id,
updated_at = NOW()
WHERE id = :id
LIMIT 1
@@ -266,6 +279,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
':address' => $address !== '' ? $address : null,
':country_id' => $countryId,
':birthday' => $birthdayValue,
':preferred_line_id' => $preferredLineId,
':id' => $userId,
]);
@@ -745,6 +759,20 @@ $avatarPath = normalizeAvatarPath($profileUser['avatar'] ?? '');
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="preferred_line_id">Linea preferita (Line View)</label>
<select class="form-select" id="preferred_line_id" name="preferred_line_id">
<option value="">Tutte (nessuna preferenza)</option>
<?php foreach ($productionLines as $pl): ?>
<option value="<?= (int)$pl['id']; ?>"
<?= ((int)($profileUser['preferred_line_id'] ?? 0) === (int)$pl['id']) ? 'selected' : ''; ?>>
<?= e($pl['name']); ?>
</option>
<?php endforeach; ?>
</select>
<div class="help-text">La pagina Line View si aprirà su questa linea.</div>
</div>
<div class="col-md-12">
<label class="form-label" for="address">Indirizzo</label>
<input type="text"