[FEAT] Modulo Controlli periodici (control monitoring §9.1/A.8.16) — v1.23.0
Registro controlli ricorrenti (owner/frequenza/metodo/controllo SoA-NIS2 collegato) + registro esecuzioni come EVIDENZA (data/esito/note) con avanzamento automatico della scadenza; da esito non conforme generazione NC o NC+azione correttiva (riuso non_conformities/capa_actions, nuova source 'monitoring'); report scheda+storico stampabile; scadenze nel Calendario unico. mig.057: periodic_controls + periodic_control_executions (FK CASCADE, UNIQUE (organization_id, code)). PeriodicControlController (slug periodic-controls) + controlli-periodici.html + CalendarController.srcPeriodicControls. Fix da flotta di verifica avversariale (9 finding confermati): - advance(): correzione overflow strtotime '+1 month' (31/01 -> 28/02, no salto mese) via DateTimeImmutable con clamp all'ultimo giorno valido (mensile/ trimestrale/semestrale/annuale); giornaliero/settimanale/custom invariati. - create(): retry-on-duplicate del codice CTL-NNN con UNIQUE (org_id, code). - update(): array_key_exists per azzerare owner_role_id / next_due_date. - delete(): rimossa cleanup morta su review_schedule (il calendario legge diretto). - CalendarController.srcReviewSchedule: esclude internal_audit/stakeholder_activity (gia' con sorgente diretta) -> fix doppia comparsa nel calendario. - calendario.html: TYPE_META 'periodic_control' (chip filtro + label + colore). Additivo. Cache-buster ?v= bump (sidebar) + sw cache nis2-shell-v1.23.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0738504fdd
commit
4fdeae7cee
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/** DDL idempotente Controlli periodici (mig.057). Crea le tabelle ed estende
|
||||
* l'ENUM non_conformities.source con 'monitoring' (preservando i valori).
|
||||
* Uso: docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_periodic_controls.php */
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS periodic_controls (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
code VARCHAR(20) NULL, title VARCHAR(255) NOT NULL, description TEXT NULL, category VARCHAR(100) NULL,
|
||||
control_ref VARCHAR(32) NULL, owner_role_id INT NULL,
|
||||
frequency ENUM('giornaliero','settimanale','mensile','trimestrale','semestrale','annuale','custom') NOT NULL DEFAULT 'mensile',
|
||||
frequency_days INT NULL, method TEXT NULL, next_due_date DATE NULL, last_executed_at DATE NULL,
|
||||
status ENUM('active','suspended') NOT NULL DEFAULT 'active', created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), UNIQUE KEY uk_pctl_code (organization_id, code),
|
||||
KEY idx_pctl_org (organization_id), KEY idx_pctl_due (organization_id, next_due_date),
|
||||
CONSTRAINT fk_pctl_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_pctl_role FOREIGN KEY (owner_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_pctl_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS periodic_control_executions (
|
||||
id INT NOT NULL AUTO_INCREMENT, control_id INT NOT NULL, executed_at DATE NOT NULL, executed_by INT NULL,
|
||||
outcome ENUM('conforme','non_conforme','parziale','non_applicabile') NOT NULL DEFAULT 'conforme',
|
||||
notes TEXT NULL, ncr_id INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_pcexec_control (control_id),
|
||||
CONSTRAINT fk_pcexec_control FOREIGN KEY (control_id) REFERENCES periodic_controls (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_pcexec_user FOREIGN KEY (executed_by) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_pcexec_ncr FOREIGN KEY (ncr_id) REFERENCES non_conformities (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
// UNIQUE (organization_id, code) idempotente — la tabella su prod può preesistere senza
|
||||
// l'indice (creata prima del fix). Anti-doppione su CTL-NNN concorrenti (vedi retry in create()).
|
||||
try { $pdo->exec("ALTER TABLE periodic_controls ADD UNIQUE KEY uk_pctl_code (organization_id, code)"); }
|
||||
catch (PDOException $e) {
|
||||
$c = $e->errorInfo[1] ?? 0;
|
||||
if ($c === 1062) { fwrite(STDERR, "WARN uk_pctl_code: doppioni esistenti, indice NON aggiunto.\n"); }
|
||||
elseif (!in_array($c, [1061], true)) { throw $e; } // 1061 = indice già presente
|
||||
}
|
||||
|
||||
// Estende non_conformities.source con 'monitoring' preservando i valori esistenti.
|
||||
try {
|
||||
$cur = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'non_conformities' AND COLUMN_NAME = 'source'")->fetchColumn();
|
||||
if (stripos($cur, "'monitoring'") === false) {
|
||||
$vals = [];
|
||||
if (preg_match_all("/'([^']+)'/", $cur, $m)) { $vals = $m[1]; }
|
||||
if (!in_array('monitoring', $vals, true)) { $vals[] = 'monitoring'; }
|
||||
if (!$vals) { $vals = ['assessment','audit','incident','supplier_review','management_review','external_audit','monitoring','other']; }
|
||||
$enum = implode(',', array_map(fn($v) => "'" . $v . "'", $vals));
|
||||
$pdo->exec("ALTER TABLE non_conformities MODIFY COLUMN source ENUM($enum) NOT NULL DEFAULT 'assessment'");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
fwrite(STDERR, "WARN ALTER non_conformities.source: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['periodic_controls', 'periodic_control_executions'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
$src = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'non_conformities' AND COLUMN_NAME = 'source'")->fetchColumn();
|
||||
echo "OK seed-periodic-controls — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "non_conformities.source has monitoring: " . (str_contains($src, 'monitoring') ? 'YES' : 'NO') . "\n";
|
||||
Reference in New Issue
Block a user