[FEAT] Visura auto-fill, adesione volontaria, modulo NCR/CAPA

1. Fix auto-fill visura: mapping corretto suggested_sector e employees_range,
   indicatori visivi verdi sui campi auto-compilati, fatturato sempre manuale
2. Adesione volontaria: colonna voluntary_compliance, checkbox in onboarding
   step 5 quando not_applicable, toggle in settings, reset su ri-classificazione
3. Modulo NCR/CAPA: NonConformityController con 10 endpoint API,
   tabelle non_conformities + capa_actions, generazione NCR dai gap assessment,
   predisposizione integrazione SistemiG.agile (webhook + sync)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 08:12:57 +01:00
co-authored by Claude Opus 4.6
parent 517cab76a5
commit 4e3408e9f6
11 changed files with 985 additions and 21 deletions
+44 -4
View File
@@ -79,6 +79,11 @@
color: var(--gray-600);
border: 1px solid var(--gray-300);
}
.entity-badge.voluntary {
background: var(--primary-bg);
color: var(--primary);
border: 1px solid var(--primary-light);
}
.plan-badge {
display: inline-flex;
align-items: center;
@@ -573,10 +578,14 @@
function renderEntityClassification(org) {
const type = org.entity_type || 'not_applicable';
const label = entityTypeLabels[type] || 'Non Applicabile';
const isVoluntary = org.voluntary_compliance == 1 && type === 'not_applicable';
const displayLabel = isVoluntary ? 'Adesione Volontaria' : (entityTypeLabels[type] || 'Non Applicabile');
const badgeClass = isVoluntary ? 'voluntary' : type;
let description = '';
if (type === 'essential') {
if (isVoluntary) {
description = 'L\'organizzazione ha scelto di aderire <strong>volontariamente</strong> ai requisiti della Direttiva NIS2, pur non rientrando nell\'ambito di applicazione obbligatorio. Le misure di sicurezza sono adottate su base volontaria.';
} else if (type === 'essential') {
description = 'L\'organizzazione rientra tra le entita\' <strong>Essenziali</strong> ai sensi della Direttiva NIS2. Soggetta a supervisione proattiva con sanzioni fino a EUR 10M o 2% del fatturato globale.';
} else if (type === 'important') {
description = 'L\'organizzazione rientra tra le entita\' <strong>Importanti</strong> ai sensi della Direttiva NIS2. Soggetta a supervisione reattiva con sanzioni fino a EUR 7M o 1,4% del fatturato globale.';
@@ -584,15 +593,46 @@
description = 'In base ai parametri attuali, l\'organizzazione <strong>non rientra</strong> nell\'ambito di applicazione della Direttiva NIS2. Si consiglia comunque di adottare le best practice di cybersecurity.';
}
// Voluntary toggle (only visible for not_applicable entities)
const voluntaryToggle = type === 'not_applicable' ? `
<div style="margin-top:16px; padding:16px; background:var(--gray-50); border-radius:var(--border-radius); border:1px solid var(--gray-200);">
<label style="display:flex; align-items:center; gap:10px; cursor:pointer;">
<input type="checkbox" id="org-voluntary" ${isVoluntary ? 'checked' : ''}
onchange="toggleVoluntaryCompliance(this.checked)"
style="accent-color:var(--primary); width:18px; height:18px; flex-shrink:0;">
<span style="font-size:0.875rem; font-weight:600; color:var(--gray-700);">Adesione Volontaria alla NIS2</span>
</label>
<p style="font-size:0.75rem; color:var(--gray-500); margin-top:6px; margin-left:28px;">
Aderisci volontariamente ai requisiti NIS2 anche se non obbligato dalla normativa.
</p>
</div>` : '';
document.getElementById('entity-classification').innerHTML = `
<div class="entity-badge ${type}">
<div class="entity-badge ${badgeClass}">
<svg viewBox="0 0 20 20" fill="currentColor" width="20" height="20"><path fill-rule="evenodd" d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z" clip-rule="evenodd"/></svg>
Entita' ${label}
Entita': ${displayLabel}
</div>
<p style="font-size:0.8125rem; color:var(--gray-600); line-height:1.6; max-width:640px;">${description}</p>
${voluntaryToggle}
`;
}
async function toggleVoluntaryCompliance(checked) {
if (!currentOrg) return;
try {
const result = await api.updateOrganization(currentOrg.id, { voluntary_compliance: checked ? 1 : 0 });
if (result.success) {
currentOrg.voluntary_compliance = checked ? 1 : 0;
renderEntityClassification(currentOrg);
showNotification(checked ? 'Adesione volontaria attivata.' : 'Adesione volontaria disattivata.', 'success');
} else {
showNotification(result.message || 'Errore.', 'error');
}
} catch (e) {
showNotification('Errore di connessione.', 'error');
}
}
function renderSubscriptionPlan(org) {
const plan = org.subscription_plan || 'free';
const planLabels = { free: 'Free', professional: 'Professional', enterprise: 'Enterprise' };