added prototype dev settings with test import and db wipe

This commit is contained in:
2026-05-26 16:38:37 +02:00
parent 2181d1731e
commit 9d783b60a9
13 changed files with 1253 additions and 33 deletions

View File

@ -60,6 +60,7 @@ if ($method === 'POST') {
$qRows->execute([':qn' => $qnID]);
$shortIdMap = []; // shortId -> fullQuestionID
$freeTextMaxLen = []; // fullQuestionID -> maxLength
$symptomParentMap = qdb_glass_symptom_parent_map($pdo, $qnID);
foreach ($qRows->fetchAll(PDO::FETCH_ASSOC) as $qRow) {
$fullId = $qRow['questionID'];
$parts = explode('__', $fullId);
@ -89,6 +90,7 @@ if ($method === 'POST') {
$pdo->beginTransaction();
$sumPoints = 0;
$glassByParent = [];
$answerInsert = $pdo->prepare("
INSERT INTO client_answer (clientCode, questionID, answerOptionID, freeTextValue, numericValue, answeredAt)
VALUES (:cc, :qid, :aoid, :ftv, :nv, :at)
@ -103,14 +105,25 @@ if ($method === 'POST') {
$shortId = trim($a['questionID'] ?? '');
if ($shortId === '') continue;
$answeredAt = isset($a['answeredAt']) ? (int)$a['answeredAt'] : null;
// Glass-scale symptoms use string keys (not question localIds).
if (isset($symptomParentMap[$shortId])) {
$label = trim((string)($a['answerOptionKey'] ?? $a['freeTextValue'] ?? ''));
if ($label !== '') {
$parentId = $symptomParentMap[$shortId];
$glassByParent[$parentId][$shortId] = $label;
}
continue;
}
// Resolve short ID to full questionID
$fullQID = $shortIdMap[$shortId] ?? null;
if ($fullQID === null) continue; // skip unknown questions
if ($fullQID === null) continue;
$answerOptionID = null;
$freeTextValue = isset($a['freeTextValue']) ? (string)$a['freeTextValue'] : null;
$numericValue = isset($a['numericValue']) ? (float)$a['numericValue'] : null;
$answeredAt = isset($a['answeredAt']) ? (int)$a['answeredAt'] : null;
if ($freeTextValue !== null && $freeTextValue !== '') {
$maxLen = $freeTextMaxLen[$fullQID] ?? 2000;
@ -138,6 +151,23 @@ if ($method === 'POST') {
]);
}
foreach ($glassByParent as $parentQID => $symptomLabels) {
$existing = $pdo->prepare(
'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
);
$existing->execute([':cc' => $clientCode, ':qid' => $parentQID]);
$prevJson = $existing->fetchColumn();
$merged = qdb_merge_glass_symptom_json($prevJson !== false ? (string)$prevJson : null, $symptomLabels);
$answerInsert->execute([
':cc' => $clientCode,
':qid' => $parentQID,
':aoid' => null,
':ftv' => $merged,
':nv' => null,
':at' => $completedAt ?? $startedAt,
]);
}
// Upsert completed_questionnaire record
$pdo->prepare("
INSERT INTO completed_questionnaire (clientCode, questionnaireID, assignedByCoach, status, startedAt, completedAt, sumPoints)

57
handlers/dev.php Normal file
View File

@ -0,0 +1,57 @@
<?php
/**
* Admin-only dev fixture import (local / test environments).
*/
require_once __DIR__ . '/../lib/dev_fixture.php';
$tokenRec = require_valid_token_web();
require_role(['admin'], $tokenRec);
if ($method !== 'POST') {
json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
}
$body = read_json_body();
$action = trim((string)($body['action'] ?? 'import'));
try {
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
if ($action === 'remove') {
$result = qdb_remove_dev_test_data($pdo, [
'usernamePrefix' => $body['usernamePrefix'] ?? 'dev_',
'clientCodePrefix' => $body['clientCodePrefix'] ?? 'DEV-CL-',
]);
qdb_save($tmpDb, $lockFp);
json_success($result);
}
if ($action === 'wipeExceptAdmins') {
$result = qdb_wipe_all_data_except_admins($pdo);
qdb_save($tmpDb, $lockFp);
json_success($result);
}
$fixture = $body['fixture'] ?? null;
if (!is_array($fixture)) {
qdb_discard($tmpDb, $lockFp);
json_error('MISSING_FIELDS', 'fixture object is required for import', 400);
}
$result = qdb_import_dev_fixture($pdo, $fixture);
qdb_save($tmpDb, $lockFp);
json_success($result);
} catch (Throwable $e) {
if (isset($tmpDb, $lockFp)) {
qdb_discard($tmpDb, $lockFp);
}
$labels = [
'remove' => 'Remove',
'wipeExceptAdmins' => 'Wipe',
'import' => 'Import',
];
$label = $labels[$action ?? 'import'] ?? 'Operation';
error_log("dev fixture $label: " . $e->getMessage());
json_error('SERVER_ERROR', "$label failed: " . $e->getMessage(), 500);
}

View File

@ -34,7 +34,7 @@ if (!$questionnaire) {
json_error('NOT_FOUND', 'Questionnaire not found', 404);
}
$qStmt = $pdo->prepare("SELECT questionID, defaultText, orderIndex FROM question WHERE questionnaireID = :id ORDER BY orderIndex");
$qStmt = $pdo->prepare("SELECT questionID, defaultText, type, orderIndex FROM question WHERE questionnaireID = :id ORDER BY orderIndex");
$qStmt->execute([':id' => $qnID]);
$questions = $qStmt->fetchAll(PDO::FETCH_ASSOC);
$questionIDs = array_column($questions, 'questionID');
@ -94,20 +94,8 @@ foreach ($clients as $c) {
foreach ($questions as $q) {
$qid = $q['questionID'];
if (isset($answerMap[$qid])) {
$a = $answerMap[$qid];
if ($a['answerOptionID'] && isset($optionTextMap[$a['answerOptionID']])) {
$row[$q['defaultText']] = $optionTextMap[$a['answerOptionID']];
} elseif ($a['freeTextValue'] !== null && $a['freeTextValue'] !== '') {
$row[$q['defaultText']] = $a['freeTextValue'];
} elseif ($a['numericValue'] !== null) {
$row[$q['defaultText']] = $a['numericValue'];
} else {
$row[$q['defaultText']] = '';
}
} else {
$row[$q['defaultText']] = '';
}
$a = $answerMap[$qid] ?? null;
$row[$q['defaultText']] = qdb_format_client_answer_display($q, $a, $optionTextMap);
}
$rows[] = $row;
}

View File

@ -24,7 +24,7 @@ if (!$questionnaire) {
}
$qStmt = $pdo->prepare("
SELECT questionID, defaultText, type, orderIndex, isRequired
SELECT questionID, defaultText, type, orderIndex, isRequired, configJson
FROM question WHERE questionnaireID = :id ORDER BY orderIndex
");
$qStmt->execute([':id' => $qnID]);