Assign every demo waiter to a doctor and sync Queue tickets.
Deploy Ladill Care / deploy (push) Successful in 1m54s

Link DemoWorld doctor staff to practitioners, clear unresolved routing, distribute staff across branches, and restore scoped consultation/pharmacy ticket sync so demos are not left Unassigned.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-18 05:34:45 +00:00
co-authored by Cursor
parent 86d79b5099
commit 88f84a4f96
2 changed files with 141 additions and 6 deletions
+131 -6
View File
@@ -114,6 +114,7 @@ class DemoTenantSeeder
$departments = $this->departmentsByBranch($branches, $ownerRef); $departments = $this->departmentsByBranch($branches, $ownerRef);
} }
$practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes); $practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes);
$this->linkStaffDoctorsToPractitioners($organization, $ownerRef, $plan, $branches, $practitioners);
$patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes); $patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes);
$appointmentCount = $this->seedAppointmentsAndClinical( $appointmentCount = $this->seedAppointmentsAndClinical(
$organization, $organization,
@@ -124,6 +125,7 @@ class DemoTenantSeeder
$ownerRef, $ownerRef,
$volumes, $volumes,
); );
$this->assignAllDemoWaiters($organization, $ownerRef, $branches, $practitioners);
if (in_array($plan, ['pro', 'enterprise'], true)) { if (in_array($plan, ['pro', 'enterprise'], true)) {
$appointmentCount += $this->seedSpecialtyDemoData( $appointmentCount += $this->seedSpecialtyDemoData(
$organization->fresh(), $organization->fresh(),
@@ -173,10 +175,126 @@ class DemoTenantSeeder
*/ */
private function syncQueueTicketsForDemo(Organization $organization, array $branches): void private function syncQueueTicketsForDemo(Organization $organization, array $branches): void
{ {
// Intentionally no-op during demo seed. Queue HTTP (branch × context) can if (! data_get($organization->settings, 'queue_integration_enabled')) {
// take many minutes on Enterprise volumes and hung production reseeds. return;
// Specialty/care waiting lists still come from local appointments; Queue }
// tickets backfill lazily on first Ops / specialty page visit.
try {
$bridge = app(CareQueueBridge::class);
if (! $bridge->isEnabled($organization)) {
return;
}
// Scoped per branch/context (not full-org) so demos finish quickly while
// still leaving waiters with real tickets instead of "Unassigned".
$contexts = [
CareQueueContexts::CONSULTATION,
CareQueueContexts::PHARMACY,
CareQueueContexts::LABORATORY,
CareQueueContexts::BILLING,
];
foreach ($branches as $branch) {
foreach ($contexts as $context) {
try {
$bridge->syncMissingTickets($organization, $context, (int) $branch->id, 50);
} catch (\Throwable) {
// Continue — page load will retry for that context.
}
}
}
} catch (\Throwable) {
// Demo seed should not fail if Queue API is unreachable.
}
}
/**
* Point DemoWorld doctor staff at a real practitioner so Call next / rooms work.
*
* @param list<Branch> $branches
* @param list<Practitioner> $practitioners
*/
private function linkStaffDoctorsToPractitioners(
Organization $organization,
string $ownerRef,
string $plan,
array $branches,
array $practitioners,
): void {
$doctorEmail = null;
$doctorName = null;
foreach (DemoWorld::staff($plan) as $staff) {
if (($staff['roles']['care'] ?? null) === 'doctor') {
$doctorEmail = strtolower((string) $staff['email']);
$doctorName = (string) $staff['name'];
break;
}
}
if ($doctorEmail === null || $practitioners === []) {
return;
}
$member = Member::query()
->where('organization_id', $organization->id)
->where('user_ref', $doctorEmail)
->first();
$byBranch = [];
foreach ($practitioners as $practitioner) {
$byBranch[(int) $practitioner->branch_id] ??= $practitioner;
}
foreach ($branches as $branch) {
$practitioner = $byBranch[(int) $branch->id] ?? null;
if (! $practitioner) {
continue;
}
$practitioner->forceFill([
'user_ref' => $doctorEmail,
'member_id' => $member?->id,
'name' => $doctorName !== '' ? $doctorName : $practitioner->name,
])->save();
}
}
/**
* Every waiting / checked-in demo appointment gets a doctor and a clean routing
* state so Queue sync can issue tickets (no amber Unassigned leftovers).
*
* @param list<Branch> $branches
* @param list<Practitioner> $practitioners
*/
private function assignAllDemoWaiters(
Organization $organization,
string $ownerRef,
array $branches,
array $practitioners,
): void {
if ($practitioners === []) {
return;
}
$byBranch = [];
foreach ($practitioners as $practitioner) {
$byBranch[(int) $practitioner->branch_id][] = $practitioner;
}
$waiters = Appointment::withTrashed()
->owned($ownerRef)
->where('organization_id', $organization->id)
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
->get();
foreach ($waiters as $index => $appointment) {
$branchPracs = $byBranch[(int) $appointment->branch_id] ?? $practitioners;
$practitioner = $branchPracs[$index % max(1, count($branchPracs))] ?? $practitioners[0];
$appointment->forceFill([
'practitioner_id' => $practitioner->id,
'queue_routing_status' => null,
'deleted_at' => null,
])->save();
}
} }
/** /**
@@ -473,18 +591,25 @@ class DemoTenantSeeder
/** /**
* Local Member rows for DemoWorld staff (user_ref = email until SSO remaps to public_id). * Local Member rows for DemoWorld staff (user_ref = email until SSO remaps to public_id).
* Multi-branch demos spread staff across branches so each site has a role roster.
* *
* @param list<Branch> $branches * @param list<Branch> $branches
*/ */
private function seedStaffMembers(Organization $organization, string $ownerRef, string $plan, array $branches): void private function seedStaffMembers(Organization $organization, string $ownerRef, string $plan, array $branches): void
{ {
$hq = $branches[0] ?? null; $hq = $branches[0] ?? null;
foreach (DemoWorld::staff($plan) as $staff) { $staffList = DemoWorld::staff($plan);
foreach ($staffList as $index => $staff) {
$role = $staff['roles']['care'] ?? null; $role = $staff['roles']['care'] ?? null;
if (! is_string($role) || $role === '') { if (! is_string($role) || $role === '') {
continue; continue;
} }
$branch = $hq;
if ($role !== 'hospital_admin' && $branches !== []) {
$branch = $branches[$index % count($branches)];
}
Member::query()->updateOrCreate( Member::query()->updateOrCreate(
[ [
'organization_id' => $organization->id, 'organization_id' => $organization->id,
@@ -493,7 +618,7 @@ class DemoTenantSeeder
[ [
'owner_ref' => $ownerRef, 'owner_ref' => $ownerRef,
'role' => $role, 'role' => $role,
'branch_id' => in_array($role, ['hospital_admin'], true) ? null : $hq?->id, 'branch_id' => $role === 'hospital_admin' ? null : $branch?->id,
], ],
); );
} }
+10
View File
@@ -342,5 +342,15 @@ class DemoSeedCommandTest extends TestCase
->where('role', 'receptionist') ->where('role', 'receptionist')
->exists() ->exists()
); );
$waiters = Appointment::query()
->where('owner_ref', $user->public_id)
->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
->get();
$this->assertNotEmpty($waiters);
foreach ($waiters as $waiter) {
$this->assertNotNull($waiter->practitioner_id, 'Demo waiters must all have a doctor assigned');
$this->assertNotSame('unresolved', $waiter->queue_routing_status);
}
} }
} }