extending results page to show metadata and group by clientid to improve tracking progress
Some checks failed
PHPUnit / test (push) Has been cancelled

This commit is contained in:
2026-07-01 09:26:18 +02:00
parent 13119f300a
commit 223f38a11f
5 changed files with 415 additions and 141 deletions

View File

@ -289,16 +289,13 @@ function qdb_backfill_submissions_from_live(PDO $pdo): bool {
}
/**
* Build CSV rows for all submission versions of one questionnaire (RBAC-scoped).
* RBAC-scoped submission rows for one questionnaire (client, then version).
*
* @return list<array<string, string>>
* @return list<array<string, mixed>>
*/
function qdb_export_all_versions_rows(
function qdb_fetch_questionnaire_submissions_scoped(
PDO $pdo,
string $questionnaireID,
array $questions,
array $resultColumns,
array $optionTextMap,
array $tokenRec
): array {
[$rbacClause, $rbacParams] = rbac_client_filter($tokenRec, 'cl');
@ -308,6 +305,7 @@ function qdb_export_all_versions_rows(
qs.completedAt AS submissionCompletedAt, qs.sumPoints AS submissionSumPoints,
qs.startedAt AS submissionStartedAt, qs.status AS submissionStatus,
qs.structureRevision, qs.structureSnapshotJson,
qs.programCycleNumber, qs.programSessionID, qs.programSessionNumber,
cl.clientCode, cl.coachID,
co.username AS coachUsername,
sv.username AS supervisorUsername
@ -321,58 +319,99 @@ function qdb_export_all_versions_rows(
$params = array_merge([':qnid' => $questionnaireID], $rbacParams);
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$submissions = $stmt->fetchAll(PDO::FETCH_ASSOC);
$defaultQuestionIDs = array_column($questions, 'questionID');
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/** @return array<string, string> */
function qdb_submission_submitter_username_map(PDO $pdo): array {
$userMap = [];
$userStmt = $pdo->query('SELECT userID, username FROM users');
foreach ($userStmt->fetchAll(PDO::FETCH_ASSOC) as $u) {
$userMap[$u['userID']] = $u['username'];
}
return $userMap;
}
/**
* Resolve per-submission question columns from structure snapshot or questionnaire defaults.
*
* @return array{submissionColumns: array, submissionOptionMap: array, submissionQuestionIDs: list<string>}
*/
function qdb_submission_display_context(
PDO $pdo,
array $submissionRow,
array $defaultQuestions,
array $defaultResultColumns,
array $defaultOptionTextMap
): array {
$snap = json_decode($submissionRow['structureSnapshotJson'] ?? '{}', true) ?: [];
if (!empty($snap['questions'])) {
$snapCtx = qdb_display_context_from_manifest($pdo, $snap);
return [
'submissionColumns' => $snapCtx['resultColumns'],
'submissionOptionMap' => $snapCtx['optionTextMap'],
'submissionQuestionIDs' => array_column($snapCtx['questions'], 'questionID'),
];
}
return [
'submissionColumns' => $defaultResultColumns,
'submissionOptionMap' => $defaultOptionTextMap,
'submissionQuestionIDs' => array_column($defaultQuestions, 'questionID'),
];
}
/**
* Build CSV rows for all submission versions of one questionnaire (RBAC-scoped).
*
* @return list<array<string, string>>
*/
function qdb_export_all_versions_rows(
PDO $pdo,
string $questionnaireID,
array $questions,
array $resultColumns,
array $optionTextMap,
array $tokenRec
): array {
$submissions = qdb_fetch_questionnaire_submissions_scoped($pdo, $questionnaireID, $tokenRec);
$userMap = qdb_submission_submitter_username_map($pdo);
$rows = [];
foreach ($submissions as $s) {
$snap = json_decode($s['structureSnapshotJson'] ?? '{}', true) ?: [];
if (!empty($snap['questions'])) {
$snapCtx = qdb_display_context_from_manifest($pdo, $snap);
$submissionColumns = $snapCtx['resultColumns'];
$submissionOptionMap = $snapCtx['optionTextMap'];
$submissionQuestionIDs = array_column($snapCtx['questions'], 'questionID');
} else {
$submissionColumns = $resultColumns;
$submissionOptionMap = $optionTextMap;
$submissionQuestionIDs = $defaultQuestionIDs;
}
$ctx = qdb_submission_display_context($pdo, $s, $questions, $resultColumns, $optionTextMap);
$row = [
'submissionID' => $s['submissionID'],
'version' => (string)(int)$s['version'],
'structureRevision' => (string)max(1, (int)($s['structureRevision'] ?? 1)),
'submittedAt' => $s['submittedAt'] ? date('Y-m-d H:i', (int)$s['submittedAt']) : '',
'submittedByRole' => $s['submittedByRole'] ?? '',
'submittedBy' => $userMap[$s['submittedByUserID'] ?? ''] ?? ($s['submittedByUserID'] ?? ''),
'clientCode' => $s['clientCode'],
'coach' => $s['coachUsername'] ?? $s['coachID'],
'supervisor' => $s['supervisorUsername'] ?? '',
'status' => $s['submissionStatus'] ?? '',
'sumPoints' => $s['submissionSumPoints'] ?? '',
'startedAt' => $s['submissionStartedAt'] ? date('Y-m-d H:i', (int)$s['submissionStartedAt']) : '',
'completedAt' => $s['submissionCompletedAt'] ? date('Y-m-d H:i', (int)$s['submissionCompletedAt']) : '',
'clientCode' => $s['clientCode'],
'version' => (string)(int)$s['version'],
'programCycleNumber' => $s['programCycleNumber'] !== null ? (string)(int)$s['programCycleNumber'] : '',
'programSessionNumber' => $s['programSessionNumber'] !== null ? (string)(int)$s['programSessionNumber'] : '',
'programSessionID' => $s['programSessionID'] ?? '',
'startedAt' => $s['submissionStartedAt'] ? date('Y-m-d H:i', (int)$s['submissionStartedAt']) : '',
'completedAt' => $s['submissionCompletedAt'] ? date('Y-m-d H:i', (int)$s['submissionCompletedAt']) : '',
'submittedAt' => $s['submittedAt'] ? date('Y-m-d H:i', (int)$s['submittedAt']) : '',
'coach' => $s['coachUsername'] ?? $s['coachID'],
'supervisor' => $s['supervisorUsername'] ?? '',
'status' => $s['submissionStatus'] ?? '',
'sumPoints' => $s['submissionSumPoints'] ?? '',
'submissionID' => $s['submissionID'],
'structureRevision' => (string)max(1, (int)($s['structureRevision'] ?? 1)),
'submittedByRole' => $s['submittedByRole'] ?? '',
'submittedBy' => $userMap[$s['submittedByUserID'] ?? ''] ?? ($s['submittedByUserID'] ?? ''),
];
$answerMap = qdb_load_client_answer_map(
$pdo,
$s['clientCode'],
$submissionQuestionIDs,
$ctx['submissionQuestionIDs'],
'client_answer_submission',
$s['submissionID']
);
foreach ($submissionColumns as $col) {
foreach ($ctx['submissionColumns'] as $col) {
$qid = $col['questionID'];
$a = $answerMap[$qid] ?? null;
$row[$col['header']] = qdb_results_column_cell_value($col, $a, $submissionOptionMap);
$row[$col['header']] = qdb_results_column_cell_value($col, $a, $ctx['submissionOptionMap']);
}
$rows[] = $row;
}
@ -380,6 +419,68 @@ function qdb_export_all_versions_rows(
return $rows;
}
/**
* Submission rows for the results API (all versions, grouped by client in sort order).
*
* @return list<array<string, mixed>>
*/
function qdb_results_submission_entries(
PDO $pdo,
string $questionnaireID,
array $questions,
array $resultColumns,
array $optionTextMap,
array $tokenRec
): array {
$submissions = qdb_fetch_questionnaire_submissions_scoped($pdo, $questionnaireID, $tokenRec);
$userMap = qdb_submission_submitter_username_map($pdo);
$entries = [];
foreach ($submissions as $s) {
$ctx = qdb_submission_display_context($pdo, $s, $questions, $resultColumns, $optionTextMap);
$answerMap = qdb_load_client_answer_map(
$pdo,
$s['clientCode'],
$ctx['submissionQuestionIDs'],
'client_answer_submission',
$s['submissionID']
);
$answers = [];
foreach ($answerMap as $qid => $a) {
$answers[$qid] = [
'answerOptionID' => $a['answerOptionID'],
'freeTextValue' => $a['freeTextValue'],
'numericValue' => $a['numericValue'] !== null ? (float)$a['numericValue'] : null,
'answeredAt' => null,
];
}
$entries[] = [
'submissionID' => $s['submissionID'],
'version' => (int)$s['version'],
'clientCode' => $s['clientCode'],
'coachID' => $s['coachID'],
'coachUsername' => $s['coachUsername'],
'supervisorUsername' => $s['supervisorUsername'],
'status' => $s['submissionStatus'] ?? '',
'sumPoints' => $s['submissionSumPoints'] !== null ? (int)$s['submissionSumPoints'] : null,
'startedAt' => $s['submissionStartedAt'] !== null ? (int)$s['submissionStartedAt'] : null,
'completedAt' => $s['submissionCompletedAt'] !== null ? (int)$s['submissionCompletedAt'] : null,
'submittedAt' => $s['submittedAt'] !== null ? (int)$s['submittedAt'] : null,
'programCycleNumber' => $s['programCycleNumber'] !== null ? (int)$s['programCycleNumber'] : null,
'programSessionID' => $s['programSessionID'],
'programSessionNumber' => $s['programSessionNumber'] !== null ? (int)$s['programSessionNumber'] : null,
'structureRevision' => max(1, (int)($s['structureRevision'] ?? 1)),
'submittedByRole' => $s['submittedByRole'] ?? '',
'submittedBy' => $userMap[$s['submittedByUserID'] ?? ''] ?? ($s['submittedByUserID'] ?? ''),
'answers' => $answers,
];
}
return $entries;
}
/**
* @return array{questions: array, resultColumns: array, optionTextMap: array}
*/
@ -428,7 +529,8 @@ function qdb_export_current_rows(
SELECT cl.clientCode, cl.coachID,
co.username AS coachUsername,
sv.username AS supervisorUsername,
cq.status, cq.sumPoints, cq.startedAt, cq.completedAt
cq.status, cq.sumPoints, cq.startedAt, cq.completedAt,
cq.programCycleNumber, cq.programSessionID, cq.programSessionNumber
FROM client cl
INNER JOIN completed_questionnaire cq ON cq.clientCode = cl.clientCode AND cq.questionnaireID = :qnid
LEFT JOIN coach co ON co.coachID = cl.coachID
@ -451,13 +553,16 @@ function qdb_export_current_rows(
$rows = [];
foreach ($clients as $c) {
$row = [
'clientCode' => $c['clientCode'],
'coach' => $c['coachUsername'] ?? $c['coachID'],
'supervisor' => $c['supervisorUsername'] ?? '',
'status' => $c['status'],
'sumPoints' => $c['sumPoints'],
'startedAt' => $c['startedAt'] ? date('Y-m-d H:i', (int)$c['startedAt']) : '',
'completedAt' => $c['completedAt'] ? date('Y-m-d H:i', (int)$c['completedAt']) : '',
'clientCode' => $c['clientCode'],
'programCycleNumber' => $c['programCycleNumber'] !== null ? (string)(int)$c['programCycleNumber'] : '',
'programSessionNumber' => $c['programSessionNumber'] !== null ? (string)(int)$c['programSessionNumber'] : '',
'programSessionID' => $c['programSessionID'] ?? '',
'startedAt' => $c['startedAt'] ? date('Y-m-d H:i', (int)$c['startedAt']) : '',
'completedAt' => $c['completedAt'] ? date('Y-m-d H:i', (int)$c['completedAt']) : '',
'coach' => $c['coachUsername'] ?? $c['coachID'],
'supervisor' => $c['supervisorUsername'] ?? '',
'status' => $c['status'],
'sumPoints' => $c['sumPoints'],
];
$bindParams = array_merge([$c['clientCode']], $questionIDs);
@ -523,7 +628,10 @@ function qdb_export_rows_to_csv_string(array $rows, array $fallbackHeader = []):
}
function qdb_export_current_csv_fallback_header(array $resultColumns): array {
$header = ['clientCode', 'coach', 'supervisor', 'status', 'sumPoints', 'startedAt', 'completedAt'];
$header = [
'clientCode', 'programCycleNumber', 'programSessionNumber', 'programSessionID',
'startedAt', 'completedAt', 'coach', 'supervisor', 'status', 'sumPoints',
];
foreach ($resultColumns as $col) {
$header[] = $col['header'];
}
@ -532,8 +640,10 @@ function qdb_export_current_csv_fallback_header(array $resultColumns): array {
function qdb_export_all_versions_csv_fallback_header(array $resultColumns): array {
$header = [
'submissionID', 'version', 'submittedAt', 'submittedByRole', 'submittedBy',
'clientCode', 'coach', 'supervisor', 'status', 'sumPoints', 'startedAt', 'completedAt',
'clientCode', 'version', 'programCycleNumber', 'programSessionNumber', 'programSessionID',
'startedAt', 'completedAt', 'submittedAt',
'coach', 'supervisor', 'status', 'sumPoints',
'submissionID', 'structureRevision', 'submittedByRole', 'submittedBy',
];
foreach ($resultColumns as $col) {
$header[] = $col['header'];