Files
ladill-care/app/Services/Care/ServiceQueuePresenter.php
T
isaaccladandCursor dec282d25d
Deploy Ladill Care / deploy (push) Successful in 53s
Embed Ladill Queue on role pages and add specialty modules under Settings.
Remove the standalone Service Queues nav so call-next/now-serving lives on clinical queue, appointments, pharmacy, lab, and specialty surfaces; Pro orgs can activate dentistry and related modules with branch departments and queue stubs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 15:55:47 +00:00

250 lines
8.1 KiB
PHP

<?php
namespace App\Services\Care;
use App\Models\Branch;
use App\Models\Member;
use App\Models\Organization;
use App\Services\Queue\QueueClient;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Request;
/**
* Loads Ladill Queue console state for embedding on role-native pages
* (clinical queue, pharmacy, lab, specialty modules) instead of a standalone Service Queues nav.
*/
class ServiceQueuePresenter
{
/** @var array<string, list<string>> */
protected array $contextKeywords = [
'clinical' => ['reception', 'consult', 'outpatient', 'general', 'doctor', 'nurse', 'triage', 'waiting'],
'pharmacy' => ['pharm', 'dispens', 'drug', 'rx'],
'laboratory' => ['lab', 'sample', 'patholog', 'investig'],
'dentistry' => ['dent', 'dental', 'oral'],
'ophthalmology' => ['eye', 'ophthal', 'optom'],
'physiotherapy' => ['physio', 'rehab', 'therapy'],
'maternity' => ['matern', 'antenatal', 'obstetric'],
'radiology' => ['radio', 'imaging', 'x-ray', 'xray', 'ultrasound', 'ct', 'mri'],
];
public function __construct(
protected QueueClient $queue,
protected CarePermissions $permissions,
protected PlanService $plans,
protected OrganizationResolver $resolver,
) {}
/**
* @return array{
* enabled: bool,
* counters: list<array<string, mixed>>,
* counter_uuid: ?string,
* state: ?array<string, mixed>,
* stubs: list<array<string, mixed>>,
* error: ?string,
* context: string
* }|null
*/
public function panel(
Request $request,
Organization $organization,
?Member $member,
string $context = 'clinical',
?string $preferredCounterUuid = null,
): ?array {
if (! $this->shouldShow($organization, $member)) {
return null;
}
$owner = (string) $organization->owner_ref;
$branchScope = $this->resolver->branchScope($member);
$branchName = null;
if ($branchScope) {
$branchName = Branch::owned($owner)->where('id', $branchScope)->value('name');
}
$stubs = $this->stubsForContext($organization, $context, $branchScope);
if (! $this->queue->configured()) {
return [
'enabled' => true,
'counters' => [],
'counter_uuid' => null,
'state' => null,
'stubs' => $stubs,
'error' => 'Ladill Queue API is not configured on this Care instance.',
'context' => $context,
];
}
try {
$counters = $this->queue->counters($owner);
} catch (RequestException $e) {
if ($e->response?->status() === 403) {
try {
$this->queue->syncIntegration($owner, true);
$counters = $this->queue->counters($owner);
} catch (RequestException) {
return [
'enabled' => true,
'counters' => [],
'counter_uuid' => null,
'state' => null,
'stubs' => $stubs,
'error' => 'Queue integration is not linked. Re-save Settings with Queue enabled.',
'context' => $context,
];
}
} else {
return [
'enabled' => true,
'counters' => [],
'counter_uuid' => null,
'state' => null,
'stubs' => $stubs,
'error' => 'Could not reach Ladill Queue.',
'context' => $context,
];
}
}
$counters = $this->filterCounters($counters, $branchName, $context);
$counterUuid = $preferredCounterUuid
?: (string) ($counters[0]['uuid'] ?? '');
if ($counterUuid !== '' && ! collect($counters)->contains(fn ($c) => ($c['uuid'] ?? '') === $counterUuid)) {
$counterUuid = (string) ($counters[0]['uuid'] ?? '');
}
$state = null;
if ($counterUuid !== '') {
try {
$state = $this->queue->console($owner, $counterUuid);
} catch (RequestException) {
return [
'enabled' => true,
'counters' => $counters,
'counter_uuid' => $counterUuid,
'state' => null,
'stubs' => $stubs,
'error' => 'Counter console unavailable.',
'context' => $context,
];
}
}
return [
'enabled' => true,
'counters' => $counters,
'counter_uuid' => $counterUuid !== '' ? $counterUuid : null,
'state' => $state,
'stubs' => $stubs,
'error' => null,
'context' => $context,
];
}
public function shouldShow(Organization $organization, ?Member $member): bool
{
if (! $member) {
return false;
}
if (! $this->plans->canUseQueueIntegration($organization)) {
return false;
}
if (! data_get($organization->settings, 'queue_integration_enabled')) {
return false;
}
return $this->permissions->can($member, 'service_queues.console');
}
/**
* @param list<array<string, mixed>> $counters
* @return list<array<string, mixed>>
*/
protected function filterCounters(array $counters, ?string $branchName, string $context): array
{
$keywords = $this->keywordsFor($context);
$filtered = collect($counters);
if ($branchName) {
$branchFiltered = $filtered->filter(function ($counter) use ($branchName) {
$counterBranch = (string) ($counter['branch'] ?? '');
return $counterBranch === '' || strcasecmp($counterBranch, $branchName) === 0;
});
if ($branchFiltered->isNotEmpty()) {
$filtered = $branchFiltered;
}
}
if ($keywords !== []) {
$keywordFiltered = $filtered->filter(function ($counter) use ($keywords) {
$haystack = strtolower(implode(' ', [
(string) ($counter['name'] ?? ''),
collect($counter['queues'] ?? [])->pluck('name')->implode(' '),
]));
foreach ($keywords as $keyword) {
if ($keyword !== '' && str_contains($haystack, $keyword)) {
return true;
}
}
return false;
});
// Fall back to branch-scoped counters when no keyword match (demo / generic counters).
if ($keywordFiltered->isNotEmpty()) {
$filtered = $keywordFiltered;
}
}
return $filtered->values()->all();
}
/**
* @return list<string>
*/
protected function keywordsFor(string $context): array
{
if (isset($this->contextKeywords[$context])) {
return $this->contextKeywords[$context];
}
$module = config("care.specialty_modules.{$context}.queue_keywords");
return is_array($module) ? $module : [];
}
/**
* @return list<array<string, mixed>>
*/
protected function stubsForContext(Organization $organization, string $context, ?int $branchId): array
{
if (! array_key_exists($context, config('care.specialty_modules', []))) {
return [];
}
$queues = data_get($organization->settings, "specialty_module_provisioning.{$context}.queues", []);
if (! is_array($queues)) {
return [];
}
return array_values(array_filter($queues, function ($queue) use ($branchId) {
if (! ($queue['active'] ?? false)) {
return false;
}
if ($branchId !== null && (int) ($queue['branch_id'] ?? 0) !== $branchId) {
return false;
}
return true;
}));
}
}