Enable all specialty modules with demo traffic for Pro/Enterprise demos.
Deploy Ladill Care / deploy (push) Successful in 1m9s
Deploy Ladill Care / deploy (push) Successful in 1m9s
Free and real onboarding stay opt-in; Pro/Enterprise demos activate every catalog specialty and seed waiting appointments so module pages are populated. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -110,6 +110,7 @@ class DemoTenantSeeder
|
||||
$departments = $this->seedDepartments($branches, $ownerRef, $volumes);
|
||||
if (in_array($plan, ['pro', 'enterprise'], true)) {
|
||||
$this->seedSpecialtyModules($organization, $ownerRef);
|
||||
$departments = $this->departmentsByBranch($branches, $ownerRef);
|
||||
}
|
||||
$practitioners = $this->seedPractitioners($organization, $branches, $departments, $ownerRef, $volumes);
|
||||
$patients = $this->seedPatients($organization, $branches, $ownerRef, $volumes);
|
||||
@@ -122,6 +123,15 @@ class DemoTenantSeeder
|
||||
$ownerRef,
|
||||
$volumes,
|
||||
);
|
||||
if (in_array($plan, ['pro', 'enterprise'], true)) {
|
||||
$appointmentCount += $this->seedSpecialtyDemoData(
|
||||
$organization->fresh(),
|
||||
$branches,
|
||||
$practitioners,
|
||||
$patients,
|
||||
$ownerRef,
|
||||
);
|
||||
}
|
||||
|
||||
if ($volumes['paid_modules']) {
|
||||
$this->seedPaidModules(
|
||||
@@ -173,13 +183,17 @@ class DemoTenantSeeder
|
||||
return;
|
||||
}
|
||||
|
||||
$contexts = [
|
||||
CareQueueContexts::CONSULTATION,
|
||||
CareQueueContexts::PHARMACY,
|
||||
CareQueueContexts::LABORATORY,
|
||||
CareQueueContexts::BILLING,
|
||||
CareQueueContexts::IMAGING,
|
||||
...array_keys(config('care.specialty_modules', [])),
|
||||
];
|
||||
|
||||
foreach ($branches as $branch) {
|
||||
foreach ([
|
||||
CareQueueContexts::CONSULTATION,
|
||||
CareQueueContexts::PHARMACY,
|
||||
CareQueueContexts::LABORATORY,
|
||||
CareQueueContexts::BILLING,
|
||||
] as $context) {
|
||||
foreach ($contexts as $context) {
|
||||
$bridge->syncMissingTickets($organization, $context, (int) $branch->id, 100);
|
||||
}
|
||||
}
|
||||
@@ -395,10 +409,11 @@ class DemoTenantSeeder
|
||||
'plan_expires_at' => now()->addYear()->toIso8601String(),
|
||||
'billed_branches' => max(1, $billedBranches),
|
||||
'demo' => true,
|
||||
// Pro/Enterprise demos: Queue on natural role pages + dentistry specialty module.
|
||||
// Pro/Enterprise demos: Queue on natural role pages + every specialty module.
|
||||
// Free / non-demo tenants leave specialty_modules empty (opt-in via Settings).
|
||||
'queue_integration_enabled' => in_array($plan, ['pro', 'enterprise'], true),
|
||||
'specialty_modules' => in_array($plan, ['pro', 'enterprise'], true)
|
||||
? ['dentistry' => true]
|
||||
? $this->allSpecialtyFlagsEnabled()
|
||||
: [],
|
||||
// Demo seed uses the legacy clinical path. Explicitly clear leftover
|
||||
// onboarding rollout so a failed race that created a real workflow
|
||||
@@ -543,14 +558,30 @@ class DemoTenantSeeder
|
||||
return $byBranch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
private function allSpecialtyFlagsEnabled(): array
|
||||
{
|
||||
$flags = [];
|
||||
foreach (array_keys(config('care.specialty_modules', [])) as $key) {
|
||||
$flags[$key] = true;
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
private function seedSpecialtyModules(Organization $organization, string $ownerRef): void
|
||||
{
|
||||
try {
|
||||
// Activates dentistry (depts + stubs). When queue_integration_enabled and
|
||||
// QUEUE_API_* are configured, SpecialtyModuleService creates real Queue counters.
|
||||
app(SpecialtyModuleService::class)->activate($organization->fresh(), $ownerRef, 'dentistry');
|
||||
} catch (\Throwable) {
|
||||
// Demo seed should not fail if Queue API is unreachable; settings flag remains.
|
||||
$service = app(SpecialtyModuleService::class);
|
||||
foreach (array_keys($service->catalog()) as $key) {
|
||||
try {
|
||||
// Activates depts + stubs. When queue_integration_enabled and QUEUE_API_*
|
||||
// are configured, SpecialtyModuleService creates real Queue counters.
|
||||
$service->activate($organization->fresh(), $ownerRef, $key);
|
||||
} catch (\Throwable) {
|
||||
// Demo seed should not fail if Queue API is unreachable; settings flag remains.
|
||||
}
|
||||
}
|
||||
|
||||
if (data_get($organization->fresh()->settings, 'queue_integration_enabled')) {
|
||||
@@ -562,6 +593,149 @@ class DemoTenantSeeder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Branch> $branches
|
||||
* @return array<int, list<Department>> keyed by branch id
|
||||
*/
|
||||
private function departmentsByBranch(array $branches, string $ownerRef): array
|
||||
{
|
||||
$byBranch = [];
|
||||
foreach ($branches as $branch) {
|
||||
$byBranch[$branch->id] = Department::withTrashed()
|
||||
->owned($ownerRef)
|
||||
->where('branch_id', $branch->id)
|
||||
->where('is_active', true)
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
|
||||
return $byBranch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waiting / completed specialty appointments so each module page has demo traffic.
|
||||
*
|
||||
* @param list<Branch> $branches
|
||||
* @param list<Practitioner> $practitioners
|
||||
* @param list<Patient> $patients
|
||||
*/
|
||||
private function seedSpecialtyDemoData(
|
||||
Organization $organization,
|
||||
array $branches,
|
||||
array $practitioners,
|
||||
array $patients,
|
||||
string $ownerRef,
|
||||
): int {
|
||||
if ($branches === [] || $patients === [] || $practitioners === []) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$reasons = [
|
||||
'dentistry' => ['Toothache', 'Dental cleaning', 'Extraction review'],
|
||||
'ophthalmology' => ['Blurry vision', 'Eye exam', 'Contact lens fitting'],
|
||||
'physiotherapy' => ['Back pain rehab', 'Post-injury physio', 'Mobility session'],
|
||||
'maternity' => ['Antenatal visit', 'Pregnancy check', 'Postnatal review'],
|
||||
'radiology' => ['X-ray referral', 'Ultrasound', 'Imaging review'],
|
||||
];
|
||||
|
||||
$count = 0;
|
||||
$catalog = config('care.specialty_modules', []);
|
||||
foreach ($catalog as $key => $definition) {
|
||||
$type = (string) ($definition['department_type'] ?? '');
|
||||
if ($type === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$moduleReasons = $reasons[$key] ?? ['Specialty visit'];
|
||||
|
||||
foreach ($branches as $branchIndex => $branch) {
|
||||
$department = Department::owned($ownerRef)
|
||||
->where('branch_id', $branch->id)
|
||||
->where('type', $type)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
if (! $department) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$practitioner = Practitioner::withTrashed()->updateOrCreate(
|
||||
[
|
||||
'organization_id' => $organization->id,
|
||||
'user_ref' => sprintf('demo-specialty-%s-%s', $key, $branch->id),
|
||||
],
|
||||
[
|
||||
'owner_ref' => $ownerRef,
|
||||
'branch_id' => $branch->id,
|
||||
'department_id' => $department->id,
|
||||
'member_id' => null,
|
||||
'name' => ($definition['label'] ?? ucfirst($key)).' Clinic',
|
||||
'specialty' => (string) ($definition['label'] ?? $key),
|
||||
'room' => ($definition['label'] ?? 'Specialty').' Bay',
|
||||
'is_active' => true,
|
||||
'deleted_at' => null,
|
||||
],
|
||||
);
|
||||
|
||||
foreach ([Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN, Appointment::STATUS_COMPLETED] as $slot => $status) {
|
||||
$patient = $patients[($branchIndex + $slot + $count) % count($patients)];
|
||||
$scheduledAt = now()->subHours(($branchIndex * 3) + $slot + 1);
|
||||
$token = "specialty|{$key}|{$branch->id}|{$slot}";
|
||||
|
||||
$visit = null;
|
||||
if (in_array($status, [Appointment::STATUS_CHECKED_IN, Appointment::STATUS_WAITING, Appointment::STATUS_COMPLETED], true)) {
|
||||
$visit = Visit::withTrashed()->updateOrCreate(
|
||||
['uuid' => $this->demoUuid("visit|{$ownerRef}|{$token}")],
|
||||
[
|
||||
'owner_ref' => $ownerRef,
|
||||
'organization_id' => $organization->id,
|
||||
'branch_id' => $branch->id,
|
||||
'patient_id' => $patient->id,
|
||||
'status' => $status === Appointment::STATUS_COMPLETED
|
||||
? Visit::STATUS_COMPLETED
|
||||
: Visit::STATUS_IN_PROGRESS,
|
||||
'checked_in_at' => $scheduledAt->copy()->addMinutes(5),
|
||||
'completed_at' => $status === Appointment::STATUS_COMPLETED
|
||||
? $scheduledAt->copy()->addHour()
|
||||
: null,
|
||||
'checked_in_by' => $ownerRef,
|
||||
'deleted_at' => null,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Appointment::withTrashed()->updateOrCreate(
|
||||
['uuid' => $this->demoUuid("appt|{$ownerRef}|{$token}")],
|
||||
[
|
||||
'owner_ref' => $ownerRef,
|
||||
'organization_id' => $organization->id,
|
||||
'branch_id' => $branch->id,
|
||||
'patient_id' => $patient->id,
|
||||
'practitioner_id' => $practitioner->id,
|
||||
'department_id' => $department->id,
|
||||
'visit_id' => $visit?->id,
|
||||
'type' => Appointment::TYPE_SCHEDULED,
|
||||
'status' => $status,
|
||||
'scheduled_at' => $scheduledAt,
|
||||
'checked_in_at' => $visit?->checked_in_at,
|
||||
'completed_at' => $status === Appointment::STATUS_COMPLETED
|
||||
? $scheduledAt->copy()->addHour()
|
||||
: null,
|
||||
'cancelled_at' => null,
|
||||
'queue_position' => $slot + 1,
|
||||
'reason' => $moduleReasons[$slot % count($moduleReasons)],
|
||||
'created_by' => $ownerRef,
|
||||
'deleted_at' => null,
|
||||
],
|
||||
);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Branch> $branches
|
||||
* @param array<int, list<Department>> $departments
|
||||
|
||||
Reference in New Issue
Block a user