[FEAT] ISO-readiness: Audit interni (§9.2) + Riesame Direzione (§9.3) + Calendario scadenze (mig.055-056, v1.22.0)
Rilascio unico (3 moduli) costruito in flotta parallela + verifica avversariale. MODULO A — Audit interni (ISO 27001 §9.2, mig.055): internal_audits + internal_audit_items; codice AUD-NNN; checklist pre-popolata (clausole 4-10 + Annex A applicabili dal SoA); esiti per riga; apertura non conformità collegata a NCR/CAPA (source polimorfico); report HTML stampabile. InternalAuditController + internal-audits.html. MODULO B — Riesame di Direzione (ISO 27001 §9.3, mig.056): management_reviews + management_review_decisions; codice RD-AAAA-NN; INPUT aggregati automaticamente dai moduli (gather: NC/CAPA, audit interni, rischi+trattamenti, KPI/score, obiettivi, formazione, stakeholder, normative, scadenze), congelati nello snapshot all'approvazione; decisioni; verbale stampabile. ManagementReviewController + management-review.html. MODULO C — Calendario unico scadenze: aggregatore SOLA LETTURA (nessuna migrazione) di tutte le scadenze (incidenti/policy/rischi/NC-CAPA/formazione/stakeholder/audit/riesame/review_schedule), griglia mensile + lista + filtri + deep-link. CalendarController + calendario.html. Integrazione: router (3 controller+actionMap), api.js, sidebar V2+legacy, help.js (3 sezioni), i18n (IT+EN), review_schedule ENUM += internal_audit. v1.22.0 + sw cache + cache-buster 20260630. Verifica flotta (28 agenti, 4 dim + avversariale): 9 finding confermati, TUTTI corretti — MAJOR gatherRisks (risk_treatments.organization_id inesistente → JOIN risks); nextCode numerico (no dup >99/anno); footer report audit con etichetta "ISO buona prassi, non obbligo"; help 24→25 clausole; ARIA tab/calendario; focus modali; tasti su celle calendario; rimossi helper api morti. Smoke prod OK (calendario 18 eventi, audit AUD-001 25 item + report + audit→NCR + audit→calendario, riesame gather/decisione/approve/report, gatherRisks ora available); org 151 ripulita. Additivo. 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
18a4a0b509
commit
79ca72fba9
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — DDL idempotente Audit interni (Modulo A, mig.055).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle del modulo Audit interni (ISO 27001 §9.2):
|
||||
* - internal_audits (programma/sessioni di audit)
|
||||
* - internal_audit_items (checklist: clausole 4-10, Annex A, NIS2, custom)
|
||||
* NESSUN dato di sistema: le checklist sono pre-popolate per-org dal controller
|
||||
* al create (clausole statiche + Annex A dal SoA dell'org).
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS (re-eseguibile senza errore).
|
||||
* Allineato a docs/sql/055_internal_audits.sql.
|
||||
*
|
||||
* Uso (dentro il container app):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_internal_audits.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 internal_audits (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
code VARCHAR(20) NULL, title VARCHAR(255) NOT NULL,
|
||||
scope TEXT NULL, criteria TEXT NULL,
|
||||
planned_date DATE NULL, executed_date DATE NULL,
|
||||
status ENUM('planned','in_progress','completed','cancelled') NOT NULL DEFAULT 'planned',
|
||||
lead_auditor_user_id INT NULL, lead_auditor_role_id INT NULL,
|
||||
conclusion TEXT NULL, created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_intaud_org (organization_id),
|
||||
CONSTRAINT fk_intaud_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_intaud_lead_user FOREIGN KEY (lead_auditor_user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_intaud_lead_role FOREIGN KEY (lead_auditor_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_intaud_creator 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 internal_audit_items (
|
||||
id INT NOT NULL AUTO_INCREMENT, audit_id INT NOT NULL,
|
||||
ref_type ENUM('clause','annex_control','nis2_measure','custom') NOT NULL DEFAULT 'clause',
|
||||
ref_code VARCHAR(32) NULL, checkpoint TEXT NOT NULL,
|
||||
result ENUM('da_verificare','conforme','non_conforme','osservazione','opportunita','non_applicabile') NOT NULL DEFAULT 'da_verificare',
|
||||
note TEXT NULL, ord INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id), KEY idx_intauditem_audit (audit_id),
|
||||
CONSTRAINT fk_intauditem_audit FOREIGN KEY (audit_id) REFERENCES internal_audits (id) ON DELETE CASCADE
|
||||
) 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; } }
|
||||
}
|
||||
|
||||
// Estende l'ENUM del calendario (review_schedule) con 'internal_audit' così la
|
||||
// data pianificata dell'audit compare nel calendario NIS2. Idempotente in effetto.
|
||||
// Include i valori già introdotti da seeder precedenti (stakeholder_activity) per
|
||||
// non regredirli; se 'internal_audit' è già presente l'ALTER è un no-op.
|
||||
try {
|
||||
$cur = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
|
||||
if (!str_contains($cur, "'internal_audit'")) {
|
||||
$vals = ['role','skill','inventory','procedure','risk','supplier','measure','custom','stakeholder_activity','internal_audit'];
|
||||
// conserva eventuali valori extra già presenti nell'ENUM corrente
|
||||
if (preg_match_all("/'([^']+)'/", $cur, $m)) {
|
||||
foreach ($m[1] as $v) { if (!in_array($v, $vals, true)) { $vals[] = $v; } }
|
||||
}
|
||||
$enum = "'" . implode("','", $vals) . "'";
|
||||
$pdo->exec("ALTER TABLE review_schedule MODIFY COLUMN entity_type ENUM($enum) NOT NULL");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
fwrite(STDERR, "WARN ALTER review_schedule: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['internal_audits', 'internal_audit_items'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
$enum = $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
|
||||
echo "OK seed-internal-audits — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "review_schedule.entity_type has internal_audit: " . (str_contains((string) $enum, 'internal_audit') ? 'YES' : 'NO') . "\n";
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — DDL idempotente Riesame di Direzione (ISO 27001 §9.3, mig.056).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle del Modulo B "Riesame di Direzione":
|
||||
* - management_reviews (il verbale: input aggregati congelati + esiti)
|
||||
* - management_review_decisions (decisioni/output → owner ruolo, scadenza, CAPA)
|
||||
*
|
||||
* NESSUN dato di sistema: i riesami sono per-org (creati dall'utente).
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS (re-eseguibile senza errore; in caso
|
||||
* di tabella già presente ignora gli errno 1050/1061). Allineato a
|
||||
* docs/sql/056_management_reviews.sql.
|
||||
*
|
||||
* Uso (dentro il container app, o sull'host con la stessa connessione PDO):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_management_reviews.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 management_reviews (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
organization_id INT NOT NULL,
|
||||
code VARCHAR(20) NULL,
|
||||
review_date DATE NULL,
|
||||
period_label VARCHAR(100) NULL,
|
||||
chair_user_id INT NULL,
|
||||
attendees JSON NULL,
|
||||
status ENUM('draft','approved') NOT NULL DEFAULT 'draft',
|
||||
approved_by INT NULL,
|
||||
approved_at DATETIME NULL,
|
||||
snapshot JSON NULL,
|
||||
conclusions TEXT NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_mgr_org (organization_id),
|
||||
CONSTRAINT fk_mgr_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_mgr_chair FOREIGN KEY (chair_user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_mgr_approved FOREIGN KEY (approved_by) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_mgr_creator 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 management_review_decisions (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
review_id INT NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
owner_role_id INT NULL,
|
||||
due_date DATE NULL,
|
||||
status ENUM('open','in_progress','done') NOT NULL DEFAULT 'open',
|
||||
capa_id INT NULL,
|
||||
ord INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_mrd_review (review_id),
|
||||
CONSTRAINT fk_mrd_review FOREIGN KEY (review_id) REFERENCES management_reviews (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_mrd_owner FOREIGN KEY (owner_role_id) REFERENCES org_roles (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; } }
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['management_reviews', 'management_review_decisions'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
echo "OK seed-management-reviews — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Reference in New Issue
Block a user