From 99bd293745f318d1857f4432db59afaf19e2792c Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Thu, 18 Jun 2026 19:37:51 +0200 Subject: [PATCH] =?UTF-8?q?[FEAT]=20#384=20p.6=20=E2=80=94=20Matrice=20RAC?= =?UTF-8?q?I=20sulle=20policy=20+=20R/A=20obbligatorie=20in=20approvazione?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sezione 'Matrice RACI' nel dettaglio policy: assegnazione ruoli organigramma (org_roles) alle responsabilita R/A/C/I via infrastruttura RACI esistente (raci_assignments, object_type='procedure'). In bozza libera; PolicyController::approve ora richiede almeno un ruolo R e uno A (422 RACI_REQUIRED). Nessuna migrazione. Co-Authored-By: Claude Opus 4.8 --- application/controllers/PolicyController.php | 19 +++ public/policies.html | 126 +++++++++++++++++++ public/version.json | 2 +- 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/application/controllers/PolicyController.php b/application/controllers/PolicyController.php index a7196fb..eb606ea 100644 --- a/application/controllers/PolicyController.php +++ b/application/controllers/PolicyController.php @@ -237,6 +237,25 @@ class PolicyController extends BaseController { $this->requireOrgRole(['org_admin']); + // Ticket #384 punto 6 — gate RACI: in approvazione R e A sono obbligatorie + // (in bozza l'assegnazione resta libera). Usa l'infrastruttura RACI esistente + // (raci_assignments, object_type='procedure' -> policy_id). Decisione di design + // del richiedente super_admin: NON e' un vincolo normativo, e' una regola di prodotto. + $assignedRaci = Database::fetchAll( + "SELECT DISTINCT raci FROM raci_assignments + WHERE organization_id = ? AND object_type = 'procedure' AND object_id = ?", + [$this->getCurrentOrgId(), $id] + ); + $present = array_column($assignedRaci, 'raci'); + $missing = array_values(array_diff(['R', 'A'], $present)); + if (!empty($missing)) { + $this->jsonError( + 'Per approvare la policy assegna nella Matrice RACI almeno un ruolo ' . implode(' e uno ', $missing) . '.', + 422, + 'RACI_REQUIRED' + ); + } + Database::update('policies', [ 'status' => 'approved', 'approved_by' => $this->getCurrentUserId(), diff --git a/public/policies.html b/public/policies.html index ed6dc3d..d6e046c 100644 --- a/public/policies.html +++ b/public/policies.html @@ -661,8 +661,26 @@

Formati ammessi: PDF, immagini, documenti Office, txt, csv, zip. Dimensione massima 10 MB.

+ +
+
+

Matrice RACI

+
+
+

+ Assegna le responsabilita' ai ruoli dell'organigramma: + R Responsible (esegue), A Accountable (approva/risponde), + C Consulted (consultato), I Informed (informato). + In bozza l'assegnazione e' facoltativa; per approvare la policy sono obbligatori almeno un ruolo R e uno A. +

+
+

Caricamento matrice RACI...

+
+
+
`; loadPolicyAttachments(policy.id); + loadPolicyRaci(policy.id); } function backToList() { @@ -750,6 +768,114 @@ } } + // ── Matrice RACI Policy (ticket #384 punto 6) ─────────────── + // Riusa l'infrastruttura RACI esistente (org_roles + raci_assignments, + // object_type='procedure' -> policy_id). Nessuna migrazione. + const RACI_COLS = ['', 'R', 'A', 'C', 'I']; + const RACI_TITLES = { + '': 'Nessuna responsabilita', + 'R': 'Responsible — esegue il lavoro', + 'A': 'Accountable — approva e ne risponde', + 'C': 'Consulted — viene consultato', + 'I': 'Informed — viene informato' + }; + window._policyRaciMap = window._policyRaciMap || {}; + + function canManageRaci() { + const role = (localStorage.getItem('nis2_user_role') || '').toLowerCase(); + return ['super_admin', 'org_admin', 'compliance_manager'].includes(role); + } + + async function loadPolicyRaci(policyId) { + const box = document.getElementById('policy-raci-' + policyId); + if (!box) return; + try { + const result = await api.raciMatrix(); + if (!result || !result.success || !result.data) { + box.innerHTML = '

Impossibile caricare la matrice RACI.

'; + return; + } + const roles = result.data.roles || []; + const map = {}; + (result.data.assignments || []).forEach(a => { + if (a.object_type === 'procedure' && Number(a.object_id) === Number(policyId)) { + map[a.role_id] = a.raci; + } + }); + window._policyRaciMap[policyId] = map; + + if (!roles.length) { + box.innerHTML = '

Nessun ruolo definito. Aggiungi i ruoli nell\'organigramma per assegnare la matrice RACI.

'; + return; + } + const editable = canManageRaci(); + const vals = Object.values(map); + const hasR = vals.includes('R'); + const hasA = vals.includes('A'); + const missing = [!hasR ? 'R' : null, !hasA ? 'A' : null].filter(Boolean); + const banner = (hasR && hasA) + ? 'Requisiti di approvazione soddisfatti (R e A assegnate)' + : 'Per l\'approvazione manca: ' + missing.join(' e ') + ''; + + let html = '
' + banner + '
'; + html += '
'; + html += ''; + RACI_COLS.forEach(L => { + html += ''; + }); + html += ''; + roles.forEach(r => { + const cur = map[r.role_id] || ''; + const rn = escapeHtml(r.role_name || ('Ruolo #' + r.role_id)); + html += ''; + RACI_COLS.forEach(L => { + const checked = (cur === L) ? 'checked' : ''; + const dis = editable ? '' : ' disabled'; + html += ''; + }); + html += ''; + }); + html += '
Ruolo (organigramma)' + (L === '' ? '—' : L) + '
' + rn + (r.is_governance_body ? ' organo di governo' : '') + '' + + '
'; + if (!editable) { + html += '

Solo Amministratore o Compliance Manager possono modificare la matrice RACI.

'; + } + box.innerHTML = html; + } catch (e) { + box.innerHTML = '

Errore nel caricamento della matrice RACI.

'; + } + } + + async function setPolicyRaci(policyId, roleId, raci) { + if (!canManageRaci()) { + showNotification('Non hai i permessi per modificare la matrice RACI (richiede Amministratore o Compliance Manager).', 'warning'); + loadPolicyRaci(policyId); + return; + } + const curMap = window._policyRaciMap[policyId] || {}; + const cur = curMap[roleId] || ''; + if (cur === raci) return; // no-op: stato gia' allineato + try { + let result; + if (raci === '') { + result = await api.raciUnassign({ role_id: roleId, object_type: 'procedure', object_id: policyId }); + } else { + result = await api.raciAssign({ role_id: roleId, object_type: 'procedure', object_id: policyId, raci: raci }); + } + if (result && result.success) { + showNotification('Matrice RACI aggiornata.', 'success'); + } else { + showNotification((result && result.message) || 'Errore nell\'aggiornamento della matrice RACI.', 'error'); + } + } catch (e) { + showNotification('Errore di connessione.', 'error'); + } + loadPolicyRaci(policyId); + } + // ── Create / Edit Modal ───────────────────────────────── function openCreateModal() { showPolicyFormModal(null); diff --git a/public/version.json b/public/version.json index 547768f..837c9bb 100644 --- a/public/version.json +++ b/public/version.json @@ -1 +1 @@ -{"version": "1.23.7", "build": "2026-06-18-v1.23.7", "date": "2026-06-18", "changelog": "Ticket #384 punti 2.2 e 3 (UI multi-valore + link attivi bidirezionali). In MISURE E REQUISITI la colonna 'Policy (default)' ora mostra TUTTE le policy collegate al requisito (non piu' una sola) come codici 'Policy.NN' cliccabili: il clic porta direttamente alla policy nel menu POLICY. Nel menu POLICY la colonna 'Requisito NIS2' ora mostra TUTTI i requisiti collegati con la codifica MISURA+REQUISITO (es. 'DE.CM-01 comma 1.') come link cliccabili: il clic porta al requisito in MISURE E REQUISITI, evidenziandolo. Navigazione nei due sensi (Policy <-> Misure e Requisiti). Nessuna modifica ai dati: poggia sul modello relazionale gia' introdotto (tabella requisito_policy, mig.059). Resta a fasi: matrice RACI assegnabile per policy con R e A obbligatorie in approvazione (punto 6)."} +{"version": "1.23.8", "build": "2026-06-18-v1.23.8", "date": "2026-06-18", "changelog": "Ticket #384 punto 6 - Matrice RACI sulle policy. Nel dettaglio di ogni policy compare la sezione 'Matrice RACI': si assegnano i ruoli dell'organigramma alle responsabilita R (esegue), A (approva/risponde), C (consultato), I (informato). In bozza l'assegnazione e' libera; in fase di APPROVAZIONE sono ora obbligatori almeno un ruolo R e uno A (altrimenti l'approvazione viene bloccata con un messaggio che invita a completare la matrice). La A resta coerente con lo stato di approvazione gia' presente. Poggia sull'infrastruttura RACI esistente (org_roles + raci_assignments, object_type='procedure'), nessuna migrazione. Resta da rifinire help contestuale/traduzioni del nuovo pannello RACI."}