[FEAT] A2 UI: wizard onboarding esteso (bilancio, autonomia/gruppo, categorie size-independent art.3 c.5, mission GV.OC, codice etico facoltativo, 3 criteri sotto-soglia c.9 b/c/d) + classificazione v2 lato server + disclaimer ACN 'autovalutazione preliminare'. Markup Bootstrap Italia/AGID

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-06-15 17:17:34 +02:00
co-authored by Claude Opus 4.8
parent c5fce7bdf7
commit 717f9ac396
+119 -7
View File
@@ -303,6 +303,48 @@
<label class="active" for="annual-turnover">Fatturato Annuo EUR <span aria-hidden="true">*</span></label> <label class="active" for="annual-turnover">Fatturato Annuo EUR <span aria-hidden="true">*</span></label>
<input type="number" id="annual-turnover" class="form-control" placeholder="Es. 15000000" min="0" required> <input type="number" id="annual-turnover" class="form-control" placeholder="Es. 15000000" min="0" required>
</div> </div>
<div class="form-group">
<label class="active" for="total-balance">Totale di bilancio EUR</label>
<input type="number" id="total-balance" class="form-control" placeholder="Es. 12000000 (facoltativo)" min="0">
<small class="form-text text-muted">Terzo criterio dimensionale (Racc. 2003/361/CE): grande impresa se &gt; 43M&euro; di totale di bilancio.</small>
</div>
<div class="form-group">
<label class="active" for="is-autonomous">Struttura dell'impresa</label>
<select id="is-autonomous" class="form-select select-pdnd">
<option value="1">Impresa autonoma</option>
<option value="0">Parte di un gruppo (imprese collegate/associate)</option>
</select>
<small class="form-text text-muted">Se parte di un gruppo, le soglie vanno calcolate in modo consolidato (art. 6 Racc. 2003/361/CE).</small>
</div>
<div class="form-group">
<label class="active" for="size-independent-category">Categoria speciale NIS2 (a prescindere dalla dimensione)</label>
<select id="size-independent-category" class="form-select select-pdnd">
<option value="none">Nessuna delle seguenti</option>
<option value="dns">Fornitore di servizi DNS</option>
<option value="tld">Gestore di registri TLD</option>
<option value="ixp">Fornitore IXP (Internet Exchange Point)</option>
<option value="trust_qualified">Prestatore di servizi fiduciari qualificati</option>
<option value="trust_unqualified">Prestatore di servizi fiduciari non qualificati</option>
<option value="ecomm_public_network">Fornitore di reti pubbliche di comunicazione elettronica</option>
<option value="ecomm_public_service">Fornitore di servizi di comunicazione elettronica accessibili al pubblico</option>
<option value="domain_registrar">Fornitore di servizi di registrazione nomi di dominio</option>
<option value="pa_central">Pubblica Amministrazione centrale</option>
<option value="pa_regional_local">Pubblica Amministrazione regionale/locale</option>
</select>
<small class="form-text text-muted">Questi soggetti rientrano nell'ambito a prescindere dalle soglie (art. 3, comma 5, D.Lgs. 138/2024).</small>
</div>
<div class="form-group">
<label class="active" for="mission">Mission / contesto organizzativo</label>
<textarea id="mission" class="form-control" rows="2" placeholder="Obiettivi, servizi critici e dipendenze principali (facoltativo)"></textarea>
<small class="form-text text-muted">Alimenta il contesto organizzativo e il registro dei sistemi rilevanti (misura GV.OC).</small>
</div>
<div class="form-group mb-0">
<div class="form-check">
<input type="checkbox" id="has-code-of-ethics" class="form-check-input">
<label class="form-check-label" for="has-code-of-ethics">L'organizzazione ha adottato un codice etico</label>
</div>
<small class="form-text text-muted">Facoltativo &mdash; buona prassi di governance, non richiesto dalla NIS2.</small>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -384,6 +426,21 @@
<div class="classification-result-desc" id="classification-desc"></div> <div class="classification-result-desc" id="classification-desc"></div>
</div> </div>
<ul class="mb-4" id="classification-obligations" style="display:none"></ul>
<div class="card card-bg has-bkg-grey shadow mb-4" id="subthreshold-block">
<div class="card-header bg-white"><h3 class="h6 mb-0">Sei sotto le soglie dimensionali? — criteri qualitativi (art. 3, comma 9, D.Lgs. 138/2024)</h3></div>
<div class="card-body">
<p class="small text-muted mb-3">Compila solo se la tua organizzazione <strong>non supera</strong> le soglie dimensionali: anche un soggetto sotto-soglia può essere individuato dall'Autorità (ACN) se ricorre uno di questi criteri.</p>
<div class="form-check"><input type="checkbox" id="crit-sole" class="form-check-input"><label class="form-check-label" for="crit-sole">Siamo l'<strong>unico fornitore nazionale</strong> di un servizio essenziale per attività sociali o economiche fondamentali <em>(lett. b)</em></label></div>
<div class="form-check"><input type="checkbox" id="crit-impact" class="form-check-input"><label class="form-check-label" for="crit-impact">Un'interruzione del nostro servizio avrebbe <strong>impatto significativo su sicurezza, incolumità o salute pubblica</strong> <em>(lett. c)</em></label></div>
<div class="form-check"><input type="checkbox" id="crit-systemic" class="form-check-input"><label class="form-check-label" for="crit-systemic">Un'interruzione comporterebbe un <strong>rischio sistemico significativo</strong>, anche transfrontaliero <em>(lett. d)</em></label></div>
<button class="btn btn-sm btn-outline-primary mt-3" type="button" onclick="runClassificationAndSummary()">Ricalcola classificazione</button>
</div>
</div>
<div class="alert alert-info" id="classification-disclaimer" role="alert" style="font-size:.85rem;display:none"></div>
<div class="voluntary-section" id="voluntary-section"> <div class="voluntary-section" id="voluntary-section">
<div class="voluntary-card" id="voluntary-card"> <div class="voluntary-card" id="voluntary-card">
<input type="checkbox" id="voluntary-checkbox" onchange="onVoluntaryChange(this.checked)" aria-label="Adesione volontaria alla NIS2"> <input type="checkbox" id="voluntary-checkbox" onchange="onVoluntaryChange(this.checked)" aria-label="Adesione volontaria alla NIS2">
@@ -472,7 +529,10 @@
visuraFile: null, visuraFile: null,
company: { company: {
name: '', vat_number: '', fiscal_code: '', address: '', city: '', name: '', vat_number: '', fiscal_code: '', address: '', city: '',
website: '', email: '', phone: '', sector: '', employee_count: '', annual_turnover: '' website: '', email: '', phone: '', sector: '', employee_count: '', annual_turnover: '',
total_balance: '', is_autonomous: 1, size_independent_category: 'none',
crit_sole_supplier: 0, crit_public_impact: 0, crit_systemic_risk: 0,
mission: '', has_code_of_ethics: 0
}, },
profile: { full_name: '', role: '', phone: '' }, profile: { full_name: '', role: '', phone: '' },
classification: null, classification: null,
@@ -714,6 +774,11 @@
wizardState.company.sector = sector; wizardState.company.sector = sector;
wizardState.company.employee_count = parseInt(employees); wizardState.company.employee_count = parseInt(employees);
wizardState.company.annual_turnover = parseInt(turnover); wizardState.company.annual_turnover = parseInt(turnover);
wizardState.company.total_balance = parseInt(document.getElementById('total-balance').value) || 0;
wizardState.company.is_autonomous = parseInt(document.getElementById('is-autonomous').value);
wizardState.company.size_independent_category = document.getElementById('size-independent-category').value;
wizardState.company.mission = document.getElementById('mission').value.trim();
wizardState.company.has_code_of_ethics = document.getElementById('has-code-of-ethics').checked ? 1 : 0;
goToStep(3); goToStep(3);
} }
@@ -774,20 +839,43 @@
} }
} }
function runClassificationAndSummary() { async function runClassificationAndSummary() {
const c = wizardState.company; const c = wizardState.company;
const sector = c.sector;
const employees = parseInt(c.employee_count) || 0; const employees = parseInt(c.employee_count) || 0;
const turnover = parseInt(c.annual_turnover) || 0; const turnover = parseInt(c.annual_turnover) || 0;
const result = classifyLocally(sector, employees, turnover); // criteri qualitativi sotto-soglia (art. 3 c. 9) letti dai checkbox dello step 4
c.crit_sole_supplier = (document.getElementById('crit-sole') || {}).checked ? 1 : 0;
c.crit_public_impact = (document.getElementById('crit-impact') || {}).checked ? 1 : 0;
c.crit_systemic_risk = (document.getElementById('crit-systemic') || {}).checked ? 1 : 0;
// Classificazione v2 lato server (single source of truth: Nis2ClassificationService)
let result = null;
try {
const resp = await api.post('/organizations/classify', {
sector: c.sector, employee_count: employees, annual_turnover_eur: turnover,
total_balance_eur: parseInt(c.total_balance) || 0,
is_autonomous: c.is_autonomous, size_independent_category: c.size_independent_category,
crit_sole_supplier: c.crit_sole_supplier, crit_public_impact: c.crit_public_impact, crit_systemic_risk: c.crit_systemic_risk
});
if (resp && resp.success && resp.data) {
const d = resp.data;
result = { classification: d.entity_type, label: d.label, description: d.explanation,
designation_basis: d.designation_basis, sub_threshold_candidate: !!d.sub_threshold_candidate,
disclaimer: d.disclaimer || '', obligations: d.obligations || [] };
}
} catch (e) { /* fallback locale sotto */ }
if (!result) {
const r = classifyLocally(c.sector, employees, turnover);
result = { classification: r.classification, label: r.label, description: r.description, obligations: [], disclaimer: '', sub_threshold_candidate: false };
}
wizardState.classification = result; wizardState.classification = result;
// Adesione volontaria: visibile solo per soggetti non in ambito
wizardState.voluntaryCompliance = false; wizardState.voluntaryCompliance = false;
const voluntaryCheckbox = document.getElementById('voluntary-checkbox'); const voluntaryCheckbox = document.getElementById('voluntary-checkbox');
if (voluntaryCheckbox) voluntaryCheckbox.checked = false; if (voluntaryCheckbox) voluntaryCheckbox.checked = false;
const voluntaryCard = document.getElementById('voluntary-card'); const voluntaryCard = document.getElementById('voluntary-card');
if (voluntaryCard) voluntaryCard.classList.remove('checked'); if (voluntaryCard) voluntaryCard.classList.remove('checked');
const voluntarySection = document.getElementById('voluntary-section'); const voluntarySection = document.getElementById('voluntary-section');
if (result.classification === 'not_applicable') voluntarySection.classList.add('visible'); if (result.classification === 'not_applicable') voluntarySection.classList.add('visible');
else voluntarySection.classList.remove('visible'); else voluntarySection.classList.remove('visible');
@@ -809,8 +897,24 @@
iconEl.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>'; iconEl.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
} }
labelEl.textContent = result.label; // Etichetta + badge "autovalutazione preliminare"
descEl.textContent = result.description; labelEl.textContent = (result.label || '') + ' — autovalutazione preliminare';
descEl.textContent = result.description || '';
// Obblighi (dal server, citano il D.Lgs. 138/2024)
const obEl = document.getElementById('classification-obligations');
if (obEl) {
if (result.obligations && result.obligations.length) {
obEl.innerHTML = result.obligations.map(o => '<li>' + String(o).replace(/[<>]/g, '') + '</li>').join('');
obEl.style.display = '';
} else { obEl.style.display = 'none'; obEl.innerHTML = ''; }
}
// Disclaimer ACN (non vincolante)
const disEl = document.getElementById('classification-disclaimer');
if (disEl) {
if (result.disclaimer) { disEl.textContent = result.disclaimer; disEl.style.display = ''; }
else { disEl.style.display = 'none'; }
}
const sectorSelect = document.getElementById('sector'); const sectorSelect = document.getElementById('sector');
const sectorOption = sectorSelect.querySelector('option[value="' + c.sector + '"]'); const sectorOption = sectorSelect.querySelector('option[value="' + c.sector + '"]');
@@ -860,6 +964,14 @@
contact_email: c.email, contact_phone: c.phone, sector: c.sector, contact_email: c.email, contact_phone: c.phone, sector: c.sector,
employee_count: parseInt(c.employee_count) || 0, employee_count: parseInt(c.employee_count) || 0,
annual_turnover_eur: parseInt(c.annual_turnover) || 0, annual_turnover_eur: parseInt(c.annual_turnover) || 0,
total_balance_eur: parseInt(c.total_balance) || 0,
is_autonomous: c.is_autonomous,
size_independent_category: c.size_independent_category,
crit_sole_supplier: c.crit_sole_supplier,
crit_public_impact: c.crit_public_impact,
crit_systemic_risk: c.crit_systemic_risk,
mission: c.mission,
has_code_of_ethics: c.has_code_of_ethics,
voluntary_compliance: wizardState.voluntaryCompliance ? 1 : 0, voluntary_compliance: wizardState.voluntaryCompliance ? 1 : 0,
full_name: p.full_name, phone: p.phone, country: 'IT', full_name: p.full_name, phone: p.phone, country: 'IT',
}; };