[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
+211 -7
View File
@@ -598,6 +598,91 @@
gap: 12px;
}
/* ── Auto-fill indicators ──────────────────────────────────── */
.auto-filled .form-input,
.auto-filled .form-select {
border-color: #86efac;
background: #f0fdf4;
}
.auto-fill-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
margin-left: 8px;
font-size: 0.65rem;
font-weight: 600;
color: #15803d;
background: #dcfce7;
border-radius: 100px;
text-transform: uppercase;
letter-spacing: 0.03em;
vertical-align: middle;
}
/* ── Voluntary Compliance ──────────────────────────────────── */
.voluntary-section {
display: none;
margin-bottom: 24px;
}
.voluntary-section.visible {
display: block;
}
.voluntary-card {
border: 2px solid var(--primary-light);
border-radius: var(--border-radius-lg);
padding: 24px;
display: flex;
align-items: flex-start;
gap: 16px;
background: var(--card-bg);
transition: all var(--transition);
}
.voluntary-card.checked {
border-color: var(--primary);
background: var(--primary-bg);
}
.voluntary-card input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: var(--primary);
flex-shrink: 0;
margin-top: 2px;
}
.voluntary-card h4 {
font-size: 0.9375rem;
font-weight: 700;
color: var(--gray-900);
margin-bottom: 6px;
}
.voluntary-card p {
font-size: 0.8125rem;
color: var(--gray-600);
line-height: 1.6;
margin: 0;
}
.classification-result.voluntary {
border-color: var(--primary);
background: linear-gradient(135deg, #eff6ff, #dbeafe);
}
.classification-result.voluntary .classification-result-icon {
background: var(--primary);
color: #fff;
}
.classification-result.voluntary .classification-result-label {
color: var(--primary);
}
/* ── Responsive ─────────────────────────────────────────────── */
@media (max-width: 768px) {
.wizard-header {
@@ -1040,6 +1125,22 @@
<div class="classification-result-desc" id="classification-desc"></div>
</div>
<!-- Voluntary Compliance Option -->
<div class="voluntary-section" id="voluntary-section">
<div class="voluntary-card" id="voluntary-card">
<input type="checkbox" id="voluntary-checkbox" onchange="onVoluntaryChange(this.checked)">
<div>
<h4>Adesione Volontaria alla NIS2</h4>
<p>
Anche se la tua organizzazione non rientra nell'ambito di applicazione obbligatorio della
Direttiva NIS2, puoi scegliere di aderire volontariamente adottando le misure di sicurezza
previste dalla normativa. Questo consente di migliorare la postura di cybersicurezza e di
prepararsi a possibili future designazioni.
</p>
</div>
</div>
</div>
<!-- Summary -->
<div class="summary-card">
<div class="summary-card-header">Riepilogo Dati Aziendali</div>
@@ -1142,9 +1243,22 @@
role: '',
phone: ''
},
classification: null
classification: null,
autoFilledFields: new Set(),
voluntaryCompliance: false
};
// ── Helper: parse employees range string to number ────────
function parseEmployeesRange(range) {
if (!range) return '';
if (typeof range === 'number') return range;
const str = String(range).trim();
if (str.endsWith('+')) return parseInt(str);
const parts = str.split('-');
if (parts.length === 2) return parseInt(parts[0]);
return parseInt(str) || '';
}
// ── Step 1: Method selection ────────────────────────────────────
function selectMethod(method) {
wizardState.method = method;
@@ -1308,11 +1422,24 @@
wizardState.company.website = d.website || d.sito_web || '';
wizardState.company.email = d.email || d.pec || '';
wizardState.company.phone = d.phone || d.telefono || '';
wizardState.company.sector = d.sector || '';
wizardState.company.employee_count = d.employee_count || d.employees || '';
wizardState.company.annual_turnover = d.annual_turnover || d.turnover || '';
wizardState.company.sector = d.suggested_sector || d.sector || '';
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
// Fatturato resta SEMPRE manuale — non auto-compilare dalla visura
wizardState.company.annual_turnover = '';
wizardState.dataSource = 'Dati precompilati dalla visura camerale';
// Track auto-filled fields
wizardState.autoFilledFields.clear();
if (wizardState.company.name) wizardState.autoFilledFields.add('company-name');
if (wizardState.company.vat_number) wizardState.autoFilledFields.add('vat-number');
if (wizardState.company.fiscal_code) wizardState.autoFilledFields.add('fiscal-code');
if (wizardState.company.address) wizardState.autoFilledFields.add('address');
if (wizardState.company.city) wizardState.autoFilledFields.add('city');
if (wizardState.company.email) wizardState.autoFilledFields.add('company-email');
if (wizardState.company.phone) wizardState.autoFilledFields.add('company-phone');
if (wizardState.company.sector) wizardState.autoFilledFields.add('sector');
if (wizardState.company.employee_count) wizardState.autoFilledFields.add('employee-count');
showNotification('Visura analizzata con successo!', 'success');
// Auto-advance to step 3
@@ -1361,11 +1488,24 @@
wizardState.company.website = d.website || d.sito_web || '';
wizardState.company.email = d.email || d.pec || '';
wizardState.company.phone = d.phone || d.telefono || '';
wizardState.company.sector = d.sector || '';
wizardState.company.employee_count = d.employee_count || d.employees || '';
wizardState.company.annual_turnover = d.annual_turnover || d.turnover || '';
wizardState.company.sector = d.suggested_sector || d.sector || '';
wizardState.company.employee_count = parseEmployeesRange(d.employees_range) || d.employee_count || d.employees || '';
// Fatturato resta SEMPRE manuale
wizardState.company.annual_turnover = '';
wizardState.dataSource = 'Dati recuperati da CertiSource';
// Track auto-filled fields
wizardState.autoFilledFields.clear();
if (wizardState.company.name) wizardState.autoFilledFields.add('company-name');
if (wizardState.company.vat_number) wizardState.autoFilledFields.add('vat-number');
if (wizardState.company.fiscal_code) wizardState.autoFilledFields.add('fiscal-code');
if (wizardState.company.address) wizardState.autoFilledFields.add('address');
if (wizardState.company.city) wizardState.autoFilledFields.add('city');
if (wizardState.company.email) wizardState.autoFilledFields.add('company-email');
if (wizardState.company.phone) wizardState.autoFilledFields.add('company-phone');
if (wizardState.company.sector) wizardState.autoFilledFields.add('sector');
if (wizardState.company.employee_count) wizardState.autoFilledFields.add('employee-count');
showNotification('Dati aziendali recuperati con successo!', 'success');
// Auto-advance to step 3
@@ -1413,6 +1553,27 @@
} else {
banner.classList.remove('visible');
}
// Apply auto-fill visual indicators
const autoFields = wizardState.autoFilledFields;
document.querySelectorAll('#step-3 .form-input, #step-3 .form-select').forEach(el => {
const wrapper = el.closest('.form-group');
if (!wrapper) return;
// Remove previous indicators
wrapper.classList.remove('auto-filled');
const existing = wrapper.querySelector('.auto-fill-badge');
if (existing) existing.remove();
if (autoFields.has(el.id)) {
wrapper.classList.add('auto-filled');
const badge = document.createElement('span');
badge.className = 'auto-fill-badge';
badge.textContent = 'da visura';
const label = wrapper.querySelector('.form-label');
if (label) label.appendChild(badge);
}
});
}
function submitStep3() {
@@ -1553,6 +1714,21 @@
const result = classifyLocally(sector, employees, turnover);
wizardState.classification = result;
// Reset voluntary state when re-entering step 5
wizardState.voluntaryCompliance = false;
const voluntaryCheckbox = document.getElementById('voluntary-checkbox');
if (voluntaryCheckbox) voluntaryCheckbox.checked = false;
const voluntaryCard = document.getElementById('voluntary-card');
if (voluntaryCard) voluntaryCard.classList.remove('checked');
// Show/hide voluntary compliance section
const voluntarySection = document.getElementById('voluntary-section');
if (result.classification === 'not_applicable') {
voluntarySection.classList.add('visible');
} else {
voluntarySection.classList.remove('visible');
}
// Update classification UI
const container = document.getElementById('classification-result');
const labelEl = document.getElementById('classification-label');
@@ -1589,6 +1765,32 @@
document.getElementById('sum-turnover').textContent = turnover ? '\u20AC ' + turnover.toLocaleString('it-IT') : '-';
}
// ── Voluntary Compliance Toggle ───────────────────────────────
function onVoluntaryChange(checked) {
wizardState.voluntaryCompliance = checked;
const card = document.getElementById('voluntary-card');
const container = document.getElementById('classification-result');
const labelEl = document.getElementById('classification-label');
const sumEntityType = document.getElementById('sum-entity-type');
card.classList.toggle('checked', checked);
if (checked) {
container.className = 'classification-result voluntary';
container.querySelector('.classification-result-icon').innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 2.18l7 3.12v4.7c0 4.83-3.23 9.36-7 10.57-3.77-1.21-7-5.74-7-10.57V6.3l7-3.12z"/><path d="M10 12.5l-2-2-1.41 1.41L10 15.32l5.41-5.41L14 8.5l-4 4z"/></svg>';
labelEl.textContent = 'Adesione Volontaria';
document.getElementById('classification-desc').textContent = 'La tua organizzazione aderisce volontariamente alla Direttiva NIS2, adottando le misure di sicurezza previste dalla normativa per migliorare la propria postura di cybersicurezza.';
sumEntityType.textContent = 'Adesione Volontaria';
} else {
container.className = 'classification-result not-applicable';
container.querySelector('.classification-result-icon').innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><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 = wizardState.classification.label;
document.getElementById('classification-desc').textContent = wizardState.classification.description;
sumEntityType.textContent = wizardState.classification.label;
}
}
// ── Complete Onboarding ─────────────────────────────────────────
async function completeOnboarding() {
const btn = document.getElementById('complete-btn');
@@ -1612,6 +1814,8 @@
sector: c.sector,
employee_count: parseInt(c.employee_count) || 0,
annual_turnover_eur: parseInt(c.annual_turnover) || 0,
// Voluntary compliance
voluntary_compliance: wizardState.voluntaryCompliance ? 1 : 0,
// Profile data
full_name: p.full_name,
phone: p.phone,