From f6d20766846e1aba2554af387aa318be0d2a3783 Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Mon, 27 Jul 2026 15:32:59 +0200 Subject: [PATCH] =?UTF-8?q?[FEAT]=20Rischi=20#500=20opzione=20B:=20matrice?= =?UTF-8?q?=20dai=20rischi-requisito=20(probabilit=C3=A0=C3=97impatto)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ogni rischio derivato da un requisito puo' ora essere valutato con Probabilita' e Impatto (1-5) e compare nella matrice 5x5. - DB: mig 066 (org_requisito_state += likelihood/impact) + runner idempotente. - Backend: FrameworkController::setState accetta likelihood/impact; RiskController ::derivedList ritorna likelihood/impact/score. Applicata su prod + reload. - Frontend risks.html: colonne Probabilita'/Impatto/Rischio(P×I) + tasto "Valuta" (modale, riservato a super_admin/org_admin/compliance_manager); matrice calcolata client-side dai rischi derivati valutati (esclusi i non_applicabile, caveat nis2-expert) + rischi custom. Framing normativo invariato. - help.js/i18n aggiornati; version 1.25.7; buster help/i18n ?v=20260727b. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../migrate_066_requisito_risk_scoring.php | 36 ++++ .../controllers/FrameworkController.php | 21 +- application/controllers/RiskController.php | 6 +- docs/sql/066_requisito_risk_scoring.sql | 8 + public/admin/index.html | 4 +- public/admin/organizations.html | 4 +- public/admin/users.html | 4 +- public/architecture.html | 4 +- public/assessment.html | 4 +- public/assets.html | 4 +- public/calendario.html | 4 +- public/companies.html | 4 +- public/competenze.html | 4 +- public/connettori-discovery.html | 4 +- public/controlli-periodici.html | 4 +- public/cross-analysis.html | 4 +- public/dashboard.html | 4 +- public/guida.html | 4 +- public/incidents.html | 4 +- public/integrazioniext.html | 4 +- public/internal-audits.html | 4 +- public/isms.html | 4 +- public/js/help.js | 4 +- public/js/i18n.js | 5 + public/kb.html | 4 +- public/management-review.html | 4 +- public/misure-requisiti.html | 4 +- public/normative.html | 4 +- public/organigramma.html | 4 +- public/policies.html | 4 +- public/raci.html | 4 +- public/reports.html | 4 +- public/review-schedule.html | 4 +- public/risks.html | 202 ++++++++++++++++-- public/settings.html | 4 +- public/stakeholder-activities.html | 4 +- public/stakeholders.html | 4 +- public/supply-chain.html | 4 +- public/training.html | 4 +- public/version.json | 2 +- public/whistleblowing.html | 4 +- public/workflow.html | 4 +- 42 files changed, 326 insertions(+), 94 deletions(-) create mode 100644 application/cli/migrate_066_requisito_risk_scoring.php create mode 100644 docs/sql/066_requisito_risk_scoring.sql diff --git a/application/cli/migrate_066_requisito_risk_scoring.php b/application/cli/migrate_066_requisito_risk_scoring.php new file mode 100644 index 0000000..f984c09 --- /dev/null +++ b/application/cli/migrate_066_requisito_risk_scoring.php @@ -0,0 +1,36 @@ +Com.N) sulla matrice 5x5. + * Additiva e IDEMPOTENTE. Nessun dato esistente toccato (valori NULL = non ancora valutato). + * + * Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_066_requisito_risk_scoring.php + */ +if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); } +require_once __DIR__ . '/../config/env.php'; +require_once __DIR__ . '/../config/database.php'; + +$pdo = Database::getInstance(); + +function colExists(PDO $pdo, string $table, string $col): bool { + $st = $pdo->prepare( + "SELECT 1 FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?" + ); + $st->execute([$table, $col]); + return (bool) $st->fetchColumn(); +} + +$adds = [ + 'likelihood' => "ADD COLUMN likelihood TINYINT NULL COMMENT 'Probabilita 1-5 (rischio da non conformita del requisito)' AFTER valutazione_rischio", + 'impact' => "ADD COLUMN impact TINYINT NULL COMMENT 'Impatto 1-5' AFTER likelihood", +]; + +foreach ($adds as $col => $clause) { + if (colExists($pdo, 'org_requisito_state', $col)) { echo " - $col gia presente, skip\n"; continue; } + $pdo->exec("ALTER TABLE org_requisito_state $clause"); + echo " + aggiunta colonna org_requisito_state.$col\n"; +} + +echo "Migrazione 066 — org_requisito_state.likelihood/impact OK. Prossima mig=067.\n"; diff --git a/application/controllers/FrameworkController.php b/application/controllers/FrameworkController.php index a502cf6..382906a 100644 --- a/application/controllers/FrameworkController.php +++ b/application/controllers/FrameworkController.php @@ -199,21 +199,32 @@ class FrameworkController extends BaseController $note = array_key_exists('note', $body) ? ($body['note'] !== '' ? (string) $body['note'] : null) : null; + // Probabilita/impatto 1-5 (opzione B ticket #500, matrice 5x5). null = non valutato. + $scale = static function ($v) { + if ($v === null || $v === '') return null; + $n = (int) $v; + return ($n >= 1 && $n <= 5) ? $n : null; + }; + $likelihood = array_key_exists('likelihood', $body) ? $scale($body['likelihood']) : null; + $impact = array_key_exists('impact', $body) ? $scale($body['impact']) : null; + // upsert org-scoped (UNIQUE org+requisito) Database::query( 'INSERT INTO org_requisito_state - (organization_id, requisito_id, stato, valutazione_rischio, note, updated_by) - VALUES (?, ?, ?, ?, ?, ?) + (organization_id, requisito_id, stato, valutazione_rischio, note, likelihood, impact, updated_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE stato = VALUES(stato), valutazione_rischio = VALUES(valutazione_rischio), note = VALUES(note), + likelihood = VALUES(likelihood), + impact = VALUES(impact), updated_by = VALUES(updated_by)', - [$orgId, $reqId, $stato ?? 'da_valutare', $valutazione, $note, $this->getCurrentUserId()] + [$orgId, $reqId, $stato ?? 'da_valutare', $valutazione, $note, $likelihood, $impact, $this->getCurrentUserId()] ); $row = Database::fetchOne( - 'SELECT requisito_id, stato, valutazione_rischio, note, updated_at + 'SELECT requisito_id, stato, valutazione_rischio, note, likelihood, impact, updated_at FROM org_requisito_state WHERE organization_id = ? AND requisito_id = ?', [$orgId, $reqId] ); @@ -222,6 +233,8 @@ class FrameworkController extends BaseController 'stato' => $row['stato'], 'valutazione_rischio' => $row['valutazione_rischio'], 'note' => $row['note'], + 'likelihood' => $row['likelihood'] !== null ? (int) $row['likelihood'] : null, + 'impact' => $row['impact'] !== null ? (int) $row['impact'] : null, 'updated_at' => $row['updated_at'], ], 'Valutazione salvata'); } diff --git a/application/controllers/RiskController.php b/application/controllers/RiskController.php index ee1d407..d870b54 100644 --- a/application/controllers/RiskController.php +++ b/application/controllers/RiskController.php @@ -70,7 +70,7 @@ class RiskController extends BaseController "SELECT q.id AS requisito_id, q.n, q.misura_code, q.requisito_code, q.requisito_descr, q.applies_important, q.applies_essential, m.misura_descr, m.nist_code, m.area_politica, m.ord, - s.stato, s.valutazione_rischio, s.note + s.stato, s.valutazione_rischio, s.note, s.likelihood, s.impact FROM cfg_nis2_requisiti q JOIN cfg_nis2_misure m ON m.misura_code = q.misura_code LEFT JOIN org_requisito_state s @@ -105,6 +105,10 @@ class RiskController extends BaseController 'stato' => $q['stato'] ?? 'da_valutare', 'valutazione_rischio' => $q['valutazione_rischio'] ?? null, 'note' => $q['note'] ?? null, + 'likelihood' => isset($q['likelihood']) && $q['likelihood'] !== null ? (int) $q['likelihood'] : null, + 'impact' => isset($q['impact']) && $q['impact'] !== null ? (int) $q['impact'] : null, + 'score' => (isset($q['likelihood'], $q['impact']) && $q['likelihood'] !== null && $q['impact'] !== null) + ? (int) $q['likelihood'] * (int) $q['impact'] : null, ]; } diff --git a/docs/sql/066_requisito_risk_scoring.sql b/docs/sql/066_requisito_risk_scoring.sql new file mode 100644 index 0000000..5471698 --- /dev/null +++ b/docs/sql/066_requisito_risk_scoring.sql @@ -0,0 +1,8 @@ +-- 066_requisito_risk_scoring.sql — Ticket #500 (opzione B, matrice 5x5 dei rischi derivati). +-- Aggiunge probabilita/impatto (1-5) alla valutazione per-requisito, così i rischi +-- derivati dai requisiti (RSK-Com.N) si posizionano sulla matrice. +-- Additiva e idempotente (i valori NULL = non ancora valutato). Runner: +-- application/cli/migrate_066_requisito_risk_scoring.php +ALTER TABLE org_requisito_state + ADD COLUMN likelihood TINYINT NULL COMMENT 'Probabilita 1-5' AFTER valutazione_rischio, + ADD COLUMN impact TINYINT NULL COMMENT 'Impatto 1-5' AFTER likelihood; diff --git a/public/admin/index.html b/public/admin/index.html index 3746f21..e05fd6f 100644 --- a/public/admin/index.html +++ b/public/admin/index.html @@ -166,8 +166,8 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +