[FEAT] Seed template "NIS2 base" fornitori (26 domande GV.SC) idempotente per-org

Script scripts/seed_supplier_template.php: crea il template predefinito NIS2 base
(is_default, pass_threshold 70) + 26 domande dal JSON canonico
docs/supplier-portal/template-nis2-base.questions.json, mappate alle misure ACN
GV.SC (Allegato 2 Det. 164179/2025) con nis2_ref/vuln_flag/high_criticality_only.

Idempotente: template per nome, domande per question_code.
Applicato a org 129 (Agile Technology, dogfooding): template id=1, 26 domande.
Re-run verificato: 0 inserite, 26 gia presenti.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile 2026-05-31 10:37:38 +02:00
parent f85876f2a2
commit 8d7a50abbe

View File

@ -0,0 +1,100 @@
<?php
/**
* Seed idempotente del template "NIS2 base" (questionario fornitori GV.SC) per una org.
*
* Uso (host): docker exec nis2-app php /var/www/nis2-agile/public/_seed_tpl.php <org_id>
* (scripts/ NON e bind-mountato nel container: copiare in public/ per l'esecuzione,
* oppure eseguire via CLI host con il giusto include path).
*
* Sorgente domande: docs/supplier-portal/template-nis2-base.questions.json (26 domande,
* mappate alle misure ACN GV.SC, Allegato 2 Det. 164179/2025).
*
* Idempotente: se esiste gia un template is_default con name "NIS2 base" per l'org,
* non duplica (verifica per nome) e salta le domande gia presenti (per question_code).
*/
$ROOT = '/var/www/nis2-agile';
require $ROOT . '/application/config/env.php';
require $ROOT . '/application/config/config.php';
require $ROOT . '/application/config/database.php';
$orgId = (int) ($argv[1] ?? 0);
if ($orgId <= 0) {
fwrite(STDERR, "ERRORE: org_id mancante. Uso: php seed_supplier_template.php <org_id>\n");
exit(1);
}
$org = Database::fetchOne('SELECT id, name FROM organizations WHERE id = ?', [$orgId]);
if (!$org) {
fwrite(STDERR, "ERRORE: organizzazione {$orgId} non trovata.\n");
exit(1);
}
$jsonPath = $ROOT . '/docs/supplier-portal/template-nis2-base.questions.json';
if (!is_file($jsonPath)) {
fwrite(STDERR, "ERRORE: file domande non trovato: {$jsonPath}\n");
exit(1);
}
$data = json_decode((string) file_get_contents($jsonPath), true);
$questions = $data['questions'] ?? [];
if (empty($questions)) {
fwrite(STDERR, "ERRORE: nessuna domanda nel JSON.\n");
exit(1);
}
// 1. Template (idempotente per nome)
$tplName = 'NIS2 base - Sicurezza catena di fornitura';
$tpl = Database::fetchOne(
'SELECT id FROM questionnaire_templates WHERE organization_id = ? AND name = ?',
[$orgId, $tplName]
);
if ($tpl) {
$tplId = (int) $tpl['id'];
echo "Template gia presente (id={$tplId}) per org {$orgId} ({$org['name']}).\n";
} else {
$tplId = Database::insert('questionnaire_templates', [
'organization_id' => $orgId,
'category_id' => null,
'name' => $tplName,
'description' => 'Template predefinito mappato alle misure ACN GV.SC (Allegato 2, Det. 164179/2025) e Art. 21.2(d)/21.3 Dir. (UE) 2022/2555. 26 domande, scoring per-vulnerabilita.',
'current_version' => '1.0',
'status' => 'active',
'pass_threshold' => 70,
'is_default' => 1,
'created_by' => null,
]);
echo "Template creato (id={$tplId}) per org {$orgId} ({$org['name']}).\n";
}
// 2. Domande (idempotente per question_code)
$inserted = 0; $skipped = 0;
foreach ($questions as $q) {
$code = $q['code'] ?? null;
if ($code === null) { continue; }
$exists = Database::fetchOne(
'SELECT id FROM questionnaire_questions WHERE template_id = ? AND question_code = ?',
[$tplId, $code]
);
if ($exists) { $skipped++; continue; }
Database::insert('questionnaire_questions', [
'template_id' => $tplId,
'organization_id' => $orgId,
'question_code' => $code,
'question_text' => $q['text'] ?? '',
'question_type' => $q['type'] ?? 'yes_no_partial',
'options' => isset($q['options']) && $q['options'] !== null
? json_encode($q['options'], JSON_UNESCAPED_UNICODE) : null,
'weight' => (float) ($q['weight'] ?? 1),
'is_required' => !empty($q['required']) ? 1 : 0,
'order_index' => (int) ($q['order_index'] ?? 0),
'nis2_ref' => $q['nis2_ref'] ?? null,
'vuln_flag' => $q['vuln_flag'] ?? null,
'high_criticality_only' => !empty($q['high_criticality_only']) ? 1 : 0,
]);
$inserted++;
}
$total = Database::fetchOne('SELECT COUNT(*) AS n FROM questionnaire_questions WHERE template_id = ?', [$tplId]);
echo "Domande: inserite={$inserted}, gia presenti={$skipped}, totale nel template={$total['n']}.\n";
echo "OK.\n";