This commit is contained in:
@ -7,6 +7,9 @@
|
||||
* GET ?clients=1 -> list of clients assigned to the authenticated coach
|
||||
* GET ?clientCode=X&answers=1 -> all completed questionnaire answers for one client (encrypted)
|
||||
* GET ?answersBulk=1 -> all assigned clients' answers in one payload (coach, encrypted)
|
||||
* GET ?scoringProfiles=1 -> active scoring profile definitions (encrypted)
|
||||
* GET ?scoringReview=1 -> coach review state (coachBand only; computed on device)
|
||||
* POST action=coachScoringReview -> coach agrees with or overrides calculated band (encrypted)
|
||||
* POST -> submit interview answers for a client (coach / supervisor / admin).
|
||||
* Re-posting the same clientCode + questionnaireID updates answers in place (re-edit).
|
||||
* Sensitive POST/GET ?clients=1 bodies use encrypted payloads (HKDF from Bearer token).
|
||||
@ -27,6 +30,58 @@ if ($method === 'POST') {
|
||||
require_role(['admin', 'supervisor', 'coach'], $tokenRec);
|
||||
|
||||
$body = read_encrypted_json_body($tokenRec['_token']);
|
||||
|
||||
if (($body['action'] ?? '') === 'coachScoringReview') {
|
||||
require_fields($body, ['clientCode', 'profileID', 'coachBand']);
|
||||
$clientCode = trim((string)$body['clientCode']);
|
||||
$profileID = trim((string)$body['profileID']);
|
||||
$coachBand = trim((string)($body['coachBand'] ?? ''));
|
||||
$calculatedBand = trim((string)($body['calculatedBand'] ?? ''));
|
||||
$weightedTotal = isset($body['weightedTotal']) ? (float)$body['weightedTotal'] : null;
|
||||
|
||||
try {
|
||||
$opened = qdb_open_write_or_fail();
|
||||
[$pdo, $tmpDb, $lockFp] = $opened;
|
||||
|
||||
[$rbacClause, $rbacParams] = rbac_client_filter($tokenRec, 'cl');
|
||||
$clStmt = $pdo->prepare(
|
||||
"SELECT cl.clientCode FROM client cl WHERE cl.clientCode = :cc AND ($rbacClause)"
|
||||
);
|
||||
$clStmt->execute(array_merge([':cc' => $clientCode], $rbacParams));
|
||||
if (!$clStmt->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
if (!qdb_is_valid_scoring_band($coachBand)) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('INVALID_FIELD', 'coachBand must be green, yellow, or red', 400);
|
||||
}
|
||||
|
||||
$profile = qdb_save_coach_scoring_review(
|
||||
$pdo,
|
||||
$clientCode,
|
||||
$profileID,
|
||||
$coachBand,
|
||||
(string)($tokenRec['userID'] ?? ''),
|
||||
$weightedTotal,
|
||||
$calculatedBand !== '' ? $calculatedBand : null,
|
||||
);
|
||||
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['profile' => $profile]);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
qdb_discard($tmpDb ?? null, $lockFp ?? null);
|
||||
json_error('INVALID_FIELD', $e->getMessage(), 400);
|
||||
} catch (RuntimeException $e) {
|
||||
qdb_discard($tmpDb ?? null, $lockFp ?? null);
|
||||
json_error('NOT_FOUND', $e->getMessage(), 404);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'Coach scoring review', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
require_fields($body, ['questionnaireID', 'clientCode', 'answers']);
|
||||
|
||||
$qnID = trim($body['questionnaireID']);
|
||||
@ -126,7 +181,6 @@ if ($method === 'POST') {
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$sumPoints = 0;
|
||||
$glassByParent = [];
|
||||
$submittedQuestionIds = [];
|
||||
$answerInsert = $pdo->prepare("
|
||||
@ -183,19 +237,11 @@ if ($method === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve option key to answerOptionID and accumulate points
|
||||
// Resolve option key to answerOptionID (points computed by scoring engine after save)
|
||||
$optionKey = $a['answerOptionKey'] ?? null;
|
||||
if ($optionKey !== null && isset($optionMap[$fullQID][(string)$optionKey])) {
|
||||
$opt = $optionMap[$fullQID][(string)$optionKey];
|
||||
$answerOptionID = $opt['answerOptionID'];
|
||||
$sumPoints += $opt['points'];
|
||||
} elseif (($shortIdToType[$shortId] ?? '') === 'multi_check_box_question'
|
||||
&& $freeTextValue !== null && $freeTextValue !== '') {
|
||||
foreach (qdb_decode_multi_check_values($freeTextValue) as $mk) {
|
||||
if (isset($optionMap[$fullQID][$mk])) {
|
||||
$sumPoints += $optionMap[$fullQID][$mk]['points'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hasValue = $answerOptionID !== null
|
||||
@ -247,6 +293,9 @@ if ($method === 'POST') {
|
||||
}
|
||||
|
||||
// Upsert completed_questionnaire record
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
$sumPoints = qdb_compute_questionnaire_score($pdo, $clientCode, $qnID);
|
||||
|
||||
$pdo->prepare("
|
||||
INSERT INTO completed_questionnaire (clientCode, questionnaireID, assignedByCoach, status, startedAt, completedAt, sumPoints)
|
||||
VALUES (:cc, :qn, :abc, 'completed', :sa, :ca, :sp)
|
||||
@ -277,6 +326,8 @@ if ($method === 'POST') {
|
||||
$assignedByCoach
|
||||
);
|
||||
|
||||
qdb_recompute_profile_scores_for_client($pdo, $clientCode, $qnID);
|
||||
|
||||
$pdo->commit();
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
|
||||
@ -298,8 +349,50 @@ $translations = $_GET['translations'] ?? '';
|
||||
$fetchClients = $_GET['clients'] ?? '';
|
||||
$fetchAnswers = $_GET['answers'] ?? '';
|
||||
$fetchAnswersBulk = $_GET['answersBulk'] ?? '';
|
||||
$fetchScoringProfiles = $_GET['scoringProfiles'] ?? '';
|
||||
$fetchScoringReview = $_GET['scoringReview'] ?? '';
|
||||
$clientCode = trim($_GET['clientCode'] ?? '');
|
||||
|
||||
if ($fetchScoringProfiles) {
|
||||
require_role(['coach'], $tokenRec);
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
$profiles = array_values(array_filter(
|
||||
qdb_list_scoring_profiles($pdo),
|
||||
static fn(array $p): bool => (int)($p['isActive'] ?? 0) === 1
|
||||
));
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_success_sensitive(['profiles' => $profiles], $tokenRec['_token']);
|
||||
}
|
||||
|
||||
if ($fetchScoringReview) {
|
||||
require_role(['coach'], $tokenRec);
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
|
||||
$coachID = $tokenRec['entityID'] ?? '';
|
||||
if ($clientCode !== '') {
|
||||
[$rbacClause, $rbacParams] = rbac_client_filter($tokenRec, 'cl');
|
||||
$clStmt = $pdo->prepare(
|
||||
"SELECT cl.clientCode FROM client cl WHERE cl.clientCode = :cc AND ($rbacClause)"
|
||||
);
|
||||
$clStmt->execute(array_merge([':cc' => $clientCode], $rbacParams));
|
||||
if (!$clStmt->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
|
||||
}
|
||||
$clientCodes = [$clientCode];
|
||||
} else {
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT clientCode FROM client WHERE coachID = :cid ORDER BY clientCode'
|
||||
);
|
||||
$stmt->execute([':cid' => $coachID]);
|
||||
$clientCodes = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
$clients = qdb_app_scoring_review_for_clients($pdo, $clientCodes);
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_success_sensitive(['clients' => $clients], $tokenRec['_token']);
|
||||
}
|
||||
|
||||
if ($fetchAnswersBulk) {
|
||||
require_role(['coach'], $tokenRec);
|
||||
require_once __DIR__ . '/../lib/app_answers.php';
|
||||
@ -441,6 +534,14 @@ if ($qnID) {
|
||||
$q['options'] = $config['valueOptions'];
|
||||
}
|
||||
|
||||
if (in_array($layout, ['glass_scale_question', 'slider_question', 'value_spinner'], true)) {
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
$ruleMap = qdb_score_rule_map_for_question($pdo, $dbQ['questionID']);
|
||||
if ($ruleMap !== []) {
|
||||
$q['scoreRuleMap'] = $ruleMap;
|
||||
}
|
||||
}
|
||||
|
||||
$ao = $pdo->prepare("
|
||||
SELECT defaultText, points, nextQuestionId
|
||||
FROM answer_option WHERE questionID = :qid ORDER BY orderIndex
|
||||
|
||||
@ -36,8 +36,21 @@ case 'GET':
|
||||
$stmt->execute();
|
||||
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
require_once __DIR__ . '/../lib/submissions.php';
|
||||
$scoringSummary = qdb_clients_scoring_summary_for_list($pdo, $tokenRec);
|
||||
foreach ($clients as &$client) {
|
||||
$client['scoringProfiles'] = qdb_client_scoring_dots_for_client(
|
||||
$client['clientCode'],
|
||||
$scoringSummary
|
||||
);
|
||||
}
|
||||
unset($client);
|
||||
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_success(['clients' => $clients]);
|
||||
json_success([
|
||||
'clients' => $clients,
|
||||
'scoringProfiles' => $scoringSummary['profiles'],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'clients', null, $tmpDb ?? null, $lockFp ?? null);
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
|
||||
$tokenRec = require_valid_token_web();
|
||||
|
||||
switch ($method) {
|
||||
@ -39,7 +41,9 @@ case 'GET':
|
||||
}
|
||||
unset($opt);
|
||||
if (($q['type'] ?? '') === 'glass_scale_question') {
|
||||
$q['glassSymptoms'] = qdb_glass_symptoms_with_labels($pdo, $cfg);
|
||||
$q['glassSymptoms'] = qdb_glass_symptoms_with_score_rules($pdo, $q['questionID'], $cfg);
|
||||
} elseif (in_array($q['type'] ?? '', ['slider_question', 'value_spinner'], true)) {
|
||||
$q['scoreRules'] = qdb_get_score_rules_for_question($pdo, $q['questionID']);
|
||||
}
|
||||
$tr = $pdo->prepare("SELECT languageCode, text FROM question_translation WHERE questionID = :qid");
|
||||
$tr->execute([':qid' => $q['questionID']]);
|
||||
@ -111,6 +115,10 @@ case 'POST':
|
||||
if ($type === 'glass_scale_question') {
|
||||
qdb_sync_glass_symptom_strings($pdo, $glassSymptoms);
|
||||
}
|
||||
$scoreRules = qdb_parse_score_rules_body($body, $type, $config);
|
||||
if ($scoreRules !== [] || in_array($type, ['slider_question', 'value_spinner', 'glass_scale_question'], true)) {
|
||||
qdb_sync_question_score_rules($pdo, $id, $scoreRules);
|
||||
}
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['question' => [
|
||||
'questionID' => $id, 'questionnaireID' => $qnID, 'defaultText' => $text,
|
||||
@ -118,7 +126,9 @@ case 'POST':
|
||||
'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
|
||||
'configJson' => $configJson, 'answerOptions' => [], 'translations' => [],
|
||||
'glassSymptoms' => $type === 'glass_scale_question'
|
||||
? qdb_glass_symptoms_with_labels($pdo, $config) : [],
|
||||
? qdb_glass_symptoms_with_score_rules($pdo, $id, $config) : [],
|
||||
'scoreRules' => in_array($type, ['slider_question', 'value_spinner'], true)
|
||||
? qdb_get_score_rules_for_question($pdo, $id) : [],
|
||||
]]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
|
||||
@ -172,6 +182,10 @@ case 'PUT':
|
||||
if ($type === 'glass_scale_question') {
|
||||
qdb_sync_glass_symptom_strings($pdo, $glassSymptoms);
|
||||
}
|
||||
$scoreRules = qdb_parse_score_rules_body($body, $type, $config);
|
||||
if ($scoreRules !== [] || in_array($type, ['slider_question', 'value_spinner', 'glass_scale_question'], true)) {
|
||||
qdb_sync_question_score_rules($pdo, $id, $scoreRules);
|
||||
}
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['question' => [
|
||||
'questionID' => $id, 'questionnaireID' => $row['questionnaireID'],
|
||||
@ -179,7 +193,9 @@ case 'PUT':
|
||||
'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
|
||||
'configJson' => $configJson,
|
||||
'glassSymptoms' => $type === 'glass_scale_question'
|
||||
? qdb_glass_symptoms_with_labels($pdo, $config) : [],
|
||||
? qdb_glass_symptoms_with_score_rules($pdo, $id, $config) : [],
|
||||
'scoreRules' => in_array($type, ['slider_question', 'value_spinner'], true)
|
||||
? qdb_get_score_rules_for_question($pdo, $id) : [],
|
||||
]]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
|
||||
@ -203,6 +219,7 @@ case 'DELETE':
|
||||
$pdo->prepare("DELETE FROM answer_option_translation WHERE answerOptionID = :id")->execute([':id' => $aoid]);
|
||||
}
|
||||
$pdo->prepare("DELETE FROM answer_option WHERE questionID = :id")->execute([':id' => $id]);
|
||||
$pdo->prepare("DELETE FROM question_score_rule WHERE questionID = :id")->execute([':id' => $id]);
|
||||
$pdo->prepare("DELETE FROM question_translation WHERE questionID = :id")->execute([':id' => $id]);
|
||||
$pdo->prepare("DELETE FROM client_answer WHERE questionID = :id")->execute([':id' => $id]);
|
||||
$pdo->prepare("DELETE FROM question WHERE questionID = :id")->execute([':id' => $id]);
|
||||
|
||||
203
handlers/scoring_profiles.php
Normal file
203
handlers/scoring_profiles.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Scoring profiles CRUD (weighted multi-questionnaire Green/Yellow/Red bands).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../lib/scoring.php';
|
||||
|
||||
$tokenRec = require_valid_token_web();
|
||||
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
try {
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
|
||||
$profileID = trim($_GET['id'] ?? '');
|
||||
if ($profileID !== '') {
|
||||
$profile = qdb_get_scoring_profile($pdo, $profileID);
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
if (!$profile) {
|
||||
json_error('NOT_FOUND', 'Scoring profile not found', 404);
|
||||
}
|
||||
json_success(['profile' => $profile]);
|
||||
}
|
||||
json_success(['profiles' => qdb_list_scoring_profiles($pdo)]);
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'List scoring profiles', null, $tmpDb ?? null, $lockFp ?? null);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
require_role(['admin', 'supervisor'], $tokenRec);
|
||||
$body = read_json_body();
|
||||
$name = trim($body['name'] ?? '');
|
||||
if ($name === '') {
|
||||
json_error('BAD_REQUEST', 'name is required', 400);
|
||||
}
|
||||
$bands = qdb_parse_scoring_bands_body($body);
|
||||
$bandErr = qdb_validate_scoring_bands($bands);
|
||||
if ($bandErr !== null) {
|
||||
json_error('INVALID_FIELD', $bandErr, 400);
|
||||
}
|
||||
$members = qdb_parse_profile_questionnaires($body['questionnaires'] ?? []);
|
||||
if ($members === []) {
|
||||
json_error('INVALID_FIELD', 'At least one questionnaire is required', 400);
|
||||
}
|
||||
try {
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||
$profileID = bin2hex(random_bytes(16));
|
||||
$now = time();
|
||||
$pdo->prepare(
|
||||
'INSERT INTO scoring_profile (profileID, name, description, isActive,
|
||||
greenMin, greenMax, yellowMin, yellowMax, redMin, createdAt, updatedAt)
|
||||
VALUES (:id, :name, :desc, :act, :gmin, :gmax, :ymin, :ymax, :rmin, :ca, :ua)'
|
||||
)->execute([
|
||||
':id' => $profileID,
|
||||
':name' => $name,
|
||||
':desc' => trim($body['description'] ?? ''),
|
||||
':act' => !empty($body['isActive']) ? 1 : 0,
|
||||
':gmin' => $bands['greenMin'],
|
||||
':gmax' => $bands['greenMax'],
|
||||
':ymin' => $bands['yellowMin'],
|
||||
':ymax' => $bands['yellowMax'],
|
||||
':rmin' => $bands['redMin'],
|
||||
':ca' => $now,
|
||||
':ua' => $now,
|
||||
]);
|
||||
qdb_sync_profile_questionnaires($pdo, $profileID, $members);
|
||||
qdb_recompute_all_profiles_after_change($pdo);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['profile' => qdb_get_scoring_profile($pdo, $profileID)]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'Create scoring profile', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
require_role(['admin', 'supervisor'], $tokenRec);
|
||||
$body = read_json_body();
|
||||
$profileID = trim($body['profileID'] ?? '');
|
||||
if ($profileID === '') {
|
||||
json_error('BAD_REQUEST', 'profileID is required', 400);
|
||||
}
|
||||
try {
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||
$existing = qdb_get_scoring_profile($pdo, $profileID);
|
||||
if (!$existing) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('NOT_FOUND', 'Scoring profile not found', 404);
|
||||
}
|
||||
$name = trim($body['name'] ?? $existing['name']);
|
||||
$bands = qdb_normalize_scoring_bands(array_merge($existing, $body));
|
||||
$bandErr = qdb_validate_scoring_bands($bands);
|
||||
if ($bandErr !== null) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('INVALID_FIELD', $bandErr, 400);
|
||||
}
|
||||
$members = isset($body['questionnaires'])
|
||||
? qdb_parse_profile_questionnaires($body['questionnaires'])
|
||||
: null;
|
||||
if ($members !== null && $members === []) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('INVALID_FIELD', 'At least one questionnaire is required', 400);
|
||||
}
|
||||
$pdo->prepare(
|
||||
'UPDATE scoring_profile SET name = :name, description = :desc, isActive = :act,
|
||||
greenMin = :gmin, greenMax = :gmax, yellowMin = :ymin, yellowMax = :ymax, redMin = :rmin,
|
||||
updatedAt = :ua WHERE profileID = :id'
|
||||
)->execute([
|
||||
':name' => $name,
|
||||
':desc' => trim($body['description'] ?? $existing['description']),
|
||||
':act' => isset($body['isActive']) ? (!empty($body['isActive']) ? 1 : 0) : $existing['isActive'],
|
||||
':gmin' => $bands['greenMin'],
|
||||
':gmax' => $bands['greenMax'],
|
||||
':ymin' => $bands['yellowMin'],
|
||||
':ymax' => $bands['yellowMax'],
|
||||
':rmin' => $bands['redMin'],
|
||||
':ua' => time(),
|
||||
':id' => $profileID,
|
||||
]);
|
||||
if ($members !== null) {
|
||||
qdb_sync_profile_questionnaires($pdo, $profileID, $members);
|
||||
}
|
||||
qdb_recompute_all_profiles_after_change($pdo, $profileID);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['profile' => qdb_get_scoring_profile($pdo, $profileID)]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'Update scoring profile', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
require_role(['admin', 'supervisor'], $tokenRec);
|
||||
$body = read_json_body();
|
||||
$profileID = trim($body['profileID'] ?? '');
|
||||
if ($profileID === '') {
|
||||
json_error('BAD_REQUEST', 'profileID is required', 400);
|
||||
}
|
||||
try {
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||
$pdo->prepare('DELETE FROM scoring_profile WHERE profileID = :id')->execute([':id' => $profileID]);
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
json_success(['deleted' => true]);
|
||||
} catch (Throwable $e) {
|
||||
qdb_handler_fail($e, 'Delete scoring profile', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{questionnaireID: string, weight: float, orderIndex: int}>
|
||||
*/
|
||||
function qdb_parse_profile_questionnaires(array $rows): array {
|
||||
$out = [];
|
||||
$idx = 0;
|
||||
foreach ($rows as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$qnID = trim((string)($row['questionnaireID'] ?? ''));
|
||||
if ($qnID === '') {
|
||||
continue;
|
||||
}
|
||||
$weight = (float)($row['weight'] ?? 1.0);
|
||||
if ($weight <= 0) {
|
||||
$weight = 1.0;
|
||||
}
|
||||
$out[] = [
|
||||
'questionnaireID' => $qnID,
|
||||
'weight' => $weight,
|
||||
'orderIndex' => (int)($row['orderIndex'] ?? $idx),
|
||||
];
|
||||
$idx++;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function qdb_sync_profile_questionnaires(PDO $pdo, string $profileID, array $members): void {
|
||||
$pdo->prepare('DELETE FROM scoring_profile_questionnaire WHERE profileID = :pid')
|
||||
->execute([':pid' => $profileID]);
|
||||
$ins = $pdo->prepare(
|
||||
'INSERT INTO scoring_profile_questionnaire (profileID, questionnaireID, weight, orderIndex)
|
||||
VALUES (:pid, :qn, :w, :o)'
|
||||
);
|
||||
foreach ($members as $m) {
|
||||
$ins->execute([
|
||||
':pid' => $profileID,
|
||||
':qn' => $m['questionnaireID'],
|
||||
':w' => $m['weight'],
|
||||
':o' => $m['orderIndex'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function qdb_recompute_all_profiles_after_change(PDO $pdo, ?string $profileID = null): void {
|
||||
$clients = $pdo->query('SELECT DISTINCT clientCode FROM completed_questionnaire WHERE status = \'completed\'')
|
||||
->fetchAll(PDO::FETCH_COLUMN);
|
||||
foreach ($clients as $clientCode) {
|
||||
qdb_recompute_profile_scores_for_client($pdo, (string)$clientCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user