328 lines
12 KiB
PHP
328 lines
12 KiB
PHP
<?php
|
|
|
|
$tokenRec = require_valid_token_web();
|
|
|
|
$callerRole = $tokenRec['role'];
|
|
$callerEntityID = $tokenRec['entityID'] ?? '';
|
|
|
|
switch ($method) {
|
|
|
|
case 'GET':
|
|
try {
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
|
|
|
|
$clientCode = trim((string)($_GET['clientCode'] ?? ''));
|
|
if ($clientCode !== '' && !empty($_GET['detail'])) {
|
|
require_once __DIR__ . '/../lib/submissions.php';
|
|
$detail = qdb_client_detail($pdo, $tokenRec, $clientCode);
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_success($detail);
|
|
}
|
|
|
|
$archiveFilter = trim((string)($_GET['archiveFilter'] ?? 'active'));
|
|
if (!in_array($archiveFilter, ['active', 'archived', 'all'], true)) {
|
|
$archiveFilter = 'active';
|
|
}
|
|
[$clause, $params] = rbac_client_filter($tokenRec, 'cl', $archiveFilter);
|
|
$stmt = $pdo->prepare(
|
|
"SELECT cl.clientCode, cl.coachID, cl.archived, cl.archivedAt,
|
|
co.username AS coachUsername,
|
|
CASE WHEN EXISTS (
|
|
SELECT 1 FROM completed_questionnaire cq WHERE cq.clientCode = cl.clientCode
|
|
) OR EXISTS (
|
|
SELECT 1 FROM questionnaire_submission qs WHERE qs.clientCode = cl.clientCode
|
|
) THEN 1 ELSE 0 END AS hasResponseData
|
|
FROM client cl
|
|
LEFT JOIN coach co ON co.coachID = cl.coachID
|
|
WHERE $clause
|
|
ORDER BY cl.archived ASC, hasResponseData DESC, cl.clientCode ASC"
|
|
);
|
|
foreach ($params as $k => $v) $stmt->bindValue($k, $v);
|
|
$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,
|
|
'scoringProfiles' => $scoringSummary['profiles'],
|
|
]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'clients', null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'POST':
|
|
$body = read_json_body();
|
|
|
|
if (($body['action'] ?? '') === 'reset') {
|
|
$clientCode = trim((string)($body['clientCode'] ?? ''));
|
|
if ($clientCode === '') {
|
|
json_error('MISSING_FIELDS', 'clientCode is required', 400);
|
|
}
|
|
|
|
try {
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
|
|
[$clause, $params] = rbac_client_filter($tokenRec, 'cl', 'all');
|
|
$chk = $pdo->prepare(
|
|
"SELECT clientCode FROM client cl WHERE cl.clientCode = :cc AND ($clause)"
|
|
);
|
|
$chk->execute(array_merge([':cc' => $clientCode], $params));
|
|
if (!$chk->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
|
|
}
|
|
|
|
require_once __DIR__ . '/../lib/submissions.php';
|
|
|
|
$pdo->beginTransaction();
|
|
$cleared = qdb_delete_client_response_data($pdo, [$clientCode]);
|
|
$pdo->commit();
|
|
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success([
|
|
'clientCode' => $clientCode,
|
|
'reset' => true,
|
|
'cleared' => $cleared,
|
|
]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
}
|
|
|
|
$coachID = trim($body['coachID'] ?? '');
|
|
$bulk = !empty($body['bulk']);
|
|
|
|
if ($coachID === '') {
|
|
json_error('MISSING_FIELDS', 'coachID is required', 400);
|
|
}
|
|
|
|
try {
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
|
|
if ($callerRole === 'supervisor') {
|
|
$chkCoach = $pdo->prepare("SELECT coachID FROM coach WHERE coachID = :cid AND supervisorID = :sid");
|
|
$chkCoach->execute([':cid' => $coachID, ':sid' => $callerEntityID]);
|
|
} else {
|
|
$chkCoach = $pdo->prepare("SELECT coachID FROM coach WHERE coachID = :cid");
|
|
$chkCoach->execute([':cid' => $coachID]);
|
|
}
|
|
if (!$chkCoach->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Counselor not found or not authorized', 404);
|
|
}
|
|
|
|
if ($bulk) {
|
|
$fromCode = trim($body['fromCode'] ?? '');
|
|
$toCode = trim($body['toCode'] ?? '');
|
|
if ($fromCode === '' || $toCode === '') {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('MISSING_FIELDS', 'fromCode and toCode are required for bulk creation', 400);
|
|
}
|
|
|
|
require_once __DIR__ . '/../lib/client_code_range.php';
|
|
try {
|
|
$codes = qdb_expand_client_code_range($fromCode, $toCode);
|
|
} catch (InvalidArgumentException $e) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('INVALID_RANGE', $e->getMessage(), 400);
|
|
}
|
|
|
|
$chk = $pdo->prepare("SELECT clientCode FROM client WHERE clientCode = :cc");
|
|
$insert = $pdo->prepare(
|
|
"INSERT INTO client (clientCode, coachID, archived, archivedAt) VALUES (:cc, :cid, 0, 0)"
|
|
);
|
|
|
|
$created = [];
|
|
$skipped = [];
|
|
foreach ($codes as $code) {
|
|
$chk->execute([':cc' => $code]);
|
|
if ($chk->fetch()) {
|
|
$skipped[] = ['clientCode' => $code, 'reason' => 'duplicate'];
|
|
continue;
|
|
}
|
|
$insert->execute([':cc' => $code, ':cid' => $coachID]);
|
|
$created[] = $code;
|
|
}
|
|
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success([
|
|
'coachID' => $coachID,
|
|
'fromCode' => $fromCode,
|
|
'toCode' => $toCode,
|
|
'created' => $created,
|
|
'skipped' => $skipped,
|
|
'createdCount' => count($created),
|
|
'skippedCount' => count($skipped),
|
|
]);
|
|
}
|
|
|
|
$clientCode = trim($body['clientCode'] ?? '');
|
|
if ($clientCode === '') {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('MISSING_FIELDS', 'clientCode is required', 400);
|
|
}
|
|
|
|
$chk = $pdo->prepare("SELECT clientCode FROM client WHERE clientCode = :cc");
|
|
$chk->execute([':cc' => $clientCode]);
|
|
if ($chk->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('DUPLICATE', 'Client code already exists', 409);
|
|
}
|
|
|
|
$pdo->prepare(
|
|
"INSERT INTO client (clientCode, coachID, archived, archivedAt) VALUES (:cc, :cid, 0, 0)"
|
|
)->execute([':cc' => $clientCode, ':cid' => $coachID]);
|
|
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success(['clientCode' => $clientCode, 'coachID' => $coachID]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'clients', null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'PUT':
|
|
$body = read_json_body();
|
|
$clientCode = trim((string)($body['clientCode'] ?? ''));
|
|
$profileID = trim((string)($body['profileID'] ?? ''));
|
|
$coachBand = trim((string)($body['coachBand'] ?? ''));
|
|
|
|
if ($clientCode === '' || $profileID === '' || $coachBand === '') {
|
|
json_error('MISSING_FIELDS', 'clientCode, profileID, and coachBand are required', 400);
|
|
}
|
|
|
|
$calculatedBand = trim((string)($body['calculatedBand'] ?? ''));
|
|
$weightedTotal = isset($body['weightedTotal']) ? (float)$body['weightedTotal'] : null;
|
|
|
|
try {
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
|
|
[$clause, $params] = rbac_client_filter($tokenRec, 'cl');
|
|
$chk = $pdo->prepare(
|
|
"SELECT clientCode FROM client cl WHERE cl.clientCode = :cc AND ($clause)"
|
|
);
|
|
$chk->execute(array_merge([':cc' => $clientCode], $params));
|
|
if (!$chk->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, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'PATCH':
|
|
$body = read_json_body();
|
|
$clientCode = trim((string)($body['clientCode'] ?? ''));
|
|
if ($clientCode === '' || !array_key_exists('archived', $body)) {
|
|
json_error('MISSING_FIELDS', 'clientCode and archived are required', 400);
|
|
}
|
|
$archived = !empty($body['archived']) ? 1 : 0;
|
|
|
|
try {
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
|
|
[$clause, $params] = rbac_client_filter($tokenRec, 'cl', 'all');
|
|
$chk = $pdo->prepare(
|
|
"SELECT clientCode, archived FROM client cl WHERE cl.clientCode = :cc AND ($clause)"
|
|
);
|
|
$chk->execute(array_merge([':cc' => $clientCode], $params));
|
|
$row = $chk->fetch(PDO::FETCH_ASSOC);
|
|
if (!$row) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
|
|
}
|
|
|
|
$archivedAt = $archived ? time() : 0;
|
|
$pdo->prepare(
|
|
'UPDATE client SET archived = :a, archivedAt = :at WHERE clientCode = :cc'
|
|
)->execute([':a' => $archived, ':at' => $archivedAt, ':cc' => $clientCode]);
|
|
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success([
|
|
'clientCode' => $clientCode,
|
|
'archived' => $archived,
|
|
'archivedAt' => $archivedAt,
|
|
]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
case 'DELETE':
|
|
$body = read_json_body();
|
|
$clientCode = trim($body['clientCode'] ?? '');
|
|
|
|
if ($clientCode === '') {
|
|
json_error('MISSING_FIELDS', 'clientCode is required', 400);
|
|
}
|
|
|
|
try {
|
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
|
|
|
// Verify the client exists and the caller can see it (RBAC)
|
|
[$clause, $params] = rbac_client_filter($tokenRec, 'cl');
|
|
$chk = $pdo->prepare(
|
|
"SELECT clientCode FROM client cl WHERE cl.clientCode = :cc AND ($clause)"
|
|
);
|
|
$chk->execute(array_merge([':cc' => $clientCode], $params));
|
|
if (!$chk->fetch()) {
|
|
qdb_discard($tmpDb, $lockFp);
|
|
json_error('NOT_FOUND', 'Client not found or not authorized', 404);
|
|
}
|
|
|
|
require_once __DIR__ . '/../lib/submissions.php';
|
|
|
|
$pdo->beginTransaction();
|
|
qdb_delete_client_response_data($pdo, [$clientCode]);
|
|
$pdo->prepare('DELETE FROM client WHERE clientCode = :cc')
|
|
->execute([':cc' => $clientCode]);
|
|
$pdo->commit();
|
|
|
|
qdb_save($tmpDb, $lockFp);
|
|
json_success([]);
|
|
} catch (Throwable $e) {
|
|
qdb_handler_fail($e, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
|
|
}
|