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 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + +