added assorbimento corso

This commit is contained in:
2026-07-24 08:32:50 +02:00
parent 6d60f89be2
commit c11adf19e8
6 changed files with 691 additions and 7 deletions
+150 -2
View File
@@ -7,6 +7,8 @@ $pdo = $db->getConnection();
/* ==========================================
PAGE DATA
========================================== */
require_once __DIR__ . '/include/training_coverage.php';
$sql = "
SELECT tt.*,
(SELECT COUNT(*) FROM employee_trainings et WHERE et.training_topic_id = tt.id) AS trainings_count
@@ -14,6 +16,30 @@ $sql = "
ORDER BY tt.sort_order ASC, tt.name ASC
";
$topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
/* Coperture: [topic_id => [ ['id'=>..,'name'=>..], ... ]] */
$coverageRows = $pdo->query("
SELECT c.topic_id, c.covered_topic_id, tt.name AS covered_name
FROM training_topic_coverage c
JOIN training_topics tt ON tt.id = c.covered_topic_id
ORDER BY tt.sort_order, tt.name
")->fetchAll(PDO::FETCH_ASSOC);
$coverageByTopic = [];
foreach ($coverageRows as $cr) {
$coverageByTopic[(int)$cr['topic_id']][] = [
'id' => (int)$cr['covered_topic_id'],
'name' => $cr['covered_name'],
];
}
/* Mappa inversa: chi copre questo corso */
$coveredByTopic = [];
foreach ($coverageRows as $cr) {
$coveredByTopic[(int)$cr['covered_topic_id']][] = (int)$cr['topic_id'];
}
$topicNames = [];
foreach ($topics as $t) $topicNames[(int)$t['id']] = $t['name'];
?>
<!doctype html>
<html lang="it">
@@ -274,6 +300,42 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
color: #94a3b8;
padding: 24px 0;
}
.cov-chip {
display: inline-block;
padding: 2px 9px;
border-radius: 999px;
background: #ede9fe;
color: #5b21b6;
border: 1px solid #ddd6fe;
font-size: 0.78rem;
font-weight: 600;
margin: 2px 3px 2px 0;
white-space: nowrap;
}
.cov-chip-parent {
background: #fef3c7;
color: #92400e;
border-color: #fde68a;
}
.cov-cell {
max-width: 240px;
text-align: left;
white-space: normal;
}
.cov-help {
font-size: .85rem;
color: #64748b;
margin-top: 4px;
}
#addCovered,
#editCovered {
min-height: 140px;
}
</style>
</head>
@@ -328,6 +390,7 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
<th>Ordine</th>
<th>Stato</th>
<th>Formazioni</th>
<th>Copre</th>
<th>Azioni</th>
</tr>
</thead>
@@ -368,6 +431,25 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
<td><?= $sortOrder ?></td>
<td><span class="badge-status <?= $statusClass ?>"><?= $statusLabel ?></span></td>
<td><?= $cnt ?></td>
<td class="cov-cell">
<?php $covList = $coverageByTopic[$id] ?? []; ?>
<?php if ($covList): ?>
<?php foreach ($covList as $cv): ?>
<span class="cov-chip">⛓️ <?= htmlspecialchars($cv['name']) ?></span>
<?php endforeach; ?>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
<?php if (!empty($coveredByTopic[$id])): ?>
<div class="mt-1">
<?php foreach ($coveredByTopic[$id] as $parentId): ?>
<span class="cov-chip cov-chip-parent" title="Questo corso è assorbito da">
↑ <?= htmlspecialchars($topicNames[$parentId] ?? ('#' . $parentId)) ?>
</span>
<?php endforeach; ?>
</div>
<?php endif; ?>
</td>
<td>
<button class="btn btn-sm btn-outline-secondary edit-topic"
data-id="<?= $id ?>"
@@ -377,7 +459,8 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
data-rem="<?= $rem ?>"
data-sort_order="<?= $sortOrder ?>"
data-is_active="<?= $isActive ?>"
data-is_mandatory="<?= $isMandatory ?>">
data-is_mandatory="<?= $isMandatory ?>"
data-covered="<?= htmlspecialchars(json_encode(array_column($coverageByTopic[$id] ?? [], 'id')), ENT_QUOTES) ?>">
✏️ Modifica
</button>
<button class="btn btn-sm btn-outline-danger delete-topic"
@@ -430,6 +513,17 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
<span><b>Formazioni:</b> <?= $cnt ?></span>
<span><b>Ordine:</b> <?= $sortOrder ?></span>
</div>
<?php $covList = $coverageByTopic[$id] ?? []; ?>
<?php if ($covList || !empty($coveredByTopic[$id])): ?>
<div class="mb-2">
<?php foreach ($covList as $cv): ?>
<span class="cov-chip">⛓️ <?= htmlspecialchars($cv['name']) ?></span>
<?php endforeach; ?>
<?php foreach (($coveredByTopic[$id] ?? []) as $parentId): ?>
<span class="cov-chip cov-chip-parent">↑ <?= htmlspecialchars($topicNames[$parentId] ?? ('#' . $parentId)) ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="tt-card-actions">
<button class="btn btn-sm btn-outline-secondary edit-topic"
data-id="<?= $id ?>"
@@ -439,7 +533,8 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
data-rem="<?= $rem ?>"
data-sort_order="<?= $sortOrder ?>"
data-is_active="<?= $isActive ?>"
data-is_mandatory="<?= $isMandatory ?>">
data-is_mandatory="<?= $isMandatory ?>"
data-covered="<?= htmlspecialchars(json_encode(array_column($coverageByTopic[$id] ?? [], 'id')), ENT_QUOTES) ?>">
✏️ Modifica
</button>
<button class="btn btn-sm btn-outline-danger delete-topic"
@@ -520,6 +615,19 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
Se attivo, i dipendenti senza registrazione di questo corso compaiono come "Non presente" nello storico.
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">⛓️ Corsi assorbiti da questo</label>
<select class="form-select" id="addCovered" multiple size="6">
<?php foreach ($topics as $t): ?>
<option value="<?= (int)$t['id'] ?>"><?= htmlspecialchars($t['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="cov-help">
Chi completa questo corso risulta automaticamente in regola anche per quelli selezionati.
La scadenza del corso assorbito viene ricalcolata con la sua frequenza propria.
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva</button>
</div>
@@ -590,6 +698,18 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
Se attivo, i dipendenti senza registrazione di questo corso compaiono come "Non presente" nello storico.
</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">⛓️ Corsi assorbiti da questo</label>
<select class="form-select" id="editCovered" multiple size="6">
<?php foreach ($topics as $t): ?>
<option value="<?= (int)$t['id'] ?>"><?= htmlspecialchars($t['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="cov-help">
Chi completa questo corso risulta automaticamente in regola anche per quelli selezionati.
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-add">💾 Salva Modifiche</button>
</div>
@@ -604,6 +724,10 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
<script>
$(document).ready(function() {
$('#tabellaTopics').DataTable({
columnDefs: [{
targets: [8, 9],
orderable: false
}],
order: [
[5, 'asc'],
[1, 'asc']
@@ -660,6 +784,9 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
p.append('sort_order', $("#addSortOrder").val());
p.append('is_active', $("#addIsActive").val());
p.append('is_mandatory', $("#addIsMandatory").is(':checked') ? '1' : '0');
($("#addCovered").val() || []).forEach(function(v) {
p.append('covered_ids[]', v);
});
ajaxPost("ajax/training_topics/save.php", p, "Salvato!", "Impossibile salvare il corso.");
});
@@ -678,6 +805,24 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
$("#editSortOrder").val(b.data("sort_order"));
$("#editIsActive").val(String(b.data("is_active")));
$("#editIsMandatory").prop('checked', String(b.data("is_mandatory")) === '1');
// Coperture: nascondi il corso stesso e preseleziona le esistenti
var selfId = String(b.data("id"));
var covered = b.data("covered") || [];
if (typeof covered === 'string') {
try {
covered = JSON.parse(covered);
} catch (e) {
covered = [];
}
}
covered = covered.map(String);
$("#editCovered option").each(function() {
$(this).prop('hidden', this.value === selfId)
.prop('disabled', this.value === selfId);
});
$("#editCovered").val(covered);
$("#editTopicModal").modal("show");
});
@@ -692,6 +837,9 @@ $topics = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
p.append('sort_order', $("#editSortOrder").val());
p.append('is_active', $("#editIsActive").val());
p.append('is_mandatory', $("#editIsMandatory").is(':checked') ? '1' : '0');
($("#editCovered").val() || []).forEach(function(v) {
p.append('covered_ids[]', v);
});
ajaxPost("ajax/training_topics/save.php", p, "Aggiornato!", "Impossibile aggiornare il corso.");
});