-- ===================================================================== -- 059 — Modello relazionale REQUISITO <-> POLICY (Ticket #384, punti 2.2 + 3) -- ===================================================================== -- Fonte spec: ticket #384 (super_admin) + istruzioni.pdf allegato. -- p.2.2 "ogni riga di requisito puo' avere piu' policy e ogni policy puo' -- avere piu' requisiti associati" -> serve una M:N esplicita. -- p.3 "i codici devono funzionare come link attivi" (Policy <-> Misure -- e Requisiti) -> serve poter risalire dalla policy ai requisiti -- e viceversa. -- -- PROBLEMA STRUTTURALE risolto qui: quando le 42 procedure di default -- (cfg_nis2_procedure) vengono copiate nell'elenco `policies` di una org -- (PolicyController::ensureFrameworkProcedures, mig.048), il `proc_code` -- canonico NON veniva conservato sulla riga policy -> il legame -- requisito (cfg_nis2_requisiti.proc_code) <-> policy andava perso. -- -- COSA FA (tutto ADDITIVO, IDEMPOTENTE, REVERSIBILE — nessun DROP/DELETE): -- 1) policies.proc_code VARCHAR(16) NULL — chiave di join verso il -- catalogo canonico (cfg_nis2_procedure.proc_code / cfg_nis2_requisiti.proc_code). -- 2) Tabella requisito_policy — M:N org-scoped requisito<->policy. -- requisito_id -> cfg_nis2_requisiti.id (canonico, SYSTEM-level) -- policy_id -> policies.id (per-org) -- organization_id -> organizations.id (lo stesso link e' per-org perche' -- le policy sono per-org). -- 3) Backfill SICURO (solo letture->scritture additive): valorizza -- policies.proc_code per match esatto title==proc_descr e popola -- requisito_policy con le associazioni di default gia' esistenti. -- -- Runner: application/cli/migrate_059_requisito_policy.php (PDO app, TLS, -- guardato via information_schema, idempotente). NON usa DELIMITER/stored-proc. -- -- ROLLBACK: -- DROP TABLE IF EXISTS requisito_policy; -- ALTER TABLE policies DROP COLUMN proc_code; -- ===================================================================== -- 1) chiave di join sulla riga policy (NULL = policy non legata al catalogo) ALTER TABLE policies ADD COLUMN proc_code VARCHAR(16) NULL AFTER nis2_article; -- 2) M:N esplicita requisito <-> policy (org-scoped) CREATE TABLE IF NOT EXISTS requisito_policy ( id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL, requisito_id INT NOT NULL, policy_id INT NOT NULL, is_default TINYINT(1) NOT NULL DEFAULT 0, -- 1 = derivata dal framework created_by INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_req_pol (organization_id, requisito_id, policy_id), KEY idx_rp_org (organization_id), KEY idx_rp_req (requisito_id), KEY idx_rp_pol (policy_id), CONSTRAINT fk_rp_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE, CONSTRAINT fk_rp_req FOREIGN KEY (requisito_id) REFERENCES cfg_nis2_requisiti (id) ON DELETE CASCADE, CONSTRAINT fk_rp_pol FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE, CONSTRAINT fk_rp_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 3) Backfill (eseguito dal runner, qui documentato): -- UPDATE policies p JOIN cfg_nis2_procedure c ON p.title = c.proc_descr -- SET p.proc_code = c.proc_code WHERE p.proc_code IS NULL; -- INSERT IGNORE INTO requisito_policy (organization_id, requisito_id, policy_id, is_default) -- SELECT p.organization_id, q.id, p.id, 1 -- FROM policies p JOIN cfg_nis2_requisiti q ON q.proc_code = p.proc_code -- WHERE p.proc_code IS NOT NULL;