210 lines
8.1 KiB
PHP
210 lines
8.1 KiB
PHP
<?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);
|
|
}
|
|
$processCompleteBands = qdb_parse_process_complete_bands($body['processCompleteBands'] ?? []);
|
|
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, processCompleteBands, createdAt, updatedAt)
|
|
VALUES (:id, :name, :desc, :act, :gmin, :gmax, :ymin, :ymax, :rmin, :pcb, :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'],
|
|
':pcb' => qdb_encode_process_complete_bands($processCompleteBands),
|
|
':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);
|
|
}
|
|
$processCompleteBands = array_key_exists('processCompleteBands', $body)
|
|
? qdb_parse_process_complete_bands($body['processCompleteBands'])
|
|
: qdb_row_process_complete_bands($existing);
|
|
$pdo->prepare(
|
|
'UPDATE scoring_profile SET name = :name, description = :desc, isActive = :act,
|
|
greenMin = :gmin, greenMax = :gmax, yellowMin = :ymin, yellowMax = :ymax, redMin = :rmin,
|
|
processCompleteBands = :pcb, 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'],
|
|
':pcb' => qdb_encode_process_complete_bands($processCompleteBands),
|
|
':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);
|
|
}
|
|
}
|