Fix patient queue showing position 1 for every waiter.
Deploy Ladill Care / deploy (push) Successful in 1m44s

Specialty demo seed reused slot+1 per module; repair duplicates on queue load and renumber after demo seed so waiting lists show 1..n.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 05:54:47 +00:00
co-authored by Cursor
parent 6cfecc1763
commit 4e910a1db7
3 changed files with 125 additions and 2 deletions
+33
View File
@@ -73,6 +73,8 @@ class AppointmentService
*/
public function queue(string $ownerRef, int $branchId, ?int $practitionerId = null): Collection
{
$this->repairDuplicateQueuePositions($ownerRef, $branchId);
$query = Appointment::owned($ownerRef)
->where('branch_id', $branchId)
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
@@ -381,6 +383,37 @@ class AppointmentService
return ($max ?? 0) + 1;
}
/**
* Demo specialty seed used to set every module's waiter to position 1.
* Repair duplicates in place so the patient queue shows 1..n.
*/
protected function repairDuplicateQueuePositions(string $ownerRef, int $branchId): void
{
$waiters = Appointment::owned($ownerRef)
->where('branch_id', $branchId)
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
->orderByRaw('COALESCE(waiting_at, checked_in_at, scheduled_at) asc')
->orderBy('id')
->get(['id', 'queue_position']);
if ($waiters->count() < 2) {
return;
}
$positions = $waiters->pluck('queue_position');
if ($positions->filter(fn ($p) => $p !== null)->unique()->count() === $waiters->count()) {
return;
}
foreach ($waiters as $index => $appointment) {
$position = $index + 1;
if ((int) $appointment->queue_position === $position) {
continue;
}
Appointment::whereKey($appointment->id)->update(['queue_position' => $position]);
}
}
protected function assertTransition(Appointment $appointment, string ...$allowedFrom): void
{
if (! in_array($appointment->status, $allowedFrom, true)) {
+46 -2
View File
@@ -125,7 +125,6 @@ class DemoTenantSeeder
$ownerRef,
$volumes,
);
$this->assignAllDemoWaiters($organization, $ownerRef, $branches, $practitioners);
if (in_array($plan, ['pro', 'enterprise'], true)) {
$appointmentCount += $this->seedSpecialtyDemoData(
$organization->fresh(),
@@ -135,6 +134,8 @@ class DemoTenantSeeder
$ownerRef,
);
}
$this->assignAllDemoWaiters($organization, $ownerRef, $branches, $practitioners);
$this->renumberWaitingQueuePositions($organization, $ownerRef, $branches);
if ($volumes['paid_modules']) {
$this->seedPaidModules(
@@ -297,6 +298,39 @@ class DemoTenantSeeder
}
}
/**
* Give each waiting/checked-in appointment a unique 1..n position per branch.
*
* @param list<Branch> $branches
*/
private function renumberWaitingQueuePositions(
Organization $organization,
string $ownerRef,
array $branches,
): void {
foreach ($branches as $branch) {
$waiters = Appointment::withTrashed()
->owned($ownerRef)
->where('organization_id', $organization->id)
->where('branch_id', $branch->id)
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
->orderByRaw('COALESCE(waiting_at, checked_in_at, scheduled_at) asc')
->orderBy('id')
->get();
foreach ($waiters as $index => $appointment) {
$position = $index + 1;
if ((int) $appointment->queue_position === $position) {
continue;
}
$appointment->forceFill([
'queue_position' => $position,
'deleted_at' => null,
])->save();
}
}
}
/**
* Ensure the demo tenant has an onboarded organization + owner member.
* Safe to call on the SSO request path before redirecting.
@@ -751,6 +785,8 @@ class DemoTenantSeeder
$count = 0;
$catalog = config('care.specialty_modules', []);
/** @var array<int, int> $nextPositionByBranch */
$nextPositionByBranch = [];
foreach ($catalog as $key => $definition) {
$type = (string) ($definition['department_type'] ?? '');
if ($type === '') {
@@ -814,6 +850,13 @@ class DemoTenantSeeder
);
}
$inQueue = in_array($status, [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN], true);
$queuePosition = null;
if ($inQueue) {
$nextPositionByBranch[$branch->id] = ($nextPositionByBranch[$branch->id] ?? 0) + 1;
$queuePosition = $nextPositionByBranch[$branch->id];
}
Appointment::withTrashed()->updateOrCreate(
['uuid' => $this->demoUuid("appt|{$ownerRef}|{$token}")],
[
@@ -828,11 +871,12 @@ class DemoTenantSeeder
'status' => $status,
'scheduled_at' => $scheduledAt,
'checked_in_at' => $visit?->checked_in_at,
'waiting_at' => $inQueue ? $scheduledAt->copy()->addMinutes(5) : null,
'completed_at' => $status === Appointment::STATUS_COMPLETED
? $scheduledAt->copy()->addHour()
: null,
'cancelled_at' => null,
'queue_position' => $slot + 1,
'queue_position' => $queuePosition,
'reason' => $moduleReasons[$slot % count($moduleReasons)],
'created_by' => $ownerRef,
'deleted_at' => null,
+46
View File
@@ -209,6 +209,52 @@ class CareAppointmentTest extends TestCase
->assertSee('Patient queue');
}
public function test_queue_repairs_duplicate_positions(): void
{
$second = Patient::create([
'uuid' => (string) \Illuminate\Support\Str::uuid(),
'owner_ref' => $this->user->public_id,
'organization_id' => $this->organization->id,
'branch_id' => $this->branch->id,
'patient_number' => 'LC-2026-00002',
'first_name' => 'Kofi',
'last_name' => 'Mensah',
]);
foreach ([$this->patient, $second] as $index => $patient) {
Appointment::create([
'uuid' => (string) \Illuminate\Support\Str::uuid(),
'owner_ref' => $this->user->public_id,
'organization_id' => $this->organization->id,
'branch_id' => $this->branch->id,
'patient_id' => $patient->id,
'practitioner_id' => $this->practitioner->id,
'type' => Appointment::TYPE_WALK_IN,
'status' => Appointment::STATUS_WAITING,
'scheduled_at' => now()->subMinutes(10 - $index),
'waiting_at' => now()->subMinutes(10 - $index),
'queue_position' => 1,
'reason' => $index === 0 ? 'Toothache' : 'Blurry vision',
'created_by' => $this->user->public_id,
]);
}
$this->actingAs($this->user)
->get(route('care.queue.index', ['branch_id' => $this->branch->id]))
->assertOk()
->assertSee('Toothache')
->assertSee('Blurry vision');
$positions = Appointment::query()
->where('branch_id', $this->branch->id)
->where('status', Appointment::STATUS_WAITING)
->orderBy('waiting_at')
->pluck('queue_position')
->all();
$this->assertSame([1, 2], array_map('intval', $positions));
}
public function test_api_can_book_and_check_in(): void
{
Sanctum::actingAs($this->user);