[FEAT] Supply chain: bottone 'Invia questionario' al fornitore (finding review CISO 🔴C)

Il backend sendQuestionnaire/questionnaire-status esisteva ma la UI non lo esponeva (guida prometteva una funzione non azionabile). Aggiunti:
- bottone 'Invia questionario' in lista (azioni) e in dettaglio fornitore
- funzione sendSupplierQuestionnaire (chiede email opzionale, mostra il link sq_ generato)
- api.js: sendSupplierQuestionnaire + getSupplierQuestionnaireStatus
Distinto 'Valuta (interna)' da 'Invia questionario' (link esterno). E2E prod: 201 + link generato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile 2026-05-31 08:17:05 +02:00
parent fffee8e0bc
commit 5ff58aeceb
2 changed files with 30 additions and 2 deletions

View File

@ -269,7 +269,9 @@ class NIS2API {
importAssets(data) { return this.post('/assets/import', data); } // P2 import CMDB/CSV importAssets(data) { return this.post('/assets/import', data); } // P2 import CMDB/CSV
getControlsMonitoring() { return this.get('/audit/controlsMonitoring'); } getControlsMonitoring() { return this.get('/audit/controlsMonitoring'); }
getAcnRequirements() { return this.get('/audit/acnRequirements'); } // requisiti ACN per org getAcnRequirements() { return this.get('/audit/acnRequirements'); } // requisiti ACN per org
updateAcnRequirement(id, status, note) { return this.put(`/audit/acnRequirements/${id}`, { status, evidence_note: note }); } // P1 continuous control monitoring (JWT) updateAcnRequirement(id, status, note) { return this.put(`/audit/acnRequirements/${id}`, { status, evidence_note: note }); }
sendSupplierQuestionnaire(id, email) { return this.post(`/supply-chain/${id}/send-questionnaire`, email ? { email } : {}); } // self-assessment fornitore (link esterno)
getSupplierQuestionnaireStatus(id) { return this.get(`/supply-chain/${id}/questionnaire-status`); } // P1 continuous control monitoring (JWT)
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════
// Audit // Audit

View File

@ -661,9 +661,12 @@
<button class="btn btn-sm btn-ghost" onclick="openEditModal(${s.id})" title="Modifica"> <button class="btn btn-sm btn-ghost" onclick="openEditModal(${s.id})" title="Modifica">
<svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg> <svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg>
</button> </button>
<button class="btn btn-sm btn-primary" onclick="openAssessmentModal(${s.id})" title="Valuta"> <button class="btn btn-sm btn-primary" onclick="openAssessmentModal(${s.id})" title="Valuta (interna)">
<svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm9.707 5.707a1 1 0 00-1.414-1.414L9 12.586l-1.293-1.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg> <svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm9.707 5.707a1 1 0 00-1.414-1.414L9 12.586l-1.293-1.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>
</button> </button>
<button class="btn btn-sm btn-ghost" onclick="sendSupplierQuestionnaire(${s.id}, '${escapeHtml((s.name||'').replace(/'/g,"\\'"))}')" title="Invia questionario al fornitore">
<svg viewBox="0 0 20 20" fill="currentColor" width="14" height="14"><path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/><path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/></svg>
</button>
</div> </div>
</td> </td>
</tr>`; </tr>`;
@ -1130,6 +1133,29 @@
} }
} }
// ── Invio questionario self-assessment al fornitore (link esterno) ──
async function sendSupplierQuestionnaire(supplierId, supplierName) {
const def = '';
const email = prompt('Invia il questionario di sicurezza a "' + (supplierName || 'fornitore') +
'".\nEmail del fornitore (lascia vuoto per usare il contatto in anagrafica):', def);
if (email === null) return; // annullato
try {
const result = await api.sendSupplierQuestionnaire(supplierId, email.trim() || null);
if (result.success) {
const link = result.data?.link;
showNotification('Questionario inviato' + (result.data?.sent_to ? ' a ' + result.data.sent_to : '') + '.', 'success');
if (link) {
// mostra anche il link generato (utile se l'email non parte / per invio manuale)
prompt('Link del questionario (valido 30 giorni) — puoi inoltrarlo manualmente:', link);
}
} else {
showNotification(result.message || 'Errore nell\'invio del questionario.', 'error');
}
} catch (e) {
showNotification('Errore di connessione.', 'error');
}
}
// ── Init ──────────────────────────────────────────────── // ── Init ────────────────────────────────────────────────
loadSuppliers(); loadSuppliers();
</script> </script>