exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"); $ddl = [ "CREATE TABLE IF NOT EXISTS stk_questionnaire_templates ( id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL, name VARCHAR(255) NOT NULL, kind ENUM('questionnaire','read_ack') NOT NULL DEFAULT 'questionnaire', description TEXT NULL, content TEXT NULL, questions JSON NULL, status ENUM('draft','active','archived') 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), KEY idx_stkqt_org (organization_id), CONSTRAINT fk_stkqt_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE, CONSTRAINT fk_stkqt_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 stk_template_procedures ( template_id INT NOT NULL, policy_id INT NOT NULL, PRIMARY KEY (template_id, policy_id), KEY idx_stktp_policy (policy_id), CONSTRAINT fk_stktp_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE, CONSTRAINT fk_stktp_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", "CREATE TABLE IF NOT EXISTS stk_template_misure ( template_id INT NOT NULL, misura_code VARCHAR(16) NOT NULL, PRIMARY KEY (template_id, misura_code), KEY idx_stktm_misura (misura_code), CONSTRAINT fk_stktm_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE, CONSTRAINT fk_stktm_misura FOREIGN KEY (misura_code) REFERENCES cfg_nis2_misure (misura_code) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", "CREATE TABLE IF NOT EXISTS stk_template_requisiti ( template_id INT NOT NULL, requisito_id INT NOT NULL, PRIMARY KEY (template_id, requisito_id), KEY idx_stktr_req (requisito_id), CONSTRAINT fk_stktr_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE, CONSTRAINT fk_stktr_req FOREIGN KEY (requisito_id) REFERENCES cfg_nis2_requisiti (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", "CREATE TABLE IF NOT EXISTS stk_activities ( id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL, title VARCHAR(255) NOT NULL, type ENUM('questionnaire','read_ack','action') NOT NULL DEFAULT 'questionnaire', template_id INT NULL, description TEXT NULL, assign_mode ENUM('by_code','individual') NOT NULL DEFAULT 'individual', stak_code VARCHAR(16) NULL, planned_date DATE NULL, due_date DATE NULL, status ENUM('draft','scheduled','sent','in_progress','completed','cancelled') NOT NULL DEFAULT 'draft', 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_stka_org (organization_id), KEY idx_stka_tpl (template_id), CONSTRAINT fk_stka_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE, CONSTRAINT fk_stka_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE SET NULL, CONSTRAINT fk_stka_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 stk_activity_targets ( id INT NOT NULL AUTO_INCREMENT, activity_id INT NOT NULL, stakeholder_id INT NOT NULL, access_token_hash CHAR(64) NULL, state ENUM('pending','sent','responded','acknowledged','expired') NOT NULL DEFAULT 'pending', sent_at DATETIME NULL, responded_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_stkat (activity_id, stakeholder_id), KEY idx_stkat_stk (stakeholder_id), KEY idx_stkat_token (access_token_hash), CONSTRAINT fk_stkat_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE, CONSTRAINT fk_stkat_stk FOREIGN KEY (stakeholder_id) REFERENCES stakeholders (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", "CREATE TABLE IF NOT EXISTS stk_activity_procedures ( activity_id INT NOT NULL, policy_id INT NOT NULL, PRIMARY KEY (activity_id, policy_id), KEY idx_stkap_policy (policy_id), CONSTRAINT fk_stkap_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE, CONSTRAINT fk_stkap_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", // C5.2b (mig.053) — feedback: risposte + commenti "CREATE TABLE IF NOT EXISTS stk_activity_responses ( id INT NOT NULL AUTO_INCREMENT, target_id INT NOT NULL, answers JSON NULL, acknowledged_at DATETIME NULL, respondent_name VARCHAR(255) NULL, respondent_email VARCHAR(255) NULL, ip_address VARCHAR(45) NULL, submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uq_stkar_target (target_id), CONSTRAINT fk_stkar_target FOREIGN KEY (target_id) REFERENCES stk_activity_targets (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", "CREATE TABLE IF NOT EXISTS stk_activity_comments ( id INT NOT NULL AUTO_INCREMENT, activity_id INT NOT NULL, body TEXT NOT NULL, author_kind ENUM('internal','external') NOT NULL DEFAULT 'internal', author_user_id INT NULL, author_label VARCHAR(255) NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_stkac_act (activity_id), CONSTRAINT fk_stkac_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE, CONSTRAINT fk_stkac_user FOREIGN KEY (author_user_id) REFERENCES users (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; } } } // Estende l'ENUM del calendario (review_schedule). Idempotente in effetto. try { $pdo->exec("ALTER TABLE review_schedule MODIFY COLUMN entity_type ENUM('role','skill','inventory','procedure','risk','supplier','measure','custom','stakeholder_activity') NOT NULL"); } catch (PDOException $e) { fwrite(STDERR, "WARN ALTER review_schedule: " . $e->getMessage() . "\n"); } // mig.054 — scadenza magic-link (idempotente: ignora 1060 colonna già esistente). try { $pdo->exec("ALTER TABLE stk_activity_targets ADD COLUMN token_expires_at DATETIME NULL DEFAULT NULL AFTER access_token_hash"); } catch (PDOException $e) { if (($e->errorInfo[1] ?? 0) !== 1060) { fwrite(STDERR, "WARN ALTER stk_activity_targets: " . $e->getMessage() . "\n"); } } $counts = []; foreach (['stk_questionnaire_templates','stk_template_procedures','stk_template_misure','stk_template_requisiti', 'stk_activities','stk_activity_targets','stk_activity_procedures', 'stk_activity_responses','stk_activity_comments'] 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-stakeholder-activities — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n"; echo "review_schedule.entity_type has stakeholder_activity: " . (str_contains((string) $enum, 'stakeholder_activity') ? 'YES' : 'NO') . "\n";