Issue Queue tickets for every waiting appointment when integration is on.
Deploy Ladill Care / deploy (push) Successful in 51s

Patient queue syncs consultation and specialty waiters; specialty contexts now provision desks so check-in/onboarding no longer leaves half the list on local positions only.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 06:12:32 +00:00
co-authored by Cursor
parent 4e910a1db7
commit e94708d46c
6 changed files with 312 additions and 66 deletions
+115 -17
View File
@@ -198,9 +198,21 @@ class CareQueueProvisioner
$owner = (string) $organization->owner_ref;
$priorByKey = collect($stub['points'] ?? [])->keyBy(fn ($p) => (string) ($p['external_key'] ?? ''));
$points = match ($context) {
CareQueueContexts::CONSULTATION => $this->practitionerPoints($organization, $branch, $priorByKey),
CareQueueContexts::TRIAGE => $this->memberRolePoints(
$points = match (true) {
$context === CareQueueContexts::CONSULTATION => $this->practitionerPoints(
$organization,
$branch,
CareQueueContexts::CONSULTATION,
$priorByKey,
),
CareQueueContexts::isSpecialty($context) => $this->practitionerPoints(
$organization,
$branch,
$context,
$priorByKey,
data_get($organization->settings, "specialty_module_provisioning.{$context}.department_ids", []),
),
$context === CareQueueContexts::TRIAGE => $this->memberRolePoints(
$organization,
$branch,
['nurse'],
@@ -208,7 +220,7 @@ class CareQueueProvisioner
'Nurse station',
$priorByKey,
),
CareQueueContexts::PHARMACY => $this->memberRolePoints(
$context === CareQueueContexts::PHARMACY => $this->memberRolePoints(
$organization,
$branch,
['pharmacist'],
@@ -216,7 +228,7 @@ class CareQueueProvisioner
'Pharmacy counter',
$priorByKey,
),
CareQueueContexts::LABORATORY, CareQueueContexts::IMAGING => $this->memberRolePoints(
in_array($context, [CareQueueContexts::LABORATORY, CareQueueContexts::IMAGING], true) => $this->memberRolePoints(
$organization,
$branch,
['lab_technician'],
@@ -224,7 +236,7 @@ class CareQueueProvisioner
$context === CareQueueContexts::IMAGING ? 'Imaging room' : 'Lab desk',
$priorByKey,
),
CareQueueContexts::BILLING => $this->memberRolePoints(
$context === CareQueueContexts::BILLING => $this->memberRolePoints(
$organization,
$branch,
['cashier'],
@@ -232,7 +244,7 @@ class CareQueueProvisioner
'Cashier desk',
$priorByKey,
),
CareQueueContexts::RECEPTION => $this->memberRolePoints(
$context === CareQueueContexts::RECEPTION => $this->memberRolePoints(
$organization,
$branch,
['receptionist'],
@@ -264,23 +276,37 @@ class CareQueueProvisioner
}
/**
* @param list<int|string>|null $departmentIds
* @param \Illuminate\Support\Collection<string, array<string, mixed>> $priorByKey
* @return list<array<string, mixed>>
*/
protected function practitionerPoints(Organization $organization, Branch $branch, $priorByKey): array
{
protected function practitionerPoints(
Organization $organization,
Branch $branch,
string $context,
$priorByKey,
?array $departmentIds = null,
): array {
$practitioners = Practitioner::owned($organization->owner_ref)
->where('organization_id', $organization->id)
->where('branch_id', $branch->id)
->where('is_active', true)
->when(
is_array($departmentIds) && $departmentIds !== [],
fn ($q) => $q->whereIn('department_id', array_map('intval', $departmentIds)),
)
->orderBy('name')
->get();
$points = [];
$roomIndex = 1;
$roomPrefix = CareQueueContexts::isSpecialty($context)
? (string) (config("care.specialty_modules.{$context}.label") ?? 'Room')
: 'Consultation Room';
foreach ($practitioners as $practitioner) {
$key = CareQueueContexts::pointExternalKey(
CareQueueContexts::CONSULTATION,
$context,
$branch->id,
'practitioner',
$practitioner->id,
@@ -288,7 +314,7 @@ class CareQueueProvisioner
$prior = $priorByKey->get($key, []);
$room = trim((string) ($practitioner->room ?? ''));
if ($room === '') {
$room = 'Consultation Room '.$roomIndex;
$room = $roomPrefix.' '.$roomIndex;
}
$roomIndex++;
@@ -407,13 +433,17 @@ class CareQueueProvisioner
}
/**
* Resolve a service point for a practitioner (consultation) or member staff_ref.
* Resolve a service point for a practitioner on a queue context.
*
* @return array<string, mixed>|null
*/
public function pointForPractitioner(Organization $organization, int $branchId, int $practitionerId): ?array
{
$resources = $this->resourcesFor($organization, CareQueueContexts::CONSULTATION, $branchId);
public function pointForPractitioner(
Organization $organization,
int $branchId,
int $practitionerId,
string $context = CareQueueContexts::CONSULTATION,
): ?array {
$resources = $this->resourcesFor($organization, $context, $branchId);
if (! $resources) {
return null;
}
@@ -495,7 +525,11 @@ class CareQueueProvisioner
if ($existing && ! empty($existing['queue_uuid']) && (
! empty($existing['points']) || ! empty($existing['counter_uuid'])
) && $modeMatches) {
return $existing;
// Specialty module stubs often only store a legacy counter — still provision
// practitioner points so check-in / sync can assign real desks.
if (! CareQueueContexts::isSpecialty($context) || ! empty($existing['points'])) {
return $existing;
}
}
if (! $this->shouldProvision($organization)) {
@@ -503,7 +537,8 @@ class CareQueueProvisioner
}
if (CareQueueContexts::isSpecialty($context)) {
return $existing;
return $this->provisionSpecialtyContextBranch($organization->fresh(), $context, $branchId)
?? $existing;
}
if (! CareQueueContexts::isDepartment($context)) {
@@ -515,6 +550,69 @@ class CareQueueProvisioner
return $this->resourcesFor($organization->fresh(), $context, $branchId);
}
/**
* Provision a single specialty queue for one branch (idempotent).
*
* @return array<string, mixed>|null
*/
public function provisionSpecialtyContextBranch(Organization $organization, string $context, int $branchId): ?array
{
if (! CareQueueContexts::isSpecialty($context) || ! $this->shouldProvision($organization)) {
return $this->resourcesFor($organization, $context, $branchId);
}
$definition = config("care.specialty_modules.{$context}");
if (! is_array($definition)) {
return null;
}
$branch = Branch::owned((string) $organization->owner_ref)
->where('organization_id', $organization->id)
->whereKey($branchId)
->first();
if (! $branch) {
return null;
}
$meta = [
'name' => (string) ($definition['queue_name'] ?? $definition['label'] ?? $context),
'prefix' => (string) ($definition['queue_prefix'] ?? strtoupper(substr($context, 0, 3))),
'routing_mode' => 'assigned_only',
'type' => 'consultation',
];
$provisioning = (array) data_get($organization->settings, 'specialty_module_provisioning', []);
$record = is_array($provisioning[$context] ?? null) ? $provisioning[$context] : [];
$queues = is_array($record['queues'] ?? null) ? $record['queues'] : [];
$byBranch = collect($queues)->keyBy(fn ($q) => (string) ($q['branch_id'] ?? ''));
$prior = $byBranch->get((string) $branch->id, []);
if (! is_array($prior)) {
$prior = [];
}
$prior['module'] = $context;
$stub = $this->provisionBranchDepartment(
$organization,
(string) $organization->owner_ref,
$context,
$meta,
$branch,
$prior,
);
$byBranch->put((string) $branch->id, $stub);
$provisioning[$context] = array_merge($record, [
'active' => true,
'queues' => $byBranch->values()->all(),
]);
$settings = $organization->settings ?? [];
$settings['specialty_module_provisioning'] = $provisioning;
$organization->update(['settings' => $settings]);
return $stub;
}
/**
* Provision a single department queue for one branch (idempotent).
*/