[FEAT] Inventario: rinomina/rimuovi sottoclassi org dalla modifica bene (ticket #426)

Aggiunti endpoint PUT/DELETE /api/assets/subclasses/{id} (org-scoped, le
sottoclassi di sistema restano protette; FK ON DELETE SET NULL scollega i
beni senza cancellarli) + link UI «rinomina»/«rimuovi» accanto a «aggiungi».
Cache-buster api.js + bump version 1.24.8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-23 19:42:02 +02:00
co-authored by Claude Opus 4.8
parent 7529c59a75
commit 637e1ec510
5 changed files with 91 additions and 3 deletions
+33 -2
View File
@@ -361,7 +361,7 @@
</main>
</div>
<script src="/js/api.js?v=20260631"></script>
<script src="/js/api.js?v=20260623g"></script>
<script src="/js/common.js?v=20260623"></script>
<!-- Bootstrap Italia bundle (componenti) + override sidebar BI (common-bi.js).
common-bi.js va DOPO common.js: ridefinisce solo loadSidebar() con markup BI,
@@ -442,6 +442,33 @@
else showNotification((r && r.message) || 'Errore.', 'warning');
} catch (e) { showNotification('Errore aggiunta sottoclasse.', 'warning'); }
}
async function renameSubclassPrompt() {
const sel = document.getElementById('asset-subclass');
const id = sel.value;
if (!id) { showNotification('Seleziona prima una sottoclasse da rinominare.', 'info'); return; }
const cur = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].text : '';
const label = (prompt('Nuovo nome per la sottoclasse:', cur) || '').trim();
if (!label || label === cur) return;
const voce = document.getElementById('asset-voce').value;
try {
const r = await api.updateAssetSubclass(id, { label: label });
if (r && r.success) { await loadSubclasses(); document.getElementById('asset-subclass').innerHTML = subclassOptionsHtml(voce, id); showNotification('Sottoclasse rinominata.', 'success'); }
else showNotification((r && r.message) || 'Operazione non consentita (solo sottoclassi della tua organizzazione).', 'warning');
} catch (e) { showNotification('Errore rinomina sottoclasse.', 'warning'); }
}
async function removeSubclassPrompt() {
const sel = document.getElementById('asset-subclass');
const id = sel.value;
if (!id) { showNotification('Seleziona prima una sottoclasse da rimuovere.', 'info'); return; }
const cur = sel.options[sel.selectedIndex] ? sel.options[sel.selectedIndex].text : '';
if (!confirm('Rimuovere la sottoclasse «' + cur + '»? Gli asset che la usano resteranno senza sottoclasse.')) return;
const voce = document.getElementById('asset-voce').value;
try {
const r = await api.deleteAssetSubclass(id);
if (r && r.success) { await loadSubclasses(); document.getElementById('asset-subclass').innerHTML = subclassOptionsHtml(voce, ''); showNotification('Sottoclasse rimossa.', 'success'); }
else showNotification((r && r.message) || 'Operazione non consentita (solo sottoclassi della tua organizzazione).', 'warning');
} catch (e) { showNotification('Errore rimozione sottoclasse.', 'warning'); }
}
// ── State ───────────────────────────────────────────────
let allAssets = [];
@@ -835,7 +862,11 @@
<select class="form-select" id="asset-voce" onchange="onVoceChange()">${voceOpts}</select>
</div>
<div class="form-group">
<label for="asset-subclass" class="form-label">Sottoclasse <a href="#" onclick="addSubclassPrompt();return false;" style="font-weight:400;font-size:.78rem;margin-left:6px;">+ aggiungi</a></label>
<label for="asset-subclass" class="form-label">Sottoclasse
<a href="#" onclick="addSubclassPrompt();return false;" style="font-weight:400;font-size:.78rem;margin-left:6px;">+ aggiungi</a>
<a href="#" onclick="renameSubclassPrompt();return false;" style="font-weight:400;font-size:.78rem;margin-left:8px;">✎ rinomina</a>
<a href="#" onclick="removeSubclassPrompt();return false;" style="font-weight:400;font-size:.78rem;margin-left:8px;">🗑 rimuovi</a>
</label>
<select class="form-select" id="asset-subclass">${subOpts}</select>
</div>
</div>